6.1 自编码器与变分自编码器VAE

1.从自编码器说起:把输入压成一个低维的潜在表示

说起来,从这一章起我们正式进入生成模型这一大块。前面那些章节,模型大多在做判断,给一张图判断是猫是狗,给一段文本判断情感是正是负。这一章开始,模型要做的事情换了,它要自己造出新的东西来。我们第一站先聊自编码器(Autoencoder),它本身算不上多强的生成模型,却是后面VAE、Diffusion这些方法的思想源头,值得好好讲一讲。

自编码器的结构其实很简单,两段。前一段叫编码器(Encoder),负责把输入 xx 压成一个低维的向量 zz,这里 xx 是输入(比如一张图压平后的像素向量),zz 叫潜在表示(latent representation),维度比 xx 小得多。后一段叫解码器(Decoder),负责把这个 zz 再还原成 x^\hat{x}x^\hat{x} 就是模型重建出来的版本。整张网络训练时只有一个目标,就是让 x^\hat{x} 尽量贴近 xx,差距越小越好。这个差距靠重建误差来衡量,最常见的写法是均方误差:

Lrec=xx^2L_{rec} = \|x - \hat{x}\|^2

这里 LrecL_{rec} 表示重建误差这一项,2\|\cdot\|^2 是向量各分量差的平方和。整个训练过程不需要任何标签,输入本身就是答案,所以这是无监督学习。

最关键的地方在中间那个瓶颈。zz 的维度被人为压得很低,比方说一张 224×224224\times224 的彩图,输入维度有十五万那么多,却硬要压成一个几十维的向量。这么一压,模型就没办法把原图的每个细节都存下来,它只能去抓住最关键、最能复原图的那部分信息。这就像小张期末复习,只能带一张A4纸进考场,纸就那么大,他自然得挑最要紧的公式和要点记上去。瓶颈逼着模型学会取舍,而学到的 zz 就是输入的精华。

自编码器能做的事其实不少。拿它来压缩数据,相当于学到了一个比原图小得多的表示,存起来省地方。拿它来去噪也很在行,做法是输入一张加了噪声的图,目标却是还原干净的原图,模型学一阵子就能把噪声滤掉。我之前翻过一本讲医学影像的书,里头就拿这种去噪自编码器来处理CT片子,把扫描过程里混进来的颗粒噪声抹得干干净净,效果相当不错。

2.潜在空间没规律:能压缩、能去噪,却生不出新东西

说到这里你大概会想,既然自编码器学会了把图压成 zz 再还原,那我随便挑一个 zz 喂给解码器,是不是就能生成一张新图?这个想法很自然,可实际一跑就发现问题大了。

问题出在自编码器学出来的潜在空间毫无规律。模型训练时只盯着重建误差这一件事,zz 这个空间里的点分布得完全随性,哪里有空白、哪里挤成一团,全凭训练数据怎么喂。我们随便抓一个 zz 丢进解码器,那个位置多半从来没有见过任何训练样本,解码器要么输出一团乱码,要么输出一张毫无意义的图。

打个比方。小明有一本厚厚的画册,他给每幅画都编了一个简短的目录编号,也就是 zz。可这本目录的编号规则乱七八糟,第12号是山水,第13号突然跳到一只猫,第14号又变成抽象色块。要让他凭空说一个新编号、然后画一幅像样的画出来,他做不到,因为他脑子里根本没有一个连续顺滑的编号体系。自编码器也是这个道理,它能把见过的东西压缩和还原,却没有把潜在空间整理成一个能让人自由采样的样子,所以拿来生成新东西是不行的。

要让模型真正能生成,我们就得给潜在空间立规矩。这就是VAE要做的事。

3.VAE:让编码器输出一个分布,让潜在空间有结构

变分自编码器(Variational Autoencoder,简称VAE)的想法其实就一句话,让潜在空间有结构。它和普通自编码器最大的区别,在于编码器输出的目标变了,从原来一个确定的点 zz,变成一个分布。

