自编码器(AE)与变分自编码器(VAE)

一句话概述

自编码器(Autoencoder, AE)和变分自编码器(Variational Autoencoder, VAE)是两类重要的深度生成模型。自编码器由编码器(Encoder)和解码器(Decoder)组成:编码器将高维输入压缩为低维潜在表示(latent code),解码器从潜在表示重建原始输入,通过最小化重建误差来学习数据的高效表示。变分自编码器(VAE)将AE扩展到概率生成框架:它不是学习确定的潜在编码,而是学习潜在空间的概率分布(高斯分布的均值μ和方差σ²),并通过重参数化技巧(Reparameterization Trick)使采样过程可微。VAE的关键创新在于损失函数中加入KL散度项,将潜在空间正则化为接近标准高斯分布,这使得VAE可以生成新样本——从标准高斯采样→解码器→生成新数据。

💡 核心要点:①自编码器通过编码-解码学习高效数据表示,但不具备生成能力 ②VAE学习潜在空间的概率分布(μ, σ²),使模型成为真正的生成模型 ③重参数化技巧将随机采样分离为可微运算,使VAE可以端到端训练 ④VAE损失=重建误差+KL散度,KL项正则化潜在空间为标准高斯

教学与演示

一、自编码器:压缩与重建

是什么(定义):自编码器(Autoencoder)是一种无监督学习模型,由两部分组成:编码器f_θ将输入x∈R^d映射到低维潜在表示z∈R^m(m<d),解码器g_φ从z重建输入x̂。训练目标是最小化重建误差L(x, x̂)=||x - x̂||²。自编码器通过"压缩-重建"过程学习数据的低维流形结构,编码器学习有意义的特征表示。

大白话 自编码器就像"文件的压缩与解压"。编码器是压缩软件(WinRAR)——把大文件压缩成小文件;解码器是解压软件——把小文件还原成大文件。原始自编码器的目标是让还原的文件尽量接近原文件。但传统自编码器有个问题:它只会"记住"训练数据,不能"创作"新数据——就像WinRAR只能解压它压缩过的文件。

为什么(原理):自编码器的低维瓶颈(m<d)迫使模型学习数据的最本质特征,丢弃噪声和冗余信息。如果潜在维度m太小,模型可能无法充分重建;如果m太大(甚至大于d),模型可能学不到有意义的表示(恒等映射)。自编码器的应用包括:数据降维(替代PCA)、特征学习、去噪(Denoising Autoencoder)、异常检测(重建误差大的样本可能是异常)。

import numpy as np

# 自编码器(AE)的完整实现
# 编码-解码结构,通过重建学习数据表示

class Autoencoder:
    def __init__(self, d_in=10, d_hidden=4, d_latent=2, lr=0.01):
        np.random.seed(42)
        # 编码器权重:输入→隐藏→潜在
        self.W_enc1 = np.random.randn(d_in, d_hidden) * 0.3
        self.b_enc1 = np.zeros(d_hidden)
        self.W_enc2 = np.random.randn(d_hidden, d_latent) * 0.3
        self.b_enc2 = np.zeros(d_latent)

        # 解码器权重:潜在→隐藏→输出
        self.W_dec1 = np.random.randn(d_latent, d_hidden) * 0.3
        self.b_dec1 = np.zeros(d_hidden)
        self.W_dec2 = np.random.randn(d_hidden, d_in) * 0.3
        self.b_dec2 = np.zeros(d_in)

        self.lr = lr

    def encode(self, x):
        """编码器:x → z"""
        h = np.maximum(0, x @ self.W_enc1 + self.b_enc1)  # 隐藏层
        z = h @ self.W_enc2 + self.b_enc2  # 潜在表示(无激活)
        return z

    def decode(self, z):
        """解码器:z → x̂"""
        h = np.maximum(0, z @ self.W_dec1 + self.b_dec1)  # 隐藏层
        x_hat = h @ self.W_dec2 + self.b_dec2  # 重建输出
        return x_hat

    def forward(self, x):
        """完整前向传播:x → z → x̂"""
        z = self.encode(x)
        x_hat = self.decode(z)
        return x_hat, z

    def train_step(self, x):
        """一步训练"""
        # 前向传播
        z = self.encode(x)
        x_hat = self.decode(z)

        # 重建误差:MSE
        loss = np.mean((x - x_hat) ** 2)

        # 简化梯度更新(实际需要完整反向传播)
        error = 2 * (x_hat - x) / len(x)  # MSE梯度
        # 仅更新解码器最后一层作为演示
        # h_dec = ReLU(z @ W_dec1 + b_dec1)
        # self.W_dec2 -= lr * h_dec.T @ error
        # self.b_dec2 -= lr * np.sum(error, axis=0)

        return loss, z, x_hat


