跳到主要内容

变分自编码器

变分自编码器 (VAE) 是一种生成模型,它使用神经网络将输入数据编码到潜在空间中,然后将其解码以重建原始数据。VAE 结合了深度学习概率图模型的原理,从而能够对复杂数据分布进行无监督学习。

架构

VAE 由三个主要组件组成:

编码器

  • 将输入数据 xx 转换为潜在表示 zz
  • 输出近似后验分布 qϕ(zx)q_{\phi}(z|x) 的参数,通常是均值 μ\mu 和对数方差 logσ2\log \sigma^2
  • 实现为一个由 ϕ\phi 参数化的神经网络。

潜在空间

  • 一个低维空间,表示输入数据的编码特征。
  • 施加一个先验分布 p(z)p (z),通常是标准正态分布 N(0,I)\mathcal{N}(0, I)
  • 支持对新数据实例的采样和生成。

解码器

  • 从潜在表示 zz 重建输入数据。
  • 定义了给定潜在变量的数据的似然 pθ(xz)p_{\theta}(x|z)
  • 实现为一个由 θ\theta 参数化的神经网络。

数学公式

VAE 优化边际似然的证据下界 (ELBO)

L(ϕ,θ;x)=Eqϕ(zx)[logpθ(xz)]KL(qϕ(zx)p(z))\mathcal{L}(\phi, \theta; x) = \mathrm{E}_{q_{\phi}(z|x)} \left[ \log p_{\theta}(x|z) \right] - \mathrm{KL}\left( q_{\phi}(z|x) \parallel p(z) \right)

其中:

  • qϕ(zx)q_{\phi}(z|x): 近似后验分布。
  • pθ(xz)p_{\theta}(x|z): 给定潜在变量的数据似然。
  • KL()\mathrm{KL}(\cdot \parallel \cdot): 两个分布之间的 Kullback-Leibler 散度。

损失函数

损失函数结合了两个项:

  1. 重建损失 (Lrec\mathcal{L}_{\text{rec}}):

    衡量解码器重建输入数据的效果。

  2. 正则化项 (Lreg\mathcal{L}_{\text{reg}}):

    鼓励潜在分布 qϕ(zx)q_{\phi}(z|x) 接近先验 p(z)p (z)

重参数化技巧

为了实现通过随机变量的反向传播,使用了重参数化技巧:

z=μ+σϵ,ϵN(0,I)z = \mu + \sigma \odot \epsilon, \quad \epsilon \sim \mathcal{N}(0, I)
  • 允许梯度在训练期间流经 μ\muσ\sigma
  • \odot 表示逐元素乘法。

训练过程

  1. 编码

    • 输入数据 xx 通过编码器。
    • 输出均值 μ\mu 和对数方差 logσ2\log \sigma^2
  2. 采样

    • 使用重参数化技巧从潜在空间采样 zz
  3. 解码

    • 采样到的 zz 通过解码器重建 x^\hat{x}
  4. 损失计算

    • 计算重建损失和正则化项。
    • 将它们组合成总损失。
  5. 优化

    • 使用梯度下降更新网络参数 ϕ\phiθ\theta

关键概念

变分推断

  • 一种近似复杂概率分布的技术。
  • 将推断问题转化为优化问题。

Kullback-Leibler 散度

  • 衡量两个概率分布之间的差异。
  • 鼓励学习到的分布与先验分布相似。

应用

  • 数据生成:生成与训练数据相似的新数据样本。
  • 异常检测:通过测量重建误差来识别异常值。
  • 降维:将数据压缩到低维潜在空间。
  • 图像和文本建模:生成逼真的图像或文本序列。

扩展和变体

条件变分自编码器 (CVAE)

  • 将额外信息 yy 整合到编码器和解码器中。
  • 建模条件分布 p(xz,y)p (x|z, y)

β-VAE

  • 引入一个超参数 β\beta 以平衡重建项和正则化项:

    L=Eqϕ(zx)[logpθ(xz)]βKL(qϕ(zx)p(z))\mathcal{L} = \mathrm{E}_{q_{\phi}(z|x)} \left[ \log p_{\theta}(x|z) \right] - \beta \, \mathrm{KL}\left( q_{\phi}(z|x) \parallel p(z) \right)
  • 鼓励生成解耦的潜在表示。

