查看全课大纲(第 89 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
VAE
变分自编码器(VAE)
小象实战讲义 · 基于深度学习的计算机视觉
在进入 VAE 之前,我们先回顾经典的自编码器:它能够学习图像的压缩表示并重建图像,但无法用于生成全新的图像。本节我们将学习变分自编码器(Variational Autoencoder, VAE),它通过在编码过程中引入概率分布约束,将离散的潜在空间变为连续且平滑的,从而能够从随机噪声中生成新的、可理解的图像。学完本节,你将理解VAE的核心思想、其损失函数的构成,以及“重参数化”这一关键训练技巧。
💡 核心导读
- 从AE到VAE的跨越:经典自编码器的潜在空间是离散且不连续的,无法用于生成。VAE通过强制潜在向量服从高斯分布,解决了这一问题。
- VAE的核心机制:编码器不再输出一个确定的向量,而是输出一个高斯分布的参数(均值和方差),解码器则从该分布中采样进行重建。
- 损失函数的两部分:VAE的损失由重构损失(衡量重建质量)和KL散度损失(衡量潜在分布与标准正态分布的相似度)共同构成。
- 重参数化技巧:为了使采样过程可导,VAE采用“重参数化”技术,将随机采样转化为确定性计算,使得误差能够反向传播。
经典自编码器的局限
我们先快速回顾一下经典自编码器(Autoencoder, AE)的结构。它由编码器(Encoder)和解码器(Decoder)两部分组成。
输入图像 X --> [编码器:卷积网络] --> 潜在向量 Z (低维) --> [解码器:反卷积网络] --> 重建图像 X'训练目标很简单:最小化原始输入图像 X 与重建输出图像 X' 之间的差异(如均方误差MSE)。训练完成后,我们可以保存某张图像的编码向量 Z,之后随时将其输入解码器,就能重建出该图像。
然而,一个根本性的问题出现了:我们能否凭空创造一个潜在向量 Z,输入解码器后生成一张全新的、合理的图像?
遗憾的是,对于经典AE,答案通常是否定的。原因在于,经典AE学习到的潜在空间(Latent Space)通常是不连续且未被充分覆盖的。编码器只会为训练集中出现过的图像生成对应的 Z 值。潜在空间中大量区域是“空白”的,如果将一个随机向量放入这些空白区域,解码器输出的往往是无意义的噪声。
import numpy as np
# 模拟经典AE的潜在空间:只有少数离散点有定义
def classic_ae_decoder(z):
# 假设潜在空间只有两个有效点对应猫和狗
valid_points = {0: 'cat_image', 1: 'dog_image'}
# 如果z接近有效点,则返回对应图像标签;否则返回噪声
if np.abs(z - 0) < 0.1:
return valid_points[0]
elif np.abs(z - 1) < 0.1:
return valid_points[1]
else:
return 'noise_image'
# 测试
test_vectors = [-0.5, 0.05, 0.5, 1.05, 2.0]
for zv in test_vectors:
print(f"潜在向量 z={zv:.2f} -> 输出: {classic_ae_decoder(zv)}")潜在向量 z=-0.50 -> 输出: noise_image
潜在向量 z=0.05 -> 输出: cat_image
潜在向量 z=0.50 -> 输出: noise_image
潜在向量 z=1.05 -> 输出: dog_image
潜在向量 z=2.00 -> 输出: noise_image可以看到,只有 z 在0或1附近时,输出才有意义。这正是经典AE无法用于生成的原因。
VAE的基本思想:引入分布约束
VAE的核心思想非常直观:给编码网络加上一个约束,强制它生成的潜在向量 Z 的分布服从标准正态分布(单位高斯分布)。
具体来说,VAE的编码器不再直接输出一个潜在向量 Z,而是输出定义了一个概率分布的两个参数:
- 均值向量 μ
- 对数方差向量 log(σ²)(通常输出对数方差以保证数值稳定性)
我们假设潜在变量 Z 服从以 μ 为均值、σ² 为方差的对角高斯分布,即 Z ~ N(μ, σ²I),并且我们希望这个分布尽量接近标准正态分布 N(0, I)。
这样做的好处是什么?
- 连续且平滑的潜在空间:由于整个潜在空间都被一个连续的概率分布所填充,从该分布中任意采样一个点
Z,解码器都能生成一张有意义的图像。 - 隐式特征解耦:理想情况下,潜在空间的不同维度可能对应图像的不同语义特征(如笑容程度、肤色、发型等),并且这些特征是连续变化的。这使得我们可以在潜在空间中进行有意义的插值。
import numpy as np
# 模拟VAE的编码器:输出分布参数
np.random.seed(42) # 固定随机种子,保证输出确定
def vae_encoder(x):
# 假设输入x经过网络,得到分布的参数
mu = np.array([0.2]) # 均值
log_var = np.array([-1.0]) # 对数方差
return mu, log_var
# 从参数定义的分布中采样得到潜在向量z
def sample_z(mu, log_var):
sigma = np.exp(0.5 * log_var) # 计算标准差
eps = np.random.randn(*mu.shape) # 从标准正态分布采样噪声
z = mu + eps * sigma # 重参数化技巧
return z
# 模拟VAE的解码器
def vae_decoder(z):
# 解码器将z映射回数据空间
# 这里简化为:z的值越大,生成的“笑容程度”越高
smile_intensity = 1 / (1 + np.exp(-5 * (z - 0.5))) # sigmoid函数模拟
return f"生成图像,笑容程度: {smile_intensity[0]:.2f}"
# VAE前向过程模拟
mu, log_var = vae_encoder("输入图像")
print(f"编码器输出 -> 均值 μ: {mu[0]:.3f}, 对数方差 log_var: {log_var[0]:.3f}")
# 采样两次,得到不同的z
for i in range(2):
z_sampled = sample_z(mu, log_var)
output = vae_decoder(z_sampled)
print(f"采样 {i+1}: z = {z_sampled[0]:.3f}, {output}")编码器输出 -> 均值 μ: 0.200, 对数方差 log_var: -1.000
采样 1: z = 0.501, 生成图像,笑容程度: 0.50
采样 2: z = 0.116, 生成图像,笑容程度: 0.13注意,对于同一张输入图像,由于采样了不同的噪声 ε,我们得到了不同的潜在向量 Z,从而解码出略有差异的图像。这体现了VAE的概率生成特性。
VAE的损失函数
VAE的训练目标是双重的:
- 重建图像要像(Reconstruction Loss)。
- 学到的潜在分布要像标准正态分布(Regularization Loss)。
因此,其损失函数是这两部分的加权和:
总损失 = 重构损失 + KL散度损失
1. 重构损失
衡量解码器重建的图像 X' 与原始图像 X 的差异。对于图像数据,常用均方误差(MSE) 或二元交叉熵(BCE)。其作用是迫使编码器保留输入数据的关键信息。
L_{recon} = MSE(X, X') = ||X - X'||^2
2. KL散度损失
衡量编码器产生的潜在分布 q(Z|X) = N(μ, σ²) 与先验分布 p(Z) = N(0, I) 之间的差异。使用KL散度(Kullback-Leibler Divergence)进行度量。其作用是规范潜在空间,使其连续、平滑且易于采样。
对于对角高斯分布,其KL散度有一个漂亮的解析解:
L_{KL} = D_{KL}[q(Z|X) || p(Z)] = -0.5 * Σ (1 + log(σ²) - μ² - σ²)
其中求和是对潜在向量的每个维度进行的。
import numpy as np
# 计算VAE损失函数的示例
def compute_vae_loss(x_original, x_reconstructed, mu, log_var):
"""
计算VAE的总损失。
参数:
x_original: 原始图像数据 (展平向量)
x_reconstructed: 重建图像数据 (展平向量)
mu: 均值向量
log_var: 对数方差向量
"""
# 1. 重构损失 (MSE)
mse_loss = np.mean((x_original - x_reconstructed) ** 2)
# 2. KL散度损失 (解析解)
# KL = -0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
kl_loss = -0.5 * np.sum(1 + log_var - np.power(mu, 2) - np.exp(log_var))
# 通常对KL损失取平均,使其与重构损失尺度相近
kl_loss = kl_loss / x_original.shape[0]
# 3. 总损失
total_loss = mse_loss + kl_loss
return mse_loss, kl_loss, total_loss
# 模拟数据
np.random.seed(42)
# 假设一个4维的潜在空间,批量大小为2
batch_size = 2
latent_dim = 4
# 原始数据 (例如,2张8x8灰度图展平为64维向量)
x_orig = np.random.randn(batch_size, 64)
# 重建数据
x_recon = x_orig + np.random.randn(batch_size, 64) * 0.1 # 加一点噪声模拟重建误差
# 编码器输出的分布参数
mu = np.random.randn(batch_size, latent_dim) * 0.5
log_var = np.random.randn(batch_size, latent_dim) * 0.3 - 1 # 使方差较小
# 计算损失
recon_loss, kl_loss, total_loss = compute_vae_loss(x_orig, x_recon, mu, log_var)
print(f"重构损失 (MSE): {recon_loss:.6f}")
print(f"KL散度损失: {kl_loss:.6f}")
print(f"VAE总损失: {total_loss:.6f}")重构损失 (MSE): 0.010036
KL散度损失: 1.813922
VAE总损失: 1.823958重参数化技巧
训练VAE时,我们需要从分布 N(μ, σ²) 中采样 Z,然后将 Z 送入解码器。然而,“采样”是一个随机操作,不可导,这会阻断误差从解码器向编码器参数(μ, σ)的反向传播。
重参数化技巧(Reparameterization Trick) 完美地解决了这个问题。其核心思想是将随机性从计算图中分离出去。
具体做法:
- 从标准正态分布中采样一个噪声变量:
ε ~ N(0, I) - 通过一个确定性变换得到
Z:Z = μ + ε ⊙ σ(其中⊙表示逐元素相乘)
这样,Z 依然是随机的(因为 ε 是随机的),但随机性来源于一个与模型参数无关的输入 ε。计算图变成了 μ, σ, ε -> Z,而对 μ 和 σ 的求导路径是确定且可导的。
import numpy as np
# 对比直接采样与重参数化
np.random.seed(42)
latent_dim = 3
mu = np.array([0.2, -0.1, 0.5])
log_var = np.array([-1.0, 0.0, -2.0]) # 对应方差为 exp(log_var)
sigma = np.exp(0.5 * log_var)
print(f"均值 μ: {mu}")
print(f"标准差 σ: {sigma}")
# 方法1: 直接采样(在自动微分框架里,采样节点没有梯度)
z_direct = np.random.randn(latent_dim) * sigma + mu
print(f"直接采样 Z: {z_direct}")
# 方法2: 重参数化 (可导)
# 两行数值形式相同,区别在计算图:重参数化把随机性移到常数 ε 上,Z 对 μ、σ 可导
epsilon = np.random.randn(latent_dim) # 从标准正态采样,这个节点在反向传播时被视为常数输入
z_reparam = mu + epsilon * sigma # 确定性计算
print(f"重参数化 Z: {z_reparam}")
print(f"使用的噪声 ε: {epsilon}")
# 注意:z_direct 和 z_reparam 都是来自同一分布 N(μ, σ²) 的有效样本,只是生成方式不同。均值 μ: [ 0.2 -0.1 0.5]
标准差 σ: [0.60653066 1. 0.36787944]
直接采样 Z: [ 0.50127236 -0.2382643 0.7382713 ]
重参数化 Z: [ 1.1237643 -0.33415337 0.41386583]
使用的噪声 ε: [ 1.52302986 -0.23415337 -0.23413696]在反向传播时,损失 L 对 μ 和 σ 的梯度可以顺利计算:
∂L/∂μ = ∂L/∂z * ∂z/∂μ = ∂L/∂z * 1∂L/∂σ = ∂L/∂z * ∂z/∂σ = ∂L/∂z * ε
VAE的训练流程与效果
结合以上所有部分,VAE完整的训练流程如下所示:
输入 X
|
v
[编码器网络]
| 输出两个向量:
|-----> 均值 μ
|-----> 对数方差 log(σ²)
|
| 重参数化:
|-----> 采样 ε ~ N(0, I)
|-----> 计算 Z = μ + ε ⊙ σ
|
v
[解码器网络]
|
v
重建输出 X'
|
v
计算损失 L = L_recon(X, X') + L_KL(N(μ,σ²) || N(0,I))
|
v
反向传播,更新编码器和解码器参数VAE生成的图像质量在早期模型中表现尚可,但存在一个普遍问题:图像往往比较模糊。这主要是因为重构损失使用了MSE。MSE惩罚的是像素级的差异,它倾向于让模型输出所有可能图像的平均值,从而导致细节丢失,图像变得平滑模糊。
尽管如此,VAE为深度生成模型奠定了重要基础。它明确地将概率图模型与神经网络结合,引入了潜在变量和变分推断的思想。其“编码-解码”的框架和重参数化技巧也被后续许多模型所借鉴。
📝 动手练一练
- 概念辨析:经典自编码器(AE)与变分自编码器(VAE)在潜在向量的处理上有何根本区别?这种区别导致了它们在什么能力上的差异?
👉 点击查看参考答案
根本区别:
- 经典AE的编码器直接将输入映射为一个确定的、离散的潜在向量
Z。 - VAE的编码器将输入映射为一个概率分布的参数(均值
μ和方差σ²),潜在向量Z是从这个分布中采样得到的随机变量。
能力差异:
- 由于潜在空间离散且未被充分覆盖,经典AE无法可靠地生成新图像,随机向量输入解码器常产生噪声。
- VAE通过强制潜在分布连续且平滑(接近标准正态),使得从该分布中任意采样的点,解码后都能生成一张有意义的图像,从而具备了生成新样本的能力。
- 手算理解:假设一个VAE模型的潜在空间只有1维。对于某张输入图像,编码器输出
μ = 0.5,log(σ²) = -2。请计算: a) 方差σ²和标准差σ。 b) 该分布与标准正态分布N(0,1)之间的KL散度L_KL。
👉 点击查看参考答案
a) 计算方差和标准差: log(σ²) = -2 => σ² = exp(-2) ≈ 0.1353 σ = sqrt(σ²) = exp(-1) ≈ 0.3679
b) 计算KL散度(使用单维公式): L_KL = -0.5 * (1 + log(σ²) - μ² - σ²) = -0.5 * (1 + (-2) - (0.5)² - 0.1353) = -0.5 * (1 - 2 - 0.25 - 0.1353) = -0.5 * (-1.3853) ≈ 0.6927
因此,KL散度损失约为 0.6927。这个正值表示学到的分布 N(0.5, 0.1353) 与标准正态分布 N(0,1) 之间存在差异。
本章小结
本节我们深入探讨了变分自编码器(VAE),它是连接自编码器与生成式模型的重要桥梁。VAE通过为潜在变量引入概率分布约束,构建了一个连续、平滑的潜在空间,从而实现了从随机噪声生成新图像的能力。其巧妙的两部分损失函数(重构损失+KL散度损失)和重参数化技巧,是模型得以成功训练的关键。
今天,VAE 仍是生成建模与表示学习的基础组件,后续发展出 β-VAE、VQ-VAE 等变体;其重参数化与 ELBO(证据下界)思想也广泛用于变分推断、扩散模型与多模态表示学习。
📋 行动清单
- 理解VAE的动机:明确经典AE在生成任务上的缺陷,以及VAE通过分布约束如何解决该问题。
- 掌握VAE损失函数:能写出并解释重构损失和KL散度损失的具体形式及其作用。
- 应用重参数化技巧:理解为什么需要重参数化,并能用公式
Z = μ + ε ⊙ σ表达采样过程。
—— 小象教研组