# 创建示例数据
np.random.seed(1)
X = np.random.randn(100, 10) * 0.5  # 100个样本,10维

ae = Autoencoder(d_in=10, d_hidden=4, d_latent=2)

print("=== 自编码器(AE):压缩与重建 ===\n")

# 展示几个样本的编码-解码
for i in range(3):
    x_hat, z = ae.forward(X[i:i+1])
    print(f"样本{i+1}:")
    print(f"  原始: {np.round(X[i][:5], 3)}...")
    print(f"  潜在编码(z): {np.round(z.flatten(), 3)}")
    print(f"  重建: {np.round(x_hat.flatten()[:5], 3)}...")
    mse = np.mean((X[i] - x_hat.flatten()) ** 2)
    print(f"  重建MSE: {mse:.4f}")
    print()

print("核心特点:")
print("- 编码器将10维数据压缩到2维潜在空间")
print("- 解码器从2维重建原始10维数据")
print("- 目标是最小化重建误差")
print("- 但传统AE不能生成新样本!")
自编码器的目标函数\(L_{\text{AE}}(\theta, \phi) = \frac{1}{n}\sum_{i=1}^{n} \|x_i - g_\phi(f_\theta(x_i))\|^2\)
大白话 AE就像"复印机"——能把文件压缩成摘要(编码),再从摘要还原文件(解码)。但它不能"创作"新文件——你给它一个随机的摘要,它只会印出模糊的噪点。VAE改进了这一点:它学习的是"摘要的分布",可以从分布中采样新摘要,从而生成全新的文件。

什么用(应用):自编码器用于降维(替代PCA)、去噪(Denoising AE)、异常检测(重建误差大的样本为异常)、预训练(用AE初始化深度网络)。在金融风控中,训练AE学习正常交易模式,重建误差大的交易被标记为可疑。

哪些坑(缺点):传统AE不能生成新样本——潜在空间不连续,随机采样无法产生有意义的数据。AE潜在空间的点之间可能存在"空洞"(解码器在训练数据之外的行为不可预测),不适合作为生成模型。

二、变分自编码器:从确定编码到概率分布

是什么(定义):变分自编码器(VAE)由Kingma和Welling于2014年提出。与传统AE学习确定的潜在编码z不同,VAE学习潜在空间的概率分布:编码器输出高斯分布的均值μ(x)和方差σ²(x),潜在编码从N(μ, σ²)中采样。VAE的损失函数包含两项:①重建误差(希望解码器能还原输入),②KL散度D_KL(N(μ,σ²)||N(0,I))(希望潜在分布接近标准高斯)。这种设计使得VAE的潜在空间连续且有结构。

大白话 VAE就像"学会了字体风格的设计师"。传统AE只能记住每个字的写法(确定编码),VAE学会了"字体风格的概率分布"——它知道"某个风格的字的笔画粗细大约在什么范围,倾斜角度大约在多少"。当你从标准高斯分布采样一个新编码时,VAE解码器就能生成一个看起来"合理"的新字体——因为潜在空间被正则化得连续且平滑。