具体怎么做的呢,我们假设潜在向量 zz 的每一维都独立地服从一个一元高斯分布(也就是一元正态分布),各维互不影响。这样假设的好处是协方差矩阵成了对角阵,整个分布只需要每一维各自的均值和方差就能定下来。编码器看完输入 xx 之后,输出这个分布的两个参数,均值向量 μ\mu 和方差向量 σ2\sigma^2,其中 μ\mu(读作mu)的第 iiμi\mu_i 是该维分布的中心位置,σ2\sigma^2(读作sigma平方)的第 iiσi2\sigma_i^2 是该维分布的离散程度。有了 μ\muσ2\sigma^2,我们就定下了潜在空间里的这个分布,逐维写作 N(μi,σi2)\mathcal{N}(\mu_i, \sigma_i^2)N\mathcal{N} 表示高斯分布。然后我们从这个分布里随机采样出一个 zz,再交给解码器去还原。

这一步看起来变化不大,意义却深。普通自编码器里,每个输入对应 zz 空间里孤零零一个点,点之间是什么情况没人管。VAE则让每个输入对应一小团概率云,云和云之间会相互重叠,潜在空间于是被填得满满当当、连续顺滑。这么一来,潜在空间里随便哪个位置都至少落在某一团云的影响范围里,解码器在那儿也就不会手足无措了。

我说句题外话。我之前看过一部讲传统纹样的纪录片,里头提到日本有种水墨染布的工艺,每一笔下去都化开成一团晕染的墨色,而不是一个死板的点,相邻的墨色互相渗透,整张布面就连成了一片没有空隙的画。VAE的潜在空间给我的感觉就很像这样,每个样本撑开一小片区域,区域之间彼此交融,没有空洞。

4.重参数化技巧:让梯度能穿过采样这一步

VAE这里头有个麻烦,采样这一步没法直接求导。你看,zz 是从逐维分布 N(μi,σi2)\mathcal{N}(\mu_i, \sigma_i^2) 里随机采出来的,随机性这个操作本身没有梯度可言,反向传播到了采样这一步就走不下去了。可训练又必须让梯度能从重建误差一路传回到编码器,去调 μ\muσ2\sigma^2 这两个参数,这件事该怎么办。

解决这个麻烦的小技巧叫重参数化(reparameterization)。做法是把随机性挪到外面去。我们先从标准正态分布 N(0,1)\mathcal{N}(0, 1)(均值是 00、方差是 11 的正态分布)里采一个随机数 ϵ\epsilonϵ\epsilon(读作epsilon)是这个辅助噪声。然后把采样改写成下面这个式子:

z=μ+σϵz = \mu + \sigma \odot \epsilon

这里 \odot 表示逐元素相乘(向量对应位置相乘)。这么一写,随机性就全藏在 ϵ\epsilon 这个外部变量里了,ϵ\epsilon 我们当作常数看,不参与求导。而 μ\muσ\sigma 都成了 zz 这个表达式里的普通参数,对它们求导毫无障碍。梯度于是能从重建误差顺利回传到编码器,训练也就转起来了。

实际工程里还有个小细节。神经网络直接输出的往往是方差的对数 logσ2\log\sigma^2,而不是 σ\sigma 本身,这是因为方差必须为正数,让网络自由地输出任意实数、再通过取指数还原成正数会比较稳当。你将来翻代码看到变量名logvar,大概就是它了。

5.损失函数:重建项加上KL散度,两手一起抓

VAE的损失由两部分组成,一部分管还原得准不准,一部分管潜在空间规不规矩。在拆这两项之前,得先说清这个损失是怎么来的——它不是拍脑袋拼出来的,而是从"最大化数据似然 logp(x)\log p(x)"用变分推断推出来的,叫 ELBO(证据下界)。 这条推导是 VAE 的理论命脉,我们走一遍。

VAE 想最大化的是数据的对数似然 logp(x)\log p(x),但 p(x)=p(xz)p(z)dzp(x)=\int p(x|z)p(z)\,dz 这个积分在神经网络里没法直接算(zz 的空间太大)。变分推断的招数是引入一个编码器 qϕ(zx)q_\phi(z|x)(近似后验)来绕开它。先写出一个恒等式——对任意 qϕ(zx)q_\phi(z|x)

