基础 · 13

自编码器:从压缩到生成

用神经网络学一个非线性的 PCA。编码器压、解码器还,瓶颈层是数据的「压缩字幕」。再加点概率,它就从压缩工具变成生成模型。

13 min read

PCA 解不了的事

上一篇讲完了神经网络:把矩阵乘法和非线性激活一层层堆起来,模型就能学出极其复杂的函数。我们一直拿这个能力去做有监督任务——输入 xx、目标 yy,最小化 yy^2\|y - \hat y\|^2 之类的损失。

但神经网络的能力当然不止于此。一个有点反常的想法:

如果让神经网络的输出目标就是它的输入呢?

听起来荒谬——「学会复制粘贴」算什么本事?但只要在网络中间加一个远小于输入维度的瓶颈层,整件事就不再 trivial:网络被迫只能记住「最重要的信息」,再从那点信息把输入还原出来。

这就是自编码器(autoencoder)——一种用神经网络实现的非线性压缩。它解决了 PCA 解不了的事:

  • PCA 只能做线性投影。如果数据散布在弯曲的流形上(同一张人脸从不同角度拍出的照片、同一物体旋转下的视图),PCA 把这个流形压扁成一团,看不出本来的结构。
  • 自编码器的编码器是个非线性网络——它能**「展开」流形**,把弯曲的高维表面映射到一个直的低维空间。

更精彩的是,只要给瓶颈层加一点概率结构,自编码器从「压缩」一跃成为「生成」——这就是 Stable Diffusion 的灵魂部件 VAE 的全部直觉。

结构:编码 / 瓶颈 / 解码

自编码器的结构极其直觉:

  • 编码器(Encoder) ff:一个神经网络,把输入 x\mathbf{x}(高维)映射到低维表示 z=f(x)\mathbf{z} = f(\mathbf{x})
  • 瓶颈层(Bottleneck) z\mathbf{z}:维度远小于输入。它强迫网络只能保留最有用的信息。
  • 解码器(Decoder) gg:另一个神经网络,从瓶颈 z\mathbf{z} 重建回 x^=g(z)\hat{\mathbf{x}} = g(\mathbf{z})

训练目标就一行——最小化重建误差

minf,gi=1nxig(f(xi))2\min_{f,\,g} \sum_{i=1}^{n} \|\mathbf{x}_i - g(f(\mathbf{x}_i))\|^2

这里有几个非平凡的事实需要点明。

首先,没有标签。训练只需要 x\mathbf{x} 本身——任何数据都能用,这让自编码器属于无监督学习。它的「标签」是数据自己。

其次,瓶颈层的维度是关键超参数。瓶颈大 = 信息几乎不丢,但学不到有意义的压缩(极端情况:瓶颈等于输入维度,网络可以学一个恒等函数,重建完美但无用)。瓶颈小 = 信息丢得多,但学到的表示更「凝练」、更接近数据的真实自由度。

最后,重建好不代表表示就好。低重建误差只说明信息流通畅,不保证瓶颈层 z\mathbf{z} 是「有意义」的——可能只是输入的某种乱码加密,解码器把它解回去。后面要讲的 VAE 和 sparse AE 正是为了解决这个问题——给瓶颈加约束,逼它有结构。

拖动下面的瓶颈大小,看重建质量怎么变:

瓶颈维度
重建误差 0.023
输入Encoder瓶颈 (4D)Decoder重建
自编码器的瓶颈层强制压缩信息。维度越接近输入维度,重建越完美——但也越没学到什么。 当瓶颈 = 1 时,它学到的就是 PCA 的第一主成分。

线性自编码器 = PCA

一个漂亮的数学结果:

如果编码器和解码器都是线性的(没有激活函数),并且用 MSE 作为损失,那么自编码器学到的瓶颈表示等价于 PCA。

证明的关键观察:线性编码器 z=Wex\mathbf{z} = W_e \mathbf{x} + 线性解码器 x^=Wdz\hat{\mathbf{x}} = W_d \mathbf{z},整体就是 x^=WdWex\hat{\mathbf{x}} = W_d W_e \mathbf{x}——本质是把 x\mathbf{x} 投影到一个秩为 kk(瓶颈维度)的子空间。Eckart–Young 定理告诉我们,所有秩 kk 的近似里,由 SVD 前 kk 个主成分给出的投影是最优的。所以最优的 WdWeW_d W_e 就指向 PCA。

这个等价性给了我们一把尺子:线性自编码器 = PCA。所以非线性自编码器至少不会比 PCA 差,往往好得多——多出来的能力,全在「非线性」三个字里。

