非kaggle内容 一些自学的模型
This commit is contained in:
@@ -0,0 +1,145 @@
|
|||||||
|
#这是一个RCNN骨架的搭建 我的水平真的很拉 但是也得逼自己写一下看能不能写出来
|
||||||
|
import torch,torchvision
|
||||||
|
from torch import nn
|
||||||
|
from torchvision.models.detection.image_list import ImageList
|
||||||
|
from torchvision.models.detection.anchor_utils import AnchorGenerator
|
||||||
|
#需要传入的参数有 图片 X(batch,3,800,600)
|
||||||
|
class FasteRCNN(nn.Module):
|
||||||
|
def __init__(self,anchor_sizes,aspect_ratios,ntype):
|
||||||
|
super(FasteRCNN, self).__init__()
|
||||||
|
self.ntype=ntype
|
||||||
|
resnet = torchvision.models.resnet50(pretrained=True)
|
||||||
|
self.backbone = nn.Sequential(*list(resnet.children())[:-2]) # 去掉avgpool和fc
|
||||||
|
self.anchors_size=9
|
||||||
|
self.anchor_generator = AnchorGenerator(anchor_sizes, aspect_ratios)
|
||||||
|
self.RPN = RegionProposalNetwork(self.anchors_size)
|
||||||
|
self.roihead = ROIhead(self.ntype)
|
||||||
|
def forward(self, X):
|
||||||
|
features = self.backbone(X)
|
||||||
|
# 现在的大小就是(batch 2048 19 25)了
|
||||||
|
image_list = ImageList(X, [(X.shape[2], X.shape[3])] * X.shape[0])
|
||||||
|
anchors = self.anchor_generator(image_list, [features])
|
||||||
|
predbias,predclassify=self.RPN(features)
|
||||||
|
proposals = generate_proposals(anchors, predclassify, predbias, image_list.image_sizes)
|
||||||
|
return self.roihead(features,proposals)
|
||||||
|
|
||||||
|
class RegionProposalNetwork(nn.Module):
|
||||||
|
#anchors:anchorbox个数
|
||||||
|
def __init__(self,anchors):
|
||||||
|
super(RegionProposalNetwork, self).__init__()
|
||||||
|
#先做一个3x3的conv
|
||||||
|
self.conv1 = nn.Conv2d(2048, 2048, kernel_size=3, padding=1)
|
||||||
|
self.relu = nn.ReLU(inplace=True)
|
||||||
|
self.conv2 = nn.Conv2d(2048,4*anchors , kernel_size=1)
|
||||||
|
self.conv3 = nn.Conv2d(2048, 2*anchors, kernel_size=1)
|
||||||
|
def forward(self,features):
|
||||||
|
features = self.relu(self.conv1(features))
|
||||||
|
#第一个分支生成偏移量
|
||||||
|
predbias = self.conv2(features)
|
||||||
|
#第二个分支判断是否为背景
|
||||||
|
predclassify = self.conv3(features)
|
||||||
|
B, _, H, W = predbias.shape
|
||||||
|
predbias = predbias.permute(0, 2, 3, 1).reshape(B, -1, 4)
|
||||||
|
predclassify = predclassify.permute(0, 2, 3, 1).reshape(B, -1, 2)
|
||||||
|
return predbias, predclassify
|
||||||
|
|
||||||
|
from torchvision.ops import nms
|
||||||
|
|
||||||
|
|
||||||
|
def generate_proposals(anchors, predclassify, predbias, image_sizes):
|
||||||
|
"""
|
||||||
|
anchors: List[Tensor],每个特征图对应一个 Tensor,形状 [A_i*H_i*W_i, 4]
|
||||||
|
predclassify: Tensor [B, total_anchors, 2] (尚未 softmax)
|
||||||
|
predbias: Tensor [B, total_anchors, 4]
|
||||||
|
image_sizes: List[Tuple[int, int]],每张图片的 (H, W)
|
||||||
|
"""
|
||||||
|
# 1. 将所有层级的 anchor 合并成一个大的 Tensor
|
||||||
|
|
||||||
|
# 2. 对 predclassify 做 softmax,取前景分数(索引1)
|
||||||
|
scores = torch.softmax(predclassify, dim=-1)[:, :, 1] # [B, total_anchors]
|
||||||
|
|
||||||
|
batch_size = predclassify.shape[0]
|
||||||
|
proposals = []
|
||||||
|
|
||||||
|
for i in range(batch_size):
|
||||||
|
# 当前图片的 anchor 和预测值
|
||||||
|
cur_anchors = anchors[0]
|
||||||
|
deltas = predbias[i] # [total_anchors, 4]
|
||||||
|
score_i = scores[i] # [total_anchors]
|
||||||
|
|
||||||
|
# 3. 将偏移量应用到 anchor 上(中心点格式转换)
|
||||||
|
# anchor 格式: (x1, y1, x2, y2)
|
||||||
|
width = cur_anchors[:, 2] - cur_anchors[:, 0]
|
||||||
|
height = cur_anchors[:, 3] - cur_anchors[:, 1]
|
||||||
|
ctr_x = cur_anchors[:, 0] + 0.5 * width
|
||||||
|
ctr_y = cur_anchors[:, 1] + 0.5 * height
|
||||||
|
|
||||||
|
dx = deltas[:, 0]
|
||||||
|
dy = deltas[:, 1]
|
||||||
|
dw = deltas[:, 2]
|
||||||
|
dh = deltas[:, 3]
|
||||||
|
|
||||||
|
pred_ctr_x = dx * width + ctr_x
|
||||||
|
pred_ctr_y = dy * height + ctr_y
|
||||||
|
pred_w = torch.exp(dw) * width
|
||||||
|
pred_h = torch.exp(dh) * height
|
||||||
|
|
||||||
|
# 转回 (x1, y1, x2, y2)
|
||||||
|
pred_x1 = pred_ctr_x - 0.5 * pred_w
|
||||||
|
pred_y1 = pred_ctr_y - 0.5 * pred_h
|
||||||
|
pred_x2 = pred_ctr_x + 0.5 * pred_w
|
||||||
|
pred_y2 = pred_ctr_y + 0.5 * pred_h
|
||||||
|
|
||||||
|
boxes = torch.stack([pred_x1, pred_y1, pred_x2, pred_y2], dim=1)
|
||||||
|
|
||||||
|
# 4. 裁剪到图像边界
|
||||||
|
H, W = image_sizes[i]
|
||||||
|
boxes[:, 0] = torch.clamp(boxes[:, 0], min=0, max=W)
|
||||||
|
boxes[:, 1] = torch.clamp(boxes[:, 1], min=0, max=H)
|
||||||
|
boxes[:, 2] = torch.clamp(boxes[:, 2], min=0, max=W)
|
||||||
|
boxes[:, 3] = torch.clamp(boxes[:, 3], min=0, max=H)
|
||||||
|
|
||||||
|
# 5. 剔除宽高 <= 0 的框
|
||||||
|
keep = (boxes[:, 2] > boxes[:, 0]) & (boxes[:, 3] > boxes[:, 1])
|
||||||
|
boxes = boxes[keep]
|
||||||
|
score_i = score_i[keep]
|
||||||
|
|
||||||
|
# 6. 按分数排序取 top-N(训练时取 12000,测试取 6000)
|
||||||
|
pre_nms_top_n = 12000 # 可以根据 self.training 调整,但函数里无法获取,可以传入参数
|
||||||
|
if len(score_i) > pre_nms_top_n:
|
||||||
|
topk = torch.topk(score_i, pre_nms_top_n)
|
||||||
|
boxes = boxes[topk.indices]
|
||||||
|
score_i = score_i[topk.indices]
|
||||||
|
|
||||||
|
# 7. NMS(阈值 0.7)
|
||||||
|
keep = nms(boxes, score_i, 0.7)
|
||||||
|
boxes = boxes[keep]
|
||||||
|
score_i = score_i[keep]
|
||||||
|
|
||||||
|
# 8. 最终取 top-N(训练取 2000,测试取 1000)
|
||||||
|
post_nms_top_n = 2000
|
||||||
|
if len(score_i) > post_nms_top_n:
|
||||||
|
topk = torch.topk(score_i, post_nms_top_n)
|
||||||
|
boxes = boxes[topk.indices]
|
||||||
|
# score_i 可以丢弃,proposal 只需框
|
||||||
|
proposals.append(boxes)
|
||||||
|
|
||||||
|
return proposals # List[Tensor],每个 Tensor 形状 [N_i, 4]
|
||||||
|
import torchvision.ops.roi_align as roi_align
|
||||||
|
class ROIhead(nn.Module):
|
||||||
|
def __init__(self,ntype):
|
||||||
|
super(ROIhead, self).__init__()
|
||||||
|
self.conv1 = nn.Conv2d(2048, 512, kernel_size=3,padding=1)
|
||||||
|
self.fc1 = nn.Linear(512*7*7,4096)
|
||||||
|
self.fc2 = nn.Linear(4096,4096)
|
||||||
|
self.fc3_1 = nn.Linear(4096,ntype+1)
|
||||||
|
self.fc3_2 = nn.Linear(4096,(ntype+1)*4)
|
||||||
|
self.relu = nn.ReLU()
|
||||||
|
def forward(self,features ,proposals):
|
||||||
|
X=roi_align(features,proposals,output_size=7,spatial_scale=1.0/32)
|
||||||
|
X=self.conv1(X)
|
||||||
|
#(B,512,7,7)->(B,512*7*7)
|
||||||
|
X=X.flatten(start_dim=1,end_dim=3)
|
||||||
|
X = self.relu(self.fc2(self.relu(self.fc1(X))))
|
||||||
|
return self.fc3_1(X),self.fc3_2(X).reshape(features.shape[0],-1,4)
|
||||||
|
model = FasteRCNN(anchor_sizes = ((32, 64, 128),),aspect_ratios = ((0.5, 1.0, 2.0),),ntype=20)
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import math
|
||||||
|
import torch
|
||||||
|
from torch import nn, optim
|
||||||
|
from torch.utils.data import DataLoader
|
||||||
|
from torchvision import datasets, transforms
|
||||||
|
from torch.nn import functional as F
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SinusoidalEmbedding(nn.Module):
|
||||||
|
"""DDPM 标准的正弦位置编码,把时间步 t 编码成向量"""
|
||||||
|
def __init__(self, dim):
|
||||||
|
super().__init__()
|
||||||
|
self.dim = dim
|
||||||
|
|
||||||
|
def forward(self, t):
|
||||||
|
half_dim = self.dim // 2
|
||||||
|
emb = math.log(10000) / (half_dim - 1)
|
||||||
|
emb = torch.exp(torch.arange(half_dim, device=t.device) * -emb)
|
||||||
|
emb = t[:, None] * emb[None, :]
|
||||||
|
return torch.cat([emb.sin(), emb.cos()], dim=-1)
|
||||||
|
|
||||||
|
|
||||||
|
class ResidualBlock(nn.Module):
|
||||||
|
def __init__(self, in_ch, out_ch, stride=1, time_dim=None):
|
||||||
|
super().__init__()
|
||||||
|
# 第一个卷积:可能改变通道数和步长
|
||||||
|
self.conv1 = nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=1,
|
||||||
|
padding=1, bias=False)
|
||||||
|
self.bn1 = nn.BatchNorm2d(out_ch)
|
||||||
|
self.relu = nn.ReLU(inplace=True)
|
||||||
|
|
||||||
|
self.conv2 = nn.Conv2d(out_ch, out_ch, kernel_size=3, stride=1,
|
||||||
|
padding=1, bias=False)
|
||||||
|
self.bn2 = nn.BatchNorm2d(out_ch)
|
||||||
|
|
||||||
|
# 时间嵌入投影:把 t 的嵌入映射到 out_ch,加到特征图上
|
||||||
|
self.time_proj = None
|
||||||
|
if time_dim is not None:
|
||||||
|
self.time_proj = nn.Sequential(
|
||||||
|
nn.SiLU(),
|
||||||
|
nn.Linear(time_dim, out_ch),
|
||||||
|
)
|
||||||
|
|
||||||
|
# shortcut:如果维度变化,用1x1卷积(也可加BN)
|
||||||
|
self.shortcut = nn.Sequential()
|
||||||
|
if in_ch != out_ch :
|
||||||
|
self.shortcut = nn.Sequential(
|
||||||
|
nn.Conv2d(in_ch, out_ch, kernel_size=1, stride=1, bias=False),
|
||||||
|
nn.BatchNorm2d(out_ch)
|
||||||
|
)
|
||||||
|
|
||||||
|
def forward(self, x, t_emb=None):
|
||||||
|
residual = x
|
||||||
|
|
||||||
|
out = self.conv1(x)
|
||||||
|
out = self.bn1(out)
|
||||||
|
out = self.relu(out)
|
||||||
|
|
||||||
|
out = self.conv2(out)
|
||||||
|
out = self.bn2(out)
|
||||||
|
|
||||||
|
# 添加时间嵌入(FiLM 式相加注入)
|
||||||
|
if t_emb is not None and self.time_proj is not None:
|
||||||
|
out = out + self.time_proj(t_emb)[:, :, None, None]
|
||||||
|
|
||||||
|
# 添加 shortcut
|
||||||
|
out += self.shortcut(residual)
|
||||||
|
|
||||||
|
out = self.relu(out) # 标准做法:最后加一次激活
|
||||||
|
return out
|
||||||
|
class DownSample(nn.Module):
|
||||||
|
def __init__(self,ch):
|
||||||
|
super().__init__()
|
||||||
|
self.conv1=nn.Conv2d(ch,ch,2,2)
|
||||||
|
def forward(self,x):
|
||||||
|
return self.conv1(x)
|
||||||
|
|
||||||
|
class UpSample(nn.Module):
|
||||||
|
def __init__(self, ch):
|
||||||
|
super().__init__()
|
||||||
|
# 使用 ch//2 确保整数
|
||||||
|
self.conv1 = nn.ConvTranspose2d(ch, ch//2, 4, 2, 1)
|
||||||
|
def forward(self, x):
|
||||||
|
return self.conv1(x)
|
||||||
|
|
||||||
|
class UNet(nn.Module):
|
||||||
|
def __init__(self, time_dim=64):
|
||||||
|
super().__init__()
|
||||||
|
self.time_emb = SinusoidalEmbedding(time_dim)
|
||||||
|
self.time_mlp = nn.Sequential(
|
||||||
|
nn.Linear(time_dim, time_dim * 4),
|
||||||
|
nn.SiLU(),
|
||||||
|
nn.Linear(time_dim * 4, time_dim * 4),
|
||||||
|
)
|
||||||
|
t_dim = time_dim * 4
|
||||||
|
|
||||||
|
self.conv1 = nn.Conv2d(1, 32, 3, 1, 1)
|
||||||
|
self.res1 = ResidualBlock(32, 64, time_dim=t_dim)
|
||||||
|
self.down1 = DownSample(64)
|
||||||
|
self.res2 = ResidualBlock(64, 128, time_dim=t_dim)
|
||||||
|
self.down2 = DownSample(128)
|
||||||
|
self.res3 = ResidualBlock(128, 256, time_dim=t_dim)
|
||||||
|
self.down3 = DownSample(256)
|
||||||
|
self.res4 = ResidualBlock(256, 512, time_dim=t_dim)
|
||||||
|
|
||||||
|
# --- 上采样部分(重新设计通道匹配)---
|
||||||
|
self.up1 = UpSample(512) # 512 -> 256
|
||||||
|
self.res5 = ResidualBlock(512, 256, time_dim=t_dim) # 拼接后 512 -> 256
|
||||||
|
|
||||||
|
self.up2 = UpSample(256) # 256 -> 128
|
||||||
|
self.res6 = ResidualBlock(256, 128, time_dim=t_dim) # 拼接后 256 -> 128
|
||||||
|
|
||||||
|
self.up3 = UpSample(128) # 128 -> 64
|
||||||
|
self.res7 = ResidualBlock(128, 64, time_dim=t_dim) # 拼接后 128 -> 64
|
||||||
|
|
||||||
|
self.res8 = ResidualBlock(64, 32, time_dim=t_dim)
|
||||||
|
self.conv2 = nn.Conv2d(32, 1, 3, 1, 1)
|
||||||
|
|
||||||
|
def forward(self, x, t):
|
||||||
|
t_emb = self.time_mlp(self.time_emb(t)) # [b, time_dim*4]
|
||||||
|
# ----- 下采样(保存跳跃连接)-----
|
||||||
|
x1 = self.res1(self.conv1(x), t_emb) # [b, 64, 28, 28]
|
||||||
|
x2 = self.res2(self.down1(x1), t_emb) # [b, 128, 14, 14]
|
||||||
|
x3 = self.res3(self.down2(x2), t_emb) # [b, 256, 7, 7]
|
||||||
|
x4 = self.res4(self.down3(x3), t_emb) # [b, 512, 3, 3]
|
||||||
|
|
||||||
|
# ----- 上采样 1(3×3 → 7×7)-----
|
||||||
|
x4_up = self.up1(x4) # [b, 256, 6, 6]
|
||||||
|
x4_up = F.interpolate(x4_up, size=7, mode='bilinear') # [b, 256, 7, 7]
|
||||||
|
x4_cat = torch.cat([x4_up, x3], dim=1) # [b, 512, 7, 7]
|
||||||
|
x3_new = self.res5(x4_cat, t_emb) # [b, 256, 7, 7]
|
||||||
|
|
||||||
|
# ----- 上采样 2(7×7 → 14×14)-----
|
||||||
|
x3_up = self.up2(x3_new) # [b, 128, 14, 14] 尺寸恰好为14
|
||||||
|
x3_cat = torch.cat([x3_up, x2], dim=1) # [b, 256, 14, 14]
|
||||||
|
x2_new = self.res6(x3_cat, t_emb) # [b, 128, 14, 14]
|
||||||
|
|
||||||
|
# ----- 上采样 3(14×14 → 28×28)-----
|
||||||
|
x2_up = self.up3(x2_new) # [b, 64, 28, 28]
|
||||||
|
x2_cat = torch.cat([x2_up, x1], dim=1) # [b, 128, 28, 28]
|
||||||
|
x1_new = self.res7(x2_cat, t_emb) # [b, 64, 28, 28]
|
||||||
|
|
||||||
|
# ----- 最终输出-----
|
||||||
|
x1_new = self.res8(x1_new, t_emb) # [b, 32, 28, 28]
|
||||||
|
out = self.conv2(x1_new) # [b, 1, 28, 28]
|
||||||
|
return out
|
||||||
|
if __name__=='__main__':
|
||||||
|
# ---------- 数据加载 ----------
|
||||||
|
batch_size = 16
|
||||||
|
transform = transforms.Compose(
|
||||||
|
[
|
||||||
|
transforms.ToTensor(), # 将 [0,255] 转为 [0,1] 的 Tensor
|
||||||
|
transforms.Normalize((0.5,), (0.5,)), #归一化到[-1,1]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
train_dataset = datasets.MNIST(
|
||||||
|
root="./data", train=True, download=True, transform=transform
|
||||||
|
)
|
||||||
|
test_dataset = datasets.MNIST(
|
||||||
|
root="./data", train=False, download=True, transform=transform
|
||||||
|
)
|
||||||
|
|
||||||
|
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
|
||||||
|
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
|
||||||
|
|
||||||
|
# ---------- DDPM 超参数 ----------
|
||||||
|
timesteps = 600
|
||||||
|
betas = torch.linspace(1e-4, 0.02, timesteps) # 线性采样 beta
|
||||||
|
alphas = 1.0 - betas
|
||||||
|
alpha_bar = torch.cumprod(alphas, dim=0) # \bar{alpha}_t
|
||||||
|
|
||||||
|
def q_sample(x_0, t):
|
||||||
|
"""前向扩散:q(x_t | x_0) = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * eps"""
|
||||||
|
alpha_bar_t = alpha_bar[t][:, None, None, None]
|
||||||
|
eps = torch.randn_like(x_0)
|
||||||
|
x_t = torch.sqrt(alpha_bar_t) * x_0 + torch.sqrt(1 - alpha_bar_t) * eps
|
||||||
|
return x_t, eps
|
||||||
|
|
||||||
|
def sample(model, n=16, device="cpu"):
|
||||||
|
"""简易采样:从纯噪声逐步去噪"""
|
||||||
|
model.eval()
|
||||||
|
x = torch.randn(n, 1, 28, 28, device=device)
|
||||||
|
with torch.no_grad():
|
||||||
|
for t in reversed(range(timesteps)):
|
||||||
|
t_batch = torch.full((n,), t, device=device, dtype=torch.long)
|
||||||
|
eps_pred = model(x, t_batch)
|
||||||
|
alpha_t = alphas[t].to(device)
|
||||||
|
alpha_bar_t = alpha_bar[t].to(device)
|
||||||
|
x = (x - (1 - alpha_t) / torch.sqrt(1 - alpha_bar_t) * eps_pred) / torch.sqrt(alpha_t)
|
||||||
|
if t > 0:
|
||||||
|
x += torch.sqrt(betas[t].to(device)) * torch.randn_like(x)
|
||||||
|
model.train()
|
||||||
|
return x
|
||||||
|
|
||||||
|
# ---------- 训练 ----------
|
||||||
|
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
|
model = UNet().to(device)
|
||||||
|
optimizer = optim.Adam(model.parameters(), lr=1e-3)
|
||||||
|
epochs = 20
|
||||||
|
|
||||||
|
for epoch in range(epochs):
|
||||||
|
total_loss = 0.0
|
||||||
|
for x_0, _ in train_loader:
|
||||||
|
x_0 = x_0.to(device)
|
||||||
|
t = torch.randint(0, timesteps, (x_0.size(0),), device=device) # 随机采样时间
|
||||||
|
x_t, eps = q_sample(x_0, t)
|
||||||
|
|
||||||
|
eps_pred = model(x_t, t)
|
||||||
|
loss = F.mse_loss(eps_pred, eps) # 预测噪声,MSE 损失
|
||||||
|
|
||||||
|
optimizer.zero_grad()
|
||||||
|
loss.backward()
|
||||||
|
optimizer.step()
|
||||||
|
total_loss += loss.item()
|
||||||
|
|
||||||
|
print(f"Epoch {epoch+1}/{epochs}, Loss: {total_loss/len(train_loader):.4f}")
|
||||||
|
|
||||||
|
# ---------- 采样可视化 ----------
|
||||||
|
samples = sample(model, n=16, device=device).cpu().clamp(-1, 1)
|
||||||
|
samples = (samples + 1) / 2 # [-1,1] -> [0,1]
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(4, 4, figsize=(8, 8))
|
||||||
|
for i, ax in enumerate(axes.flat):
|
||||||
|
ax.imshow(samples[i, 0], cmap="gray")
|
||||||
|
ax.axis("off")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("samples.png")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import torch
|
||||||
|
from torch.nn import functional as F
|
||||||
|
from torchvision import transforms
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from main import UNet
|
||||||
|
|
||||||
|
|
||||||
|
def build_schedule(timesteps, device):
|
||||||
|
betas = torch.linspace(1e-4, 0.02, timesteps).to(device)
|
||||||
|
alphas = 1.0 - betas
|
||||||
|
alpha_bar = torch.cumprod(alphas, dim=0)
|
||||||
|
return betas, alphas, alpha_bar
|
||||||
|
|
||||||
|
|
||||||
|
def denoise_step(model, x, t, alphas, alpha_bar, betas):
|
||||||
|
n = x.size(0)
|
||||||
|
t_batch = torch.full((n,), t, device=x.device, dtype=torch.long)
|
||||||
|
eps_pred = model(x, t_batch)
|
||||||
|
alpha_t = alphas[t]
|
||||||
|
alpha_bar_t = alpha_bar[t]
|
||||||
|
x = (x - (1 - alpha_t) / torch.sqrt(1 - alpha_bar_t) * eps_pred) / torch.sqrt(alpha_t)
|
||||||
|
if t > 0:
|
||||||
|
x += torch.sqrt(betas[t]) * torch.randn_like(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def to_grid_image(x, ncols, cmap="gray"):
|
||||||
|
n = x.size(0)
|
||||||
|
nrows = math.ceil(n / ncols)
|
||||||
|
fig, axes = plt.subplots(nrows, ncols, figsize=(ncols * 2, nrows * 2))
|
||||||
|
axes = np.atleast_1d(axes).flatten()
|
||||||
|
for i, ax in enumerate(axes):
|
||||||
|
if i < n:
|
||||||
|
ax.imshow(x[i, 0], cmap=cmap)
|
||||||
|
ax.axis("off")
|
||||||
|
plt.subplots_adjust(wspace=0.05, hspace=0.05)
|
||||||
|
fig.canvas.draw()
|
||||||
|
buf = np.asarray(fig.canvas.buffer_rgba())[:, :, :3]
|
||||||
|
plt.close(fig)
|
||||||
|
return Image.fromarray(buf)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="DDPM 采样过程逐帧记录")
|
||||||
|
parser.add_argument("--ckpt", type=str, default="model_state_dict.pth")
|
||||||
|
parser.add_argument("--timesteps", type=int, default=600)
|
||||||
|
parser.add_argument("--samples", type=int, default=4)
|
||||||
|
parser.add_argument("--outdir", type=str, default="sampling_frames")
|
||||||
|
parser.add_argument("--gif", action="store_true", help="额外输出 GIF")
|
||||||
|
parser.add_argument("--gif-fps", type=int, default=20)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
|
|
||||||
|
model = UNet().to(device)
|
||||||
|
state = torch.load(args.ckpt, map_location=device)
|
||||||
|
if isinstance(state, dict) and "state_dict" in state:
|
||||||
|
state = state["state_dict"]
|
||||||
|
model.load_state_dict(state)
|
||||||
|
model.eval()
|
||||||
|
|
||||||
|
betas, alphas, alpha_bar = build_schedule(args.timesteps, device)
|
||||||
|
|
||||||
|
os.makedirs(args.outdir, exist_ok=True)
|
||||||
|
|
||||||
|
x = torch.randn(args.samples, 1, 28, 28, device=device)
|
||||||
|
|
||||||
|
frames = []
|
||||||
|
with torch.no_grad():
|
||||||
|
for t in reversed(range(args.timesteps)):
|
||||||
|
x = denoise_step(model, x, t, alphas, alpha_bar, betas)
|
||||||
|
grid = x.cpu().clamp(-1, 1)
|
||||||
|
grid = (grid + 1) / 2
|
||||||
|
img = to_grid_image(grid, ncols=2)
|
||||||
|
path = os.path.join(args.outdir, f"step_{t:04d}.png")
|
||||||
|
img.save(path)
|
||||||
|
frames.append(path)
|
||||||
|
if t % 50 == 0 or t == 0:
|
||||||
|
print(f"step {t} 已保存 -> {path}")
|
||||||
|
|
||||||
|
print(f"全部 {args.timesteps} 帧已保存到 {args.outdir}/")
|
||||||
|
|
||||||
|
if args.gif:
|
||||||
|
images = [Image.open(p) for p in frames]
|
||||||
|
gif_path = os.path.join(args.outdir, "sampling.gif")
|
||||||
|
images[0].save(
|
||||||
|
gif_path,
|
||||||
|
save_all=True,
|
||||||
|
append_images=images[1:],
|
||||||
|
duration=1000 // args.gif_fps,
|
||||||
|
loop=0,
|
||||||
|
)
|
||||||
|
print(f"GIF 已保存 -> {gif_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
@@ -0,0 +1,136 @@
|
|||||||
|
import torch
|
||||||
|
from torch import nn, optim
|
||||||
|
from torch.utils.data import DataLoader
|
||||||
|
from torchvision import datasets, transforms
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- 模型定义(修正) ----------
|
||||||
|
class VAE(nn.Module):
|
||||||
|
def __init__(self):
|
||||||
|
super(VAE, self).__init__() # 修正:去掉多余的 self
|
||||||
|
self.encoder = nn.Sequential(
|
||||||
|
nn.Linear(784, 256),
|
||||||
|
nn.ReLU(),
|
||||||
|
nn.Linear(256, 64),
|
||||||
|
nn.ReLU(),
|
||||||
|
nn.Linear(64, 20), # 输出 mu 和 log_var(或 sigma)
|
||||||
|
)
|
||||||
|
self.decoder = nn.Sequential(
|
||||||
|
nn.Linear(10, 64),
|
||||||
|
nn.ReLU(),
|
||||||
|
nn.Linear(64, 256),
|
||||||
|
nn.ReLU(),
|
||||||
|
nn.Linear(256, 784),
|
||||||
|
nn.Sigmoid(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
hidden = self.encoder(x)
|
||||||
|
mu, log_var = hidden.chunk(2, dim=1) # 用 log_var 更稳定
|
||||||
|
# 重参数化:sigma = exp(0.5 * log_var)
|
||||||
|
std = torch.exp(0.5 * log_var)
|
||||||
|
eps = torch.randn_like(std)
|
||||||
|
z = mu + eps * std
|
||||||
|
x_hat = self.decoder(z)
|
||||||
|
|
||||||
|
# KL 散度(按 batch 和像素平均)
|
||||||
|
KL = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())
|
||||||
|
KL = KL / (x.size(0) * 28 * 28) # 与重构损失尺度一致
|
||||||
|
|
||||||
|
return x_hat, KL
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- 超参数设置 ----------
|
||||||
|
batch_size = 128
|
||||||
|
epochs = 20
|
||||||
|
learning_rate = 1e-3
|
||||||
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
print(f"Using device: {device}")
|
||||||
|
|
||||||
|
# ---------- 数据加载 ----------
|
||||||
|
transform = transforms.Compose(
|
||||||
|
[
|
||||||
|
transforms.ToTensor(), # 将 [0,255] 转为 [0,1] 的 Tensor
|
||||||
|
transforms.Lambda(lambda x: x.view(-1)), # 展平为 784 维向量
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
train_dataset = datasets.MNIST(
|
||||||
|
root="./data", train=True, download=True, transform=transform
|
||||||
|
)
|
||||||
|
test_dataset = datasets.MNIST(
|
||||||
|
root="./data", train=False, download=True, transform=transform
|
||||||
|
)
|
||||||
|
|
||||||
|
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
|
||||||
|
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
|
||||||
|
|
||||||
|
# ---------- 初始化模型、优化器 ----------
|
||||||
|
model = VAE().to(device)
|
||||||
|
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
|
||||||
|
|
||||||
|
# 重构损失(二元交叉熵,因为像素在 [0,1])
|
||||||
|
criterion = nn.BCELoss(reduction="sum") # sum 后与 KL 求和,再除以像素数
|
||||||
|
|
||||||
|
# ---------- 训练循环 ----------
|
||||||
|
for epoch in range(1, epochs + 1):
|
||||||
|
model.train()
|
||||||
|
total_loss = 0
|
||||||
|
total_recon = 0
|
||||||
|
total_kl = 0
|
||||||
|
|
||||||
|
for batch_idx, (data, _) in enumerate(train_loader):
|
||||||
|
data = data.to(device)
|
||||||
|
optimizer.zero_grad()
|
||||||
|
|
||||||
|
x_hat, kl = model(data)
|
||||||
|
recon_loss = criterion(x_hat, data) # 按 batch 求和(每个像素的 BCE 之和)
|
||||||
|
|
||||||
|
# 总损失 = 重构损失 + KL 散度(都已除以像素数,但 recon 未除,所以需统一)
|
||||||
|
# 此处将 recon 也除以像素数,使两项量级匹配
|
||||||
|
recon_loss = recon_loss / (data.size(0) * 28 * 28)
|
||||||
|
loss = recon_loss + kl
|
||||||
|
|
||||||
|
loss.backward()
|
||||||
|
optimizer.step()
|
||||||
|
|
||||||
|
total_loss += loss.item()
|
||||||
|
total_recon += recon_loss.item()
|
||||||
|
total_kl += kl.item()
|
||||||
|
|
||||||
|
avg_loss = total_loss / len(train_loader)
|
||||||
|
avg_recon = total_recon / len(train_loader)
|
||||||
|
avg_kl = total_kl / len(train_loader)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Epoch {epoch:2d} | Avg Loss: {avg_loss:.4f} | Recon: {avg_recon:.4f} | KL: {avg_kl:.4f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 每 5 个 epoch 生成一些样本看看效果(可选)
|
||||||
|
if epoch % 5 == 0:
|
||||||
|
model.eval()
|
||||||
|
with torch.no_grad():
|
||||||
|
# 从标准正态分布采样 16 个 latent code
|
||||||
|
sample_z = torch.randn(16, 10).to(device)
|
||||||
|
generated = model.decoder(sample_z).cpu().numpy()
|
||||||
|
# 显示
|
||||||
|
fig, axes = plt.subplots(4, 4, figsize=(6, 6))
|
||||||
|
for i, ax in enumerate(axes.flat):
|
||||||
|
ax.imshow(generated[i].reshape(28, 28), cmap="gray")
|
||||||
|
ax.axis("off")
|
||||||
|
plt.suptitle(f"Epoch {epoch} Generated Samples")
|
||||||
|
plt.show()
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
# ---------- 测试集评估(可选) ----------
|
||||||
|
model.eval()
|
||||||
|
test_loss = 0
|
||||||
|
with torch.no_grad():
|
||||||
|
for data, _ in test_loader:
|
||||||
|
data = data.to(device)
|
||||||
|
x_hat, kl = model(data)
|
||||||
|
recon = criterion(x_hat, data) / (data.size(0) * 28 * 28)
|
||||||
|
test_loss += (recon + kl).item()
|
||||||
|
print(f"Test Average Loss: {test_loss / len(test_loader):.4f}")
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,195 @@
|
|||||||
|
# %%
|
||||||
|
# This Python 3 environment comes with many helpful analytics libraries installed
|
||||||
|
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
|
||||||
|
# For example, here's several helpful packages to load
|
||||||
|
|
||||||
|
import numpy as np # linear algebra
|
||||||
|
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
|
||||||
|
|
||||||
|
# Input data files are available in the read-only "../input/" directory
|
||||||
|
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory
|
||||||
|
|
||||||
|
import os
|
||||||
|
for dirname, _, filenames in os.walk('/kaggle/input'):
|
||||||
|
for filename in filenames:
|
||||||
|
print(os.path.join(dirname, filename))
|
||||||
|
|
||||||
|
# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using "Save & Run All"
|
||||||
|
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session
|
||||||
|
|
||||||
|
# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session
|
||||||
|
# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md
|
||||||
|
|
||||||
|
import kagglehub
|
||||||
|
# kagglehub.dataset_download('<owner>/<dataset-slug>')
|
||||||
|
# %%
|
||||||
|
import pandas as pd
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
||||||
|
from torch.utils.data import Dataset, DataLoader
|
||||||
|
# %%
|
||||||
|
train_df = pd.read_csv('/kaggle/input/competitions/nlp-getting-started/train.csv')
|
||||||
|
test_df = pd.read_csv('/kaggle/input/competitions/nlp-getting-started/test.csv')
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
train_df, val_df = train_test_split(train_df, test_size=0.1, random_state=42)
|
||||||
|
print(f"Train: {len(train_df)}, Val: {len(val_df)}, Test: {len(test_df)}")
|
||||||
|
# %%
|
||||||
|
model_name = "xlm-roberta-large"
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
||||||
|
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
|
||||||
|
# %%
|
||||||
|
import numpy as np
|
||||||
|
class NliDataset(Dataset):
|
||||||
|
def __init__(self, df, tokenizer, max_length=128):
|
||||||
|
self.df = df.reset_index(drop=True)
|
||||||
|
self.tokenizer = tokenizer
|
||||||
|
self.max_length = max_length
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.df)
|
||||||
|
|
||||||
|
def __getitem__(self, idx):
|
||||||
|
row = self.df.iloc[idx]
|
||||||
|
strs=''
|
||||||
|
if row['keyword'] is not np.nan:
|
||||||
|
strs=strs+'['+row['keyword']+']'
|
||||||
|
if row['location'] is not np.nan:
|
||||||
|
strs=strs+'['+row['location']+']'
|
||||||
|
strs=strs+row['text']
|
||||||
|
encoding = self.tokenizer(
|
||||||
|
strs,
|
||||||
|
truncation=True,
|
||||||
|
padding='max_length',
|
||||||
|
max_length=self.max_length,
|
||||||
|
return_tensors='pt' # 返回 PyTorch Tensor
|
||||||
|
)
|
||||||
|
# 去掉 batch 维度(因为只处理单条)
|
||||||
|
item = {
|
||||||
|
'input_ids': encoding['input_ids'].squeeze(0),
|
||||||
|
'attention_mask': encoding['attention_mask'].squeeze(0)
|
||||||
|
}
|
||||||
|
if 'target' in row:
|
||||||
|
item['labels'] = torch.tensor(row['target'], dtype=torch.long)
|
||||||
|
return item
|
||||||
|
|
||||||
|
batch_size = 16 # 根据显存调整,推荐使用 16 或 32
|
||||||
|
max_length = 128
|
||||||
|
|
||||||
|
train_dataset = NliDataset(train_df, tokenizer, max_length)
|
||||||
|
val_dataset = NliDataset(val_df, tokenizer, max_length)
|
||||||
|
|
||||||
|
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
|
||||||
|
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
|
||||||
|
# %%
|
||||||
|
for name, param in model.named_parameters():
|
||||||
|
#if param.requires_grad:
|
||||||
|
print(f"{name}: requires_grad = {param.requires_grad}")
|
||||||
|
# %%
|
||||||
|
from peft import LoraConfig, get_peft_model
|
||||||
|
target_layers = [8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]
|
||||||
|
target_modules=[]
|
||||||
|
for layer in target_layers:
|
||||||
|
target_modules.append(f"roberta.encoder.layer.{layer}.attention.self.query")
|
||||||
|
target_modules.append(f"roberta.encoder.layer.{layer}.attention.self.value")
|
||||||
|
# 配置 LoRA
|
||||||
|
config = LoraConfig(
|
||||||
|
r=8, # LoRA 的秩
|
||||||
|
lora_alpha=16, # LoRA 的缩放因子
|
||||||
|
target_modules=target_modules, # 目标模块
|
||||||
|
lora_dropout=0.1, # Dropout 概率
|
||||||
|
bias="none", # 是否更新偏置
|
||||||
|
modules_to_save=["classifier"], # 指定分类器需要被微调
|
||||||
|
)
|
||||||
|
|
||||||
|
# 封装为 LoRA 模型
|
||||||
|
model = get_peft_model(model, config)
|
||||||
|
|
||||||
|
# 验证分类器是否被微调
|
||||||
|
print("验证分类器参数是否被训练:")
|
||||||
|
for name, param in model.named_parameters():
|
||||||
|
if param.requires_grad:
|
||||||
|
print(f"{name}: requires_grad = {param.requires_grad}")
|
||||||
|
# %%
|
||||||
|
@torch.no_grad()
|
||||||
|
def validate(model,loader):
|
||||||
|
model.eval()
|
||||||
|
acc=0
|
||||||
|
total=0
|
||||||
|
for batch in loader:
|
||||||
|
input_ids = batch['input_ids'].to(device)
|
||||||
|
attention_mask = batch['attention_mask'].to(device)
|
||||||
|
labels = batch['labels'].to(device)
|
||||||
|
outputs = model(input_ids, attention_mask=attention_mask).logits
|
||||||
|
pred=torch.argmax(outputs,dim=1)
|
||||||
|
acc+=pred.eq(labels).sum()
|
||||||
|
total+=labels.size(0)
|
||||||
|
print(f"acc:{acc/total}")
|
||||||
|
return acc/total
|
||||||
|
from tqdm import tqdm
|
||||||
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||||
|
model=model.to(device)
|
||||||
|
loss_func = nn.CrossEntropyLoss()
|
||||||
|
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-4)
|
||||||
|
scheduler=torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)
|
||||||
|
epochs = 30
|
||||||
|
best_acc=0.0
|
||||||
|
for epoch in range(epochs):
|
||||||
|
model.train()
|
||||||
|
training_loss = 0
|
||||||
|
|
||||||
|
# 使用 tqdm 包装 dataloader,并设置描述信息
|
||||||
|
progress_bar = tqdm(train_loader, desc=f"Epoch {epoch+1}/{epochs}")
|
||||||
|
lens=0
|
||||||
|
for batch in progress_bar:
|
||||||
|
optimizer.zero_grad()
|
||||||
|
input_ids = batch['input_ids'].to(device)
|
||||||
|
attention_mask = batch['attention_mask'].to(device)
|
||||||
|
labels = batch['labels'].to(device)
|
||||||
|
outputs = model(input_ids, attention_mask=attention_mask).logits
|
||||||
|
loss = loss_func(outputs, labels)
|
||||||
|
loss.backward()
|
||||||
|
optimizer.step()
|
||||||
|
|
||||||
|
training_loss += loss.item()
|
||||||
|
lens+=1
|
||||||
|
# 更新进度条显示当前 batch 的损失
|
||||||
|
progress_bar.set_postfix({
|
||||||
|
'loss': f'{loss.item():.4f}',
|
||||||
|
'avg_loss': f'{training_loss / (progress_bar.n+1):.4f}' # progress_bar.n 是已处理 batch 数
|
||||||
|
})
|
||||||
|
|
||||||
|
scheduler.step()
|
||||||
|
|
||||||
|
avg_train_loss = training_loss / lens
|
||||||
|
print(f"Epoch {epoch+1} train_loss: {avg_train_loss:.4f}")
|
||||||
|
|
||||||
|
# 验证(你也可以为验证添加进度条,见下方建议)
|
||||||
|
current_acc=validate(model, val_loader)
|
||||||
|
if current_acc > best_acc :
|
||||||
|
torch.save(model.state_dict(), 'model.pth')
|
||||||
|
print(f"best model save,acc:{current_acc}")
|
||||||
|
best_acc=current_acc
|
||||||
|
# %%
|
||||||
|
test_dataset = NliDataset(test_df, tokenizer, max_length)
|
||||||
|
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
|
||||||
|
all_preds=[]
|
||||||
|
model.load_state_dict(torch.load('model.pth'))
|
||||||
|
model.eval()
|
||||||
|
with torch.no_grad():
|
||||||
|
for batch in test_loader:
|
||||||
|
input_ids = batch['input_ids'].to(device)
|
||||||
|
attention_mask = batch['attention_mask'].to(device)
|
||||||
|
outputs = model(input_ids, attention_mask=attention_mask).logits
|
||||||
|
preds = torch.argmax(outputs, dim=1)
|
||||||
|
all_preds.extend(preds.cpu().numpy())
|
||||||
|
|
||||||
|
submission = pd.DataFrame({
|
||||||
|
'id':test_df['id'],
|
||||||
|
'target': all_preds
|
||||||
|
})
|
||||||
|
|
||||||
|
print(submission)
|
||||||
|
submission.to_csv('submission.csv', index=False)
|
||||||
|
print("Submission saved!")
|
||||||
|
# %%
|
||||||
Reference in New Issue
Block a user