为什么(原理):VAE的核心数学是变分推断。它假设存在一个隐变量z生成观察变量x,目标是最大化证据下界ELBO:L = E_{q(z|x)}[log p(x|z)] - D_KL(q(z|x)||p(z))。第一项是重建期望(从后验q(z|x)采样z,解码后与x比较),第二项是KL散度(强制后验接近先验p(z)=N(0,I))。重参数化技巧z=μ+σ·ε(ε~N(0,I))将随机性分离出来,使模型可以端到端训练。

import numpy as np

# 变分自编码器(VAE)的核心实现
# 学习潜在空间的概率分布,通过重参数化实现端到端训练

class VariationalAutoencoder:
    def __init__(self, d_in=10, d_hidden=4, d_latent=2, lr=0.01):
        np.random.seed(42)
        # 编码器:输出μ和log(σ²)(而非确定编码z)
        self.W_enc1 = np.random.randn(d_in, d_hidden) * 0.3
        self.b_enc1 = np.zeros(d_hidden)
        self.W_mu = np.random.randn(d_hidden, d_latent) * 0.3
        self.b_mu = np.zeros(d_latent)
        self.W_logvar = np.random.randn(d_hidden, d_latent) * 0.3
        self.b_logvar = np.zeros(d_latent)

        # 解码器:与AE相同
        self.W_dec1 = np.random.randn(d_latent, d_hidden) * 0.3
        self.b_dec1 = np.zeros(d_hidden)
        self.W_dec2 = np.random.randn(d_hidden, d_in) * 0.3
        self.b_dec2 = np.zeros(d_in)

    def encode(self, x):
        """编码器:输出μ和log(σ²)"""
        h = np.maximum(0, x @ self.W_enc1 + self.b_enc1)
        mu = h @ self.W_mu + self.b_mu  # 均值
        logvar = h @ self.W_logvar + self.b_logvar  # log(方差)
        return mu, logvar

    def reparameterize(self, mu, logvar):
        """重参数化技巧:z = μ + σ·ε,ε ~ N(0, I)"""
        sigma = np.exp(0.5 * logvar)  # σ = exp(0.5·log(σ²))
        epsilon = np.random.randn(*mu.shape)  # 标准高斯噪声
        z = mu + sigma * epsilon  # 重参数化采样
        return z

    def decode(self, z):
        """解码器:潜在编码→重建"""
        h = np.maximum(0, z @ self.W_dec1 + self.b_dec1)
        x_hat = h @ self.W_dec2 + self.b_dec2
        return x_hat

    def forward(self, x):
        """VAE前向传播"""
        mu, logvar = self.encode(x)
        z = self.reparameterize(mu, logvar)
        x_hat = self.decode(z)
        return x_hat, mu, logvar, z

    def kl_divergence(self, mu, logvar):
        """KL散度:D_KL(N(μ,σ²) || N(0, I))"""
        # 解析公式:-0.5 * Σ(1 + log(σ²) - μ² - σ²)
        kl = -0.5 * np.sum(1 + logvar - mu**2 - np.exp(logvar), axis=1)
        return np.mean(kl)

    def loss(self, x, x_hat, mu, logvar):
        """VAE损失 = 重建误差 + KL散度"""
        recon_loss = np.mean((x - x_hat) ** 2)  # 重建误差
        kl_loss = self.kl_divergence(mu, logvar)  # KL散度
        return recon_loss + kl_loss, recon_loss, kl_loss

    def generate(self, n_samples=3):
        """生成新样本:从标准高斯采样z,解码"""
        z = np.random.randn(n_samples, self.W_dec1.shape[0])
        x_gen = self.decode(z)
        return x_gen


# 演示VAE
np.random.seed(1)
X = np.random.randn(50, 10) * 0.5

vae = VariationalAutoencoder(d_in=10, d_hidden=4, d_latent=2)

