写在前面

想趁假期自学一下近年非常火热的生成模型,诸如DALLE、novel AI、chatGPT等模型,可以说是饱吸眼球了。生成模型里面,有名的有GANs、VAEs、流模型、还有近几年来火爆的扩散模型等,都有值得学习的地方~看看有没有时间把他们都看完~。看了不少材料,自以为入门一点点了,写下个人对它的理解与少量理论解释,希望小白看了也能懂!如有错误,恳请批评改之!

引言

相比起鼎鼎大名的GANs,VAEs(Variational Autoencoder,VAE)似乎名气小一些,但VAEs因为其较强的可解释性,有严谨的理论基础,所以一直有一定的应用价值,早前公布的DALLE就是基于VAE的思想提出的生成模型。

一句话概括VAE的作用:不同于传统的神经网络,把图像编码成一个n维向量,VAE为了让神经网络具有更好的生成能力,它借助贝叶斯流派的思想,试图让每一张图片能用一个概率分布表示,从这个分布上采样得到的点,能生成和原图像相似的图,从而具有较好的生成能力和泛化能力。

在接下来的几个章节中,第一部分主要回顾一些关于自动编码器(Autoencoder,AE)的重要概念,这将有助于理解 VAE。在第二部分中,主要展示为什么AE不能用于生成新数据,并将介绍VAE,某种意义上,它是AE的正则化版本。在最后一章中,会基于变分推理对 VAE 进行更数学化的展示。但是不想深入了解 VAE 的数学细节的读者可以跳过本节,也并不会过多影响理解。

一、自动编码器(Autoencoder,AE)

在介绍VAE之前,需要先对AE进行简单的解释。AE是一种无监督学习方法,它的原理很简单:先将高维的原始数据映射到一个低维特征空间,然后从低维特征学习重建原始的数据。一个AE模型包含两部分网络:

Encoder:将原始的高维数据映射到低维特征空间,这个特征维度一般比原始数据维度要小,这样就起到降维的目的,这个低维特征也往往被称为中间隐含特征(latent representation);鉴于Encoder的作用,理论上,我们可以将它用来对图像等高维数据做降维使用,或是当预训练模型的权重。Decoder:基于压缩后的低维特征来重建原始数据;因为它是用于生成的,理论上,我们应该是可以将它拿来作为生成器,生成图像等数据。

在对AE训练的时候,我们希望重建的数据和原来的数据近似一致的,那么AE的训练损失函数可以采用简单的MSE:

LAE=||x−d(e(x))||2L_{AE}=|| x – d(e(x)) ||^2

PS:以上,就是AE的简单介绍。但在写这些的时候,忍不住去想一些有的没的:大数据时代,信息的巨量增长,也给人们带来的储存,表达上的不方便,人们需要一种降维算法方便储存或是表达。以图像为例,一张 512×512512 \times512 的单通道图像,有差不多25w个数值,太多了,一个很简单直接的想法就是,我们将这个 512×512512 \times512 的图像矩阵,变成 1×(512∗512)1 \times (512*512) 的维度,后面接一个 ()(512∗512)×64(512*512)\times 64 的转换矩阵,那图像就变成了 1×641\times 64 的一个向量了!如果我们有一个海量图像库,我们现在只需要存储一下这个低维向量和转换矩阵即可,大大减少了内存占用,但是突然发现这个转换矩阵它不是方阵,他不可逆!我们不能通过简单求逆的方式得到一个 64×(512∗512)64 \times (512*512) 的矩阵将低维向量还原回去。。。。不过现在的神经网络很牛,既然数学上求解不出来,那就交给神经网络去学习,希望它能把这两个矩阵的近似解搞出来,那也是可以的,事实证明,神经网络确实能搞出来,就是太容易过拟合了。。。以上,只是个人角度理解的AE,hh

AE缺陷

虽然AE的Encoder和Decoder看起来很靠谱。。。都有各自的用途。但是,在实际的应用中,人们发现AE很容易过拟合,我们的神经网络很可能过于强大,以致于它根本没必要学会样本含有的语义信息,只是无脑的知道训练集中某个编码进来,那他的输出就是这样的,它的loss能非常接近于0,但是对训练集外的样本,做成编码传到decoder之后,网络只能干瞪眼,没有泛化能力,也就失去了生成能力。

说的详细点,对于encoder的输出,首先,我们自然是希望Encoder得到的隐特征是尽可能多的保留原始信息,最好仍有原来的语义信息,但是实际得到的特征,不一定有这性质,就像下图一样,我们希望两个8,两个9能比较靠近,但显然,下图中它们没做到。

