AI/ML 详解:生成模型

Day 20 · 2026-06-06 · 难度 ★★★★☆
面向:有编程经验的非 AI 方向工程师

前面几天的模型大多在做判别(discriminative):给一张图判断猫狗,给一句话预测下一个 token。今天换一个根本不同的问题——生成(generative):不是判断现有数据,而是凭空造出从未存在、却像真的的新样本,这是 Stable Diffusion 画图、Sora 生成视频背后的核心。我们走一遍 2014–2022 的进化线:GAN → VAE → Diffusion → Flow Matching,每一步都是对前一步某个具体缺陷的精确回应。理解它,你会看清「图像生成」不是魔法,而是四种「如何把随机噪声搬运成真实数据」的工程答案。

生成对抗网络GAN · Generative Adversarial Network

对抗博弈隐式建模
一句话类比

GAN 是造假者 vs 验钞机的军备竞赛。更贴你的后端世界:像一个 fuzzer(生成器) 不断造出畸形输入,去骗过一套 断言检查器(判别器);检查器每被骗一次就升级规则,fuzzer 又被迫造得更逼真。两者互相施压、协同进化,最后 fuzzer 造出的「假数据」逼真到检查器只能瞎猜——此时生成器就学会了真实数据的分布。

它解决什么问题 + 工作机制

生成的根本难题:真实数据(如所有「人脸」)的概率分布 p(x) 极其复杂,显式写不出公式。GAN(Goodfellow et al. 2014)的天才之处是绕过它——不直接建模 p(x),而是训练两个网络对抗:

  • 生成器 G:吃一个随机噪声向量 z,输出一张假图 G(z)。目标是骗过 D;
  • 判别器 D:吃一张图,输出「这是真图的概率」。目标是分辨真假。

关键洞察:判别器本身就是一个「可学习的 loss 函数」。手工设计的 loss(如像素级 MSE)会逼模型生成模糊的平均脸;而 D 会随 G 进步不断变聪明,提供一个永远「恰到好处」的训练信号。两者玩一个 minimax 博弈(极小极大)

对抗训练循环

噪声 z生成器 G假图 G(z)

真图 x────────────→判别器 D 真/假 概率

D 想:正确分类(真→1,假→0) G 想:让 D 把 G(z) 判成真(→1)
↑ 两个目标直接对立 → 此消彼长 → 收敛到纳什均衡时 G 学会真实分布

那个 minimax 目标函数写出来是:
minG maxD  E[log D(x)] + E[log(1 − D(G(z)))]
拆开看:第一项 D 想让 D(x) 趋近 1(真图判真);第二项 D 想让 D(G(z)) 趋近 0(假图判假),而 G 想反过来让它趋近 1(骗成功)。两者抢同一个式子的最大/最小——这就是「对抗」二字的数学含义。

代码示例
import torch, torch.nn as nn

G = nn.Sequential(nn.Linear(100, 256), nn.ReLU(), nn.Linear(256, 784), nn.Tanh())
D = nn.Sequential(nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 1), nn.Sigmoid())
bce = nn.BCELoss()  # 二分类交叉熵:真=1, 假=0

for real in dataloader:        # real: 一批真图 (B, 784)
    z = torch.randn(real.size(0), 100)
    fake = G(z)
    # ① 训判别器:真图判真、假图判假(fake.detach 切断 G 的梯度)
    loss_D = bce(D(real), ones) + bce(D(fake.detach()), zeros)
    opt_D.zero_grad(); loss_D.backward(); opt_D.step()
    # ② 训生成器:让 D 把假图判成真(标签故意填 1)
    loss_G = bce(D(fake), ones)
    opt_G.zero_grad(); loss_G.backward(); opt_G.step()
常见误区 + 实践场景
"GAN 训练就是普通的梯度下降"——错。它在解一个动态博弈,不是优化一个固定 loss。最臭名昭著的失败是 mode collapse(模式坍缩):生成器发现「只画一种特别逼真的人脸」就能稳定骗过判别器,于是放弃多样性,反复生成几乎相同的样本。本质是它找到了博弈的一个退化捷径。这也是 GAN 后来被 Diffusion 取代的核心原因——稳定性太差。
📌 BigCat 场景:GAN 的对抗范式是可迁移的跨学科思维工具。做决策时让一个 AI 扮演「提案者(G)」、另一个扮演「红队批判者(D)」互相施压迭代——这套「对抗式自我博弈」能逼出比单轮思考更稳健的方案,和你熟悉的红蓝对抗同源。
Takeaway + 思考题
💡 GAN 的革命不在网络结构,而在用一个可学习的判别器替代手工 loss——让「什么是逼真」由数据自己定义。
🤔 当 loss 函数本身可以被学习、还会随对手进化,"优化目标"这个概念是否还稳定?这对你设计任何「目标会漂移」的系统有什么启发?

