花式解释AutoEncoder与VAE

什么是自动编码器

自动编码器(AutoEncoder)最开始作为一种数据的压缩方法,其特点有:

1、跟数据相关程度很高,这意味着自动编码器只能压缩与训练数据相似的数据,这个其实比较显然,因为使用神经网络提取的特征一般是高度相关于原始的训练集,使用人脸训练出来的自动编码器在压缩自然界动物的图片是表现就会比较差,因为它只学习到了人脸的特征,而没有能够学习到自然界图片的特征;

2、压缩后数据是有损的,这是因为在降维的过程中不可避免的要丢失掉信息;

到了2012年,人们发现在卷积网络中使用自动编码器做逐层预训练可以训练更加深层的网络,但是很快人们发现良好的初始化策略要比费劲的逐层预训练有效地多,2014年出现的Batch Normalization技术也是的更深的网络能够被被有效训练,到了15年底,通过残差(ResNet)我们基本可以训练任意深度的神经网络。

所以现在自动编码器主要应用有两个方面,第一是数据去噪,第二是进行可视化降维。然而自动编码器还有着一个功能就是生成数据。

我们之前讲过GAN,它与GAN相比有着一些好处,同时也有着一些缺点。我们先来讲讲其跟GAN相比有着哪些优点。

第一点,我们使用GAN来生成图片有个很不好的缺点就是我们生成图片使用的随机高斯噪声,这意味着我们并不能生成任意我们指定类型的图片,也就是说我们没办法决定使用哪种随机噪声能够产生我们想要的图片,除非我们能够把初始分布全部试一遍。但是使用自动编码器我们就能够通过输出图片的编码过程得到这种类型图片的编码之后的分布,相当于我们是知道每种图片对应的噪声分布,我们就能够通过选择特定的噪声来生成我们想要生成的图片。

第二点,这既是生成网络的优点同时又有着一定的局限性,这就是生成网络通过对抗过程来区分“真”的图片和“假”的图片,然而这样得到的图片只是尽可能像真的,但是这并不能保证图片的内容是我们想要的,换句话说,有可能生成网络尽可能的去生成一些背景图案使得其尽可能真,但是里面没有实际的物体。

自动编码器的结构

首先我们给出自动编码器的一般结构

从上面的图中,我们能够看到两个部分,第一个部分是编码器(Encoder),第二个部分是解码器(Decoder),编码器和解码器都可以是任意的模型,通常我们使用神经网络模型作为编码器和解码器。输入的数据经过神经网络降维到一个编码(code),接着又通过另外一个神经网络去解码得到一个与输入原数据一模一样的生成数据,然后通过去比较这两个数据,最小化他们之间的差异来训练这个网络中编码器和解码器的参数。当这个过程训练完之后,我们可以拿出这个解码器,随机传入一个编码(code),希望通过解码器能够生成一个和原数据差不多的数据,上面这种图这个例子就是希望能够生成一张差不多的图片。

decoder.png

这件事情能不能实现呢?其实是可以的,下面我们会用PyTorch来简单的实现一个自动编码器。

首先我们构建一个简单的多层感知器来实现一下。

class autoencoder(nn.Module):    
def __init__(self):        
super(autoencoder, self).__init__()        
self.encoder = nn.Sequential(            
nn.Linear(28*28, 128),            
nn.ReLU(True),            
nn.Linear(128, 64),           
nn.ReLU(True),            
nn.Linear(64, 12),            
nn.ReLU(True),            
nn.Linear(12, 3)       
 )        
self.decoder = nn.Sequential(            
nn.Linear(3, 12),            
nn.ReLU(True),            
nn.Linear(12, 64),            
nn.ReLU(True),            
nn.Linear(64, 128),            
nn.ReLU(True),            
nn.Linear(128, 28*28),           
 nn.Tanh()        )    
def forward(self, x):        
x = self.encoder(x)        
x = self.decoder(x)        
return x

这里我们定义了一个简单的4层网络作为编码器,中间使用ReLU激活函数,最后输出的维度是3维的,定义的解码器,输入三维的编码,输出一个 $28\times 28$ 的图像数据,特别要注意最后使用的激活函数是Tanh,这个激活函数能够将最后的输出转换到-1 $\thicksim$ 1之间,这是因为我们输入的图片已经变换到了-1 $\thicksim$ 1之间了,这里的输出必须和其对应。

训练过程也比较简单,我们使用最小均方误差来作为损失函数,比较生成的图片与原始图片的每个像素点的差异。

