跳转到内容

扩散模型第9章:自包含迷你实验

这一章只做一件事:把前面学到的公式变成能运行的最小实验。旧版本依赖当前仓库并不存在的外部脚本;现在所有关键代码直接放在正文里。

实验一只需要 NumPy 和 Matplotlib;实验二增加 PyTorch。

终端窗口
python -m pip install numpy matplotlib torch

不要把这里的训练速度、最佳步数或曲线形状当成真实生物任务的 benchmark。它们只是算法单元测试。

2. 实验一:画出 αˉt\bar\alpha_t 与 log-SNR

Section titled “2. 实验一:画出 αˉt\bar\alpha_tαˉt​ 与 log-SNR”

下面的脚本比较一个简单 linear beta schedule 和 cosine αˉt\bar\alpha_t 设计。重点不是判断谁“更好”,而是看清 schedule 如何改变各时间点的信号比例。

import numpy as np
import matplotlib.pyplot as plt
T = 1000
# Linear beta schedule
beta = np.linspace(1e-4, 2e-2, T)
alpha = 1.0 - beta
abar_linear = np.cumprod(alpha)
# Cosine alpha_bar, following the common improved-DDPM construction
s = 0.008
t = np.arange(T + 1) / T
f = np.cos(((t + s) / (1 + s)) * np.pi / 2) ** 2
f = f / f[0]
abar_cosine = f[1:]
def log_snr(abar):
abar = np.clip(abar, 1e-8, 1 - 1e-8)
return np.log(abar) - np.log1p(-abar)
plt.figure(figsize=(8, 4))
plt.plot(abar_linear, label='linear alpha_bar')
plt.plot(abar_cosine, label='cosine alpha_bar')
plt.xlabel('t')
plt.ylabel('alpha_bar')
plt.legend()
plt.tight_layout()
plt.show()
plt.figure(figsize=(8, 4))
plt.plot(log_snr(abar_linear), label='linear log-SNR')
plt.plot(log_snr(abar_cosine), label='cosine log-SNR')
plt.xlabel('t')
plt.ylabel('log-SNR')
plt.legend()
plt.tight_layout()
plt.show()

你应该验证三件事:αˉt\bar\alpha_t 从接近 1 向 0 下降;log-SNR 随时间降低;不同 schedule 改变的是不同噪声水平在时间轴上的分配。真实任务需要根据模型参数化和数据空间重新验证 schedule。

3. 实验二:一个真正闭环的 2D DDPM

Section titled “3. 实验二:一个真正闭环的 2D DDPM”

下面用二维环形数据训练一个很小的噪声预测 MLP。它包含完整的四步:采样真实数据、一步闭式加噪、回归噪声、从高斯噪声反向采样。

import math
import torch
from torch import nn
import matplotlib.pyplot as plt
torch.manual_seed(0)
device = 'cpu'
T = 200
betas = torch.linspace(1e-4, 2e-2, T, device=device)
alphas = 1.0 - betas
abar = torch.cumprod(alphas, dim=0)
def real_data(n):
theta = 2 * math.pi * torch.rand(n, device=device)
r = 2.0 + 0.12 * torch.randn(n, device=device)
return torch.stack([r * torch.cos(theta), r * torch.sin(theta)], dim=1)
class EpsNet(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(3, 128), nn.SiLU(),
nn.Linear(128, 128), nn.SiLU(),
nn.Linear(128, 2),
)
def forward(self, x, t):
t = (t.float() / (T - 1)).unsqueeze(1)
return self.net(torch.cat([x, t], dim=1))
model = EpsNet().to(device)
opt = torch.optim.Adam(model.parameters(), lr=2e-3)
for step in range(4000):
x0 = real_data(512)
t = torch.randint(0, T, (x0.shape[0],), device=device)
eps = torch.randn_like(x0)
a = abar[t].unsqueeze(1)
xt = a.sqrt() * x0 + (1 - a).sqrt() * eps
pred = model(xt, t)
loss = ((pred - eps) ** 2).mean()
opt.zero_grad()
loss.backward()
opt.step()
@torch.no_grad()
def sample(n=2000):
x = torch.randn(n, 2, device=device)
for i in reversed(range(T)):
t = torch.full((n,), i, device=device, dtype=torch.long)
eps = model(x, t)
alpha_t = alphas[i]
abar_t = abar[i]
mean = (x - (1 - alpha_t) / (1 - abar_t).sqrt() * eps) / alpha_t.sqrt()
if i > 0:
abar_prev = abar[i - 1]
posterior_var = betas[i] * (1 - abar_prev) / (1 - abar_t)
x = mean + posterior_var.sqrt() * torch.randn_like(x)
else:
x = mean
return x.cpu()
real = real_data(2000).cpu()
gen = sample(2000)
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
ax[0].scatter(real[:, 0], real[:, 1], s=3)
ax[0].set_title('real')
ax[1].scatter(gen[:, 0], gen[:, 1], s=3)
ax[1].set_title('generated')
for a in ax:
a.set_aspect('equal')
plt.tight_layout()
plt.show()

如果生成分布完全散掉,先检查训练 loss 是否下降、数据尺度是否合理,以及反向公式中的 alpha_t / abar_t 是否与训练定义一致。这个实验最有价值的部分不是得到漂亮圆环,而是亲手确认“训练参数化与采样公式必须一致”。

在不改网络结构的情况下,可以做三个安全的小实验:

  1. 改变 TT 并重新训练,观察离散化和训练难度怎样变化;
  2. 画不同 tt 下的 xtx_t,检查前向噪声是否覆盖从高 SNR 到低 SNR 的区间;
  3. 把训练数据换成 8 个高斯簇,观察模型是否覆盖所有 mode。

不要从这个玩具直接推出“真实模型应该 20 步”“cosine 一定优于 linear”或“loss 低就说明生物上有效”。这些结论都需要真实任务的评价协议。

玩具实验可以验证数学和代码:前向闭式是否正确、网络能否学噪声、采样是否与训练参数化匹配。真实项目还要额外解决数据表示、条件信息、几何/离散约束、模型规模、独立测试划分和任务级验收。

如果下一步要使用 Hugging Face Diffusers,建议先核对 checkpoint 的 prediction_type、scheduler 配置和官方 pipeline,再做 sampler/NFE 消融。对于蛋白、分子和组学模型,应优先使用该模型作者提供的推理代码与 benchmark,而不是套用图像模型的 scheduler 默认值。

前 3 章回答扩散机制,第 4–5 章回答如何与其他生成模型和现代变体建立坐标系,第 6 章回答采样与条件控制,第 7–8 章回答如何选型、评估并落到生物任务。本章只负责让最核心的训练—采样闭环真正跑一次。

本页导航

目录