变分自编码器VAE · Variational Autoencoder

概率隐空间重参数化
一句话类比

VAE 是一个带不确定性的有损 codec。普通 autoencoder 像 JPEG:把图压成一个固定的压缩码(latent code),再解压还原。但 VAE 不把图编码成一个点,而是编码成一小团概率云(一个高斯分布)——类似数据库里把记录存成「带误差棒的索引」而非精确坐标。这一改让整个隐空间连续、无空洞,随手在里面采样一个点解码,就能得到一张全新的合理图像。

它解决什么问题 + 工作机制

普通 autoencoder 能压缩还原,但不能生成:它的隐空间「布满洞」,你在两个已知点之间随便采一个,解码出来往往是噪声垃圾——因为它从没被要求让隐空间「填满」。VAE(Kingma & Welling 2013)的解法是强制隐变量服从标准正态分布,靠两个力量拉扯达成:

  • 重构损失:解码出来要像原图(保证信息没丢光);
  • KL 散度:编码出的高斯分布要贴近标准正态 N(0,1)(保证隐空间被规整地填满、可采样)。

两者之和就是著名的 ELBO(证据下界)——最大化它等价于最大化数据似然。但这里有个工程障碍:「采样」这个操作不可微,梯度没法穿过随机节点回传到编码器。VAE 的杀手锏是 重参数化技巧(reparameterization trick)

VAE 数据流 + 重参数化技巧

原图 x编码器μ, σz = μ + σ·ε解码器重构 x̂

ε ~ N(0,1) 随机性"外置"到这里

把随机采样 z~N(μ,σ) 改写成 z=μ+σ·ε,ε 独立采样。
这样 μ,σ 在确定的计算路径上 → 梯度能穿过 → 编码器可训练

直觉:随机性是「生成多样性」的来源,却挡住了梯度。重参数化把随机部分(ε)抽离成外部输入,剩下 μ、σ 全在可微主干上——既保留随机性,又让梯度畅通。这个技巧后来在强化学习、扩散模型里反复出现。

代码示例
import torch, torch.nn.functional as F

def vae_step(x, encoder, decoder):
    mu, logvar = encoder(x).chunk(2, dim=-1)   # 编码器同时吐 μ 和 log(σ²)
    # 重参数化:z = μ + σ·ε,σ = exp(½·logvar)
    std = torch.exp(0.5 * logvar)
    eps = torch.randn_like(std)              # ε ~ N(0,1),随机性外置
    z = mu + std * eps
    x_hat = decoder(z)
    # ELBO = 重构损失 + KL 散度(让隐分布贴近 N(0,1))
    recon = F.mse_loss(x_hat, x, reduction="sum")
    kl = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
    return recon + kl                       # 最小化它 = 最大化 ELBO
# 生成新样本:z = torch.randn(...); decoder(z) —— 无需编码器
常见误区 + 实践场景
"VAE 生成的图比 GAN 差,所以 VAE 没用"——这是只看像素质量的偏见。VAE 的图确实偏模糊(高斯假设 + 逐像素重构损失会"求平均",抹掉锐利细节),但它换来 GAN 没有的两样东西:训练稳定 和一个结构化、可插值的隐空间。今天的 Stable Diffusion 其实跑在 VAE 压缩出的隐空间里——VAE 把大图压成小特征图,扩散在这个低维空间里做,算力省百倍。VAE 没被淘汰,只是换了岗位。
📌 BigCat 场景:把 VAE 隐空间理解成一个「语义坐标系」——这是「表示学习」的直觉原型,也是「把模糊概念映射到可比较、可插值空间」的数学化身。理解 VAE 能帮你看清后面 embedding(Day 30)为什么能"语义做算术"。
Takeaway + 思考题
💡 VAE 的核心遗产不是生成质量,而是重参数化技巧「把数据编码进一个规整概率空间」的思想——这两样支撑了后续几乎所有生成模型。
🤔 VAE 用 KL 散度把隐空间"压平"成标准正态。这种"为了可采样而牺牲一点重构精度"的取舍,和你熟悉的哪些有损系统设计同构?

