阶段 4 · 学会创造

VAE:为什么自编码器
不能"生成"新东西

上一章的自编码器压缩得挺好,但你想让它造一张猫时——它做不到。 问题不在解码器,在潜空间:那里是一堆互不相连的孤岛, 岛屿之间的海域,解码出来全是垃圾。

1

诊断:潜空间为什么是"孤岛"

普通自编码器的损失函数只有一项:重建误差。它只关心 "这个数据点还原得好不好",完全不关心点与点之间的空间长什么样。

于是编码器可以随意摆放这些点——只要彼此分得开就行。结果就是: 训练数据的位置被占满了,中间全是没定义的虚空。

互动 · 点一下潜空间的任意位置,看解码出来是什么

💡 试试点在"岛屿之间"

那是训练数据里从来没有过的位置。普通自编码器在那里没有任何约束, 解码器完全不知道该怎么办——输出就是随机的。
VAE 的全部工作就是:把这片海域填满,让整个潜空间变成一张连续的地图。

2

两处关键改动

对比项普通自编码器VAE
编码器的输出 一个点 z 一个分布:均值 μ 和标准差 σ
采样方式 直接用 z z = μ + σ ⊙ ε,其中 ε ∼ N(0, I)
⊙ 是逐个数相乘;ε 的每一维各自独立地从「平均 0、方差 1」的钟形分布(N(0, I))里抽
损失函数 只有重建误差 重建误差 + KL 散度
潜空间 散落的孤岛 连续、平滑、覆盖完整的地图
能生成吗 不能 能——从 N(0,I) 采样一个 z 直接解码

这行读作:KL 量两片分布差多远,0 就是一模一样;q(z|x) 是编码器给出的那片分布; ‖ 读作「相对于」;β 是 KL 的权重——第 4 节那个滑块拖的就是它。

💡 每一项在管什么

重建误差:逼解码器把学到的信息用起来(不能糊弄)。
KL 散度:逼每个数据点的编码分布贴近标准正态分布。
为什么这能填满海域?每片分布都是 N(0,I) 附近的一小团, 天然会彼此重叠、覆盖整个空间——空洞会少得多。

3

重参数化:一个让训练能跑起来的小技巧

VAE 有个看起来要命的问题:采样是一个随机操作,随机操作没有梯度。 「从分布里抽一个数」输出和 μ、σ 之间没有能求导的公式——反向传播走到 z = 采样(μ, σ) 就断了。 重参数化把随机性挪到 ε 身上——ε 照样是随机抽的,但它和 μ、σ 无关,抽好之后在这一次计算里就是个定数:z = μ + σ·ε 成了普通乘加,就能求导了。

互动 · 重参数化前后

❌ 直接采样 z ∼ N(μ, σ²)

随机数生成器没有梯度。
反向传播走到这里就断了,编码器完全收不到训练信号。
✅ 重参数化

把随机性挪到 ε 上,ε 不需要梯度。
μ 和 σ 就成了普通变量,梯度可以正常穿过。
💡 换个角度理解

重参数化做的事,就是“把随机性从计算图里挪到输入端”: z 是 ε 的确定性函数,ε 是外部喂进来的噪声——函数可导,随机采样那一步就没了。
这个技巧本身比 VAE 更有普适性:扩散模型(从噪声里一步步雕出图片,后面有专章)用它加噪声, 强化学习里的连续动作采样用它保梯度,Gumbel-Softmax(让「从一堆里选一个」也能求导的近似办法)也用它。

4

一场拉锯战:β 这个旋钮

两项损失在互相拉扯。KL 项盯着一件事:每片的编码分布要和标准正态 N(0, I)(先验)对齐; 编码器吐出的后验分布 q(z|x),就是被它按着的那片。理解这场拉锯,就理解了 VAE 的一切。

互动 · 拖动 β,看潜空间怎么变形(点云是按 β 画的示意,不是训练出来的)

示意:八个颜色代表八类数字,点云按 β 的规律画出来,不是训练结果;两个读数也按同一套点云算。

β会发生什么结果
≈ 0退化成普通自编码器 潜空间是散点,不能采样
0.5 ~ 2两项平衡 潜空间连续,重建也还行。常规选择
很大时β 远大于 1、解码器又很强时,正则项压倒重建,编码器干脆什么都不编码 后验坍塌:所有输入都被编码成同一个 N(0,I),解码器只能瞎猜
⚠️ 后验坍塌是 VAE 的经典失败模式

界限在这里:β 稍大于 1(β-VAE 常用 4 左右)能让各维更分开;β 大到 KL 压倒一切、 尤其解码器又很强时,编码器才放弃编码信息——同一个旋钮,适度是好事,过头就坍塌。

坍塌是怎么发生的:KL 权重一大,编码器发现"输出恒等于 N(0,I) 完全不用费力, 损失反而更低"——它放弃编码任何信息了。
症状:重建图像全都变成一个模糊的平均样子。
解法:KL 退火(训练前期把 β 从 0 慢慢升上去)、 β-VAE(显式控制权衡)、或者用 自由比特技巧(给每个潜变量维度留一点 KL 免罚额度)。 这也是为什么 VQ-VAE(潜空间换成离散字典,见第 6 节)会绕开这个 KL 项。