又或者,像下面的图一样,看似每一类都聚合的很不错,但是左侧的大豁口暗示着这模型过拟合严重。我们希望得到的聚类图应该是样本能尽量散布全图,整体轮廓比较“圆润”,然后边界也比较清晰。但像下图这样,就不太“圆润”,若取豁口处的点作为样本输出decoder中,模型输出的图大概率是啥都不像,这就意味着decoder没有它应有的生成能力。

再换个角度,上面讲到AE是把一个样本映射成一个n维向量,这个向量我们可以将它看作是某个高维空间上的一个坐标点,也就是说我们把一个样本映射到了一个点上。限于AE的泛化能力,encoder得到的向量所含的语义信息不太强,导致这个点周围的样本点,并不一定和我们的输入样本很相关,所以我们在这个隐空间中随机采样时,将采样得到的点输入decoder中,它生成的样本也就不太满意了。就如下图所示。

无意义的隐空间使我们采样到的点不能生成有效的内容。

虽然也有相应的模型与技术针对AE的缺陷进行了改进,但这些不是本文的重点,接下来看一下VAE是怎么解决以上的缺陷的。

二、VAE介绍

首先,我们给出一些符号的定义,假设已知的数据集为X={x(i)}i=1N\mathbf{X}=\{x^{(i)}\}_{i=1}^N,在VAE中,除了已知的数据外还存在一个隐变量,记为zz,它服从先验分布 p(z)p(\mathbf{z}),假设是标准正态分布 。基于这些符号,我们可以把VAE中的encoder写作 p(z|x)p(z|x) ,decoder写作 p(x|z)p(x|z)

AE和VAE的比较

鉴于前面讨论过的AE过拟合严重这一问题,很自然的想法就是我们增加正则化手段以增加模型的泛化性,VAE从某个角度来看就是AE的正则化版本。既然最初的AE将一个样本映射成一个点很容易使得没在训练集里出现的点都失去意义,太容易过拟合了,那我们就引入贝叶斯理论的一些思想:将训练集的每一个样本映射成一个专属的分布,从这个分布上取到的点,都有概率和原样本比较相似,且越容易被采样到的点越像原样本,从而增强模型的泛化能力。

那按这个思路,我们会想到这么一个问题。我们怎么让神经网络去学一个分布。这里VAE假设encoder学出来的分布 p(z|x)p(z|x) 是正态分布,而要想确定一个正太分布,我们只需要知道两个统计量就好了,也就是它的均值和方差。因此,我们要学这个分布,只需要让神经网络学均值和方差即可,再具体点说,如果学的是一元高斯分布,我们就在encoder后面加一个二维的全连接层,输出的二维向量分别表示均值、方差,如果是二元高斯分布,那就是后面接一个4维的全连接层,以此类推。VAE的具体流程如下。

从图中可以看到,每一个样本,得到的正太分布都是不一样的,这也和前面的论述对上了。

从图中,我们可以知道训练的目标,仍然是减少 X^\hat{X}XX 之间的误差,但不同于AE,VAE中间多了个采样的过程,这实际上也是增加噪声的过程,加大了模型的训练难度,从而起到了正则化的作用。但是,仔细想一下,这样的操作还是不够的,因为增加噪声主要靠的是方差,但若是模型投机取巧,把方差训练成了0,那这个采样的过程不就毫无作用了,起不到增加噪声,完成正则化的目的了。

因此,VAE在训练的时候,还要求中间的正态分布都向标准正态分布靠拢,这样也就防止了方差变成0。

我们希望encoder学出来的分布p(z|x)p(z|x)是服从标准正态分布的原因,直觉上可以这么理解,在训练中,我们不能让方差退化成接近0,如果方差几乎为0,那意味着我们这个分布几乎退化成了一个点,那就和AE没区别了,还是过拟合。但也不是说方差可以很大,这会使得数据差异太大,显然也不太合理,会过度增加模型的训练难度。方差上有限制,均值也同样有,我们希望每个样本的均值都比较接近,也就是每一个样本得到的高斯分布之间要紧凑,太分散的话,同样会出现离散的点,生成的图像四不像;紧凑一点,有的点可能属于多个分布,那这个点就是每个样本都像一点,那也符合预期,下面这个图可以很好的概括~。

三个圆圈挨得近,说明均值比较靠近,圆圈半径比较大,说明方差要稍微大一点

理论上来看,如果所有的 p(z|x)p(z|x) 都可以近似的看作是标准正态分布,那么根据定义:

p(z)=∫p(z|x)p(x)dx=∫N(0,I)p(x)dx=N(0,I)∫p(x)dx=N(0,I)p(\mathbf{z})=\int{p(z|x)p(x)}dx=\int N(0,I)p(x)dx=N(0,I)\int p(x)dx=N(0,I)

这时候,我们就会发现,前面假设Z的先验分布是标准正态分布就用上了,我们可以放心的从标准正太分布上采样,并得到我们想要的结果了。

在实际使用中,我们就可以这么使用VAE生成数据:

1.从先验分布p(z)p(\mathbf{z})中采样一个z(i)\mathbf{z}^{(i)}

2.根据decoder的p(x|z)p(x|z),用z(i)\mathbf{z}^{(i)}生成x(i)\mathbf{x}^{(i)}

解决了这个问题后,还有一个问题就是怎么优化。AE的loss只能使得decoder的输出尽可能地拟合原始输出,但是如何让encoder的输出p(z|x)p(z|x)满足正态分布呢,我们知道,KL散度是可以判断两个分布是否相似的一个标准,所以新加的正则化项就是encoder拟合的分布与高斯分布之间的 Kulback-Leibler 散度。

最终VAE的loss可以写成如下这种形式:

LVAE=||x−d(e(x))||2+KL[N(μx,σx2),N(0,1)]L_{VAE}=|| x – d(e(x)) ||^2+KL[N(\mu_x,\sigma^2_x),N(0,1)]

VAE代码实现及重采样技术

这里给出一个非常简单的VAE代码实现,请结合注释使用。encoder和decoder就是简单的全连接+relu,增加一些非线性性。

class VAE(nn.Module): def __init__(self,image_size=512,h_dim=400,z_dim=20): super().__init__() self.encoder = nn.Sequential( nn.Linear(image_size, h_dim), nn.ReLu(), nn.Linear(h_dim, z_dim*2) ) self.decoder = nn.Sequential( nn.Linear(z_dim, h_dim), nn.ReLu(), nn.Linear(h_dim, image_size), nn.Sigmoid() ) def reparameterize(self, mu, logvar): #logvar=2logstd,所以要求得std,先乘0.5,再求指数 std = logvar.mul(0.5).exp_() #从标准高斯分布中随机生成一个样本 esp = torch.randn(*mu.size()) #根据标准高斯分布的样本点变换到需要的高斯分布 z = mu + std * esp return z def forward(self, x): h = self.encoder(x) mu, logvar = torch.chunk(h, 2, dim=-1) #重采样技术,一个很重要的trick z = self.reparameterize(mu, logvar) return self.decoder(z), mu, logvar

看forward函数了解模型的前向流程,首先,图像经过encoder,得到均值和方差,然后用chunk函数,将它切分开来,这里的方差求的是logvar,因为var是大于0的一个数,而我们的全连接输出是整个实数域,所以加一个log将值域从全体实数域转到正数。

reparameterize函数是一个重采样函数,也是模型的一个重点,前面讲到得到均值和方差后,我们就确定了一个高斯分布,但如果直接在这个分布上随机采样一个点传到decoder中,采样这一过程并不可导,会导致梯度断裂,模型无法更新,所以我们需要一个合理的采样方式,让梯度正常传递才行。这里采用的做法就是利用标准高斯分布经过缩放,可以变成任何高斯分布的特点,从标准高斯分布中随机生成一个点,再利用均值和方差进行变换,这样就把均值方差用上了,让梯度连接了起来,从而可以正常更新参数。

每次训练的时候,我们都会在一个分布上采样一个点,那假设我们训练100个epoch,就意味着我们在输入对应的分布上采样了一百次,采样的每个点,我们都希望他和原图相似,且采样概率大的点,因此更容易被采到,一般会更像,采样概率小的点,可能就是有一点像。

三、数学推导

预备知识

一些需要用到的公式:

KL散度,也叫相对熵,其公式为

KL(p||q)=H(p,q)−H(p)KL(p||q) = H(p,q)-H(p)

=−∫p(x)logq(x)dx−∫−p(x)logp(x)dx=-\int p(x)logq(x)dx-\int -p(x)logp(x)dx

=−∫p(x)(log⁡q(x)−log⁡(p(x)))dx=-\int p(x)(\log q(x)-\log(p(x)))dx

=−∫p(x)log⁡q(x)p(x)dx=-\int p(x)\log \frac{q(x)}{p(x)}dx