扩散模型Diffusion Models · DDPM

迭代去噪分治SOTA
一句话类比

扩散模型把「一步生成」这个难问题,拆成几百步微小的去噪修正——和分布式系统里「把一次大事务拆成幂等的小步骤、逐步收敛到目标状态」是同一种智慧。具体地:前向过程像让一张清晰照片在噪声里慢慢"腐烂",每步加一点高斯噪声,几百步后变成纯雪花点;反向过程训练一个网络把这个腐烂过程一步步倒放,从纯噪声里逐渐"显影"出一张图。

它解决什么问题 + 工作机制

GAN 不稳定、会坍缩;VAE 模糊。扩散模型(Ho, Jain & Abbeel 2020,即 DDPM)用一个朴素到惊艳的思路同时解决两者:把生成拆成大量极简单的子任务。它有两个过程:

  • 前向加噪 q:固定、无需学习。给真图 x₀ 反复加高斯噪声,T 步后 x_T ≈ 纯噪声。数学上可一步跳到任意 t,不必真跑 t 次;
  • 反向去噪 p:唯一要训练的部分。网络 εθ(x_t, t) 学习:「给定带噪图 x_t 和步数 t,预测里面加进去的噪声」

最妙的是 loss 被简化到极致——就是个噪声回归
L = ‖ ε − εθ(x_t, t) ‖²
其中 ε 是前向时真实加进去的噪声(你自己加的,是已知答案),εθ 是网络预测。这就是普通的均方误差监督学习——没有对抗、没有 KL 配平,训练稳如磐石。这是扩散打败 GAN 的根本原因。

前向加噪(固定) vs 反向去噪(训练)

x₀ 真图→+噪→x₁→+噪→→+噪→x_T 纯噪声
↑ 前向 q:逐步加高斯噪声,无需学习("腐烂")

x_T 纯噪声→去噪→→去噪→x₁→去噪→x₀ 新图
↑ 反向 p:网络 εθ 每步预测并减去噪声("显影")—— 采样从纯噪声出发
代码示例
import torch, torch.nn.functional as F

def diffusion_loss(x0, model, alpha_bars):
    B = x0.size(0)
    t = torch.randint(0, T, (B,))         # 每张图随机选一个时间步 t
    ab = alpha_bars[t].view(B, 1, 1, 1)  # ᾱ_t:累积保留比例
    noise = torch.randn_like(x0)         # ε:真实加进去的噪声(已知答案)
    # 一步直接生成 x_t(闭式解,不用真跑 t 次)
    x_t = ab.sqrt() * x0 + (1 - ab).sqrt() * noise
    pred = model(x_t, t)                  # 网络预测噪声
    return F.mse_loss(pred, noise)        # 就是噪声回归,简单稳定
# 采样:从 x_T~N(0,1) 出发,循环 T 步,每步减去 model 预测的噪声
常见误区 + 实践场景
"扩散模型在脑子里画完整张图,再一次输出"——错。它从纯噪声出发,迭代几十到上千步逐渐去噪,每步只做一点点修正。这也暴露了扩散最大的代价:采样慢。GAN/VAE 一次前向就出图,扩散要跑几百次网络前向。整个加速史(DDIM、蒸馏、潜空间扩散、以及下一节的 Flow Matching)几乎都在和「步数太多」搏斗。稳定性是用速度换来的。
📌 BigCat 场景:扩散的「把难问题拆成大量幂等小步、逐步收敛」是极强的通用方法论。处理棘手的大重构 / 大决策时,与其追求"一次想清楚完美方案",不如设计成「从粗糙草稿出发,每轮只做一处小修正」的迭代去噪式流程——更稳、更不易卡死,和你熟悉的渐进式迁移、灰度发布同构。
Takeaway + 思考题
💡 扩散模型的胜利是「分治 + 简单监督」对「一步到位 + 复杂博弈」的胜利——把生成拆成几百个简单回归,换来无与伦比的训练稳定性。
🤔 扩散用「更多计算步数」换「更好的训练稳定性和质量」。在你做过的系统里,哪些地方也存在这种"用运行时迭代换设计期简洁"的根本权衡?

流匹配Flow Matching · Rectified Flow

速度场ODE前沿
一句话类比

