算法可视化与交互学习平台

VAE:从 latent space 到概率生成VAE: From Latent Space to Probabilistic Generation

从最小二维 latent space 出发,理解 Encoder 如何输出 μ 和 σ、重参数化如何采样 z、Decoder 如何生成结果,以及 reconstruction loss 与 KL loss 如何共同塑造可生成的连续空间。

Deep LearningIntermediateFree
Kernel
1

先抓住 VAE 的核心直觉

VAE 的关键不是把样本压缩成一个固定点,而是学习一个连续、可采样、可生成的 latent space。它把每个样本编码成 latent space 中的一个概率云团,再从这个云团里采样并解码生成数据。

普通 AutoEncoder 可以粗略写成:

x -> Encoder -> z -> Decoder -> x'

VAE 则写成:

x -> Encoder -> μ, σ -> z = μ + σ·ε -> Decoder -> x'

这一节会围绕同一条链路展开:Encoder 学会每个样本在 latent space 里的中心 μ 和不确定性 σ,然后通过重参数化采样得到 z,最后由 Decoder 把 z 翻译回数据空间。训练后,我们也可以不输入原始 x,而是直接从 N(0, I) 采样新的 z,再交给 Decoder 生成新样本。

2

从一个点,到一个概率云团

1. 普通 AutoEncoder 学的是一个点

普通 AutoEncoder 会把输入 x 压缩成一个 latent vector z,再从 z 重建出 x'。这对压缩和重建很有用,但 latent space 可能是一堆离散孤岛:训练样本附近能解码,中间区域不一定有意义。

x -> Encoder -> z -> Decoder -> x'

2. VAE 学的是一个分布

VAE 的 Encoder 不直接输出一个点,而是输出一个高斯分布的参数:中心 μ 和方差相关的 logvar。因此每个样本不再只是 latent space 中的一个点,而是一片有范围的不确定区域。

x -> Encoder -> μ, logvar -> sample z -> Decoder -> x'

3. 两个样本手算一次真实 VAE 前向传播

为了不把 Encoder 和 Decoder 当成黑盒,先看一个一维输入、二维 hidden、二维 latent 的最小神经网络。这里的权重是人为指定的,目的不是得到好模型,而是让每一步都可以直接算出来。绿色数字表示这个最小网络里人为指定、实际训练中会由神经网络学习得到的权重/偏置或分布参数,避免它们看起来像突然出现的常量。右侧图表使用完全相同的 μσεz 数值,把这段演算画成两个概率云团。

样本:x1 = 0, x2 = 2 Encoder hidden: h1 = ReLU(0.5x + 0) h2 = ReLU(0.4x + 0) h = (h1, h2) Latent heads: μ1 = 1.0h1 + 0.0h2 + 0 μ2 = 0.0h1 + 1.0h2 + 0 logvar1 = 0.0h1 + 0.0h2 + -1.386 logvar2 = 0.0h1 + -1.2775h2 + -1.386 σ = exp(0.5 * logvar)

什么是 Latent heads?

这里的 Latent heads 不是新的算法,而是神经网络里很常见的“输出分支”:Encoder 先用共享隐藏层提取输入结构特征 h,再从同一个 h 分出两个输出层,一个预测 μ,另一个预测 logvar

输入 x ↓ 共享隐藏层 h = (h1, h2) ↓ ┌───────────────┐ ↓ ↓ μ head logvar head ↓ ↓ μ logvar ↓ ↓ 中心位置 扩散范围

之所以需要两个 head,是因为 VAE 的 Encoder 不再输出一个确定点,而是输出一个高斯分布 q(z|x) 的参数。μ head 像定位器,决定这个样本的概率云团放在 latent space 的哪里;logvar head 像不确定性估计器,决定这个云团有多分散。

实际模型通常学习 logvar = log(σ²),而不是直接学习 σ,因为 σ 必须为正,直接学习容易出现负数或数值不稳定。训练时网络输出 logvar,再用 σ = exp(0.5 * logvar) 还原标准差。