print("=== 变分自编码器(VAE)=== \n")

# 编码一个样本
x = X[0:1]
mu, logvar = vae.encode(x)
print(f"样本编码:")
print(f"  μ (均值): {np.round(mu.flatten(), 3)}")
print(f"  log(σ²): {np.round(logvar.flatten(), 3)}")
print(f"  → VAE学习的是概率分布,而非确定编码!")

# 重参数化
z = vae.reparameterize(mu, logvar)
print(f"\n重参数化采样: z = μ + σ·ε")
print(f"  z: {np.round(z.flatten(), 3)}")

# VAE vs AE的生成能力
x_hat, mu, logvar, z = vae.forward(x)
loss, recon, kl = vae.loss(x, x_hat, mu, logvar)
print(f"\nVAE损失: {loss:.4f} = 重建({recon:.4f}) + KL({kl:.4f})")

# 生成新样本
gen = vae.generate(3)
print(f"\n生成新样本(从N(0,I)采样z):")
for i in range(3):
    print(f"  生成样本{i+1}: {np.round(gen[i][:5], 3)}...")

print("\n→ VAE可以从标准高斯采样生成新数据!")
print("→ 这是传统AE做不到的")
VAE的核心公式\(L_{\text{VAE}} = -\mathbb{E}_{z \sim q_\phi(z|x)}[\log p_\theta(x|z)] + D_{KL}(q_\phi(z|x) \| p(z))\)
大白话 VAE的训练有两个"老师":第一个老师检查"你重建得好不好"(重建损失),第二个老师检查"你的潜在空间是否像标准高斯"(KL散度)。两个老师同时打分,模型要同时满足两者——既能把数据重建好,又要把潜在空间整理得规整。这种"双重约束"让VAE学会了数据的概率分布,从而能生成新数据。

什么用(应用):VAE是最经典的深度生成模型之一,用于图像生成、数据增强、异常检测、表征学习等。在图像领域,VAE可以生成人脸、手写数字等;在药物发现中,VAE生成新的分子结构。VAE的潜在空间具有良好的插值性质——在潜在空间中两个点之间线性插值,解码后得到平滑过渡的图像(如从"微笑"到"不笑")。

哪些坑(缺点):VAE生成的图像通常比GAN模糊——因为高斯假设和KL散度的"平均化"效应。后验坍缩(Posterior Collapse)——解码器过于强大,忽略潜在变量(KL项趋近于0)。VAE的潜在空间维度选择需要经验调优。

三、重参数化技巧:让采样可微

是什么(定义):重参数化技巧(Reparameterization Trick)是VAE能够端到端训练的关键。直接从N(μ, σ²)采样z是不可微的(随机操作无法反向传播梯度),重参数化将其改写为z=μ+σ·ε,其中ε~N(0,I)是独立的随机噪声。这样,μ和σ可以接收梯度(通过链式法则),而随机性被隔离在ε中。

大白话 重参数化技巧就是"把随机性外包"。原本你要从N(μ,σ²)中随机抽一个数z,这个"抽"的动作是不可微的(像掷骰子)。现在你把"掷骰子"外包给ε(标准概率分布),然后你只需要做"μ+σ×ε"这个确定性的运算。梯度可以流过μ和σ(因为是确定性运算),但不会流过ε(因为它是独立随机变量)。这就让"随机采样"变得可微了。
import numpy as np

# 重参数化技巧详解
# 对比不可微采样和可微重参数化