以上, H(p,q)H(p,q) 表示p和q的交叉熵, H(p)H(p) 表示p的熵。实际上,可以将上式改写为期望的形式,借鉴

∫f(x)p(x)dx=Ep(x)(f(x))\int f(x)p(x)dx=E_{p(x)}(f(x))

所以,下面这三条公式很重要,会在后面用到。

H(p)=Ep(x)(log⁡1p(x))=−Ep(x)(log⁡p(x))H(p)=E_{p(x)}(\log \frac{1}{p(x)})=-E_{p(x)}(\log {p(x)})

H(p,q)=Ep(x)(log⁡1q(x))=−Ep(x)(log⁡q(x))H(p,q)=E_{p(x)}(\log \frac{1}{q(x)})=-E_{p(x)}(\log {q(x)}) (1)

∫p(x)log1q(x)dx=Ep(x)(log⁡1q(x))\int p(x)log{\frac{1}{q(x)}}dx=E_{p(x)}(\log \frac{1}{q(x)})

推导

我们沿用前面的符号定义,decoder自然地由 p(x|z)p(x|z) 定义,它描述了给定隐变量后的decoder生成的变量的分布,而encoder由 p(z|x)p(z|x) 定义,它描述了给定样本后,隐变量的分布。

在前面我们提到过KL散度,为了让中间的分布逼近标准正态分布,我们采用了它。而实际上,我们采用这种方法就默认了我们使用了一种变分推断技术(变分推断 (Variational Inference,VI) 作为一种近似复杂分布的技术,它的想法是设置一个参数化的分布族(例如高斯族,其参数是均值和协方差)并在这个族中寻找我们的目标分布的最佳近似值。族中最好的元素是最小化loss时(大多数情况下是近似值和目标之间的 KL 散度)的那个元素,并且是通过梯度下降找到的。)

试想一下我们不用神经网络,那从理论上看我们是否可以直接得到p(z|x)p(z|x)呢,根据贝叶斯公式,后验 p(z|x) p(z|x) 可以与先验 p(z) p(z) 、似然 p(x|z)p(x|z) 建立联系,即

p(z|x)=p(x|z)p(z)∫p(x|u)p(u)dup(z|x)=\frac{p(x|z)p(z)}{\int p(x|u)p(u)du}

此时我们会遇到一个棘手的问题,也就是分母处的积分!可能会很难求,甚至没有具体的解析解,如果p(z|x)p(z|x)能直接求出来,也就没神经网络什么事了,直接上精确的解析解了。

所以,这里我们试图用变分推断的技术寻找一个高斯分布 q(z|x)q(z|x) 来近似 p(z|x)p(z|x),其均值和协方差由两个函数 g(.)g(.)h(.)h(.) 定义。因此我们可以表示,

q(z|x)≡N(g(x),h(x))q(z|x) \equiv N(g(x),h(x))

由此,我们通过这种方式定义了一组用于变分推断的候选者,现在需要找到最佳的函数 gghh,以最小化近似值和目标 p(z|x) p(z|x) 之间的误差。换句话说,我们正在寻找最优的 g* 和 h* 使得

(g∗,h∗)=argmin(g,h)∈G×H⁡KL(q(z|x),p(z|x))(g*,h*)=\mathop{argmin}\limits_{(g,h) \in G \times H}KL(q(z|x),p(z|x))

=argmin(g,h)∈G×H⁡H(q(z|x),p(z|x))−H(q(z|x))=\mathop{argmin}\limits_{(g,h) \in G \times H}H(q(z|x),p(z|x))-H(q(z|x))

=argmin(g,h)∈G×H⁡(−Eq(z|x)(log⁡p(x|z)p(z)p(x))−(−Eq(z|x)(log⁡q(z|x))))=\mathop{argmin}\limits_{(g,h) \in G \times H} \left( -E_{ q(z|x)}(\log\frac{p(x|z)p(z)}{p(x)} \right) – \left( -E_{q(z|x)}(\log q(z|x))) \right)

=argmin(g,h)∈G×H⁡(−Eq(z|x)(log⁡p(x|z))−Eq(z|x)(log⁡p(z))+Eq(z|x)(log⁡p(x))+Eq(z|x)(log⁡q(z|x)))=\mathop{argmin}\limits_{(g,h) \in G \times H}\left( -E_{q(z|x)}(\log p(x|z)) -E_{q(z|x)}(\log p(z)) + E_{q(z|x)}(\log p(x)) + E_{q(z|x)}(\log q(z|x))\right)

此处进行移项,整理,根据预备知识的公式(1),则