如果说扩散是让噪声点做布朗运动式的随机游走慢慢挪到数据,流匹配就是把这条曲折路径拉成一条直线传送带。它学一个「速度场(velocity field)」——空间里每个点都标注「此刻该往哪个方向、以多快的速度流动」,就像流体力学里的流场,或者地图导航里每个路口的箭头。沿着这个场走一条确定的直线(ODE),就能把噪声点精准搬运到数据点——路更直、步数更少。

它解决什么问题 + 工作机制

扩散虽稳但慢且数学绕(涉及随机微分方程 SDE、score 函数)。流匹配(Lipman et al. 2022)把它极度简化:直接学一个向量场 vθ(x, t),把噪声分布「流」成数据分布。训练目标简单到不可思议——给定噪声点 x₀ 和数据点 x₁,连一条直线,那么任意时刻 t 的位置和速度都是闭式的:

  • 路径:x_t = (1−t)·x₀ + t·x₁ (直线插值,t 从 0 到 1);
  • 目标速度:就是这条直线的方向 x₁ − x₀(恒定,因为是直线);
  • loss:让网络预测的速度逼近它 —— ‖ vθ(x_t, t) − (x₁ − x₀) ‖²

又是一个朴素的回归!直觉:网络在学「站在路径任意一点,该朝哪个方向走才能到数据」。生成时从噪声 x₀ 出发,用普通 ODE 求解器沿速度场积分到 t=1 就得一张图——因为路径接近直线,很少几步就能走完

扩散的弯路 vs 流匹配的直线

扩散(SDE 随机游走):噪声 ⟿ ⟿ ⟿ ⟿ ⟿ ⟿ 数据 弯弯绕绕,需几百步

流匹配(ODE 直线): 噪声 ───────→ 数据 一条直线,几步到位

速度场 v(x,t):每个点告诉你"往哪流"。最优传输路径 = 直线 = 最省力

一个深刻的统一:扩散其实是流匹配的特例(对应一种弯曲高斯路径)。流匹配把生成建模重新表述为「学一个把噪声搬到数据的速度场」,扩散只是其中一种走法——这种视角统一,正是它成为 2024–2025 前沿主流的原因。

代码示例
import torch, torch.nn.functional as F

def flow_matching_loss(x1, model):
    x0 = torch.randn_like(x1)            # 起点:纯噪声
    t = torch.rand(x1.size(0), 1)        # 路径上随机取一个时刻 t∈[0,1]
    # 直线插值:x_t 在噪声 x0 与数据 x1 之间
    x_t = (1 - t) * x0 + t * x1
    target = x1 - x0                     # 直线的速度(恒定方向)
    pred = model(x_t, t)                 # 网络预测此处的速度场
    return F.mse_loss(pred, target)       # 又是一个朴素回归
# 采样:x = randn(...); 用 ODE 求解器沿 model 的速度场从 t=0 积分到 t=1
常见误区 + 实践场景
"流匹配是和扩散完全不同的新流派,要重学一套"——错。它和扩散同源:都是「把噪声分布连续变形成数据分布」,区别只在路径不同(流匹配偏好直线/最优传输,扩散是弯曲路径)。代码上你已看到,四个模型的 loss 核心都坍缩成同一个 MSE 回归,变的只是「回归什么目标」。看穿这点,你就不会被层出不穷的新名词(rectified flow、stochastic interpolant…)唬住——它们是同一框架的不同实例。Stable Diffusion 3、Flux 等 2024–2025 主流图像模型已转向流匹配。
📌 BigCat 场景:流匹配「用直线取代随机游走」体现了一个深刻原则——找到任务的最优传输路径,能极大压缩步数。这和你做工作流设计时「消除不必要的中间态、让数据走最短路径」的直觉完全一致。理解这层,再看任何「迭代式生成」系统都能问:它走的是弯路还是直线?
Takeaway + 思考题
💡 流匹配把生成统一为「学一个搬运噪声到数据的速度场」,并指出扩散只是其中一种走法——用最简单的直线路径换来更快采样。
🤔 四种模型,loss 最终都坍缩成一个 MSE 回归,差别只在「回归什么 / 走哪条路径」。当一堆看似不同的方法被证明是同一框架的特例,这对你判断"技术热点"的真假信号有什么启发?

深入资源Further Reading

深入思考Deep Questions

