PyTorch深度学习实战(27)——变分自编码器(Variational Autoencoder, VAE)

PyTorch深度学习实战(27)——变分自编码器

    • 0. 前言
    • 1. 变分自编码器
      • 1.1 自编码器的局限性
      • 1.2 VAE 工作原理
      • 1.3 VAE 构建策略
      • 1.4 KL 散度
      • 1.5 重参数化技巧
    • 2. 构建 VAE
    • 小结
    • 系列链接

0. 前言

变分自编码器 (Variational Autoencoder, VAE) 是一种生成模型,结合了自编码器和概率模型的思想,通过学习输入数据的潜分布,能够生成新的样本。与传统的自编码器不同,变分自编码器引入了概率建模的思想,并通过编码器和解码器之间的随机性来实现生成过程。编码器将输入数据映射到潜空间中的概率分布,假设潜变量是从多元正态分布中采样得到的,解码器则将从潜空间采样得到的潜在变量映射回原始数据空间,并生成新的样本。本节中,将介绍变分自编码器的基本概念,并使用 PyTorch 实现变分自编码器生成新图像。

1. 变分自编码器

变分自编码器 (Variational Auto-Encoders, VAE) 是一种基于变分贝叶斯 (Variational Bayes, VB) 推断的生成式网络架构,与自编码器通过数值的方式描述潜空间不同,VAE 以概率分布的形式描述潜空间,使数据生成成为可能。

1.1 自编码器的局限性

相似的图像会被分组到同一集群中,使用自编码器可以重建(解码)属于给定集群的潜变量。但是,如果潜变量位于两个集群之间时,使用自编码器无法保证会生成逼真图像,在这种情况下,就需要使用变分自编码器。
在构建变分自编码器之前,首先通过采样潜变量生成图像,观察自编码器在重建位于不同集群中间的潜变量时的局限性。

(1) 使用训练的自编码器模型计算中验证图像的潜变量 (embeddings):

latent_vectors = []
classes = []
for im,clss in val_dl:latent_vectors.append(model.encoder(im))classes.extend(clss)
latent_vectors = torch.cat(latent_vectors).cpu().detach().numpy().reshape(10000, -1)

(2) 计算数据集中所有潜变量在每列上的均值均值 (mu) 和标准差 (sigma),并使用均值和标准创建随机潜变量,并将得到的随机潜变量保存在列表中 (rand_vectors):

rand_vectors = []
for col in latent_vectors.transpose(1,0):mu, sigma = col.mean(), col.std()rand_vectors.append(sigma*torch.randn(1,100) + mu)

(3) 绘制根据随机潜变量和训练后的自编码器模型重建的图像:

rand_vectors = torch.cat(rand_vectors).transpose(1,0).to(device)
ix = 1
for p in rand_vectors:img = model.decoder(p.reshape(1,64,2,2)).view(28,28)plt.subplot(10, 10, ix)plt.imshow(img.detach().cpu(), cmap='gray')ix += 1
plt.show()

生成图片
从以上输出中可以看出,使用添加了噪声的潜变量生成图像时,得到的图像较为模糊。这是一个在实际场景中的常见问题,因为我们无法事先了解真实图片的潜变量范围。变分自编码器 (Variational Auto-Encoders, VAE) 通过生成均值为 0、标准差为 1 的向量来解决此问题。
本质上,在 VAE 中指定了瓶颈层应当遵循的分布。接下来,我们将介绍 VAE 的工作原理,并介绍 KL 散度 (Kullback–Leibler divergence) 损失用于获取遵循特定分布的潜变量。

1.2 VAE 工作原理