latent heads 的本质: 把 Encoder 提取的共享结构特征 h 转化为 latent 高斯分布的参数 μ 和 σ。 μ head -> 决定概率云团的中心 logvar head -> 决定概率云团的扩散范围

先算 Encoder。为了能画出二维云团,我们让 Encoder 先输出二维 hidden,再由两个 head 输出二维 μ 和二维 σ

x1 = 0: h1 = ReLU(0.5*0 + 0) = 0 h2 = ReLU(0.4*0 + 0) = 0 h = (0, 0) μ1 = 1.0*0 + 0.0*0 + 0 = 0 μ2 = 0.0*0 + 1.0*0 + 0 = 0 μ = (0, 0) logvar1 = -1.386 -> σ1 = exp(0.5*-1.386) ≈ 0.5 logvar2 = -1.386 -> σ2 = exp(0.5*-1.386) ≈ 0.5 σ = (0.5, 0.5) x2 = 2: h1 = ReLU(0.5*2 + 0) = 1.0 h2 = ReLU(0.4*2 + 0) = 0.8 h = (1.0, 0.8) μ1 = 1.0*1.0 + 0.0*0.8 + 0 = 1.0 μ2 = 0.0*1.0 + 1.0*0.8 + 0 = 0.8 μ = (1.0, 0.8) logvar1 = -1.386 -> σ1 ≈ 0.5 logvar2 = -1.2775*0.8 + -1.386 ≈ -2.408 -> σ2 ≈ 0.3 σ = (0.5, 0.3)

固定两次二维噪声,观察重参数化如何把概率云团变成真正送入 Decoder 的采样点:

ε1 = (0.4, -0.6) ε2 = (-0.6, 0.5) z1 = μ1 + σ1*ε1 = (0, 0) + (0.5, 0.5)*(0.4, -0.6) = (0.2, -0.3) z2 = μ2 + σ2*ε2 = (1.0, 0.8) + (0.5, 0.3)*(-0.6, 0.5) = (0.7, 0.95)

Decoder 也用一个真实的小网络。为了手算简单,这里让它先汇总二维 z,再生成一维重建值:

Decoder: 对任意一个采样点 z = (z_a, z_b): g = ReLU(z_a + z_b) x' = 1.2g + 0 z_sample_1 = (0.2, -0.3): g1 = ReLU(0.2 + (-0.3)) = 0 x1' = 1.2*0 + 0 = 0 z_sample_2 = (0.7, 0.95): g2 = ReLU(0.7 + 0.95) = 1.65 x2' = 1.2*1.65 + 0 = 1.98

最后算 loss。重建项看 x' 是否接近 x;KL 项看每个样本的 latent 分布是否接近标准正态 N(0, 1)。这里的 logvar 由上面的绿色 σ 换算而来:logvar = log(σ²)

recon_loss = ((0 - 0)^2 + (1.98 - 2)^2) / 2 = (0 + 0.0004) / 2 = 0.0002 KL = -0.5 * mean(1 + logvar - μ^2 - exp(logvar)) KL1 ≈ 0.318 KL2 ≈ 0.944 kl_loss = (0.318 + 0.944) / 2 = 0.631 β = 1 loss = recon_loss + β * kl_loss = 0.0002 + 0.631 ≈ 0.6312

这个例子里,KL1KL2 分别约束两个样本自己的局部后验 q(z|x1)q(z|x2)。它不是要求所有样本变成同一个点,而是给每个概率云团一个软约束:中心不要离标准正态区域太远,尺度也不要失控。x2μ=(1.0,0.8)x1μ=(0,0) 更远离标准正态中心,所以 KL2 更大。VAE 训练时就在同时做两件事:让 x'x,又让 latent 分布规整到容易从 N(0, 1) 采样。

4. 用二维几何形状理解 latent

如果训练数据是二维几何形状,可以把一个具体圆形、三角形或方形样本理解成 latent space 中的一个 z 点;很多相似圆形样本靠在一起,就形成“圆形区域”;三角形和方形也会形成自己的区域。但要注意,z1z2 只是坐标轴,不等于“圆形轴”或“三角形轴”。