上式=argmin(g,h)∈G×H⁡(−Eq(z|x)(log⁡p(x|z))+(H(q(z|x),p(z))−H(q(z|x)))+log⁡p(x)∫q(z|x)dz)=\mathop{argmin}\limits_{(g,h) \in G \times H}\left( -E_{q(z|x)}(\log p(x|z)) + (H(q(z|x),p(z)) -H(q(z|x))) +\log p(x)\int q(z|x)dz \right)

最后一项的积分为1,中间两项可写成KL散度的形式,则

上式=argmin(g,h)∈G×H⁡(−Eq(z|x)(log⁡p(x|z))+KL(q(z|x),p(z))+log⁡p(x))=\mathop{argmin}\limits_{(g,h) \in G \times H}\left( -E_{q(z|x)}(\log p(x|z))+KL(q(z|x),p(z)) + \log p(x)\right) (2)

上式中,要找最优的参数和样本x出现的概率无关,所以最后一项可以不看,所以,我们能得到

(g*,h*)=\mathop{argmax}\limits_{(g,h) \in G \times H}\left( E_{q(z|x)}(\log p(x|z))-KL(q(z|x),p(z))\right)

在很多教程中,还会再假设p(x|z) 同样是高斯分布,也就是decoder的分布,其均值由 z 变量的确定性函数 f 定义,协方差矩阵的形式为常数 c 乘以单位矩阵 I 。假设函数 f 属于表示为 F 的函数族。因此,我们有

0″>p(x|z)\equiv N(f(z),cI), f\in F, c>0

由此,上式我们就可以改写做,

=\mathop{argmax}\limits_{(g,h) \in G \times H}\left( E_{q(z|x)}(-\frac{||x-f(z)||^2}{2c})-KL(q(z|x),p(x))\right)

在上式中,我们可以观察到存在权衡,即当近似后验 p(z|x) 时——最大化“观察”的可能性(最大化对数似然,第一项)和保持接近先验分布(对于第二项, q(z|x)p(z) 之间的 KL 散度最小化)。这种权衡对于贝叶斯推理问题来说是很自然的,它表达了我们对数据的信心和对先验的信心之间需要找到的平衡。具体地说,x 和 f(z) 之间的重构误差以及 q(z|x) p(z) 之间的 KL 散度给出的正则化项存在一个权衡,常数 c 决定了两项之间的平衡。 c 越高,我们假设模型中的“probabilistic decoder”围绕 f(z) 的方差越大,因此我们越倾向于正则化项而不是重建项(如果 c 较低,则相反)。

某种意义上,VAE 跟 GAN 一样,内部其实是包含了一个对抗的过程,只不过它们两者是混合起来,共同进化的。

最后一个问题,我们在上面得到了优化函数,但是在大多数教程中,它还会继续写下去。针对第一项,可以使用MSE函数或者二分交叉熵函数去优化,对于第二项,看到很多代码中会看到下面式子的影子

\frac{1}{2}(-\log \sigma ^2 +\sigma^2+\mu^2-1)

它是 KL(q(z|x),p(x)) 的具体结果,至于怎么计算的,因为牵扯到线性代数等知识,比较繁琐,笔者没能力自己推导,具体参考这篇这篇文章~只是想了解的,知道上式代表的是啥我想也就够了~

另外,在学VAE的概念的时候,一定会看到ELBO这个单词,实际上它就是我们的公式(2)这里出来,从另一个角度进行的理解,由公式(2),去掉argmax这个符号,我们可以得到

KL(q(z|x),p(z|x))=-E_{q(z|x)}(\log p(x|z))+KL(q(z|x),p(z)) + \log p(x)

将其移个项,可以得到,

\log p(x)-KL(q(z|x),p(z|x))=E_{q(z|x)}(\log p(x|z))-KL(q(z|x),p(z))

由于KL散度的非负性,所以有下述不等式:

\log p(x) \geq E_{q(z|x)}(\log p(x|z))- KL(q(z|x),p(z))

不等号右边的式子就是ELBO,它规定了x出现的概率下界,所以我们的优化过程,也可以被理解成是在不断提高ELBO的下界,使得x出现的概率更高。

参考资料

1.小小将:生成模型之VAE

2.https://towardsdatascience.com/understanding-variational-autoencoders-vaes-f70510919f73

3.变分自编码器(Variational AutoEncoder, VAE),直观理解、数学推导与两年前的应用

4.PaperWeekly:变分自编码器VAE:原来是这么一回事 | 附开源代码

5.KL散度(Kullback-Leibler Divergence)介绍及详细公式推导

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注