深度学习入门站 · 理解深度学习
首页 › 第 17 章

变分自编码器

把数据压缩进一个"潜空间",再从潜空间生成新样本。
图 17-1:变分自编码器(VAE)结构与隐空间
图 17-1:变分自编码器(VAE)结构与隐空间

这一章我们认识一个既能"看懂"图片、又能"凭空画出"图片的模型——变分自编码器(Variational Autoencoder,简称 VAE)。它的思路很妙:先把图片压缩成很小的"浓缩向量",再靠它还原;但光会压缩还不够,为了能生成新图,它还要让这个"浓缩空间"变得连续、有规律。学完这一章,你会明白自编码器和 VAE 差在哪、"重参数化"是怎么回事,以及为什么 VAE 画出来的图总是有点模糊。

自编码器:先学会"打包压缩"

想象你开了一家快递公司。客户送来一个大箱子(一张图片,比如 784 个像素),你要把它压成一张小纸条(一个很小的向量),寄到另一个城市,那边的同事凭纸条把箱子重新装好。这个"压缩再还原"的结构就是自编码器(Autoencoder,简称 AE),它分两半:前半段叫编码器(Encoder)$g(x, \theta)$,把图片 $x$ 压成低维向量 $z$;后半段叫解码器(Decoder)$f(z, \phi)$,把 $z$ 还原成图片 $\hat{x}$:

$$\hat{x} = f[g(x, \theta), \phi]$$

这个式子意思是:图片先经过编码器变成浓缩向量,再经过解码器变回图片。训练时只提一个要求——还原得越像越好。衡量"像不像"的是重建损失,比如逐像素的均方误差:

$$\mathcal{L} = \|x - \hat{x}\|^2$$

意思是:原图和还原图每个像素差一点就记一笔账,差得越多扣分越多;损失越小,压缩得越"无损"。但训练完的 AE 只会"压缩-还原":每张图片只对应潜空间里孤零零的一个点,点与点之间的大片区域解码器根本没见过。想"凭空生成",随便指一个点,得到的往往是乱码。

📦
生活类比
AE 就像快递打包:把大箱子压缩成一张写满要点的小纸条,另一头的快递员凭纸条"脑补"还原。纸条写得越精炼,还原越接近原样;可要是纸条是瞎编的,还原出来就是一坨乱码。

VAE 的关键改动:输出"一个区域"而不是"一个点"

想让潜空间连续、有规律,变分自编码器只做了一个看似很小的改动:编码器不再输出一个确定的点,而是输出一个区域——均值 $\mu$ 和方差 $\sigma^2$,意思是"这张图片对应的潜变量大概落在这片区域里"。然后从这片区域里随机采一个样 $z$,再交给解码器:

$$q(z|x, \theta) = \mathcal{N}_z\left[g_\mu(x, \theta), g_\Sigma(x, \theta)\right]$$

这个式子意思是:给定一张图 $x$,潜变量 $z$ 服从一个高斯分布,均值和方差都由编码器网络预测。这一步为什么神奇?原来每个图片是一个"点",现在是一朵"云"。云与云之间相互重叠,潜空间被填得满满当当,解码器在哪儿都有活儿干——随便指一个点,都不会落到"无人区"。

🗺️
生活类比
普通 AE 的潜空间像一张只有零星几个岛屿的海图,随机指一个坐标,大概率点在海面上,捞起来什么都没有。VAE 的潜空间像一块连续铺平的地毯,每个位置都踩得到"地",而且相邻的位置含义接近——这就是"连续、有规律"。

不过,光把点变成云还不够:如果不加约束,网络会偷懒把方差压成 $0$,云又缩回点。所以 VAE 还有第二条腿——KL 正则(下一节讲),两条腿一起训练,潜空间才真正连续。

重参数化技巧:让"随机采样"也能反向传播

现在有个麻烦。训练神经网络靠梯度下降,梯度靠反向传播一层层算。可"从分布里采样"是随机过程,没法对参数求导,梯度传到这儿就断了。打个比方:你想优化"掷骰子"这个环节,可骰子天生不可控,你没法告诉它"往 6 靠一点"。

重参数化技巧(reparameterization trick)的办法是:把随机性从网络里"挪出去"。先用网络预测 $\mu$ 和 $\sigma$,再从标准正态分布 $\mathcal{N}(0, I)$ 里抽一个"外部噪声" $\varepsilon$,然后这样拼出样本:

$$z = \mu + \sigma \odot \varepsilon$$

这个式子意思是:先抽一个标准的随机噪声,再把它"搬到"均值 $\mu$ 附近、按 $\sigma$ 拉宽。平移和缩放都是普通运算,可以求导;真正随机的 $\varepsilon$ 与网络参数无关。于是 $\mu$、$\sigma$ 照常收到梯度,反向传播畅通无阻。

🧠
小技巧:重参数化把"从目标分布采样"变成了"标准噪声 + 平移缩放"。随机性像一颗外接的骰子——骰子本身不可微,但搬动它的过程完全可微。