数学表达式

高斯分布的 KL 散度

KL(qϕ(zx)p(z))=12i=1d(1+logσi2μi2σi2)\mathrm{KL}\left( q_{\phi}(z|x) \parallel p(z) \right) = -\frac{1}{2} \sum_{i=1}^{d} \left(1 + \log \sigma_i^2 - \mu_i^2 - \sigma_i^2\right)

其中:

  • dd: 潜在空间的维度。
  • μi\mu_iσi2\sigma_i^2: 第 ii 个潜在维度的均值和方差。

实现示例

下面是一个简化的示例,展示了使用 Python 和 PyTorch 实现的 VAE:

import torch
import torch.nn as nn
import torch.nn.functional as F

class VAE(nn.Module):
def __init__(self, input_dim, latent_dim):
super(VAE, self).__init__()
# 编码器层
self.fc1 = nn.Linear(input_dim, 400)
self.fc_mu = nn.Linear(400, latent_dim)
self.fc_logvar = nn.Linear(400, latent_dim)
# 解码器层
self.fc2 = nn.Linear(latent_dim, 400)
self.fc3 = nn.Linear(400, input_dim)

def encode(self, x):
h1 = F.relu(self.fc1(x))
return self.fc_mu(h1), self.fc_logvar(h1)

def reparameterize(self, mu, logvar):
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + std * eps

def decode(self, z):
h2 = F.relu(self.fc2(z))
return torch.sigmoid(self.fc3(h2))

def forward(self, x):
mu, logvar = self.encode(x.view(-1, x.size(1)))
z = self.reparameterize(mu, logvar)
return self.decode(z), mu, logvar

优点与局限性

优点

  • 生成能力:可以生成新的数据样本。
  • 无监督学习:无需标记数据即可学习。
  • 连续潜在空间:支持数据点之间的平滑插值。

局限性

  • 输出模糊:生成的样本可能缺乏清晰度。
  • 训练复杂性:需要仔细调整超参数。
  • 模式崩溃:与 GAN 等其他模型相比,可能生成多样性较差的样本。

使用 PyTorch 在 MNIST 上实现变分自编码器 (VAE)

下面是使用 PyTorch 在 MNIST 数据集上实现变分自编码器 (VAE) 的完整示例。代码包括数据加载、模型定义、训练循环和重建图像的可视化。

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
import torchvision
from torchvision import datasets, transforms
import matplotlib.pyplot as plt

# 设备配置
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 超参数
latent_dim = 20
batch_size = 128
learning_rate = 1e-3
num_epochs = 10

# MNIST 数据集
transform = transforms.Compose([
transforms.ToTensor()
])

train_dataset = datasets.MNIST(root='data',
train=True,
transform=transform,
download=True)

test_dataset = datasets.MNIST(root='data',
train=False,
transform=transform)

train_loader = DataLoader(dataset=train_dataset,
batch_size=batch_size,
shuffle=True)

test_loader = DataLoader(dataset=test_dataset,
batch_size=batch_size,
shuffle=False)

# VAE 模型
class VAE(nn.Module):
def __init__(self, image_size=784, h_dim=400, z_dim=20):
super(VAE, self).__init__()
# 编码器层
self.fc1 = nn.Linear(image_size, h_dim)
self.fc_mu = nn.Linear(h_dim, z_dim) # 潜在空间的均值
self.fc_logvar = nn.Linear(h_dim, z_dim) # 潜在空间的对数方差
# 解码器层
self.fc2 = nn.Linear(z_dim, h_dim)
self.fc3 = nn.Linear(h_dim, image_size)

def encode(self, x):
h = torch.relu(self.fc1(x))
mu = self.fc_mu(h)
logvar = self.fc_logvar(h)
return mu, logvar

def reparameterize(self, mu, logvar):
std = torch.exp(0.5 * logvar) # 标准差
eps = torch.randn_like(std) # 随机张量
return mu + eps * std