同时我们也可以将多层感知器换成卷积神经网络,这样对图片的特征提取有着更好的效果。

class autoencoder(nn.Module):    
def __init__(self):        
super(autoencoder, self).__init__()        
self.encoder = nn.Sequential(            
nn.Conv2d(1, 16, 3, stride=3, padding=1),  # b, 16, 10, 10            
nn.ReLU(True),            
nn.MaxPool2d(2, stride=2),  # b, 16, 5, 5           
 nn.Conv2d(16, 8,3, stride=2, padding=1),  # b, 8, 3, 3            
nn.ReLU(True),            
nn.MaxPool2d(2, stride=1)  # b, 8, 2, 2       
 )        
self.decoder = nn.Sequential(            
nn.ConvTranspose2d(8, 16, 3, stride=2),  # b, 16, 5, 5            
nn.ReLU(True),            
nn.ConvTranspose2d(16, 8, 5, stride=3, padding=1),  # b, 8, 15, 15            
nn.ReLU(True),            
nn.ConvTranspose2d(8, 1, 2, stride=2, padding=1),  # b, 1, 28, 28            
nn.Tanh()       
 )    
def forward(self, x):        
x = self.encoder(x)        
x = self.decoder(x)       
 return x

这里使用了nn.ConvTranspose2d(),这可以看作是卷积的反操作,可以在某种意义上看作是反卷积。

我们使用卷积网络得到的最后生成的图片效果会更好,具体的图片效果我就不再这里放了,可以在我们的github上看到图片的展示。

变分自动编码器(Variational Autoencoder)

变分编码器是自动编码器的升级版本,其结构跟自动编码器是类似的,也由编码器和解码器构成。

回忆一下我们在自动编码器中所做的事,我们需要输入一张图片,然后将一张图片编码之后得到一个隐含向量,这比我们随机取一个随机噪声更好,因为这包含着原图片的信息,然后我们隐含向量解码得到与原图片对应的照片。

但是这样我们其实并不能任意生成图片,因为我们没有办法自己去构造隐藏向量,我们需要通过一张图片输入编码我们才知道得到的隐含向量是什么,这时我们就可以通过变分自动编码器来解决这个问题。

其实原理特别简单,只需要在编码过程给它增加一些限制,迫使其生成的隐含向量能够粗略的遵循一个标准正态分布,这就是其与一般的自动编码器最大的不同。

这样我们生成一张新图片就很简单了,我们只需要给它一个标准正态分布的随机隐含向量,这样通过解码器就能够生成我们想要的图片,而不需要给它一张原始图片先编码。

在实际情况中,我们需要在模型的准确率上与隐含向量服从标准正态分布之间做一个权衡,所谓模型的准确率就是指解码器生成的图片与原图片的相似程度。我们可以让网络自己来做这个决定,非常简单,我们只需要将这两者都做一个loss,然后在将他们求和作为总的loss,这样网络就能够自己选择如何才能够使得这个总的loss下降。另外我们要衡量两种分布的相似程度,如何看过之前一片GAN的数学推导,你就知道会有一个东西叫KL divergence来衡量两种分布的相似程度,这里我们就是用KL divergence来表示隐含向量与标准正态分布之间差异的loss,另外一个loss仍然使用生成图片与原图片的均方误差来表示。

我们可以给出KL divergence 的公式

$$
D_{KL} (P || Q) = \int_{-\infty}^{\infty} p(x) \log \frac{p(x)}{q(x)} dx
$$

这里变分编码器使用了一个技巧“重新参数化”来解决KL divergence的计算问题。

这时不再是每次产生一个隐含向量,而是生成两个向量,一个表示均值,一个表示标准差,然后通过这两个统计量来合成隐含向量,这也非常简单,用一个标准正态分布先乘上标准差再加上均值就行了,这里我们默认编码之后的隐含向量是服从一个正态分布的。这个时候我们是想让均值尽可能接近0,标准差尽可能接近1。而论文里面有详细的推导如何得到这个loss的计算公式,有兴趣的同学可以去看看推导

下面是python的实现

reconstruction_function = nn.BCELoss(size_average=False)  # mse loss
def loss_function(recon_x, x, mu, logvar):    
"""    
recon_x: generating images    
x: origin images    
mu: latent mean    
logvar: latent log variance    
"""   
 BCE = reconstruction_function(recon_x, x)    
# loss = 0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)    
KLD_element = mu.pow(2).add_(logvar.exp()).mul_(-1).add_(1).add_(logvar)    
KLD = torch.sum(KLD_element).mul_(-0.5)    
# KL divergence    
return BCE + KLD