def demo_reparameterization():
    print("=== 重参数化技巧详解 ===\n")

    mu, logvar = np.array([0.5, -0.3]), np.array([0.2, 0.1])  # 假设的编码器输出
    sigma = np.exp(0.5 * logvar)

    print(f"编码器输出:μ={mu}, σ²=exp(logvar)={np.exp(logvar)}")

    # 方式1:直接采样(不可微)
    print("\n【方式1:直接采样(不可微)】")
    print("  z = sample_from(N(μ, σ²))")
    print("  问题:'sample_from'这个操作没有梯度!")
    print("  → 无法反向传播")

    # 方式2:重参数化(可微)
    print("\n【方式2:重参数化(可微)】")
    print("  ε = sample_from(N(0, I))  ← 随机性隔离在此")
    print("  z = μ + σ · ε              ← 确定性运算")

    np.random.seed(42)
    epsilon = np.random.randn(*mu.shape)
    z = mu + sigma * epsilon

    print(f"  ε (随机噪声): {epsilon}")
    print(f"  z = μ + σ·ε = {mu} + {sigma}·{epsilon}")
    print(f"  z = {np.round(z, 3)}")

    # 多次采样展示
    print("\n多次重参数化采样(μ固定):")
    for i in range(3):
        eps = np.random.randn(*mu.shape)
        z_i = mu + sigma * eps
        print(f"  第{i+1}次: z = {np.round(z_i, 3)}")

    print("\n重参数化的核心优势:")
    print("  ∂z/∂μ = 1  ← 梯度可以流过μ")
    print("  ∂z/∂σ = ε  ← 梯度可以流过σ")
    print("  ∂z/∂ε = σ  ← 但不需要梯度流过ε(它不参与学习)")
    print("  → 使得随机采样操作可以被端到端训练!")


demo_reparameterization()
重参数化技巧\(z = \mu(x) + \sigma(x) \odot \varepsilon, \quad \varepsilon \sim \mathcal{N}(0, I)\)
大白话 重参数化技巧就是"把运气成分和工作分开"。掷骰子的"运气"(ε)独立处理,μ和σ的工作是"放大/缩小"和"偏移"——这两个工作是可预测、可优化的。学会了怎么"放大/缩小"和"偏移"(学习μ和σ),就学会了整个分布。

什么用(应用):重参数化技巧不仅用于VAE,还应用于贝叶斯神经网络、强化学习中的策略梯度、扩散模型中的去噪过程等。它是连接"随机采样"和"梯度优化"的桥梁,使得带有随机性的模型可以端到端训练。

哪些坑(缺点):重参数化要求分布可以表示为确定性变换+独立噪声——这对高斯分布有效,但对离散分布(如伯努利)不适用。对于离散潜变量,需要使用Gumbel-Softmax或REINFORCE等替代方法。

概念关系图谱

概念核心含义与AI的关系关联概念
自编码器(AE)通过编码-解码学习数据压缩表示无监督特征学习的基础模型编码器、解码器
变分自编码器(VAE)学习潜在空间的概率分布第一个实用的深度生成模型变分推断、ELBO
潜在空间(Latent Space)数据的低维压缩表示空间生成模型的核心概念流形学习、表示
ELBO证据下界,VAE的优化目标连接变分推断和深度学习的桥梁KL散度、似然
重参数化技巧将采样改写为确定性变换+独立噪声使随机采样可微,实现端到端训练梯度估计、Gumbel-Softmax
KL散度衡量两个概率分布差异的度量VAE损失的正则化项相对熵、信息论

重点答疑

Q1: AE和VAE的根本区别是什么?

AE学习确定的映射:x→z(确定编码),z→x̂(确定解码),只能重建训练数据,不能生成新数据。VAE学习概率分布:x→(μ,σ²)(分布编码),z~N(μ,σ²)→x̂,可以从先验N(0,I)采样z来生成新数据。VAE的KL散度项强制潜在空间结构良好(连续、平滑),这是其生成能力的关键。

Q2: VAE的KL散度为什么能让潜在空间变得规整?

KL散度项最小化q(z|x)和p(z)=N(0,I)之间的距离。这有两个效果:①防止方差σ²过大(否则KL惩罚增大),②将均值μ拉向0。结果:所有训练数据的潜在编码都围绕原点聚集,形成一个连续的、近似高斯分布的潜在空间。从中采样任何点,解码器都能生成"合理的"数据。