def decode(self, z):
h = torch.relu(self.fc2(z))
x_reconst = torch.sigmoid(self.fc3(h))
return x_reconst

def forward(self, x):
mu, logvar = self.encode(x)
z = self.reparameterize(mu, logvar)
x_reconst = self.decode(z)
return x_reconst, mu, logvar

model = VAE().to(device)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 损失函数
def loss_function(x_reconst, x, mu, logvar):
# 重建损失(二元交叉熵)
BCE = nn.functional.binary_cross_entropy(x_reconst, x, reduction='sum')
# KL 散度
KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
# 总损失
return BCE + KLD

# 训练循环
model.train()
for epoch in range(num_epochs):
train_loss = 0
for batch_idx, (images, _) in enumerate(train_loader):
images = images.view(-1, 784).to(device)
optimizer.zero_grad()
x_reconst, mu, logvar = model(images)
loss = loss_function(x_reconst, images, mu, logvar)
loss.backward()
train_loss += loss.item()
optimizer.step()

avg_loss = train_loss / len(train_loader.dataset)
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}')

# 测试与可视化
model.eval()
with torch.no_grad():
# 获取一批测试图像
test_images, _ = next(iter(test_loader))
test_images = test_images.view(-1, 784).to(device)
# 重建图像
x_reconst, _, _ = model(test_images)
x_reconst = x_reconst.view(-1, 1, 28, 28).cpu()
# 原始图像
original_images = test_images.view(-1, 1, 28, 28).cpu()

# 可视化重建图像
n = 8 # 要显示的图像数量
plt.figure(figsize=(15, 4))
for i in range(n):
# 原始图像
ax = plt.subplot(2, n, i + 1)
plt.imshow(original_images[i][0], cmap='gray')
ax.axis('off')
# 重建图像
ax = plt.subplot(2, n, i + 1 + n)
plt.imshow(x_reconst[i][0], cmap='gray')
ax.axis('off')
plt.show()

解释

  • 导入:导入了必要的库,包括 torchtorchvisionmatplotlib
  • 设备配置:代码检查 GPU 是否可用并相应地设置设备。
  • 超参数:定义了关键超参数,例如 latent_dimbatch_sizelearning_ratenum_epochs
  • 数据加载:加载 MNIST 数据集并应用适当的转换,并为训练和测试创建数据加载器。
  • 模型定义:定义了一个继承自 nn.ModuleVAE 类。它包括编码、重参数化、解码和前向传播的方法。
    • 编码器:将输入图像映射到潜在空间参数(mulogvar)。
    • 重参数化技巧:使用 mulogvar 从潜在空间中采样 z
    • 解码器:从潜在变量 z 重建输入图像。
  • 损失函数:结合重建损失(二元交叉熵)和 KL 散度以形成总损失。
  • 训练循环:模型在指定数量的 epoch 中进行训练。在每次迭代中:
    • 输入图像被展平并移动到设备上。
    • 模型执行前向传播以获得重建图像和潜在变量。
    • 计算损失并进行反向传播。
    • 优化器更新模型参数。
  • 测试与可视化
    • 模型切换到评估模式。
    • 一批测试图像通过模型以获得重建结果。
    • 使用 matplotlib 绘制原始图像和重建图像以进行视觉比较。

注意事项

  • 重参数化技巧:通过将采样操作表示为确定性操作和噪声变量,对于允许梯度流经随机节点至关重要。
  • KL 散度:鼓励学习到的潜在分布接近先验分布(在本例中为标准正态分布)。
  • 重建损失:衡量解码器重建输入数据的效果;二元交叉熵适用于 MNIST 等二值图像。

潜在扩展

  • 超参数调优:尝试不同的潜在维度、学习率和网络架构以提高性能。
  • 条件 VAE:修改模型以根据标签进行条件化,从而实现类别条件图像生成。
  • 不同数据集:将 VAE 应用于更复杂的数据集,如 CIFAR-10 或 CelebA,并进行适当的架构更改。

总结

变分自编码器为生成建模和无监督学习提供了一个强大的框架。通过将神经网络与概率推断相结合,VAE 能够捕捉复杂的数据分布,并已成为深度学习领域的基本工具。