Geeks_Z の Blog Geeks_Z の Blog
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)

Geeks_Z

AI小学生
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)
  • Python

  • MLTutorials

  • 卷积神经网络

  • 循环神经网络

  • Transformer

  • VisionTransformer

  • 扩散模型

    • DDPM
    • VAE
      • DDIM
      • StableDiffusion
      • 扩散模型和最优传输
    • 计算机视觉

    • PTM

    • MoE

    • LoRAMoE

    • LongTailed

    • 多模态

    • 知识蒸馏

    • PEFT

    • 对比学习

    • 小样本学习

    • 迁移学习

    • 零样本学习

    • 集成学习

    • Mamba

    • PyTorch

    • CL

    • CIL

    • 小样本类增量学习FSCIL

    • UCIL

    • 多模态增量学习MMCL

    • LTCIL

    • DIL

    • 论文阅读与写作

    • 分布外检测

    • GPU

    • 深度学习调参指南

    • AINotes
    • 扩散模型
    Geeks_Z
    2024-10-17
    目录
    自动编码器(Autoencoder,AE)
    变分自动编码器(Variational Autoencoder,VAE)
    CVAE
    VAE的代码实现
    总结
    参考

    VAE

    说起生成模型,大家最容易想到的就是GAN,GAN是通过对抗训练实现的一种隐式生成模型。虽然GAN很强大,但其实还有很多与GAN不同的生成模型,最常见的就是基于最大化似然的模型,变分自动编码器(Variational Autoencoder,VAE)就属于这种类型。

    自动编码器(Autoencoder,AE)

    再讲VAE之前,有必要先简单介绍一下自动编码器AE,自动编码器是一种无监督学习方法,它的原理很简单:先将高维的原始数据映射到一个低维特征空间,然后从低维特征学习重建原始的数据。一个AE模型包含两部分网络:

    • Encoder:将原始的高维数据映射到低维特征空间,这个特征维度一般比原始数据维度要小,这样就起到压缩或者降维的目的,这个低维特征也往往成为中间隐含特征(latent representation);

    • Decoder:基于压缩后的低维特征来重建原始数据;

    如上图所示,这里gϕ encoder网络的映射函数(网络参数为ϕ),而fθ decoder网络的映射函数(网络参数为θ)。那么对于输入x,可以通过encoder得到隐含特征z=gϕ(x),然后decoder可以从隐含特征对原始数据进行重建:x′=fθ(z)=fθ(gϕ(x))。我们希望重建的数据和原来的数据近似一致的,那么AE的训练损失函数可以采用简单的MSE:

    LAE(θ,ϕ)=1n∑i=1n(x(i)−fθ(gϕ(x(i))))2

    由于训练AE并不需要对数据进行标注,所以AE是一种无监督学习方法。由于压缩后的特征能对原始数据进行重建,所以我们可以用AE的encoder对高维数据进行压缩,这和PCA非常类似,当然得到的隐含特征也可以用来做一些其它工作,比如相似性搜索等。 ​

    AE有很多变种,比如经典的去噪自编码器(Denoising Autoencoder,DAE),与原始AE不同的是,在训练过程先对输入x 行一定的扰动,比如增加噪音或者随机mask掉一部分特征。相比AE,DAE的重建难度增加,这也使得encoder学习到的隐含特征更具有代表性。

    作为一种无监督学习方法,AE除了可以对数据降维,还可以用来对深度网络进行预训练。在深度学习早期,由于存在数据和算力限制,训练深度模型是比较困难的,所以常常采用无监督学习方法先对网络进行预训练,然后在具体的任务上进行有监督finetune,经典的工作如基于DAE的堆叠去噪自编码器(Stacked Denoising Autoencoder,SDA)和基于RBM的深度信念网络(Deep Belief Network,DBN)。

    然而,随着大数据的出现(比如包含1.3M图像的ImageNet数据集)以及网络架构的优化(如ResNet的出现),这种训练方式基本被弃用了,目前的主流方式是先在大规模有标注数据集上预训练,然后用预训练初始化的模型来训练具体的任务。由于标注数据存在成本,但收集大规模无标注数据相对容易,所以最近又开始了无监督训练研究的热潮,一些基于对比学习的自监督方法如Moco和SimCLR等已经可以达到和ImageNet有标注监督训练类似的效果。而今年来,随着vision transformer的大爆发,又出现了基于MIM(mask image modeling)的自监督方法,如MAE和SimMIM,它们都是采用和AE类似的设计架构,这让基于AE的无监督训练方法再次卷土重来。

    变分自动编码器(Variational Autoencoder,VAE)

    VAE虽然名字里也带有自动编码器,但这主要是因为VAE和AE有着类似的结构,即encoder和decoder这样的架构设计。实际上,VAE和AE在建模方面存在很大的区别,从本质上讲,VAE是一种基于变分推断(Variational Inference, Variational Bayesian methods (opens new window))的概率模型(Probabilistic Model),它属于生成模型(当然也是无监督模型)。在变分推断中,除了已知的数据(观测数据,训练数据)外还存在一个隐含变量,这里已知的数据集记为X={x(i)}i=1N N 连续变量或者离散变量x 成,而未观测的随机变量记为z,那么数据的产生包含两个过程:

    1. 从一个先验分布pθ(z) 采样一个z(i);
    2. 根据条件分布pθ(x|z),用z(i) 成x(i)。

    这里的θ 的是分布的参数,比如对于高斯分布就是均值和标准差。我们希望找到一个参数θ∗ 最大化生成真实数据的概率:

    θ∗=arg⁡maxθ∏i=1npθ(x(i))

    这里pθ(x(i)) 以通过对z 分得到:

    pθ(x(i))=∫pθ(x(i)|z)pθ(z)dz

    而实际上要根据上述积分是不现实的,一方面先验分布pθ(z) 未知的,而且如果分布比较复杂,对z 举计算也是极其耗时的。为了解决这个难题,变分推断引入后验分布pθ(z|x) 联合建模,根据https://en.wikipedia.org/wiki/Bayes'_theorem (opens new window),后验等于:

    pθ(z|x)=pθ(x|z)pθ(z)pθ(x)

    这样的联合建模如上图所示,实线代表的是我们想要得到的生成模型pθ(x|z)pθ(z),其中先验分布pθ(z) 往是事先定义好的(比如标准正态分布),而pθ(x|z) 以用一个网络来学习,类比AE的话,如果把z 成隐含特征,那么这个网络就可以看成一个probabilistic decoder。虚线代表的是对后验分布pθ(z|x) 变分估计,记为qϕ(z|x),它也可以用一个网络来学习,这个网络可以看成一个probabilistic encoder。可以看到,VAE和AE在架构设计上是类似的,只不过这里probabilistic encoder和probabilistic decoder学习的是两个分布。对于VAE来说,最终目标是得到生成模型即decoder,而encoder只是为了辅助建模,但对于AE来说,常常是为了得到encoder来进行特征提取或者压缩。 ​

    建模已经完成,下面我们来推导一下VAE的优化目标。对于估计的后验qϕ(z|x),我们希望它接近真实的后验分布pθ(z|x),评估两个分布差异最常用的方式就是计算KL散度(Kullback-Leibler divergence (opens new window))。对qϕ(z|x) pθ(z|x) 算KL散度,如下所示: ​

    DKL(qϕ(z|x)∥pθ(z|x))=∫qϕ(z|x)log⁡qϕ(z|x)pθ(z|x)dz=∫qϕ(z|x)log⁡qϕ(z|x)pθ(x)pθ(z,x)dz; Because p(z|x)=p(z,x)/p(x)=∫qϕ(z|x)(log⁡pθ(x)+log⁡qϕ(z|x)pθ(z,x))dz=log⁡pθ(x)+∫qϕ(z|x)log⁡qϕ(z|x)pθ(z,x)dz; Because ∫q(z|x)dz=1=log⁡pθ(x)+∫qϕ(z|x)log⁡qϕ(z|x)pθ(x|z)pθ(z)dz; Because p(z,x)=p(x|z)p(z)=log⁡pθ(x)+Ez∼qϕ(z|x)[log⁡qϕ(z|x)pθ(z)−log⁡pθ(x|z)]=log⁡pθ(x)+DKL(qϕ(z|x)∥pθ(z))−Ez∼qϕ(z|x)log⁡pθ(x|z)

    最终可以得到:

    DKL(qϕ(z|x)∥pθ(z|x))=log⁡pθ(x)+DKL(qϕ(z|x)∥pθ(z))−Ez∼qϕ(z|x)log⁡pθ(x|z)

    这里我们适当调整一下上述等式中各个项的位置,可以得到:

    log⁡pθ(x)−DKL(qϕ(z|x)∥pθ(z|x))=Ez∼qϕ(z|x)log⁡pθ(x|z)−DKL(qϕ(z|x)∥pθ(z))

    这里log⁡pθ(x) 生成真实数据的对数似然,对于生成模型,我们希望最大化这个对数似然,而DKL(qϕ(z|x)∥pθ(z|x)) 估计的后验分布和真实分布的KL散度,我们希望最小化该KL散度(KL散度为0时两个分布没有差异),所以上述等式的左边就是联合建模的最大化优化目标,这等价于最大化等式的右边。这个等式的右边又称为Evidence lower bound,简称为ELBO,这主要是因为pθ(x) 般称为evidence,而由于KL散度的非负性,所以有下述不等式:

    log⁡pθ(x)≥Ez∼qϕ(z|x)log⁡pθ(x|z)−DKL(qϕ(z|x)∥pθ(z))

    所以ELBO是evidence的下限,https://en.wikipedia.org/wiki/Evidence_lower_bound (opens new window)是变分推断中经常用到的优化目标。对于VAE,ELBO取负就是其要最小化的训练目标:

    LVAE(θ,ϕ)=−Ez∼qϕ(z|x)log⁡pθ(x|z)+DKL(qϕ(z|x)∥pθ(z))θ∗,ϕ∗=arg⁡minθ,ϕLVAE

    对于优化目标的第二项,即计算qϕ(z|x) pθ(z) KL散度,首先我们必须要对两个分布做一定的假设:

    qϕ(z|x(i))=N(z;μ(i),σ2(i)I)pθ(z)=N(z,0,I)

    即qϕ(z|x) 各分量独立的多元高斯分布(协方差矩阵为对角矩阵),那么encoder网络预测的就是高斯分布的均值μ 方差σ2(实际处理时预测log⁡σ2,因为该值是无约束的)。而先验pθ(z) 标准正态分布,这样就变成了计算两个多元高斯分布的KL散度。对于多元高斯分布,其概率密度函数为:

    p(x)=1(2π)ndet(Σ)exp⁡{−12(x−μ)⊤Σ−1(x−μ)}

    对于两个多元高斯分布,其KL散度计算推导如下:

    KL(p1||p2)=Ep1(log⁡(p1)−log⁡(p2))=−12Ep1[log⁡(det(Σ1))+(x−μ1)⊤Σ1−1(x−μ1)−log⁡(det(Σ2))−(x−μ2)⊤Σ2−1(x−μ2)]=12log⁡det(Σ2)det(Σ1)+12Ep1[−(x−μ1)⊤Σ1−1(x−μ1)+(x−μ2)⊤Σ2−1(x−μ2)]=12log⁡det(Σ2)det(Σ1)+12Ep1[−tr((x−μ1)⊤Σ1−1(x−μ1))+tr((x−μ2)⊤Σ2−1(x−μ2))]=12log⁡det(Σ2)det(Σ1)+12Ep1[−tr(Σ1−1(x−μ1)(x−μ1)⊤)+tr(Σ2−1(x−μ2)(x−μ2)⊤)]=12log⁡det(Σ2)det(Σ1)−12tr(Σ1−1Ep1[(x−μ1)(x−μ1)⊤])+12tr(Σ2−1Ep1[(x−μ2)(x−μ2)⊤])=12log⁡det(Σ2)det(Σ1)−12tr(Σ1−1Σ1)+12tr(Σ2−1Ep1[(xx⊤−xμ2⊤−μ2x⊤+μ2μ2⊤])=12log⁡det(Σ2)det(Σ1)−12n+12tr(Σ2−1(Σ1+μ1μ1⊤−μ1μ2⊤−μ2μ1⊤+μ2μ2⊤))=12log⁡det(Σ2)det(Σ1)−12n+12tr(Σ2−1Σ1)+12(μ2−μ1)⊤Σ2−1(μ2−μ1)=12(tr(Σ2−1Σ1)+(μ2−μ1)⊤Σ2−1(μ2−μ1)−n+log⁡det(Σ2)det(Σ1))

    上述公式的推导涉及到一些线性代数的知识,如矩阵的迹运算(tr),如果不明白可以参考https://kexue.fm/archives/8512 (opens new window)。根据上述公式,就可以计算出qϕ(z|x) pθ(z) KL散度:

    KL(qϕ(z|x(i))||pθ(z))=KL(N(z;μ(i),σ2(i)I)||N(z,0,I))=12(tr(σ2(i)I)+(μ(i))⊤μ(i)−n−log⁡detσ2(i)I)=12∑j=0n((σj(i))2+(μj(i))2−1−log⁡((σj(i))2))

    这里n 的多元高斯分布分量的总数,或者说是隐变量z 元素数量。实际上由于qϕ(z|x) 各分量独立的多元高斯分布,这个计算可以简化为先计算单独计算各分量的的KL散度(即一元正态分布),然后对各分量的KL散度求和,因为一元正态分布的KL散度相对容易推导:

    KL(N(μ,σ2)‖N(0,1))=Ex∼N(μ,σ2)[log⁡e−(x−μ)2/2σ2/2πσ2e−x2/2/2π]=Ex∼N(μ,σ2)[log⁡(1σ2exp⁡(12(x2−(x−μ)2/σ2))]=12Ex∼N(μ,σ2)[−log⁡σ2+x2−(x−μ)2/σ2]=12(−log⁡σ2+σ2+μ2−1)

    综上,对于训练数据的一个样本x(i),其KL散度项的优化目标为:

    DKL(qϕ(z|x(i))∥pθ(z))=12∑j=0n((σj(i))2+(μj(i))2−1−log⁡((σj(i))2))

    现在我们来分析优化目标的第一项−Ez∼qϕ(z|x)log⁡pθ(x|z),它一般被称为重建误差(reconstruction error),因为pθ(x|z) 是给定z 生成真实数据x 似然(Likelihood)。对于一个给定的训练样本x(i),我们可以采蒙特卡洛方法(https://en.wikipedia.org/wiki/Monte_Carlo_method (opens new window))来估计这个数学期望,即从qϕ(z|x(i)) 次采样来估计:

    −Ez∼qϕ(z|x(i))log⁡pθ(x(i)|z)≈−1L∑l=1L(log⁡pθ(x(i)|z(i,l)))

    这里的L 采样的总次数,实际上在具体实现上往往L=1,即只随机采样一次(VAE论文中说当训练的mini-batch size足够大时,采样一次是有效的)。另外一个困难的地方,从qϕ(z|x(i)) 样这个操作是无法计算梯度的,VAE采用一种重参数化(reparameterization)技巧来解决这个问题,具体地,通过引入一个额外的独立随机变量ϵ∼p(ϵ) 将随机变量z 变成确定变量:z=gϕ(x,ϵ)。由于qϕ(z|x) 经假定为多元高斯分布,使用重采样技巧后则为:

    z∼qϕ(z|x(i))=N(z;μ(i),σ2(i)I)z=μ+σ⊙ϵ, where ϵ∼N(0,I); Reparameterization trick.

    直观上讲,就是首先从标准正态分布随机采样一个样本,然后乘以encoder预测的标准差,再加上encoder预测的均值,这样就能计算该损失对encoder网络参数的梯度了。

    根据建模的数据类型,pθ(x|z) 布可以是一个高斯分布也可以是一个伯努利分布,这里以更通用的高斯分布为例。假定pθ(x|z) 布也属于一个各分量独立的多元高斯分布:pθ(x|z)=N(x;μ,σ2I)。由于各个分量独立,所以我们可以单独计算每个分量:

    log⁡p(x|z)=log⁡12πσ2exp⁡(−(x−μ)22σ2)=log⁡12πσ2−12σ2(x−μ)2

    对于这个高斯分布的标准差,我们往往假定它是一个常量,而均值是由decoder预测得出:μ=fθ(z)。那么则有:

    −log⁡pθ(x(i)|z(i,l))=C1+C2∑dD(xd(i)−(fθ(z(i,l)))d)2

    这里C1 C2 是常量,而D 变量x 维度大小。如果忽略常量C1 话,那么重建误差其实就是L2损失。上面我们是假定pθ(x|z) 布是一个高斯分布,如果pθ(x|z) 一个伯努利分布即0-1分布的话,此时decoder直接预测概率值(sigmoid激活函数),重建误差就是交叉熵,:

    log⁡pθ(x|z)=∑dD(xdlog⁡(fθ(z)d)+(1−xd)log⁡(1−fθ(z)d))

    根据上述分析,对给定的一个训练样本x(i),其训练损失(假定是高斯分布)为:

    LVAE(θ,ϕ,x(i))=−Ez∼qϕ(z|x(i))log⁡pθ(x(i)|z)+DKL(qϕ(z|x(i))∥pθ(z))≈−1L∑l=1L(log⁡pθ(x(i)|z(i,l)))+DKL(qϕ(z|x(i))∥pθ(z))=CL∑l=1L∑dD(xd(i)−(fθ(z(i,l)))d)2+12∑j=0n((σj(i))2+(μj(i))2−1−log⁡((σj(i))2))

    如果把KL散度项看到一个正则化的话,那么VAE的损失函数就是重建误差+正则化,这样VAE就可以看成是一个加了约束的AE。VAE的整个训练流程如下所示:输入x,encoder首先计算出后验分布的均值和标准差,然后通过重采样方法采样得到隐变量z,然后送入decoder得到重建的数据x′。

    训练完成后,我们就得到生成模型pθ(x|z)pθ(z),其中pθ(x|z) 是decoder网络,而先验pθ(z) 标准正态分布,我们从pθ(z) 机采样一个z,送入decoder网络,就能生成与训练数据X 似的样本。

    CVAE

    条件变分自编码器(Conditional Variational Autoencoder,CVAE)是VAE的一个变种,相比VAE,CVAE要估计的是一个条件分布pθ(x|y),同样地,我们引入隐变量z 进行变分推断。此时,给定一个输入y,从先验分布pθ(z|y) 采样一个z,然后根据分布pθ(x|z,y) 成一个样本x,因而这里要求解的生成模型是pθ(z|y)pθ(x|z,y)。这个生成模型可以用两个网络来学习,其中一个网络来学习先验分布pθ(z|y),另外一个网络来学习条件分布pθ(x|z,y)。在VAE,我们假定先验pθ(z) 标准正态分布,因为不需要单独的网路来学习;而在CVAE中,先验分布pθ(z|y) 一种条件先验,如果假定z 独立与y 话,那么此时先验分布pθ(z|y)=pθ(z),更进一步地也可以简化认为先验为标准正态分布。 同样地,我们另外采用一个网络qϕ(z|x,y) 估计后验分布pθ(z|x,y)。同样地,我们可以推导出ELBO:

    log⁡pθ(x|y)≥Ez∼qϕ(z|x,y)log⁡pθ(x|z,y)−DKL(qϕ(z|x,y)∥pθ(z|y))

    那么对于CVAE,其优化目标为:

    LCVAE(θ,ϕ)=−Ez∼qϕ(z|x,y)log⁡pθ(x|z,y)+DKL(qϕ(z|x,y)∥pθ(z|y))θ∗,ϕ∗=arg⁡minθ,ϕLCVAE

    对于上述优化目标的处理,同样地可以采用和VAE一样的分析过程,这里不再详细展开,具体见https://papers.nips.cc/paper/2015/hash/8d55a249e6baa5c06772297520da2051-Abstract.html (opens new window)。下图为CVAE的一种实现方式(这里先验简化为标准正态分布):

    ​

    对于VAE和CVAE,它们最重要的区别是数据是如何生成的,对于VAE,数据的产生认为是pθ(x|z)pθ(z),而对于CVAE,其数据的产生是pθ(z|y)pθ(x|z,y),不同的数据产生方式导致了不同的建模方式和ELBO,但两者用的变分推断理论是一致的。 ​

    VAE的代码实现

    这里以MNIST数据集为例用PyTorch实现一个简单的VAE生成模型,由于MNIST数据集为灰度图,而且大部分像素点为0(黑色背景)或者白色(255,前景),所以这里可以将像素值除以255归一化到[0, 1],并认为像素值属于伯努利分布,重建误差采用交叉熵。 首先是构建encoder,这里用简单的两层卷积和一个全连接层来实现,encoder给出隐变量的mu和log_var:

    class Encoder(nn.Module):
        """The encoder for VAE"""
        
        def __init__(self, image_size, input_dim, conv_dims, fc_dim, latent_dim):
            super().__init__()
            
            convs = []
            prev_dim = input_dim
            for conv_dim in conv_dims:
                convs.append(nn.Sequential(
                    nn.Conv2d(prev_dim, conv_dim, kernel_size=3, stride=2, padding=1),
                    nn.ReLU()
                ))
                prev_dim = conv_dim
            self.convs = nn.Sequential(*convs)
            
            prev_dim = (image_size // (2 ** len(conv_dims))) ** 2 * conv_dims[-1]
            self.fc = nn.Sequential(
                nn.Linear(prev_dim, fc_dim),
                nn.ReLU(),
            )
            self.fc_mu = nn.Linear(fc_dim, latent_dim)
            self.fc_log_var = nn.Linear(fc_dim, latent_dim)
                        
        def forward(self, x):
            x = self.convs(x)
            x = torch.flatten(x, start_dim=1)
            x = self.fc(x)
            mu = self.fc_mu(x)
            log_var = self.fc_log_var(x)
            return mu, log_var
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    对于decoder,基本采用对称的结构,这里用反卷积来实现上采样,decoder根据隐变量重构样本或者生成样本:

    class Decoder(nn.Module):
        """The decoder for VAE"""
        
        def __init__(self, latent_dim, image_size, conv_dims, output_dim):
            super().__init__()
            
            fc_dim = (image_size // (2 ** len(conv_dims))) ** 2 * conv_dims[-1]
            self.fc = nn.Sequential(
                nn.Linear(latent_dim, fc_dim),
                nn.ReLU()
            )
            self.conv_size = image_size // (2 ** len(conv_dims))
            
            de_convs = []
            prev_dim = conv_dims[-1]
            for conv_dim in conv_dims[::-1]:
                de_convs.append(nn.Sequential(
                    nn.ConvTranspose2d(prev_dim, conv_dim, kernel_size=3, stride=2, padding=1, output_padding=1),
                    nn.ReLU()
                ))
                prev_dim = conv_dim
            self.de_convs = nn.Sequential(*de_convs)
            self.pred_layer = nn.Sequential(
                nn.Conv2d(prev_dim, output_dim, kernel_size=3, stride=1, padding=1),
                nn.Sigmoid()
            )
            
        def forward(self, x):
            x = self.fc(x)
            x = x.reshape(x.size(0), -1, self.conv_size, self.conv_size)
            x = self.de_convs(x)
            x = self.pred_layer(x)
            return x
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    有了encoder和decoder,然后就可以构建VAE模型了,这里的实现只对隐变量通过重采样方式采样一次,训练损失为KL散度和重建误差(交叉熵)之和:

    class VAE(nn.Module):
        """VAE"""
        
        def __init__(self, image_size, input_dim, conv_dims, fc_dim, latent_dim):
            super().__init__()
            
            self.encoder = Encoder(image_size, input_dim, conv_dims, fc_dim, latent_dim)
            self.decoder = Decoder(latent_dim, image_size, conv_dims, input_dim)
            
        def sample_z(self, mu, log_var):
            """sample z by reparameterization trick"""
            std = torch.exp(0.5 * log_var)
            eps = torch.randn_like(std)
            return mu + eps * std
        
        def forward(self, x):
            mu, log_var = self.encoder(x)
            z = self.sample_z(mu, log_var)
            recon = self.decoder(z)
            return recon, mu, log_var
        
        def compute_loss(self, x, recon, mu, log_var):
            """compute loss of VAE"""
            
            # KL loss
            kl_loss = (0.5*(log_var.exp() + mu ** 2 - 1 - log_var)).sum(1).mean()
            
            # recon loss
            recon_loss = F.binary_cross_entropy(recon, x, reduction="none").sum([1, 2, 3]).mean()
            
            return kl_loss + recon_loss
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    模型训练完成,可以从标准正态分布随机采样,然后生成新的样本,下图为一些模型生成的样本:


    VAE虽然主要用于生成,但是作为一种无监督学习方法,也能用于提取特征,下图为从MNIST验证集中提取的中间隐含层特征(encoder属于的mu)的TSNE可视化,可以看到不同类别的隐含特征具有一定的区分度:


    这里额外要提的一点,VAE模型比较容易出现posterior collapse问题,简单来说,就是由于decoder足够强大,可以不依赖隐变量而直接学习到了数据分布,此时:qϕ(z|x)=pθ(z),pθ(x|z)=pθ(x)。这对生成模型没有太大的问题,但是如果你用VAE提取特征的话,就不行了,因为隐变量和原始数据之间没有联系了。这个问题具体可以看看这篇文章https://openreview.net/forum?id=r1xaVLUYuE (opens new window)。对于posterior collapse问题,也有很多改进方案来解决或者避免,比如VQ-VAE,后面会有新的文章来讲解。

    代码的实现放在github上,具体见https://github.com/xiaohu2015/nngen (opens new window)

    总结

    这篇文章简单讲述了自动编码器的原理,并重点介绍了VAE模型的原理以及它和AE之间的联系,最后给出了一个具体的VAE代码实例。VAE模型涉及比较复杂的数学建模,理解它需要花费一定的精力,这里特别感谢一些优秀的文章(见参考)。 ​

    参考

    • https://lilianweng.github.io/lil-log/2018/08/12/from-autoencoder-to-beta-vae.html (opens new window)
    • https://arxiv.org/abs/1312.6114 (opens new window)
    • https://arxiv.org/abs/1606.05908 (opens new window)
    • https://spaces.ac.cn/archives/5253 (opens new window)
    • https://spaces.ac.cn/archives/5343 (opens new window)
    • https://spaces.ac.cn/archives/7381 (opens new window)
    • https://blog.evjang.com/2016/08/variational-bayes.html (opens new window)
    • https://towardsdatascience.com/understanding-variational-autoencoders-vaes-f70510919f73 (opens new window)
    • https://papers.nips.cc/paper/2015/hash/8d55a249e6baa5c06772297520da2051-Abstract.html (opens new window)
    • https://github.com/AntixK/PyTorch-VAE/blob/master/models/vanilla_vae.py (opens new window)
    • https://keras.io/examples/generative/vae/ (opens new window)
    • https://github.com/jojonki/AutoEncoders/blob/master/vae.ipynb (opens new window)
    • 生成模型之VAE (opens new window)
    上次更新: 2025/07/06, 13:25:25
    DDPM
    DDIM

    ← DDPM DDIM→

    最近更新
    01
    优化器
    07-04
    02
    weight_decay
    07-03
    03
    帮助信息查看
    06-08
    更多文章>
    Theme by Vdoing | Copyright © 2022-2025 Geeks_Z | MIT License
    京公网安备 11010802040735号 | 京ICP备2022029989号-1
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式