z1, z2 -> latent 坐标维度 圆形区域 -> 很多圆形样本的 z 点形成的 cluster 三角形区域 -> 很多三角形样本的 z 点形成的 cluster direction -> 从一个 z 点移动到另一个 z 点的结构变化方向

例如,同一片圆形区域内部的一个方向,可能对应“半径变大”;另一个方向,可能对应“圆逐渐拉伸成椭圆”。普通 VAE 不保证每个坐标轴都有单独语义,真正更接近语义变化的通常是区域和方向。

5. 为什么这件事重要

当模型被要求让这些概率云团靠近标准正态分布时,latent space 会变得更连续、更可采样。训练后,我们可以直接从 N(0, I) 里取一个新的 z,再送进 Decoder 生成新样本。对二维几何形状来说,这意味着可以从 latent 地图上的某个位置生成一个新圆形、一个变宽的三角形,或一个介于两类结构之间的过渡形状。

6. 本模块的最小实验

实验台使用二维 toy data,而不是一上来训练大图像模型。这样每一步都能看清楚:数据点如何被编码到 latent space,μ 如何形成聚类,σ 椭圆如何表达不确定性,Decoder 又如何把手动选择的 z 翻译回数据空间。

3图

把 μ、σ、z 画成概率云团

这组图和左侧手算例子使用同一组二维 latent 数值。第一张看 Encoder 如何给两个样本生成 μ 和 σ 椭圆;第二张看 z = μ + σ·ε 如何从云团中采样;第三张看 KL 的直觉:不是让两个云团重合,而是把每个局部后验轻轻拉向标准正态中心附近。
σ

为什么用 logvar 得到 σ

VAE 的 Encoder 通常输出 logvar = log(σ²),再用 σ = exp(0.5 * logvar) 还原标准差。这样网络可以输出任意实数 logvar,但得到的 σ 永远大于 0,避免直接学习 σ 时出现负标准差。图中 logvar = 0 时 σ = 1;logvar < 0 表示概率云团更窄;logvar > 0 表示概率云团更宽。
4

重参数化:VAE 的核心公式

Encoder 输出 μ 和 logσ²,先把 logσ² 转成标准差 σ,再把固定标准正态噪声 ε 变换成采样点 z。这个过程叫重参数化:随机性来自 ε,μ 和 σ 只参与可微的平移与缩放,因此仍然能通过反向传播学习。

z=μ+σϵ,ϵN(0,I),σ=exp(12logσ2)z = \mu + \sigma \odot \epsilon,\qquad \epsilon \sim \mathcal{N}(0, I),\qquad \sigma = \exp\left(\frac{1}{2}\log\sigma^2\right)
μ\mu
σ\sigma
ϵ\epsilon
zz
μ\mu
latent 概率云团的中心
center of the latent distribution
σ\sigma
latent 概率云团的尺度 / 不确定性
scale or uncertainty of the latent distribution
ϵ\epsilon
来自标准正态分布的随机噪声
random noise sampled from the standard normal
zz
真正送入 Decoder 的 latent 采样点;z1、z2 是坐标维度,不一定单独对应明确语义
sampled latent point fed into the decoder

1. 先做一次二维数值代入

设 Encoder 对某个样本给出 μ=(1.2,-0.5)、σ=(0.3,0.2),再从标准正态中抽到 ε=(0.8,-1.1)。公式中的 ⊙ 表示逐元素相乘,不是矩阵乘法。

μ = (1.2, -0.5) σ = (0.3, 0.2) ε = (0.8, -1.1) σ ⊙ ε = (0.3*0.8, 0.2*(-1.1)) = (0.24, -0.22) z = μ + σ ⊙ ε = (1.2, -0.5) + (0.24, -0.22) = (1.44, -0.72)

2. 按坐标展开看得更清楚

