【PyTorch][chapter 18][李宏毅深度学习]【无监督学习][ VAE]
前言:
VAE即为变分自编码器(Variational Auto-Encoder),由Kingma等研究者于2014年提出。该方法基于变分贝叶斯推断框架构建生成式模型结构。相较于传统自编码器仅通过数值手段刻画潜在空间而言,VAE采用概率方法来描述潜在空间的观测情况,并展现出显著的应用潜力。自其问世以来迅速获得深度生成模型领域的广泛关注,并与生成对抗网络(Generative Adversarial Networks)一道,在无监督学习领域被公认为最具研究价值的方法之一,在深度生成模型领域得到了日益广泛的应用
Durk Kingma 目前也是 OpenAI 的研究科学家
VAE 是我深度学习过程中偏难的一部分,涉及到的理论基础:
极大似然估计, KL 散度 ,Bayes定理,蒙特卡洛重采样思想,VI变分思想,ELBO
目录:
- AE 编码器存在的问题
- VAE编码器与AE编码器的异点在于其引入了概率模型以提高生成质量
- VAE编码器是一种改进型编码方案
- 其理论基础是基于变分推断的方法框架
- 该Python代码实例展示了其具体实现细节
一 AE 编码器缺陷
1.1 AE 简介

输入一张图片 x
编码器Encoder:
z=f(x) 通过神经网络得到低维度的特征空间Z
解码器Decoder:
\hat{x}=g(z) 通过特征空间 重构输入的图像
损失函数:
J=mse(x,\hat{x})
1.2 特征空间z
**** 单独使用解码器Decoder
特征空间z 维度为10,固定其它维度参数. 取其中两维参数,产生不同的
值(如下图星座图),然后通过Decoder 生成不同的图片.就会发现该维度
跟图像的某些特征有关联.

1.3 通过特征空间z重构缺陷 :泛化能力差

如上图:
假设通过AE 模型训练动物的图像,特征空间Z为一维度。
两种狗分别对应特征向量z_1,z_3, 我们取一个特征向量z_2,期望通过
解码器输出介于两种狗中间的一个样子的一种狗。
实际输出: ,随机输出一些乱七八糟的图像。
原因:
在训练过程中,在处理图像时,学习器将输入图像及其对应的Z空间进行离散映射至z空间。
中没有训练过的空间没有约束,所以通过解码器输出的图像也是随机的.
二 VAE 编码器 跟AE 编码器差异
2.1 AE 编码器特征空间
假设特征空间Z 为一维,

经由编码器将特征空间映射至一维空间中的一个离散点c后,随后经由解码器实现对输入x的重构
2.2 VAE 编码器


利用编码器生成一个均值参数为u、方差参数为σ的高斯分布模型,并在此模型的基础上执行采样操作
某个点c被解码器重构后的输入. 现在特征空间Z是一个高斯分布.
泛化能力更强
三 VAE 编码器

3.1 模型简介
输入 :x
经过编码器 生成一个服从高斯分布的特征空间 z \sim N(u,\sigma^2) ,
通过重参数采样技巧 采样出特征点 C=\begin{bmatrix} c_1,c_2,c_3 \end{bmatrix}
把特征点 输入解码器,重构出输入x
3.2 标准差 \sigma(黄色模块)设计原理
方差 \sigma^2 标准差 \sigma
因为标准差是非负的,但是经过编码器输出的可能是负的值,所以
认为其输出值为 a=log (\sigma) ,再经过 exp 操作,得到一个非负的标准差
\sigma=e^{a}=\sigma
很多博主用的\sigma^2,我理解是错误的,为什么直接用 标准差
参考3.3 苏剑林的 重参数采样 原理画出来的。

3.3 为什么要重参数采样 reparameterization trick
我们需要从p(Z|X)中抽取一个Z出来,在已知p(Z|X)为正态分布的前提下,其均值与方差均为模型计算所得。为了通过这一过程来实现对模型均值与方差的优化。然而,“抽样”这一操作在数学上是非可导的性质,在这种情况下虽然抽样的结果本身是可以被求导处理的。
p(Z|X) 的概率可以写成如下形式

说明

服从 N(0,1)的标准正态分布
从N(u,\sigma^2)中采样一个Z,相当于从N(0,I)标准正态分布中采样一个e,然后让
Z=u+e*\sigma
我们采用转换的方法将原分布N(u, \sigma^2)转换为标准正态分布N(0,I)。随后应用参数变换矩阵后得到的结果符合目标分布N(u, \sigma^2)。这样,“采样”操作就被排除在梯度计算之外;相反地,则采用采样的结果进行更新以实现模型的可训练性。其中u, \sigma是求导相关的参数,而e是一个已知常数参数。
3.4 损失函数
** J=J_1+J_2**
该模型有两个约束条件
1 一个输入图像x和重构的图像\hat{x},mse 误差最小
J_1= ||x-\hat{x}||_2
2 特征空间Z 要服从高斯分布(使用KL 散度)
J_2=KL(N(u,\sigma^2)||N(0,1))
该值越小越好
KL 散度简化

3.5 伪代码

四 VAE 思想
4.1 高斯混合模型
我们重构出m张图片


,

很复杂无法求解.
常用的思路是通过引入隐藏变量(latent variable) Z。
确定Z空间至X空间的映射关系后,在Z空间中进行采样操作以获取样本点数据;随后将这些采样结果映射至X空间即可生成新的图片。

我们使用多个高斯分布的