logp(x)=LELBO+DKL(qϕ(zx)p(zx))\log p(x)=\mathcal L_{\text{ELBO}}+D_{KL}\bigl(q_\phi(z|x)\,\|\,p(z|x)\bigr)

这个等式怎么来的?把 qϕ(zx)q_\phi(z|x) 乘进去再除出去:logp(x)=logp(x,z)dz=logqϕ(zx)p(x,z)qϕ(zx)dzqϕ(zx)logp(x,z)qϕ(zx)dz\log p(x)=\log\int p(x,z)\,dz=\log\int q_\phi(z|x)\frac{p(x,z)}{q_\phi(z|x)}\,dz\geq\int q_\phi(z|x)\log\frac{p(x,z)}{q_\phi(z|x)}\,dz(最后一步用了 1.4 节的 Jensen 不等式,log\log 是凹函数)。这个下界就是 ELBO,展开:

LELBO=Eqϕ(zx)[logpθ(xz)]重建项DKL(qϕ(zx)p(z))KL项\mathcal L_{\text{ELBO}}=\underbrace{\mathbb E_{q_\phi(z|x)}\bigl[\log p_\theta(x|z)\bigr]}_{\text{重建项}}-\underbrace{D_{KL}\bigl(q_\phi(z|x)\,\|\,p(z)\bigr)}_{\text{KL项}}

(推导里把 logp(x,z)qϕ(zx)=logp(xz)+logp(z)qϕ(zx)\log\frac{p(x,z)}{q_\phi(z|x)}=\log p(x|z)+\log\frac{p(z)}{q_\phi(z|x)} 拆开,前半是重建项,后半凑出 KL。)因为 logp(x)=ELBO+KL\log p(x)=\text{ELBO}+\text{KL} 而 KL 非负,所以 ELBO 是 logp(x)\log p(x) 的下界——最大化 ELBO 就是在逼近最大化似然。而我们要最小化的损失,就是负的 ELBO:

L=LELBO=Eqϕ(zx)[logpθ(xz)]Lrec+DKL(qϕ(zx)p(z))DKLL=-\mathcal L_{\text{ELBO}}=\underbrace{-\mathbb E_{q_\phi(z|x)}[\log p_\theta(x|z)]}_{L_{rec}}+\underbrace{D_{KL}\bigl(q_\phi(z|x)\,\|\,p(z)\bigr)}_{D_{KL}}

第一项就是重建项(负对数似然,均方误差或交叉熵都是它的具体形式),第二项就是 KL 项。这就解释了为什么 VAE 的损失恰好是这两项之和——它们加起来正好是负 ELBO,最小化它等价于最大化数据似然的下界。理解了这一层,下面拆开讲两项就有根了。我们一项一项说。

第一项是重建项,跟普通自编码器一样,要求解码出来的 x^\hat{x} 尽量贴近输入 xx。具体形式可以是均方误差,也可以是更贴近图像分布的交叉熵,看任务而定。这一项鼓励模型把输入信息忠实地编进 μ\mu 里。

第二项是KL散度。我们在1.4 节聊信息论基础时已经见过它了,KL散度衡量两个分布的差异程度,差距越大这项越大。这里要约束的,是编码器对每个输入 xx 在每一维上输出的分布 N(μi,σi2)\mathcal{N}(\mu_i, \sigma_i^2),要让它尽量贴近一个标准正态分布 N(0,1)\mathcal{N}(0, 1)。KL散度记作 DKLD_{KL},这一项逐维写作:

DKL(N(μi,σi2)N(0,1))D_{KL}\left(\mathcal{N}(\mu_i, \sigma_i^2) \,\|\, \mathcal{N}(0, 1)\right)

在每一维独立、单变量情形下,这一项有非常工整的闭式表达:

DKL=12i=1d(μi2+σi2logσi21)D_{KL} = \frac{1}{2}\sum_{i=1}^{d}\left(\mu_i^2 + \sigma_i^2 - \log\sigma_i^2 - 1\right)