5

真正的好处:插值与算术

潜空间连续之后,会得到一些漂亮的性质。

一张图看懂 · 两个点之间插值

能力做法典型效果
插值在 z₁ 和 z₂ 之间线性走 平滑的过渡。如果是人脸,会看到连续的表情变化
潜空间算术z(微笑) − z(中性) + z(男生) 这种向量运算 类似词向量,可以编辑属性
随机生成从 N(0,I) 采样 z,解码 得到从未存在过的、但看起来合理的样本
异常检测算一下重建误差(误差大 = 解码器还原不出来) 误差明显大的判为异常
M

数学 · 两句话,把 VAE 讲完

前面五节讲直觉,这一节把式子摊开。整章只有两个式子:
① 怎么从一片分布里拿出一个点?——就是第 3 节那个重参数化;
② 怎么保证这片分布不跑偏、还能拼成一张连续地图?——就是第 4 节那个 KL 散度项。
下面两张图都是真算的:拖一下,数字和图形一起变。

互动 · μ 定位置、σ 定宽度,ε 是这里唯一的随机数

互动 · 每个符号管图上的哪一块

🎬 自己验一遍

回到第 4 节把 β 从 0 拖到 2:左图那片散点会慢慢收拢、重叠。

再回到上面这张图拖 σ —— 是同一个动作。
注意那张图里那条虚线(一个标准的 正态分布 N(0, I))本身不动, 它在量「你这片分布离先验有多远」; β 就是决定这个距离在损失里算多少分的那个数。

6

同一个想法,五种改法

这一章的 VAE 只是最早的一版。后面的人想让它多做一件事,就只动一个地方——每处改动长出一个新名字。

一张图看懂:五处改动,各改了哪一块

变体改了哪一点想换来什么
VAE(这一章) 编码器吐一片分布(μ、σ);损失 = 重建 + KL 潜空间连续,能采样、能插值
β-VAE 损失 = 重建 + β · KL(多出来的 β 就是权重) 想让每一维各管一种属性(解耦,也叫解缠)时,适度加大 β(β-VAE 常用 4 左右)逼它分开;过头会坍塌,见第 4 节
CVAE(条件 VAE) 编码器和解码器都多喂一个条件,比如类别标签 按条件生成:说「生成一个 5」,就生成 5
VQ-VAE 潜空间换成一本离散字典,去查表,不再从高斯里采样 绕开后验坍塌;给图像换来离散的「视觉 token」
潜空间扩散里的 VAE(Stable Diffusion 的 AutoencoderKL) 只当压缩器:图片先压进潜空间,扩散全程在里面跑,最后再解压 省算力——512×512×3 压成 64×64×4,要处理的数少了 48 倍

最容易和 VAE 混的是 VQ-VAE:它的潜空间不是连续分布,而是一本离散字典—— 编码器给出的是「第几号词」,不是 μ 和 σ。图像模型说的「视觉 token」就从它开始。

7

它输给 GAN 和扩散模型的地方

先说两个对手:GAN 是「造假者与鉴定师」的对抗游戏(下一章讲), 扩散模型把噪声一步步雕回图片(后面有专章)。每一条同时也是一份「什么时候别用 VAE」的清单。

一张图看懂:为什么「取平均」看起来就是糊的

问题根源什么时候真的会痛 → 换什么
图像永远是模糊的 重建损失(MSE)的最优解是条件均值——上面那张图: 同一个编码的几种可能细节,取平均就是糊的。 (平方误差下,猜它们的平均数时总误差最小——几个可能的答案,押中间最不吃亏。) 这是数学上的必然,不是训练不够。 要给人看的高清图 → GAN(判别器直接挑「像不像真的」)或扩散(一次只去一点噪声)
潜变量各维度常常混在一起 KL 把每片编码都按向 N(0,I),但各维之间没有「各管一摊」的约束。 解耦指的就是每一维只管一种属性(比如一维管粗细、一维管倾斜)。 有意思的是:β-VAE 加大 KL 权重,反而能让这些维度更分开 想单独调某个属性 → β-VAE
训练目标有偏 第 2 节那行「重建误差 + KL」,在论文里叫 ELBO(证据下界): 它比真正想要的「数据出现的概率」(似然)略小一点,但好算,所以拿它当损失。 下界离真值差多远依赖假设,实际会低估 要精确的概率值 → 换能直接算似然的模型
指标好 ≠ 看起来好 VAE 有可报的似然分数;GAN 连似然都没有,两者本来就不好直接比。 但人眼看上去,VAE 的图更糊。 这提醒我们:别拿一个和人的感受无关的数字下结论。 评价生成模型 → 似然和感知质量分开看

为什么 GAN 和扩散不糊?GAN 让判别器直接挑「像不像真的」,不受「均值最糊」约束; 扩散每次只去一点噪声,不用一步猜出全部细节。
而 VAE 留下的两样东西今天仍在用:重参数化(扩散模型也靠它)和潜空间—— Stable Diffusion 系、FLUX、Sora 都在潜空间里工作。这个思路直接来自 VAE。