去拟合

的分布,**这里面

为已知道**

在强化学习里面,蒙特卡罗重采样也是用了该方案.
例:

如上图 P(X=红色)=2/5 ,P(X=绿色)=3/5
我们可以通过高斯混合模型原理的方法求解
P(X=红色)=P(X=红色|Z=正方形)*P(Z=正方形)+ P(X=红色|Z=圆形)*P(Z=圆形)

P(X=绿色)也是一样
4.2 极大似然估计

目标:极大似然函数

已知:
编码器的概率分布

则:

(相当于乘以1)

(因为P(x)跟z 无关,可以直接拿到积分里面)

贝叶斯定理:




1: VAE叫做“变分自编码器”,它跟变分法有什么联系
给定概率分布p和q的情况下,在任何情况下都有KL(p||q)≥0,并且仅在两分布相等时其值为零
因为KL(p(x)∥∥q(x))实际上是一个泛函,要对泛函求极值就要用到变分法

ELBO:其全称是 Evidence Lower Bound ,也被称为证据下界 。
上述 KL(q(z|x) || q(z|x)) 我们将其定位于 0 。
=

贝叶斯定理

注意: 这里面P(Z)在4.1 高斯混合模型 是已知道的概率分布,符合高斯分布


我们目标值是求L 的最大值
第一项:
因为KL 散度的非负性

极大值点为

由于p(z)遵循高斯分布。因此通过编码器生成的概率密度函数q(z|x)也应与其概率密度函数一致,并遵循高斯分布。第二项:

这部分代表重构误差,我们用

来训练该部分的误差
五 Python 代码
# -*- coding: utf-8 -*-
"""
Created on Mon Feb 26 15:47:20 2024
5. @author: chengxf2
"""
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms # transforms用于数据预处理
# 定义变分自编码器(VAE)模型
class VAE(nn.Module):
def __init__(self, latent_dim):
super(VAE, self).__init__()
# Encoder
self.encoder = nn.Sequential(
nn.Linear(in_features=784, out_features=256),
nn.ReLU(),
nn.Linear(in_features=256, out_features=128),
nn.ReLU(),
nn.Linear(in_features=128, out_features=latent_dim*2), # 输出均值和方差
nn.ReLU()
)
# Decoder
self.decoder = nn.Sequential(
nn.Linear(in_features =latent_dim , out_features=128),
nn.ReLU(),
nn.Linear(in_features=128, out_features=256),
nn.ReLU(),
nn.Linear(in_features=256, out_features=784),
nn.Sigmoid()
)
def reparameterize(self, mu, logvar):
std = torch.exp(logvar/2.0) # 计算标准差,Encoder 出来的可能有负的值,标准差为非负值,所以要乘以exp
eps = torch.randn_like(std) # 从标准正态分布中采样噪声
z = mu + eps * std # 重参数化技巧
return z
def forward(self, x):
# 编码[batch, latent_dim*2]
encoded = self.encoder(x)
#[ z = mu|logvar]
mu, logvar = torch.chunk(encoded, 2, dim=1) # 将输出分割为均值和方差
z = self.reparameterize(mu, logvar) # 重参数化
# 解码
decoded = self.decoder(z)
return decoded, mu, logvar
# 定义训练函数
def train_vae(model, train_loader, num_epochs, learning_rate):
criterion = nn.BCELoss() # 二元交叉熵损失函数
optimizer = optim.Adam(model.parameters(), lr=learning_rate) # Adam优化器
model.train() # 设置模型为训练模式
for epoch in range(num_epochs):
total_loss = 0.0
for data in train_loader:
images, _ = data
images = images.view(images.size(0), -1) # 展平输入图像
optimizer.zero_grad()
# 前向传播
outputs, mu, logvar = model(images)
# 计算重构损失和KL散度
reconstruction_loss = criterion(outputs, images)
kl_divergence = 0.5 * torch.sum( -logvar +mu.pow(2) +logvar.exp()-1)
# 计算总损失
loss = reconstruction_loss + kl_divergence
# 反向传播和优化
loss.backward()
optimizer.step()
total_loss += loss.item()
# 输出当前训练轮次的损失
print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, total_loss / len(train_loader)))
print('Training finished.')
# 示例用法
if __name__ == '__main__':
# 设置超参数
latent_dim = 32 # 潜在空间维度
num_epochs = 1 # 训练轮次
learning_rate = 1e-4 # 学习率
# 加载MNIST数据集
train_dataset = torchvision.datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=100, shuffle=True)
# 创建VAE模型
model = VAE(latent_dim)
# 训练VAE模型
train_vae(model, train_loader, num_epochs, learning_rate)
,
VAE究竟在做些什么?VAE原理讲解系列#1_哔哩哔哩_bilibili
VAE关于概率的知识。VAE详细解析系列#2:哔哩哔哩视频内容
如何理解vae损失函数这一概念?在知乎上可以看到许多关于vae相关问题的讨论与解答
具体步骤:如何搭建VQ-VAE模型(Pytorch源代码)_哔哩哔哩_bilibili
第一讲:从基础原理出发——探析变分自编码器模型的本质机制
16: 无监督学习 - 自动编码器_哔哩哔哩_Bilibili
生成模型VAE
生成模型VAE
[diffusion] 生成模型基础 VAE 原理及实现_哔哩哔哩_bilibili
[论文简析]VAE: Auto-encoding Variational Bayes[1312.6114]_哔哩哔哩_bilibili