这里 dd 是潜在向量的维度,ii 是维度编号,μi\mu_iσi2\sigma_i^2 分别是第 ii 维的均值和方差,\sum 是求和符号,12\frac{1}{2} 是前面的一个常数系数。这个式子看起来吓人,意思却很直白,它要求 μ\mu 接近 00σ2\sigma^2 接近 11。求和号里的 μi2\mu_i^2 这部分鼓励均值贴近零,σi2logσi21\sigma_i^2 - \log\sigma_i^2 - 1 这部分鼓励方差贴近一,你不妨把 σi2=1\sigma_i^2=1 代进去验证一下,这部分恰好等于零。

把两项合起来,VAE的损失大概是这个样子:

L=Lrec+βDKLL = L_{rec} + \beta \cdot D_{KL}

这里 β\beta 是一个用来调节两项权重的系数(在一种叫 β\beta-VAE 的变体里,β\beta 就是核心调参对象),训练时我们就在重建得准和潜在空间规整这两件事之间找平衡。LrecL_{rec} 想让信息编得尽量细,DKLD_{KL} 想让潜在分布尽量贴近标准正态,两者互相拉扯,最后落在一个折中的位置上。

为什么要约束成标准正态呢,这是因为一旦每个输入对应的分布都被推向 N(0,1)\mathcal{N}(0, 1),整个潜在空间就被大致填满了,而且填得规规矩矩。我们想生成新样本的时候,直接从 N(0,1)\mathcal{N}(0, 1) 里随便采一个 zz,喂给训练好的解码器,输出的多半就是一张说得过去的新图。生成这件事,到这儿就彻底通了。

6.VAE的图偏模糊,以及它身后的那些事

VAE真能生成,这一点没问题,可它生成的图通常偏模糊,细节糊糊的,少了点锐利劲儿。原因主要落在那项KL散度上,DKLD_{KL} 把每个输入的分布都用力拽向同一个标准正态,分布之间的差异被压扁了,很多精细的、个体化的信息也跟着被磨掉了。再加上重建项如果用均方误差,模型倾向于输出各处都偏平均、偏中庸的结果,画面就更糊了。

要更清楚更锐利的生成效果,业界后来走上了别的路。一种思路是引入对抗训练,让一个生成器和一个判别器互相博弈,这就是GAN(生成对抗网络)那一支,DCGAN、StyleGAN都属于这一家。另一种思路是逐步去噪,从纯噪声出发,一步一步把图雕出来,这就是DDPM开出来的扩散模型这条路,后来的Latent Diffusion、Stable Diffusion一路走下来,再往后还有把U-Net换成Transformer的DiT、转向Flow Matching的新写法,Sora的视频生成也建立在这一脉之上。

说起来,VAE并没有因此就被冷落。Diffusion那一支发展到Latent Diffusion之后,大家发现直接在像素空间里一步步去噪太贵了,于是把VAE请回来当压缩器,先用它把图压到潜在空间里再去做扩散,VAE就这样成了现代生成管线里相当重要的一环。这就好比一部老工具,原以为要被新工具取代,结果新工具做着做着又把它请回来搭档了。

VAE的思想还渗透在别处。3.4 节讲Transformer时我们见过编码器、解码器的两段式结构,VAE也是一种两段式,只不过它把中间的瓶颈换成了一个分布。4.10 节会聊到的CLIP,把图像和文本对齐到一个共享的潜在空间,那个空间讲究的也是连续、可比较,和VAE约束潜在空间的初衷是一脉相通的。

这一章我们从自编码器讲起,看清了它为什么压缩去噪在行、生成却不行,又顺着VAE看明白怎么给潜在空间立规矩、怎么用重参数化让梯度穿过采样、怎么用重建项加KL散度把模型训练起来。下一章我们继续生成模型这条路,去看GAN那一家是怎么把图生成得又清楚又锐利的。

练习

Q1. 普通自编码器学到的潜在空间为什么不能用来生成新样本?VAE是怎么解决这个问题的?