如果 latent_dim = 2,那么 z 的两个坐标分别由 μ、σ、ε 的对应坐标算出。这里的 z1、z2 是坐标维度,不是第 1 个样本和第 2 个样本。

z_1 = μ_1 + σ_1 * ε_1 = 1.2 + 0.3*0.8 = 1.44 z_2 = μ_2 + σ_2 * ε_2 = -0.5 + 0.2*(-1.1) = -0.72 z = (1.44, -0.72)

3. 这一步到底在做什么

普通 AutoEncoder 通常把输入压成一个固定 latent 点;VAE 则把输入编码成一个概率云团 q(z|x)。μ 是云团中心,σ 是每个方向上的扩散大小,ε 是标准正态噪声。

普通 AutoEncoder: x -> 一个固定 latent 点 z VAE: x -> 一个 latent 概率云团 q(z|x) μ: 云团中心 σ: 云团尺度 ε: 随机偏移 z = μ + σ ⊙ ε = 从这个局部概率云团中取出一个 latent 点

4. 为什么叫“重参数化”

如果直接写 z ~ N(μ,σ²),采样动作依赖 Encoder 输出的 μ 和 σ,随机采样本身不方便反向传播。重参数化把采样改写成“固定噪声 + 可微变换”。

原来的写法: z ~ N(μ, σ²) 重参数化写法: ε ~ N(0, I) z = μ + σ ⊙ ε 随机性: ε 可学习、可反向传播: μ 和 σ

5. 生成直觉

同一个输入不再对应单个点,而是对应 latent space 中的一小片区域。每次 ε 不同,采样到的 z 也不同,Decoder 会得到相似但略有变化的结果。这也是 latent 连续变化和插值能够成立的基础。

同一个 μ, σ ε 不同 -> z 不同 z 在同一片局部区域内变化 Decoder(z) -> 相似但有细微差异的生成结果 latent interpolation: z_start -> z_end 如果 latent space 连续,生成结果也会平滑变化
5

VAE Loss:重建 + 空间规整

重建损失让 Decoder 尽量还原输入;KL 损失约束的是每个样本自己的局部后验 q(z|x),不是 q(z|class),也不是要求整个 latent histogram 严格服从 N(0,I)。它会把每个小概率云团轻微拉向标准正态范围,防止 latent space 碎裂成不可采样的孤岛;而圆形、三角形、方形等局部区域主要来自 reconstruction loss 对结构相似性的保留。β 控制重建精度和空间规整之间的权衡;β 太大时可能过度压缩差异,β 太小时空间可能不够连续。

L=Lrecon+βDKL(qϕ(zx)p(z)),DKL=12j(1+logσj2μj2σj2)\mathcal{L}=\mathcal{L}_{recon}+\beta D_{KL}\big(q_{\phi}(z|x)\,\|\,p(z)\big),\qquad D_{KL}=-\frac{1}{2}\sum_j\left(1+\log\sigma_j^2-\mu_j^2-\sigma_j^2\right)
Lrecon\mathcal{L}_{recon}
DKLD_{KL}
β\beta
p(z)p(z)
Lrecon\mathcal{L}_{recon}
重建损失,衡量 x' 和 x 的差异
reconstruction loss between x and x prime
DKLD_{KL}
让 latent 分布靠近标准正态的约束
regularizer that pulls latent distributions toward the prior
β\beta
KL 权重;越大,空间越规整,但重建可能变差
KL weight; higher values make the space smoother but may hurt reconstruction
p(z)p(z)
通常取标准正态分布 N(0, I)
usually a standard normal prior N(0, I)
KL

KL Loss 如何约束概率云团

1. KL 到底在约束什么

VAE 里的 KL Loss 衡量的是:当前样本的局部后验 q(z|x)=N(μ,σ²),和标准正态先验 N(0,1) 有多远。它不是用来把不同类别强行分开,而是让 latent space 不要碎裂成孤岛,使训练后可以从 N(0,I) 中采样并得到有意义的生成结果。

当前样本概率云团: q(z|x) = N(μ, σ²) 希望靠近的标准云团: p(z) = N(0, 1) KL(q(z|x) || p(z)) = 两个高斯分布之间的差异