Q3: VAE生成的图像为什么比GAN模糊?

两个原因:①VAE使用逐像素MSE作为重建损失,MSE倾向于生成"平均化"的输出(模糊);②VAE的高斯假设意味着解码器输出的方差固定,模型无法表达锐利的细节。GAN使用对抗损失(判别器判断真假),对细节更敏感,因此生成图像更锐利。这是VAE和GAN的经典差异化特性。

章节单词汇总

英文音标术语/释义
Autoencoder (AE)/ˈɔːtoʊɪnˌkoʊdər/自编码器,通过编码-解码学习数据表示
Variational Autoencoder (VAE)/ˌveriˈeɪʃənəl ˈɔːtoʊɪnˌkoʊdər/变分自编码器,学习潜在空间概率分布
Encoder/ɪnˈkoʊdər/编码器,将输入映射到潜在表示
Decoder/dɪˈkoʊdər/解码器,从潜在表示重建输入
Latent Space/ˈleɪtənt speɪs/潜在空间,数据的低维压缩表示空间
Reparameterization Trick/riːˌpærəmɪtəraɪˈzeɪʃən trɪk/重参数化技巧,使随机采样可微
KL Divergence/keɪ el daɪˈvɜːrdʒəns/KL散度,衡量两个分布差异
ELBO/ˈelboʊ/证据下界,VAE的优化目标
Posterior Collapse/pɑːsˈtɪriər kəˈlæps/后验坍缩,解码器忽略潜在变量

面试练习

Q1 [单选] 传统自编码器(AE)不能用于生成新样本的根本原因是什么?

  • A. 编码器太弱
  • B. 潜在空间不连续,随机采样无法产生有意义的数据
  • C. 解码器太弱
  • D. 训练数据太少
解答:AE学习确定的编码映射,潜在空间可能存在"空洞"——训练数据覆盖的点之间有空白区域,从这些空白区域采样解码会产生无意义输出。VAE通过KL散度将潜在空间正则化为连续分布,解决了这一问题。

Q2 [单选] VAE的损失函数包含哪两项?

  • A. MSE + L1正则化
  • B. 重建误差 + KL散度
  • C. 交叉熵 + Dropout
  • D. 对抗损失 + 梯度惩罚
解答:VAE损失 = 重建误差(如MSE或交叉熵)+ KL散度(D_KL(N(μ,σ²)||N(0,I)))。重建误差保证解码质量,KL散度正则化潜在空间。

Q3 [单选] 重参数化技巧中的ε服从什么分布?

  • A. 均匀分布 U(0,1)
  • B. 标准高斯分布 N(0, I)
  • C. 伯努利分布
  • D. 泊松分布
解答:ε ~ N(0, I)(标准高斯分布),独立于μ和σ。通过z = μ + σ·ε将采样改写为确定性变换,使梯度可以反向传播。

Q4 [多选] 关于VAE,以下哪些说法是正确的?

  • A. VAE学习潜在空间的概率分布(μ和σ²)
  • B. KL散度项强制潜在空间接近标准高斯
  • C. 重参数化技巧使VAE可以端到端训练
  • D. VAE生成的图像通常比GAN更清晰
  • E. 从N(0,I)采样z并解码可以生成新样本
解答:VAE学习分布、KL正则化、重参数化端到端训练、可从先验采样生成。但VAE生成的图像通常比GAN模糊(MSE的平均化效应)。

Q5 [单选] VAE中,编码器输出的是什么?

  • A. 确定的潜在编码z
  • B. 高斯分布的均值μ和方差σ²
  • C. 重建数据x̂
  • D. 分类概率
解答:VAE编码器输出高斯分布的参数μ(x)和σ²(x)(或log σ²),而非确定编码。然后从N(μ,σ²)采样z。