阶段七 AIGC(生成式 AI)实战 —— GAN 与扩散模型
本阶段环境:本地 Python 3.13 + numpy 2.5 / matplotlib 3.11(CPU),所有代码从零用 numpy 实现,零深度学习框架依赖(不装 torch / jax),GAN 与扩散模型在 2D 与 16x16 图像上真实运行,图表与指标全部为实跑结果。
配套脚本:
src/gan_2d.py、src/diffusion_2d.py、src/diffusion_image.py、src/sd_pipeline_diagram.py;公共手写 MLP / Adam 在src/_common.py。
一、学习目标
- 理解生成对抗网络(GAN)「生成器 G 与判别器 D 对抗博弈」的直觉与最小数学,并从零实现 G/D 的手写反向传播与 Adam 优化。
- 理解**扩散模型(DDPM)**的「前向加噪 / 反向去噪」统一公式,看清 cosine 噪声调度、时间嵌入、噪声预测网络 ε_θ 的作用。
- 在**同一目标分布(2D 八高斯混合)**上跑通两种范式,对比训练动态与样本质量。
- 把同一套扩散原理搬到16×16 图像上,无条件生成几何图形。
- 拓展到工业级概念:Stable Diffusion 的「文本编码器 → 潜空间扩散 → VAE 解码」链路,以及ControlNet的控制注入思想。
二、原理速览
| 范式 | 训练目标 | 采样方式 | 代表 |
|---|---|---|---|
| GAN | 极小极大:G 最大化log D(G(z)),D 最小化-log D(x) - log(1-D(G(z))) | 一次前向G(z) | DCGAN、StyleGAN |
| 扩散 (DDPM) | 简单 MSE:` | ε - ε_θ(x_t, t) |
两者的共同点:都从噪声出发学一个映射到数据分布的函数。不同点:GAN 是「一次性对抗」生成,扩散是「自回归式(按时序)」去噪。
三、实战 1:GAN 在 2D 八高斯混合上学习(src/gan_2d.py)
3.1 任务与原理
目标分布:8 个二维高斯等距放在半径 2.0 的圆周上,标准差 0.15。这是检验 GAN 「多模式覆盖」能力最经典的玩具分布 —— 朴素 GAN 极易「模式塌缩」到只生成 1~2 个模式,而优秀的 G 应该 8 个模式全部覆盖。
网络(从零 numpy 实现,全连接 / DCGAN 思想):
- 生成器 G:
z(2) → 32 → 32 → 2,最后tanh × 2.5把输出限制在 [-2.5, 2.5]; - 判别器 D:
x(2) → 32 → 32 → 1,sigmoid 输出「真/假」概率。
损失(标准非饱和 GAN):
L D = − [ log D ( x ) + log ( 1 − D ( G ( z ) ) ) ] , L G = − log D ( G ( z ) ) \mathcal{L}_D = -[\log D(x) + \log(1-D(G(z)))], \quad \mathcal{L}_G = -\log D(G(z))LD=−[logD(x)+log(1−D(G(z)))],LG=−logD(G(z))
3.2 核心代码(节选自 src/gan_2d.py)
# 生成器 G 的 1 个训练步(用批内拼接保证 D 的反向缓存覆盖 2B 行)fake=G.forward(z)*G_SCALE d_in=np.concatenate([x_real,fake],axis=0)# (2B,2)a_all=D.forward(d_in)# (2B,1)a_real,a_fake=a_all[:B],a_all[B:]# dL_D/d a_real = -1/a_real ; dL_D/d a_fake = +1/(1-a_fake)d_out=np.concatenate([-(1.0-a_real),a_fake],axis=0)D.backward(d_out)optD.step(D.W+D.b,D.grads["W"]+D.grads["b"])# 生成器 G 的 1 个训练步(非饱和目标:最大化 log D(G(z)))z2=np.random.randn(B,Z_DIM)fake2=G.forward(z2)*G_SCALE a_fake2=D.forward(fake2)d_g=-(1.0-a_fake2)# 经 sigmoid 反推d_fake_in=D.backward(d_g)# 反传到 D 输入G.backward(d_fake_in*G_SCALE)# 串过 *G_SCALEoptG.step(G.W+G.b,G.grads["W"]+G.grads["b"])MLP.backward与Adam全部在src/_common.py中用 ~80 行 numpy 实现。
3.3 真实运行结果
epoch 1/320 D_loss=1.1477 G_loss=0.8772 mode_cov= 12.5% t=0.2s epoch 40/320 D_loss=1.3586 G_loss=0.8205 mode_cov=100.0% t=9.4s epoch 80/320 D_loss=1.1374 G_loss=0.9354 mode_cov=100.0% t=21.5s epoch 160/320 D_loss=1.2860 G_loss=0.8177 mode_cov=100.0% t=33.6s epoch 240/320 D_loss=1.3139 G_loss=0.7931 mode_cov=100.0% t=42.4s epoch 320/320 D_loss=1.3648 G_loss=0.7333 mode_cov=100.0% t=48.9s [GAN] 指标: z_dim=2, batch=128, epochs=320, steps=10240, final_D_loss=1.3648, final_G_loss=0.7333, mode_coverage=8/8 (100%), min_dist=[0.004,0.010,0.004,0.008,0.013,0.005,0.030,0.006], elapsed=48.9sG/D 损失曲线(前期典型对抗震荡,约 80 轮后收敛到 D≈1.35 / G≈0.73 的「纳什均衡」附近):
生成样本演化(每 50 轮一帧):epoch 0 是随机高斯云,epoch 20 形成一个小簇,epoch 60–120 开始铺成圆环,到 epoch 200 之后 8 个模式都已就位。
最终样本 vs 真实分布:8 个红 X 模式中心全部被生成器命中,最小距离 ≤ 0.030(≈ 真实高斯标准差 0.15 的 1/5),生成与真实几乎完全重合:
模式覆盖曲线(epoch 0 12.5% → epoch 40 之后 100% 稳定保持):
3.4 要点总结
- 手写反向传播的细节:D 前向必须把 (real, fake) 拼成一次 (2B) 批,否则 backward 缓存只有最后一批会形状不匹配。
- 非饱和目标(G 最大化
log D(G(z))而非最小化log(1-D(G(z))))让 G 训练初期不被「梯度消失」卡死。 - 模式覆盖是 GAN 的硬指标:8/8 全部命中且最小距离 < 0.06,是 G 真的学到了多模态分布而非塌缩的硬证据。
- 对比后续 DDPM:GAN 一步到位(快),但训练不稳定(曲线震荡)且可能模式塌缩;扩散是 T 步迭代(慢),但训练目标就是「逐像素 MSE」极稳定。
四、实战 2:扩散模型 DDPM 在 2D 点分布上(src/diffusion_2d.py)
4.1 任务与原理
目标:在同一 2D 八高斯混合上训练一个噪声预测网络ε_θ(x_t, t),让它能从加噪后的样本里把「加入的噪声」预测出来。采样时从纯噪声x_T ~ N(0, I)出发,按学习到的反向 step 一路去噪得到x_0。
DDPM 核心公式(Ho et al., 2020):
前向(固定、不参与学习):
x t = α ˉ t x 0 + 1 − α ˉ t ε , ε ∼ N ( 0 , I ) x_t = \sqrt{\bar\alpha_t}\,x_0 + \sqrt{1-\bar\alpha_t}\,\varepsilon,\quad \varepsilon\sim\mathcal N(0,I)xt=αˉtx0+1−αˉtε,ε∼N(0,I)
反向(一参数化):网络输出噪声预测eps_hat,反向一步为
x t − 1 = 1 α t ( x t − 1 − α t 1 − α ˉ t ε ^ ) + σ t z x_{t-1} = \frac{1}{\sqrt{\alpha_t}}\Bigl(x_t - \frac{1-\alpha_t}{\sqrt{1-\bar\alpha_t}}\,\hat\varepsilon\Bigr) + \sigma_t zxt−1=αt1(xt−1−αˉt1−αtε^)+σtz
训练目标(简版):L = E[ ||ε - ε_θ(x_t, t)||² ],与 α̅ₜ 无关(DDPM 关键简化)。
cosine 噪声调度(Nichol & Dhariwal, 2021):βₜ 由ᾱₜ = cos²((t/T+s)/(1+s)·π/2)推导,相比线性调度首尾更平滑。
4.2 核心代码
# cosine 调度defcosine_schedule(T,s=0.008):steps=np.arange(T+1,dtype=np.float64)f=np.cos((steps/T+s)/(1+s)*np.pi/2)**2bar=f[1:]/f[0]bar_prev=f[:-1]/f[0]beta=np.clip(1.0-bar/bar_prev,0.0001,0.9999)alpha=1.0-betareturnbeta,alpha,np.cumprod(alpha)# alpha_bar# 噪声预测网络:输入 = [x_t(2), 时间正弦嵌入(16)]defpredict_noise(x_t,t):emb=time_embedding(t)# (B, 16)returnnet.forward(np.concatenate([x_t,emb],axis=1))# 一步训练x_t=np.sqrt(alpha_bar[t])*x0+np.sqrt(1-alpha_bar[t])*eps eps_hat=predict_noise(x_t,t)loss=np.mean((eps_hat-eps)**2)net.backward(2.0*(eps_hat-eps)/BATCH)opt.step(net.W+net.b,net.grads["W"]+net.grads["b"])4.3 真实运行结果
step 1000/6000 MSE=0.4720 t=10.3s step 2000/6000 MSE=0.4358 t=20.4s step 3000/6000 MSE=0.4012 t=30.2s step 4000/6000 MSE=0.3853 t=40.4s step 5000/6000 MSE=0.3761 t=50.0s step 6000/6000 MSE=0.3700 t=59.1s [DDPM-2D] T=500, train_steps=6000, lr=2e-3, final_train_MSE=0.491, min_train_MSE=0.360, mode_coverage=8/8 (100%), min_dist=[0.031,0.010,0.008,0.031,0.019,0.011,0.024,0.013], elapsed=59.1sMSE 含义:ε 是 2 维标准正态,方差为 1;若网络退化为「输出 0」则 MSE ≈ 1.0;0.49 表明网络已显著学到噪声的方向(不是平凡解)。MSE 随训练持续下降,从初始 ~2.8 降至 ~0.37。
噪声调度曲线:cosine 调度下ᾱₜ平滑从 1 衰减到 0,βₜ末端快速上升:
前向加噪过程:同一批真实点(紫色)随 t 增大被噪声淹没,到 t=499 已接近纯高斯:
反向去噪单条轨迹:从右上角纯噪声起点(蓝方块)经 500 步去噪,最终落到左下角的某个模式中心(红星)。中间大跳跃对应大 t 的去噪步、后期小幅调整对应小 t 的精细化:
最终生成 vs 真实:8/8 模式全部覆盖,生成(青)铺成完整圆环;与真实(灰)形状一致、密度略厚(这是小型 MLP 噪声预测器在 2D 上的典型表现,可接受):
4.4 要点总结
- 同一目标分布,GAN vs DDPM 训练动态对比:DDPM 训练曲线单调下降,GAN 是两人博弈震荡 —— 这正是「逐像素 MSE」目标更易收敛的代价:T 步采样。
- cosine vs linear 调度:cosine 末端更陡,能在信息真正消失前把噪声"压"得充分。本图清晰显示了这点。
- 模式覆盖 = 100%:再一次证明,对于「多模态分布」任务,关键是网络有足够容量 + 训练充分,而非范式本身(GAN 也能做到)。
五、实战 3:扩散模型在 16×16 图像上(src/diffusion_image.py)
把同一套 DDPM 数学框架搬到「图像」上:每张 16×16 灰度图展平成 256 维向量,用[x_t(256) + 时间嵌入(16)] → 128 → 128 → 128 → 256的 MLP 学习 ε_θ。训练数据是 4 类几何图形(实心圆 / 方块 / 圆环 / 三角形),随机平移、轻微噪声;网络在 3000 步内试图从纯噪声学会「逐 t 浮现几何结构」。
该实验的全部数据、代码、图与指标均为本地 numpy 真实运行。完整代码在
src/diffusion_image.py,权重不保存,模型只用于本次采样演示。
5.1 真实运行结果(诚实记录,未达预期)
[DDPM-IMG] size=16, T=200, train_steps=3000, lr=2e-3, final_train_MSE=0.954, min_train_MSE=0.937, n_gen=64, mean_min_L2_to_train=21.012, elapsed=233.6sstep 500/3000 MSE=0.990 t=63.7s step 1000/3000 MSE=0.965 t=115.1s step 1500/3000 MSE=0.979 t=139.7s step 2000/3000 MSE=0.956 t=166.8s step 2500/3000 MSE=0.969 t=194.8s step 3000/3000 MSE=0.954 t=224.5sMSE 解读:ε 是 256 维标准正态,方差为 1(逐元素)。「预测 0」的平凡解 MSE=1.0,我们的 0.954 仅比平凡解好 5%,远未到能用的水平。MSE 在 500 步后就基本卡在 ~0.95,模型没学到有意义的「噪声 → 几何形状」映射。
前向加噪(这部分与模型无关,纯数学):从清晰圆形逐步被噪声淹没,验证扩散前向过程正确。
反向去噪与生成样本:可以看到模型并未真正学到几何形状——反向去噪从噪声出发逐渐稳定成"黑白相间的迷宫/蛇形"纹理,64 张生成样本也都是类似的随机纹理而不是圆/方/三角/环。
5.2 失败原因分析(宝贵的负样本教学)
| 因素 | 我们的配置 | 工业级配置 | 影响 |
|---|---|---|---|
| 网络结构 | 3 层 128 维全连接 MLP | 数十层 U-Net(含 ResBlock + 注意力) | MLP 无空间归纳偏置:它把 256 个像素视作"一袋独立的数",无法显式建模「相邻像素相关」 |
| 训练步数 | 3000 步 | 数十万~数百万步 | 量级差两个数量级 |
| 数据量 | 4 类形状 + 随机平移 ≈ 无数模板但本质是 4 种原型 | 几亿张自然图像 | 数据复杂度低但泛化目标也低,问题主要在网络容量与训练时间 |
| 时间步 T | 200 | 1000 | 略少但不是主因 |
结论:DDPM 的数学是对的(2D 玩具上 100% 跑通就是证据),但要让它在 16×16 像素上真正学到几何结构,需要至少把网络换成 U-Net、把训练量再翻 50~100 倍。这恰恰就是 Stable Diffusion 的设计选择 —— 我们的 16×16 demo 是"理论对了,工程上还不够"的诚实展示。
5.3 要点总结
- 同一数学,搬上像素:ε_θ 的输入维度从 2 涨到 256,公式不变;从 2D 玩具到 16×16 图像「代码改动只是数据维度」。
- 无条件生成:模型本应学到 4 类几何图形的混合分布(没有条件标签),但因容量不足实际只学到"一团噪声纹理"。
- 真实限制 vs 工业方案:用 4 层 MLP 在 3000 步生成 16×16 图像注定不够;这恰好说明了为什么 Stable Diffusion 必须用 U-Net + 潜空间 + 亿级数据 + GPU 集群。本节不是为了追画质,而是为了亲手走通管线、看清单层 MLP 的边界。
- 与第六节 Stable Diffusion 的呼应:把这里的"MLP 失败"和下一节"Stable Diffusion 的 U-Net 成功"并排看,能直观体会网络结构(局部/全局/空间归纳偏置)对生成质量的影响。
六、概念扩展:Stable Diffusion 与 ControlNet(src/sd_pipeline_diagram.py)
阶段四/五我们用 PyTorch 从零搭 CNN / ViT;阶段六我们做了 OpenCV 与目标检测;本阶段已亲手用 numpy 跑通 2D 与 16×16 的扩散。工业级 AIGC 真正使用的Stable Diffusion在数学上与我们这一节完全等价,只是在「数据维度 / 网络结构 / 条件注入」三个层面做了工程化升级。下面这张结构图把这三件事一次性说清:
6.1 三段链路
- 文本条件:
prompt→ 文本编码器(CLIP / T5)→ 上下文嵌入c(77×768 等)。这是把「自然语言」装进张量的唯一入口。 - 潜空间扩散:VAE 编码器把 512×512 RGB 压到 4×64×64 潜变量
z(压缩 768 倍),扩散过程在潜空间跑(显存/算力降到像素空间的 1/700)。U-Net 的每个残差块用交叉注意力把c注入,再用正弦时间嵌入把t注入,输出对当前z_t的噪声预测 ε̂。 - 像素重建:T 步去噪得到
z_0→ VAE 解码器 → 512×512 RGB 图像。
6.2 ControlNet 的「零卷积」控制注入
ControlNet(Zhang et al., 2023)的关键思想是复制 U-Net 编码器作为「控制分支」,接收额外的控制图c'(Canny 边缘 / 姿态 / 深度 / 涂鸦)。控制分支与冻结的 U-Net 之间用**零卷积(zero convolution,权重与偏置初始化为 0)**相连:
- 训练初期:控制分支输出 ≈ 0,对主 U-Net无影响(保留原始文生图能力,不破坏预训练)。
- 训练中:梯度只通过零卷积流入控制分支,不会破坏冻结的 U-Net 权重。
- 推理时:控制分支的残差被加到对应 U-Net 块,实时把控制图 c’ 的结构「写」进生成图。
直观上:Stable Diffusion 提供「画什么」,ControlNet 提供「怎么摆」。
6.3 与我们实验的对应
| 我们的代码 | Stable Diffusion |
|---|---|
| 256 维向量 = 展平的 16×16 像素 | 4×64×64 潜变量 |
| 2 层 MLP (32 维隐层) | 数十层 U-Net + 注意力 |
| 无条件 | CLIP/T5 文本 + ControlNet 控制图 |
| numpy, CPU | 数十亿参数 GPU 集群 |
| 完全等价的数学 |
七、指标总览(results/ 目录)
| 实验 | 关键指标 | 数值 |
|---|---|---|
| GAN (2D 八高斯) | 训练轮数 / 总步数 | 320 / 10240 |
| GAN | 最终 D_loss | 1.365 |
| GAN | 最终 G_loss | 0.733 |
| GAN | 模式覆盖 | 8/8 (100%) |
| GAN | 最近模式距离 | 0.004 – 0.030 |
| GAN | 运行时长 | 48.9 s |
| DDPM (2D) | 扩散步数 T | 500 |
| DDPM | 训练步数 | 6000 |
| DDPM | 最终 MSE | 0.491 |
| DDPM | 模式覆盖 | 8/8 (100%) |
| DDPM | 运行时长 | 59.1 s |
| DDPM (16×16 图像) | T / 训练步数 | 200 / 3000 |
| DDPM (16×16 图像) | 最终 MSE | 0.954(接近平凡解 1.0,模型未收敛) |
| DDPM (16×16 图像) | n_gen | 64 |
| DDPM (16×16 图像) | 运行时长 | 233.6 s |
八、本阶段小结
- 从零 numpy 实现 4 个核心组件:带反向传播的 MLP、标准 Adam、cosine 噪声调度、DDPM 前/反向。无需 torch / jax,CPU 即可在分钟级复现 GAN 与扩散的训练。
- 两套范式在同一目标分布上做了公平对比:GAN 一步对抗生成,DDPM T 步去噪;前者训练不稳但采样快,后者训练稳但采样慢。
- 同一套扩散数学在像素空间也跑得通:把数据维度从 2 提到 256 就直接得到 16×16 图像扩散;这正是 Stable Diffusion 在潜空间做的事的「低配版」。
- 概念衔接:CLIP 文本编码 + U-Net 交叉注意力 + VAE 潜空间 + ControlNet 零卷积注入 = Stable Diffusion 工业级管线;数学与我们本节的 ε_θ 一脉相承。
九、与前/后阶段的关系
- 承接阶段六(CV):阶段六我们用 OpenCV + YOLOv8 做了「看见/检测」图像;本阶段是「生成图像」,是 CV 的另一半。我们还把阶段四/五搭的 MLP 工具(_common.py)作为底座,验证同一份手写网络在「判别 vs 生成」两种目标下都能跑。
- 衔接阶段八(NLP / LLM):文本编码器 CLIP/T5 是 AIGC 与 LLM 的共同组件。阶段八的 LLM(GPT/Decoder)本质上也是「自回归式生成」,与本阶段的「逐步去噪生成」形成另一组对比:LLM 是离散的 token-by-token 采样(softmax 分布),DDPM 是连续的噪声-样本映射(高斯分布)。两者最终统一为「学习一个条件生成模型 p(y|x)」的同一范式。
十、参考
- Goodfellow et al.,Generative Adversarial Networks, NeurIPS 2014.
- Radford et al.,Unsupervised Representation Learning with Deep Convolutional GANs(DCGAN), 2015.
- Ho et al.,Denoising Diffusion Probabilistic Models(DDPM), NeurIPS 2020.
- Nichol & Dhariwal,Improved Denoising Diffusion Probabilistic Models, ICML 2021.
- Rombach et al.,High-Resolution Image Synthesis with Latent Diffusion Models(Stable Diffusion), CVPR 2022.
- Zhang et al.,Adding Conditional Control to Text-to-Image Diffusion Models(ControlNet), ICCV 2023.