加上 ReLU、Sigmoid 这类激活之后,编码器能学到 PCA 学不到的东西:

  • 展开弯曲流形——把瑞士卷展平、把旋转角度解耦出来。
  • 学到离散的语义特征——比如「这张图有没有微笑」、「这段代码是不是 Python」,这些特征在 PCA 的连续主成分里是混在一起的。
  • 针对感兴趣的分布定制——PCA 对所有数据无差别,自编码器只优化你给它看到的数据,更针对、更精细。

去噪与稀疏自编码器

朴素自编码器有一个隐患:网络可能学到一个几乎是恒等的映射——只要瓶颈足够大,网络就能找到一种「绕过瓶颈」的方式,比如把高频细节藏在某些维度里,重建时再调回来。这种「记住输入」而不是「理解数据」的解,泛化能力差。

两种最常用的对策,都是给训练过程加约束。

去噪自编码器(Denoising Autoencoder) 故意在输入上加噪声,但要求网络重建干净的版本

minf,gixig(f(xi+ϵi))2\min_{f,\,g} \sum_i \|\mathbf{x}_i - g(f(\mathbf{x}_i + \boldsymbol{\epsilon}_i))\|^2

直觉:要从「带噪的 x\mathbf{x}」恢复出干净的 x\mathbf{x},网络就不能记忆——它必须学到数据的本质规律(「人脸应该有两只对称的眼睛」「这一笔之后应该接什么字」),才能去掉随机噪声。Stacked Denoising Autoencoder 是 2010 年代早期深度学习预训练的核心方法之一,思想直接影响了后来的 BERT mask 重建和 diffusion 的训练目标。

稀疏自编码器(Sparse Autoencoder) 不对输入加噪,而是对瓶颈层加 L1 正则,让大部分瓶颈节点为零:

L=xx^2+λz1\mathcal{L} = \|\mathbf{x} - \hat{\mathbf{x}}\|^2 + \lambda \|\mathbf{z}\|_1

效果是:每个输入只激活一小撮瓶颈节点。每个节点专责一种「概念」,不同输入对应不同的稀疏激活模式——这给瓶颈带来了可解释性。Anthropic 在 2024-2025 年用稀疏自编码器从 Claude 的中间层激活里「拆出」单义概念(比如「金门大桥」「代码缩进」),就是这个方法的现代版。

VAE:从压缩到生成

朴素自编码器学的瓶颈 z\mathbf{z} 是一个确定的向量——给同样的输入永远输出同样的瓶颈。这有一个问题:瓶颈空间是离散稀疏的——只有训练数据对应的那些 z\mathbf{z} 点是「合法」的,瓶颈空间里其他位置都是「未定义」。

如果你想做生成——从瓶颈空间随机采一个 z\mathbf{z},喂给解码器,得到一张全新的图片——朴素自编码器解码出来的多半是垃圾。因为你采的那个 z\mathbf{z} 几乎一定落在「未见过的虚空」里。

变分自编码器(VAE) 用一个巧妙的修改解决了这件事——让瓶颈层不再是一个向量,而是一个概率分布

zN(μ(x), σ2(x))\mathbf{z} \sim \mathcal{N}(\boldsymbol{\mu}(\mathbf{x}),\ \boldsymbol{\sigma}^2(\mathbf{x}))

编码器不再输出 z\mathbf{z},而是输出一对 (μ,σ)(\boldsymbol{\mu}, \boldsymbol{\sigma})。训练时从这个分布采样得到 z\mathbf{z},再交给解码器。

光这样还不够——网络可能学得每个 σ\boldsymbol{\sigma} 都极小(让分布塌缩成点,退化回朴素 AE)。所以 VAE 在损失里额外加一项,要求每个 x\mathbf{x} 的后验分布都接近一个标准的先验 N(0,I)\mathcal{N}(0, I)

L=xx^2重建+βDKL(q(zx)N(0,I))先验约束\mathcal{L} = \underbrace{\|\mathbf{x} - \hat{\mathbf{x}}\|^2}_{\text{重建}} + \beta \cdot \underbrace{D_{\mathrm{KL}}\big(q(\mathbf{z}|\mathbf{x})\,\|\,\mathcal{N}(0, I)\big)}_{\text{先验约束}}

这就是大名鼎鼎的 ELBO(Evidence Lower Bound)。两项的拉扯产生了一个奇妙的效果:瓶颈空间被压紧到接近一个标准高斯——每一处都「合法」,每一处都能解码出像样的图像。生成新数据就变成了一行代码:

z = torch.randn(latent_dim)
new_image = decoder(z)

VAE 是第一个真正能从「先验 → 数据」生成新图片的端到端模型——它打开了生成式 AI 这扇门,是后来 Stable Diffusion / DALL-E 的直接思想源头。

这个想法在前沿里