普通自编码器训练时只盯着重建误差,zz 空间里的点分布完全随性,哪里有空白、哪里挤成一团全凭数据怎么喂,随便抓一个 zz 丢进解码器,那个位置多半没见过任何训练样本,输出一团乱码。VAE把编码器输出的目标从一个确定的点 zz 改成一个分布(每维独立的高斯 N(μi,σi2)\mathcal{N}(\mu_i,\sigma_i^2)),每个输入对应一小团概率云,云和云互相重叠,潜在空间被填得满满当当、连续顺滑,随便哪个位置都落在某团云里,解码器在那儿不会手足无措,这才真能生成。

Q2. VAE的重参数化技巧 z=μ+σϵz=\mu+\sigma\odot\epsilonϵN(0,1)\epsilon\sim\mathcal{N}(0,1))解决了什么问题?

解决了"采样这一步没法直接求导"的问题。zz 是从分布 N(μ,σ2)\mathcal{N}(\mu,\sigma^2) 里随机采出来的,随机性这个操作本身没有梯度,反向传播到采样这步就走不下去了,可训练又必须让梯度从重建误差传回编码器去调 μ\muσ\sigma。重参数化把随机性挪到外部变量 ϵ\epsilon(当常数不参与求导),μ\muσ\sigma 成了表达式里的普通参数,对它们求导毫无障碍,梯度就能顺利回传,训练转起来了。

Q3. 易错点:VAE的损失为什么是"重建项 + KL散度"两项之和,这两项是不是各管各的、互不相干?

不是互不相干,它们加起来正好是负的ELBO(证据下界),最小化它等价于最大化数据似然的下界。从变分推断推出来:logp(x)=LELBO+DKL(qϕ(zx)p(zx))\log p(x)=\mathcal{L}_{\text{ELBO}}+D_{KL}(q_\phi(z|x)\|p(z|x)),而KL非负,所以ELBO是 logp(x)\log p(x) 的下界,最大化ELBO就在逼近最大化似然。展开ELBO得到重建项(让 x^\hat{x} 贴近 xx)减去KL项(让编码器输出的分布贴近标准正态 N(0,1)\mathcal{N}(0,1)),取负号就是损失。两项互相拉扯——重建项想编细信息,KL项想让分布规整,最后落在折中点。理解这一层就知道这两项不是拼凑,是有理论根的。

Q4.(面试题) 请解释 VAE 损失中 KL 散度项 DKL=12i(μi2+σi2logσi21)D_{KL}=\frac{1}{2}\sum_i(\mu_i^2+\sigma_i^2-\log\sigma_i^2-1) 为什么能把潜在空间推向标准正态,并说明这带来什么好处和代价。

这一项逐维写成求和,每一维的 μi2\mu_i^2 鼓励均值贴近0,σi2logσi21\sigma_i^2-\log\sigma_i^2-1 鼓励方差贴近1(把 σi2=1\sigma_i^2=1 代进去这部分恰好等于0,是它的最小值)。所以整个KL项就是在把编码器对每个输入输出的分布 N(μi,σi2)\mathcal{N}(\mu_i,\sigma_i^2) 尽量推向标准正态 N(0,1)\mathcal{N}(0,1)。好处是:一旦每个输入的分布都被推向 N(0,1)\mathcal{N}(0,1),整个潜在空间就被大致填满而且规规矩矩,想生成新样本时直接从 N(0,1)\mathcal{N}(0,1) 随便采一个 zz 喂给解码器,输出的多半就是一张说得过去的新图,生成这件事就通了。代价是:KL把分布都用力拽向同一个标准正态,分布之间的差异被压扁,很多精细、个体化的信息被磨掉,再加上重建项若用均方误差模型倾向输出偏平均中庸的结果,所以VAE生成的图通常偏模糊、细节糊糊的,少了锐利劲儿。这也是后来生成模型走向GAN(对抗求锐利)和扩散模型(逐步去噪)的重要原因。

相关标签
深度学习生成模型VAE自编码器