去年第一次接触Stable Diffusion时,我被其神奇的图像生成效果震撼,但同时也对黑箱式的模型调用感到不安。作为技术人员,我始终相信只有亲手实现过核心算法,才能真正掌握这项技术。经过三个月的反复尝试,我终于从零构建了一个可运行的Diffusion模型训练脚本,虽然生成的图像质量远不及商业产品,但整个实现过程让我对扩散模型的认知产生了质的飞跃。
这个项目适合有以下需求的开发者:
提示:完整实现需要Python 3.8+环境、PyTorch 1.12+以及至少8GB显存的GPU。如果显存不足,可以调小batch size或图像分辨率。
扩散模型的核心思想是通过逐步添加噪声破坏图像(正向过程),再训练神经网络学习逆向去噪过程(逆向过程)。在我的实现中,正向过程采用线性噪声调度:
python复制def linear_beta_schedule(timesteps):
beta_start = 0.0001
beta_end = 0.02
return torch.linspace(beta_start, beta_end, timesteps)
这个调度决定了噪声添加的强度变化曲线。经过对比测试,线性调度虽然简单,但在小规模实验中表现稳定。更复杂的cosine调度可能需要更多训练资源。
基础U-Net结构包含下采样和上采样路径,我为其添加了几个关键组件:
python复制class AttentionBlock(nn.Module):
def __init__(self, channels):
super().__init__()
self.norm = nn.GroupNorm(32, channels)
self.qkv = nn.Conv2d(channels, channels*3, 1)
self.proj = nn.Conv2d(channels, channels, 1)
def forward(self, x):
B, C, H, W = x.shape
qkv = self.qkv(self.norm(x))
q, k, v = qkv.chunk(3, dim=1)
# 简化版注意力计算
attn = torch.einsum("bchw,bcHW->bhwHW", q, k) * (C ** -0.5)
attn = attn.softmax(dim=-1)
out = torch.einsum("bhwHW,bcHW->bchw", attn, v)
return x + self.proj(out)
我使用CelebA数据集进行训练,处理流程包括:
python复制transform = transforms.Compose([
transforms.Resize(64),
transforms.CenterCrop(64),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5])
])
经过多次实验,以下参数组合效果较好:
训练损失采用简化的MSE损失:
python复制def p_losses(model, x0, t):
noise = torch.randn_like(x0)
xt = q_sample(x0, t, noise)
predicted_noise = model(xt, t)
return F.mse_loss(noise, predicted_noise)
DDPM采样需要迭代执行以下步骤:
python复制@torch.no_grad()
def p_sample(model, x, t, t_index):
betas_t = extract(betas, t, x.shape)
sqrt_one_minus_alphas_cumprod_t = extract(
sqrt_one_minus_alphas_cumprod, t, x.shape)
sqrt_recip_alphas_t = extract(sqrt_recip_alphas, t, x.shape)
# 预测噪声方向
pred_noise = model(x, t)
x0_from_noise = sqrt_recip_alphas_t * (
x - betas_t * pred_noise / sqrt_one_minus_alphas_cumprod_t
)
x0_from_noise = torch.clamp(x0_from_noise, -1., 1.)
# 计算下一步的均值方差
posterior_variance_t = extract(posterior_variance, t, x.shape)
noise = torch.randn_like(x) if t_index > 0 else 0
x_prev = x0_from_noise + torch.sqrt(posterior_variance_t) * noise
return x_prev
经过50k步训练后,模型可以生成可辨认的人脸图像,但存在以下典型问题:
通过以下改进可以提升质量:
现象:损失值剧烈波动或出现NaN
解决方案:
现象:图像始终是模糊的噪声
检查清单:
对于8GB显存设备:
在基础版本运行稳定后,可以考虑以下扩展:
一个简单的文本条件扩展方案:
python复制class CondDiffusion(nn.Module):
def __init__(self, text_encoder):
super().__init__()
self.unet = UNet()
self.text_proj = nn.Linear(768, 512)
self.text_encoder = text_encoder
def forward(self, x, t, text):
text_emb = self.text_encoder(text)[1]
text_emb = self.text_proj(text_emb)
return self.unet(x, t, text_emb)
实现过程中最深的体会是:Diffusion模型对超参数极其敏感,同样的架构,调整噪声调度或学习率可能使结果天差地别。建议在修改任何参数后,先用小规模数据(100张图)快速验证效果,确认方向正确后再进行完整训练。