2. 从数学公式到代码公式

一维高斯 N(μ,σ²) 到标准正态 N(0,1) 的 KL 可以写成:

KL(N(μ, σ²) || N(0, 1)) = 0.5 * (μ² + σ² - log(σ²) - 1)

代码中通常不直接学习 σ,而是学习 logvar = log(σ²)。因此 exp(logvar) 就是 σ²,公式可以改写为:

logvar = log(σ²) exp(logvar) = σ² KL = -0.5 * mean(1 + logvar - μ² - exp(logvar))

3. 三个核心项的直觉

这个公式真正做的事,可以拆成三个约束:中心不要太远,云团不要太宽,也不要缩成一个点。

μ² 项: μ 越大,云团中心离 0 越远,KL 越大。 作用: 把概率云团的中心往标准正态中心附近拉。 exp(logvar) = σ² 项: σ² 越大,云团扩散越宽,KL 越大。 作用: 防止概率云团无限扩散。 logvar = log(σ²) 项: 当 σ -> 0 时,log(σ²) -> -∞,KL 会变大。 作用: 防止概率云团塌缩成一个确定点。

4. 为什么要 mean()

训练时一个 batch 里有很多样本,每个样本都有自己的 q(z|x) 和自己的 KL。代码里的 mean() 表示把样本维度和 latent 维度上的 KL 取平均,得到当前 batch 的平均正则化压力。

样本 1: KL1 样本 2: KL2 ... 样本 n: KLn mean(KL) = 当前 batch 的平均 KL

5. 回到本模块的两个样本

手算示例里 KL1 ≈ 0.318KL2 ≈ 0.944。这说明第 2 个样本的概率云团比第 1 个样本更偏离标准正态:它的中心 μ=(1.0,0.8) 离原点更远,而且第二个方向上的 σ=0.3 比标准正态更窄,所以 KL 更大。

好的 latent 云团: 位置适中,大小适中 太远: μ 很大,中心飘离 N(0,I) 太大: σ² 很大,云团过度扩散 太小: σ 很小,云团塌缩成点 KL Loss 的作用: 让每个样本的概率云团保持合理、连续、可生成。
6

和前面模块如何连起来

1. 和线性模型中的梯度下降相同

VAE 训练仍然遵循同一条优化链路:先 forward,计算 loss,再 backward,最后更新参数。只是这里的参数不再是 w,b,而是 Encoder 和 Decoder 的所有神经网络权重。

x_recon, mu, logvar, z = model(x)
loss = recon_loss + beta * kl_loss
optimizer.zero_grad()
loss.backward()
optimizer.step()

2. 和 MLP 模块相同

本模块里的 Encoder 和 Decoder 都是小 MLP。Encoder 把二维输入加工成 hidden representation,再输出 mulogvar;Decoder 把二维 z 转回二维数据点。

3. 和 Transformer 模块的表示空间相通

Transformer 关注 token 在表示空间中的关系;VAE 更进一步,显式地把表示空间建模成概率分布。你看到的 μ 点、σ 椭圆和采样点 z,就是“表示空间 + 不确定性”的可视化版本。

7

圆环 VAE 可交互实验

点击“训练圆环 VAE”后,会生成二维圆环数据并训练一个 2D latent VAE。调整样本数、圆环噪声、训练轮数、学习率或 KL 权重后,需要重新训练;手动 z1/z2 用于观察 Decoder 如何把 latent 点映射回数据空间。

Parameter Panel
9 Params
8

VAE 训练与 latent space 实验台

训练一个最小 MLP-VAE。数据是四组二维点,模型学习把它们编码为二维 latent 分布,再解码回原始空间。调节 β 可以观察 reconstruction 和 KL 之间的权衡;调节 manual z 可以直接观察 Decoder 如何把 latent 点翻译成数据空间位置。

Parameter Panel
9 Params
AI
问问 LLM:把你的实验结果解释成 VAE 直觉