8

小结

这一章只有一个想法,而且它跟大多数人的直觉正好相反: 不是「让模型学得更准」,而是故意让它学得没那么准。

它对应哪条线 ① 表达力 vs 泛化——KL 项就是那把「故意不让它拟合得太好」的刹车
一句话 这一章的做法是用「重建不够精确」去换「潜空间处处都有意义」: 牺牲每个样本上的表达力,换取整片空间的泛化。
它牺牲了什么 牺牲了重建的清晰度:MSE 的最优解是条件均值,均值天生就是糊的(第 7 节那张表是这笔账,回扣暗线 B)。

一张图看完这一章

💡 检验一下:你现在能指着哪个互动说这句话

回到第 4 节,把「β(KL 项的权重)」滑块从最左边 ≈0 往右拖到 2—— 左边那张潜空间图会从一堆散落的孤岛,慢慢收拢、重叠; 同时右边那行「类别之间的可分离程度」读数会一路往下掉——类别不再分得那么开。

那个瞬间就是「表达力换泛化」:滑块往右,模型松开「每个点都要重建准」的执念, 换来「任意位置都有东西可解码」。

两条损失,各自在管什么

重建逼模型记住细节,KL 逼潜空间连续,β 在中间调天平——第 4 节那个滑块拖的就是它。 采样那一步 z = μ + σ ⊙ ε 里没有可以省的东西:随机性全在 ε 身上(§M 那张卡)。

它在六条暗线里站在哪

暗线这一章的回答
A 信息流动 形状一路在变窄再变宽:MNIST(手写数字图,拉直就是一串 784 个数) → 编码器压成 d 维的 μ 和 σ(d 常常只有 2~20) → 采样出 z → 解码器撑回 784 维。 真正的瓶颈是中间那个 d。
VAE 比普通自编码器多输出一个 σ:流过去的不是一条线,是一片分布
B 什么被牺牲了 牺牲了逐点精确重建的自由:普通自编码器的重建可以做得非常清晰, 但潜空间是孤岛,从中间采样解码出来是垃圾。
VAE 把这份清晰度换成了潜空间连续 + 可采样 + 可插值, 而「清晰度」就输给了 GAN 和扩散模型(第 7 节)
C 参数账本 给几个能对照的真实数字:
・一个 MNIST 的 MLP-VAE(784→400→20)大约 65 万个参数 (编码器约 33 万 + 解码器约 32 万)。 编码器分两路输出:一路出 μ,一路出 log σ²(σ 的对数——神经网络输出任意实数,取对数才方便当优化目标);
・真正在用的是 Stable Diffusion 的 KL-f8 VAE:把 512×512×3 = 786432 个数 压成 64×64×4 = 16384 个数,压缩约 48 倍—— 扩散全程在这个潜空间里跑,省下来的算力就在这里
D 跑在什么上 带宽受限的那一类。解码器主要是卷积 + 逐元素运算(中间也夹着注意力层), 算术强度(每搬一次数据能做多少次运算)很低,GPU 大部分时间在等显存搬数据。
最贵的动作是解码:把 64×64×4 的潜码放大回 512×512×3, 第一层就变成 64×64×512(约 128 倍)——这正是「为什么要在潜空间做扩散」的硬件答案。 算力 / 带宽账见 《硬件与算力账本》
E 它假设了什么 假设「整张潜空间都能被一个标准正态先验覆盖」—— 真实数据在高维里聚成的那个曲面,可以被一个各向同性的高斯(各个方向都一样)完全罩住。 这个假设在简单数据(MNIST 数字)上成立,在复杂图像上就不成立, 会进一步加剧模糊(糊的主因是 MSE 的条件均值,见第 7 节)
F 违背了哪个直觉 「注入噪声」通常是坏事,这里却是好事。z = μ + σ·ε 里的 ε 是真随机噪声, 它看上去只是在干扰训练——但正是它让编码器不能把信息藏在某个具体坐标, 得把整个区域撑起来。
第二个反直觉:「重建误差更小」不等于「模型更好」(第 7 节那张表)
🎯 前后钩子

它接住了上一章的什么:普通自编码器能把数据压到低维(《embedding 与向量空间》那套思路), 但它压出来的是孤岛,不能当生成模型的底座。

它给下一章留了什么:这一章用 MSE 逼像,糊就是代价——下一章 《GAN 生成对抗网络》不比像素,只要骗过对手;更后面的 《扩散模型 DDPM》再把它和「逐步去噪」结合。

一句话带走 VAE

VAE = 给自编码器的潜空间加一条"必须像正态分布"的约束, 把散落的孤岛连成一张连续的地图。
编码器输出 μ 和 σ,用 z = μ + σ·ε 重参数化来保住梯度; 损失 = 重建 + KL,两项互相拉扯,β 就是那个旋钮,调太大(解码器又强时)容易后验坍塌。
它的图像生成质量输给了 GAN 和扩散模型——因为 MSE 的最优解就是模糊的。 但它留下的重参数化和潜空间两个思想,是今天主流生成模型的地基。

9

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式