VAE 的损失:重建误差 + 潜空间正则

VAE 的训练目标叫证据下界(Evidence Lower Bound,ELBO),它由两项组成:

$$\mathrm{ELBO}[\theta, \phi] = \mathbb{E}_{q(z|x,\theta)}[\log \Pr(x|z, \phi)] - D_{KL}[q(z|x,\theta) \| \Pr(z)]$$

这个式子意思是:第一项是"重建得分"——从编码器给的区域里采样 $z$,解码后越像原图越好;第二项是"正则罚分"——编码器给的分布越偏离标准正态,扣分越多。训练就是让总分尽量高,等价于让负 ELBO 尽量小。

第二项里的 $D_{KL}$ 叫 KL 散度(Kullback–Leibler divergence),可以粗浅地理解成"两个分布差多远"。这里要求每个样本的分布 $q(z|x,\theta)$ 都往标准正态 $\Pr(z) = \mathcal{N}(0, I)$ 靠拢:均值往 $0$ 拉、方差别太夸张。当 $q$ 取对角高斯时,这一项有闭式解:

$$D_{KL}[q(z|x,\theta)\|\Pr(z)] = \frac{1}{2}\sum_{j=1}^{D_z}\left(\mu_j^2 + \sigma_j^2 - 1 - \log \sigma_j^2\right)$$

这个式子意思是:$D_z$ 是潜空间维度,公式算的是"每个维度偏离标准正态多远"的总账。第一项的期望没法精确算,但可以用蒙特卡洛估计:从 $q$ 里采一个样本 $z^*$ 来近似:

$$\mathrm{ELBO} \approx \log \Pr(x|z^*, \phi) - D_{KL}[q(z|x,\theta) \| \Pr(z)]$$

意思是:采一个样就够用——只要采样走的是重参数化(上一节),整条路就能求导,训练照常进行。把损失换成代码,就是一个完整的 PyTorch VAE(MNIST 手写数字,潜空间 2 维,方便可视化):

import torch
import torch.nn as nn

class VAE(nn.Module):
    def __init__(self, dim_x=784, dim_z=2):
        super().__init__()
        # 编码器:输出 2*dim_z 个数,前半是 mu,后半是 log_var
        self.encoder = nn.Sequential(
            nn.Linear(dim_x, 256), nn.ReLU(),
            nn.Linear(256, 2 * dim_z))
        # 解码器:从潜变量 z 重建图片
        self.decoder = nn.Sequential(
            nn.Linear(dim_z, 256), nn.ReLU(),
            nn.Linear(256, dim_x), nn.Sigmoid())

    def reparameterize(self, mu, log_var):
        eps = torch.randn_like(mu)                    # 外部随机噪声
        return mu + torch.exp(0.5 * log_var) * eps    # z = mu + sigma * eps

    def forward(self, x):
        mu, log_var = self.encoder(x).chunk(2, dim=-1)
        z = self.reparameterize(mu, log_var)          # 从"区域"里采一个样
        return self.decoder(z), mu, log_var

注意代码里编码器输出的是 $\mu$ 和 $\log \sigma^2$(取对数是为了让方差恒为正、数值更稳)。训练时损失函数就是负 ELBO:重建损失加上 KL 项,用 Adam 之类的优化器更新参数。跑完后,你会看到不同数字的"云"分布在潜空间的不同区域——连续、有规律,正是我们想要的。

💡
**核心思想**:AE 只要求"压得扁、还原得像";VAE 额外要求"潜空间连续有规律"。两个改动完成这件事——编码器输出均值+方差(把点变成区域),损失里加 KL 正则(把区域往标准正态拉)。生成时从标准正态随手采一个点,解码器就能画出一张合理的新图。

训练好的 VAE:生成与应用

生成新图很简单:从标准正态里抽一个 $z^*$(相当于在潜空间里随便指一个点),丢给解码器就得到一张新图——不需要任何原图。可惜标准 VAE 生成的人脸往往有点模糊:解码器对每个 $z$ 输出的是高斯分布的均值,而"最稳妥的均值"往往是所有可能图片的平均,像一张"平均脸"。模糊换来的好处是多样:潜空间连续,采到哪儿都能生成一张合理但可能不一样的图。

除了生成,VAE 还有几个实用场景:

  • 去噪:把带噪声的图编码再解码,噪声被"平均"掉一部分。
  • 异常检测:正常样本重建误差小,没见过的东西重建误差大,可以当"报警器"。
  • 重合成(属性编辑):把"微笑"和"中性"的人脸分别编码,求两类潜向量的平均差,得到"微笑方向"向量;加到任意人脸的潜向量上再解码,就能"加上微笑"。潜空间连续,所以这种加减才有意义。

🎯 小测验

1. 普通自编码器(AE)和 VAE 最根本的区别是什么?
2. 重参数化技巧解决了什么问题?
3. 为什么标准 VAE 生成的图像常常比较模糊?