1. 四种模型——GAN、VAE、Diffusion、Flow Matching——本质上都在解同一个问题。那个共同问题用一句话怎么概括?它们的根本分歧又在哪?
共同问题:如何把简单分布(高斯噪声)变形成复杂分布(真实数据),从而能采样新样本。所有生成模型都是「噪声 → 数据」的搬运工,分歧只在怎么搬GAN 一步直接搬,靠对抗博弈定义「像不像」——快但不稳、会坍缩;VAE 经过规整的概率隐空间,用 ELBO 建模似然——稳但模糊;Diffusion 拆成几百步沿弯曲路径的去噪回归——稳且高质但慢;Flow Matching 把路径拉直成 ODE 直线、学速度场——又稳又快。看穿「都是噪声到数据的分布搬运」,你就有了俯瞰整个领域的统一视角。
2. GAN 一步出图、扩散要几百步,但扩散反而赢了。"更多计算步数 = 更差"这个工程直觉为什么在这里失效?
因为真正的瓶颈不是推理速度,而是训练可达性(trainability)。GAN 一步出图省了推理算力,却把全部难度压进一个极难优化的对抗博弈——坍缩、不收敛、对超参极度敏感,很多任务根本训不出来。扩散把同样的难度摊薄到几百个简单去噪步,每步都是稳定的 MSE 回归,于是「稳定训练到高质量」本身才成为可能。这是个深刻的权衡:把难优化的问题,换成计算量更大但每步都简单可解的问题,整体反而更优。分布式里也同构——用更多轮幂等小步重试换最终一致性,而非赌一次复杂原子大操作。算力便宜,"训不出来"才是真成本。
3. VAE 的重参数化技巧、扩散的噪声预测、流匹配的速度回归——三者都把"随机生成"巧妙转化成了"确定性的可微回归"。这个反复出现的模式说明了什么?
它揭示了深度学习的底层方法论梯度下降只能优化确定、可微、有明确监督信号的目标,所以一切「学习随机生成」的努力,最终都要把随机性挪到梯度路径之外,只在主干留下干净的回归问题。VAE 用重参数化把 ε 外置;扩散把「自己加的噪声」当已知答案;流匹配把「直线速度」当目标——三者都在制造「自监督的确定性标签」。这也解释了它们为何极稳:一旦还原成「有标准答案的回归」,深度学习就开足马力。反过来 GAN 之所以难,正因它没有固定标签——它的「标签」(判别器)一直在动。
4. 把"生成"和"判别"看成一枚硬币的两面:判别模型学 p(标签|数据),生成模型学 p(数据)。为什么学 p(数据) 难那么多?这对理解大语言模型有何启发?
判别只需在数据给定下切一条决策边界,是低维、有界的问题。而建模 p(数据) 要捕捉整个高维数据流形的全部结构——所有可能的人脸及它们之间所有连续过渡——这是天文数字级的高维分布,没有闭式公式,正是四种模型要绕过的核心难点。启发:大语言模型其实就是生成模型,它学 p(下一个 token | 上文),自回归地把联合分布 p(文本) 拆成一连串条件概率的乘积——这是又一种「绕过高维联合分布」的策略(链式法则拆解,而非噪声搬运)。所以 GPT 和 Stable Diffusion 是同一枚硬币:都在学 p(数据),只是一个用自回归分解、一个用连续变形。理解这条主线,你就握住了当代 AI 两大支柱(语言 + 视觉)共同的数学根基。
5. 从 GAN(2014)到 Flow Matching(2022)只用了 8 年,每一步都在简化前一步。这种"越先进越简单"的演化,和你在分布式系统等领域见过的技术演化规律一致吗?
高度一致,揭示了成熟领域的普遍规律:早期靠"巧妙的复杂机制"突破,成熟期靠"找到正确的简单抽象"统治。GAN 的对抗博弈绝妙但脆弱;扩散用「简单回归 + 多步」把复杂性从「机制」转移到「计算量」;流匹配进一步指出「其实就是学一个速度场,扩散只是特例」,用更干净的数学统一全局。这和你熟悉的演化同源:共识从 Paxos 到更易懂的 Raft,并发从手写锁到 async/await。共同模式是——真正的进步往往不是加东西,而是找到一个让原有复杂性"显得理所当然"的新视角。给「AI 超级个体」的启发:评估新技术时,与其问"它多强大",不如问"它是否让某类问题更简单、更统一"——后者才是会留下来的信号。