Advertisement

【PyTorch][chapter 18][李宏毅深度学习]【无监督学习][ VAE]

阅读量:

前言:

VAE即为变分自编码器(Variational Auto-Encoder),由Kingma等研究者于2014年提出。该方法基于变分贝叶斯推断框架构建生成式模型结构。相较于传统自编码器仅通过数值手段刻画潜在空间而言,VAE采用概率方法来描述潜在空间的观测情况,并展现出显著的应用潜力。自其问世以来迅速获得深度生成模型领域的广泛关注,并与生成对抗网络(Generative Adversarial Networks)一道,在无监督学习领域被公认为最具研究价值的方法之一,在深度生成模型领域得到了日益广泛的应用

Durk Kingma 目前也是 OpenAI 的研究科学家

VAE 是我深度学习过程中偏难的一部分,涉及到的理论基础:

极大似然估计, KL 散度 ,Bayes定理,蒙特卡洛重采样思想,VI变分思想,ELBO


目录:

  1. AE 编码器存在的问题
  2. VAE编码器与AE编码器的异点在于其引入了概率模型以提高生成质量
  3. VAE编码器是一种改进型编码方案
  4. 其理论基础是基于变分推断的方法框架
  5. 该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张图片

X=egin{Bmatrix} x_1 &x_2 & ... & x_m nd{Bmatrix}
P=rod_i^{m} P

,

P

很复杂无法求解.

常用的思路是通过引入隐藏变量(latent variable) Z。

确定Z空间至X空间的映射关系后,在Z空间中进行采样操作以获取样本点数据;随后将这些采样结果映射至X空间即可生成新的图片。

P=nt _z PPdz

我们使用多个高斯分布的

P

去拟合

P

的分布,**这里面

P

为已知道**

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

例:

如上图 P(X=红色)=2/5 ,P(X=绿色)=3/5

我们可以通过高斯混合模型原理的方法求解

P(X=红色)=P(X=红色|Z=正方形)*P(Z=正方形)+ P(X=红色|Z=圆形)*P(Z=圆形)

P(X=绿色)也是一样

4.2 极大似然估计

目标:极大似然函数

L= logP

已知:

编码器的概率分布

nt_z qdz=1

则:

L=L*nt_z qdz

(相当于乘以1)

=nt_z q log Pdz

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

=nt_z qlog rac{P}{p}

贝叶斯定理:

P=pp
=nt qlog rac{p}{p}rac{q}{q}
=nt_z qlog rac{q}{p}+nt_z qlog rac{p}{q}
=KL||q+nt_z qlog rac{p}{q}

1: VAE叫做“变分自编码器”,它跟变分法有什么联系

给定概率分布p和q的情况下,在任何情况下都有KL(p||q)≥0,并且仅在两分布相等时其值为零

因为KL(p(x)∥∥q(x))实际上是一个泛函,要对泛函求极值就要用到变分法

eq L_b=nt_z qlograc{p}{q}

ELBO:其全称是 Evidence Lower Bound ,也被称为证据下界 。

上述 KL(q(z|x) || q(z|x)) 我们将其定位于 0 。
=

=nt_z qlog rac{pp}{q}

贝叶斯定理

p=pp

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

=nt_z qlog p+nt_z qlog rac{p}{p}
=-KL||p+H||p

我们目标值是求L 的最大值

第一项:

因为KL 散度的非负性

-KL||p

极大值点为

p=q

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

H||p

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

mse

来训练该部分的误差


五 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

全部评论 (0)

还没有任何评论哟~