VAE 使用深度学习构建概率模型,将输入数据映射到一个低维度的潜空间中,并通过解码器将潜空间中的分布转换回数据空间中,以生成与原始数据相似的数据。与传统的自编码器相比,VAE 更加稳定,生成样本的质量更高。
VAE 的核心思想是利用概率模型来描述高维的输入数据,将输入数据采样于一个低维度的潜变量分布中,并通过解码器生成与原始数据相似的输出。具体来说,VAE 同样是由编码器和解码器组成:

  • 编码器将数据 x x x 映射到一个潜在空间 z z z 中,该空间定义在低维正态分布中,即 z ∼ N ( 0 , I ) z∼N(0,I) zN(0,I),编码器由两个部分组成:一是将数据映射到均值和方差,即 z ∼ N ( μ , σ 2 ) z∼N(μ,σ^2) zN(μ,σ2);二是通过重参数化技巧,将均值和方差的采样过程分离出来,并引入随机变量 ϵ ∼ N ( 0 , I ) ϵ∼N(0,I) ϵN(0,I),使得 z = μ + ϵ σ z=μ+ϵσ z=μ+ϵσ
  • 解码器将潜在变量 z z z 映射回数据空间中,生成与原始数据 x x x 相似的数据 x ′ x′ x,为了使生成的数据 x ′ x′ x 能够与原始数据 x x x 较高的相似度,VAE 在损失函数中使用重构误差和正则化项,重构误差表示生成数据与原始数据之间的差异,正则化项用于约束潜在变量的分布,使其满足高斯正态分布,使得 VAE 从潜空间中生成的样本质量更高

VAE 具有广泛的应用场景,如图像生成、语音、自然语言处理等领域,它能够通过有限的数据样本学习到输入数据背后的潜在规律,生成与原始数据类似的新数据,具有很强的潜数据的可解释性。

1.3 VAE 构建策略

VAE 中,基于预定义分布获得的随机向量生成逼真图像,而在传统自编码器中并未指定在网络中生成图像的数据分布。可以通过以下策略,实现 VAE:

  1. 编码器的输出包括两个向量:
    • 输入图像平均值
    • 输入图像标准差
  2. 根据以上两个向量,通过在均值和标准差之和中引入随机变量 ( ϵ ∼ N ( 0 , I ) ϵ∼N(0,I) ϵN(0,I)) 获取随机向量 ( z = μ + ϵ σ z=μ+ϵσ z=μ+ϵσ)
  3. 将上一步得到的随机向量作为输入传递给解码器以重构图像
  4. 损失函数是均方误差和 KL 散度损失的组合:
    • KL 散度损失衡量由均值向量 μ \mu μ 和标准差向量 σ \sigma σ 构建的分布与 N ( 0 , I ) N(0,I) N(0,I) 分布的偏差
    • 均方损失用于优化重建(解码)图像

通过训练网络,指定输入数据满足由均值向量 μ \mu μ 和标准差向量 σ \sigma σ 构建的 N ( 0 , 1 ) N(0,1) N(0,1) 分布,当我们生成均值为 0 且标准差为 1 的随机噪声时,解码器将能够生成逼真的图像。
需要注意的是,如果只最小化 KL 散度,编码器将预测均值向量为 0,标准差为 1。因此,需要同时最小化 KL 散度损失和均方损失。在下一节中,让我们介绍 KL 散度,以便将其纳入模型的损失值计算中。

1.4 KL 散度

KL 散度(也称相对熵)可以用于衡量两个概率分布之间的差异:

K L ( P ∣ ∣ Q ) = ∑ x ∈ X P ( x ) l n ( P ( i ) Q ( i ) ) KL(P||Q) = \sum_{x∈X} P(x) ln(\frac {P(i)}{Q(i)}) KL(P∣∣Q)=xXP(x)ln(Q(i)P(i))

其中, P P P Q Q Q 为两个概率分布,KL 散度的值越小,两个分布的相似性就越高,当且仅当 P P P Q Q Q 两个概率分布完全相同时,KL 散度等于 0。在 VAE 中,我们希望瓶颈特征值遵循平均值为 0 和标准差为 1 的正态分布。因此,我们可以使用 KL 散度衡量变分自编码器中编码器输出的分布与标准高斯分布 N ( 0 , 1 ) N(0,1) N(0,1) 之间的差异。
可以通过以下公式计算 KL 散度损失:

∑ i = 1 n σ i 2 + μ i 2 − l o g ∗ ( σ i ) − 1 \sum_{i=1}^n\sigma_i^2+\mu_i^2-log*(\sigma_i)-1 i=1nσi2+μi2log(σi)1

在上式中, σ σ σ μ μ μ 表示每个输入图像的均值和标准差值:

  • 确保均值向量分布在 0 附近:
    • 最小化上式中的均方误差 ( μ i 2 \mu_i^2 μi2) 可确保 μ \mu μ 尽可能接近 0
  • 确保标准差向量分布在 1 附近:
    • 上式中其余部分(除了 μ i 2 \mu_i^2 μi2 )用于确保标准差 ( s i g m a sigma sigma) 分布在 1 附近