另外变分编码器除了可以让我们随机生成隐含变量,还能够提高网络的泛化能力。

最后是VAE的代码实现

class VAE(nn.Module): def __init__(self):

super(VAE, self).__init__()        
self.fc1 = nn.Linear(784, 400)        
self.fc21 = nn.Linear(400, 20)        
self.fc22 = nn.Linear(400, 20)        
self.fc3 = nn.Linear(20, 400)        
self.fc4 = nn.Linear(400, 784)   
 def encode(self, x): 
h1 = F.relu(self.fc1(x))        
return self.fc21(h1), self.fc22(h1)    
def reparametrize(self, mu, logvar): 
std = logvar.mul(0.5).exp_()       
 if torch.cuda.is_available():            
eps = torch.cuda.FloatTensor(std.size()).normal_()        
else:           
 eps = torch.FloatTensor(std.size()).normal_()        
eps = Variable(eps)        
return eps.mul(std).add_(mu)    
def decode(self, z): 
h3 = F.relu(self.fc3(z))        
return F.sigmoid(self.fc4(h3))   
 def forward(self, x): 
mu, logvar = self.encode(x)       
 z = self.reparametrize(mu, logvar)       
 return self.decode(z), mu, logvar

VAE的结果比普通的自动编码器要好很多,下面是结果

VAE的缺点也很明显,他是直接计算生成图片和原始图片的均方误差而不是像GAN那样去对抗来学习,这就使得生成的图片会有点模糊。现在已经有一些工作是将VAE和GAN结合起来,使用VAE的结构,但是使用对抗网络来进行训练,具体可以参考一下这篇论文(https://arxiv.org/pdf/1512.09300.pdf)。

参考内容: kvfrans blog(http://kvfrans.com/variational-autoencoders-explained/)

原文发布于微信公众号 - 人工智能LeadAI(atleadai)

原文发表时间:2017-10-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏新智元

Bengio 最新深度学习论文:使用深度神经网络避免难解性

【新智元导读】训练基于能量的概率模型面临着难解的加和问题(intractable sums),Yoshua Bengio 和学生 Taesup Kim 只使用深...

3004
来自专栏CDA数据分析师

R语言中的情感分析与机器学习

利用机器学习可以很方便的做情感分析。本篇文章将介绍在R语言中如何利用机器学习方法来做情感分析。在R语言中,由Timothy P.Jurka开发的情感分析以及更一...

1769
来自专栏张俊红

EM算法

总第82篇 01|概念及原理: EM算法是一种迭代算法,用于含有隐变量的概率模型参数的极大似然估计,或极大后验概率估计。EM算法的每次迭代分两步完成:E步,求期...

2466
来自专栏专知

【干货】一文读懂什么是变分自编码器

【导读】本文是工程师Irhum Shafkat的一篇博文,主要梳理了变分自编码器的相关知识。我们知道,变分自编码器是一种生成模型,在文本生成、图像风格迁移等诸多...

5799
来自专栏专知

双人协作游戏带你理解变分自编码器-Part1

1866
来自专栏应兆康的专栏

机器学习概念:梯度下降

机器学习中大部分都是优化问题,大多数的优化问题都可以使用梯度下降/上升法处理,所以,搞清楚梯度算法就非常重要

5079
来自专栏机器学习算法原理与实践

MCMC(一)蒙特卡罗方法

    作为一种随机采样方法,马尔科夫链蒙特卡罗(Markov Chain Monte Carlo,以下简称MCMC)在机器学习,深度学习以及自然语言处理等领域...

31418
来自专栏AI科技评论

开发 | 深度学习自动编码器还能用于数据生成?这篇文章告诉你答案

AI 科技评论按:本文作者廖星宇,原载于作者知乎专栏,经授权发布。 什么是自动编码器 自动编码器(AutoEncoder)最开始作为一种数据的压缩方法,其特...

2074
来自专栏Aloys的开发之路

判断一个数是不是2的幂

我们经常会遇到这样一个问题,就是判断某个数据是否为2的n次方(1,2,4,8,16...)。例如如果用户输入的不是2^n,则要求用户重新输入。为了说明这种判断算...

1678
来自专栏自然语言处理

揭秘马尔可夫模型神秘面纱1

安德烈·马尔可夫,俄罗斯人,物理-数学博士,圣彼得堡科学院院士,彼得堡数学学派的代表人物,以数论和概率论方面的工作著称,他的主要著作有《概率演算》等。1878年...

562

扫描关注云+社区