当均值 ( μ μ μ) 为 0 且标准差为 1 时,以上损失函数值达到最小,通过引入标准差的对数,确保 σ \sigma σ 值不为负。通过最小化以上损失可以确保编码器输出遵循预定义分布。

1.5 重参数化技巧

下图左侧显示了 VAE 网络。编码器获取输入 x x x,并估计潜矢量 z z z 的多元高斯分布的均值 μ μ μ 和标准差 σ σ σ,解码器从潜矢量 z z z 采样,以将输入重构为 x x x
VAE
但是反向传播梯度不会通过随机采样块。虽然可以为神经网络提供随机输入,但梯度不可能穿过随机层。解决此问题的方法是将“采样”过程作为输入,如图右侧所示。 采样计算为:

S a m p l e = μ + ε σ Sample=\mu + εσ Sample=μ+εσ

如果 ε ε ε σ σ σ 以矢量形式表示,则 ε σ εσ εσ 是逐元素乘法,使用上式,令采样好像直接来自于潜空间。 这种技术被称为重参数化技巧 (Reparameterization trick)。

2. 构建 VAE

在本节中,使用 PyTorch 实现 VAE 模型生成手写数字图像。

(1) 由于使用与训练自编码器模型相同的数据集,因此数据集的加载和构建方式与全连接自编码器完全相同:

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torchvision.utils import make_grid
import numpy as np
from matplotlib import pyplot as plt
device = 'cuda' if torch.cuda.is_available() else 'cpu'train_dataset = datasets.MNIST(root='MNIST/', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = datasets.MNIST(root='MNIST/', train=False, transform=transforms.ToTensor(), download=True)train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

(2) 定义神经网络类 VAE

__init__ 方法中定义所需网络层:

class VAE(nn.Module):def __init__(self, x_dim, h_dim1, h_dim2, z_dim):super(VAE, self).__init__()self.d1 = nn.Linear(x_dim, h_dim1)self.d2 = nn.Linear(h_dim1, h_dim2)self.d31 = nn.Linear(h_dim2, z_dim)self.d32 = nn.Linear(h_dim2, z_dim)self.d4 = nn.Linear(z_dim, h_dim2)self.d5 = nn.Linear(h_dim2, h_dim1)self.d6 = nn.Linear(h_dim1, x_dim)

d1d2 层对应于编码器部分,d5d6 层将对应于解码器部分,d31d32 层分别用于获取均值和标准差向量,为了方便计算,使用 d32 层获取方差向量的对数表示。

定义编码器方法 encoder

    def encoder(self, x):h = F.relu(self.d1(x))h = F.relu(self.d2(h))return self.d31(h), self.d32(h)

编码器返回两个向量:均值 self.d31(h) 和方差值的对数 self.d32(h)

定义从编码器输出中采样 (sampling) 的方法:

    def sampling(self, mean, log_var):std = torch.exp(0.5*log_var)eps = torch.randn_like(std)return eps.mul(std).add_(mean)

计算 torch.exp(0.5*log_var)) 获取标准差 std,此外,通过添加随机噪声(标准差乘以满足正态分布的随机数)获取随机向量,确保即使编码器向量发生了微小变化,也可以生成高质量图像。

定义解码器方法 decoder

    def decoder(self, z):h = F.relu(self.d4(z))h = F.relu(self.d5(h))return torch.sigmoid(self.d6(h))

定义前向计算方法 forward

    def forward(self, x):mean, log_var = self.encoder(x.view(-1, 784))z = self.sampling(mean, log_var)return self.decoder(z), mean, log_var

在以上方法中,确保编码器返回均值和方差值的对数,对均值与 epsilon 乘以方差的对数之和进行采样,并在通过解码器后返回结果。

(3) 定义模型在批数据上进行训练和验证的函数:

def train_batch(data, model, optimizer, loss_function):model.train()data = data.to(device)optimizer.zero_grad()recon_batch, mean, log_var = model(data)loss, mse, kld = loss_function(recon_batch, data, mean, log_var)loss.backward()optimizer.step()return loss, mse, kld, log_var.mean(), mean.mean()@torch.no_grad()
def validate_batch(data, model, loss_function):model.eval()data = data.to(device)recon, mean, log_var = model(data)loss, mse, kld = loss_function(recon, data, mean, log_var)return loss, mse, kld, log_var.mean(), mean.mean()

(4) 定义损失函数:

def loss_function(recon_x, x, mean, log_var):RECON = F.mse_loss(recon_x, x.view(-1, 784), reduction='sum')KLD = -0.5 * torch.sum(1 + log_var - mean.pow(2) - log_var.exp())return RECON + KLD, RECON, KLD

在以上代码中,获取原始图像 x 和重建图像 recon_x 之间的 MSE 损失 RECON,并计算了 KL 散度损失 KLD

(5) 定义模型对象 vae 和优化器函数:

vae = VAE(x_dim=784, h_dim1=512, h_dim2=256, z_dim=50).to(device)
optimizer = optim.AdamW(vae.parameters(), lr=1e-3)

(6) 训练模型:

n_epochs = 20
train_loss_epochs = []
val_loss_epochs = []
for epoch in range(n_epochs):N = len(train_loader)trn_loss = []val_loss = []for batch_idx, (data, _) in enumerate(train_loader):loss, recon, kld, log_var, mean = train_batch(data, vae, optimizer, loss_function)pos = (epoch + (batch_idx+1)/N)trn_loss.append(loss.item())train_loss_epochs.append(np.average(trn_loss))N = len(test_loader)for batch_idx, (data, _) in enumerate(test_loader):loss, recon, kld, log_var, mean = validate_batch(data, vae, loss_function)pos = epoch + (1+batch_idx)/Nval_loss.append(loss.item())val_loss_epochs.append(np.average(val_loss))with torch.no_grad():z = torch.randn(64, 50).to(device)sample = vae.decoder(z).to(device)images = make_grid(sample.view(64, 1, 28, 28)).permute(1,2,0)plt.imshow(images.detach().cpu(), cmap='gray')plt.show()# show(images)epochs = np.arange(n_epochs)+1
plt.plot(epochs, train_loss_epochs, 'bo', label='Training loss')
plt.plot(epochs, val_loss_epochs, 'r-', label='Test loss')
plt.title('Training and Test loss over increasing epochs')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid('off')
plt.show()

VAE 首先生成一个随机向量 z 并将其传递给解码器 vae.decoder 以获取图像样本,make_grid 函数用于绘制图像。损失值变化的情况和生成的图像样本如下所示:

模型性能检测生成图像
可以看到,VAE 模型能够生成原始图像中不存在的逼真的新图像。

小结

变分自编码器是一种结合了自编码器和概率建模的生成模型,通过编码器将输入数据映射到潜在空间中的概率分布,并通过解码器将从潜在空间采样得到的潜在变量映射回原始数据空间,实现了数据的生成和特征学习。

系列链接

PyTorch深度学习实战(1)——神经网络与模型训练过程详解
PyTorch深度学习实战(2)——PyTorch基础
PyTorch深度学习实战(3)——使用PyTorch构建神经网络
PyTorch深度学习实战(4)——常用激活函数和损失函数详解
PyTorch深度学习实战(5)——计算机视觉基础
PyTorch深度学习实战(6)——神经网络性能优化技术
PyTorch深度学习实战(7)——批大小对神经网络训练的影响
PyTorch深度学习实战(8)——批归一化
PyTorch深度学习实战(9)——学习率优化
PyTorch深度学习实战(10)——过拟合及其解决方法
PyTorch深度学习实战(11)——卷积神经网络
PyTorch深度学习实战(12)——数据增强
PyTorch深度学习实战(13)——可视化神经网络中间层输出
PyTorch深度学习实战(14)——类激活图
PyTorch深度学习实战(15)——迁移学习
PyTorch深度学习实战(16)——面部关键点检测
PyTorch深度学习实战(17)——多任务学习
PyTorch深度学习实战(18)——目标检测基础
PyTorch深度学习实战(19)——从零开始实现R-CNN目标检测
PyTorch深度学习实战(20)——从零开始实现Fast R-CNN目标检测
PyTorch深度学习实战(21)——从零开始实现Faster R-CNN目标检测
PyTorch深度学习实战(22)——从零开始实现YOLO目标检测
PyTorch深度学习实战(23)——使用U-Net架构进行图像分割
PyTorch深度学习实战(24)——从零开始实现Mask R-CNN实例分割
PyTorch深度学习实战(25)——自编码器(Autoencoder)
PyTorch深度学习实战(26)——卷积自编码器(Convolutional Autoencoder)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/298804.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

测试C#使用AForge从摄像头获取图片

百度“C# 摄像头”关键词,从搜索结果来看,使用OpenCV、AForge、window动态链接库获取摄像头数据的居多,本文学习基于Aforge.net连接摄像头并从摄像头获取图片的基本方法。   AForge相关包(尤其是相关的控件)主要针对…

【Mathematical Model】Python拟合一元一/二次方程(线性回归)

Python中可以使用多种库进行拟合方程,其中最常用的是NumPy和SciPy。NumPy是一个用于处理数组和矩阵的库,而SciPy则提供了大量的科学计算函数,包括拟合算法。 1 一元一次方程拟合 需要注意的是我们这里的方程需要我们自己定义好,然…

OpenCV-Python(9):图像基础操作

目录 学习目标 获取图像像素并修改像素值 获取图像属性 图像ROI 拆分及合并图像通道 图像边缘扩充 学习目标 获取像素值并修改获取图像的属性(信息)图像的ROI获取图像通道拆分及合并图像扩边 获取图像像素并修改像素值 几乎所有这些操作与Numpy 的关系要比与OpenCV 的…

Unity Shader Early-Z技术

Unity Shader Early-Z技术 Early-Z技术Unity渲染顺序总结Alpha Test(Discard)在移动平台消耗较大的原因 Early-Z技术 传统的渲染管线中,ZTest其实是在Blending阶段,这时候进行深度测试,所有对象的像素着色器都会计算一…

电力系统风储联合一次调频MATLAB仿真模型

微❤关注“电气仔推送”获得资料(专享优惠) 简介: 同一电力系统在不同风电渗透率下遭受同一负荷扰动时,其频率变化规律所示: (1)随着电力系统中风电渗透率的不断提高,风电零惯性响…

LaTex设置标题页、修改文字颜色和文字高亮

目录 一、标题页 1)常用的代码 2)添加脚注 二、修改文字颜色和文字高亮 1)设置文本的颜色 2)添加文本高亮 3)给文本添加有颜色的方框 一、标题页 主要的代码: \begin{titlepage} \noindent\fonts…

OpenAI开发者大会简介

文章目录 GPT-4 Turbo 昨天晚上 OpenAI的首届开发者大会召开 Sam Altman也做了公开演讲,应该说 这是继今年春天发布GPT-4之后 OpenAI在AI行业又创造的一个不眠夜 过去一年 ChatGPT绝对是整个科技领域最热的词汇 OpenAI 也依靠ChatGPT取得了惊人的成绩 ChatG…

VL53L4CX TOF开发(1)----驱动TOF进行测距

VL53L4CX TOF开发.1--驱动TOF进行测距 概述视频教学样品申请完整代码下载主要特点硬件准备技术规格系统框图应用示意图生成STM32CUBEMX选择MCU串口配置IIC配置 XSHUTX-CUBE-TOF1演示结果 概述 VL53L4CX 是一款先进的激光距离传感器,专为长距离和多目标测量设计&…

【Docker容器精解篇 】深入探索Docker技术的概念与容器思想

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《docker容器精解篇》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 文章目录 前言一、Docker 的介绍1.1 Docker 的由来1.1.1 环境不一致1.1.2 隔离性1.1.3 弹性伸缩1.1.4 学习成本 1.2 Doc…

jQuery: 整理3---操作元素的内容

1.html("内容") ->设置元素的内容&#xff0c;包含html标签&#xff08;非表单元素&#xff09; <div id"html1"></div><div id"html2"></div>$("#html1").html("<h2>上海</h2>") …

论文解读--Compensation of Motion-Induced Phase Errors in TDM MIMO Radars

TDM MIMO雷达运动相位误差补偿 摘要 为了实现高分辨率的到达方向估计&#xff0c;需要大孔径。这可以通过提供宽虚拟孔径的多输入多输出雷达来实现。但是&#xff0c;它们的工作必须满足正交发射信号的要求。虽然发射单元的时分复用是一种低硬件成本的正交实现&#xff0c;但在…

家庭教育|孩子教育小妙招,让孩子更优秀

Q: 什么是暗示效应&#xff1f; A: 暗示效应是指在无对抗的条件下&#xff0c;用含蓄、抽象诱导的间接方法对人们的心理和行为产生影响&#xff0c;从而诱导人们按照一定的方式去行动或接受一定的意见&#xff0c;使其思想、行为与暗示者期望的目标相符合。一般说来&#xf…