变分自编码器(VAE)的代码理解

来自我的个人网站: http://wangbch.com

variational_autoencoder.py

变分自编码器(VAE)
参考自:http://blog.csdn.net/jackytintin/article/details/53641885
- 自编码器(autoencoder) 是一种非监督式学习的神经网络模型,采用原始数据作为输入和输出,含有一个数量小于输入输出的隐藏层。
- 从输入到隐藏层,这一段神经元数量下降,被称为”encoder”,也可以称为模式识别模型或判别模型;而从隐藏层到输出,这一段神经元数量上升,被称为”decoder”,也称生成模型。因而编码器在一定程度上是类似于GAN的
- 由于隐藏层数量小于输入,所以会对数据进行压缩,之后输出神经元数量大于隐藏层,压缩后的隐藏层相互组合重现原始输出
- 它尝试使用一个函数,将输入逼近输出,经过训练之后,撤去输入层,仅凭隐藏层也可以实现重现输出
- 这样隐藏层就需要做到提取主要成分的任务。这样的功能实现了隐藏层对于数据特征的提取,类似于主成分分析(PCA)
- 稀疏自编码器(sparse autoencoder) 中的隐藏层数量会多于输入输出,此时需要抑制神经元。激活函数会使神经元处于抑制状态,这种抑制是稀疏性限制。比如采用sigmoid作为激活函数,就引入一个接近于0的稀疏性参数,这样会使得神经元的输出接近于0,从而在经过sigmoid函数之后会被抑制
- 需要注意的是,在编码器中使用的维度变换,可以为任意结构如MLP, CNN, RNN等
- 变分自编码器(VAE) 常常和GAN一同被提及,它们都属于生成模型,采用少量样本即可实现训练。步骤如下(结合后面的模型结构进行分析):
1. encoder或判别器将输入的n维数据转换为2个m维数据,其中第一个视作**m个高斯分布的均值,第二个视作**m个高斯分布方差的对数
2. 根据上述2个m维的数据,生成m维的服从高斯分布的随机数
3. 高斯分布先生成(None, m)的正态分布矩阵a,然后out = mean + exp(log_var/2) * a 输出encoder层的结果,注意是先生成N(0,1)再乘,而不是直接生成N(mean,log_var),这样只涉及线性操作
4. 模型结构如下:

layer shape notes
input (None, n) 输入
encoder (None, ?) 判别器,可采用MLP,CNN,RNN等
mean (None, m) 均值,连接到hidden层
log_var (None, m) 方差对数,和mean层并列,直接连接到hidden层
gaussian_out (None, m) 生成高斯分布随机数
decoder (None, ?) 生成器
output (None, n) 恢复原始输出

- 变分自编码器的意义以及和GAN的简单对比: 变分自编码器将decoder训练过后,根据正态分布的随机数,即可生成原始图像。而训练出的encoder,也可以作为数据降维,高维数据可视化的手段。
- 与GAN的差异在于,VAE采用原始数据输入和输出,目的是训练encoder和decoder的能力,使其重现输入
- 而GAN的discriminator(和encoder类似)并不是抽取高维特征,而是直接辨别真假结果,但是训练时也会逐渐习得特征。GAN的generator(和decoder)类似,是从随机数中生成数据用于判别,并逐渐训练使其接近原始输入
- VAE和GAN是非常相似的,GAN的G-D模型,实际上可以理解为VAE的decoder,而GAN的D模型,也可理解为VAE的encoder
- VAE的输入经过encoder之后,会提取均值和对数方差,这就是对于不同特征的样本,生成了不同特征的随机数,之后用随机数做生成 ,就不依赖于特定输入输出,GAN也是采用随机数作为输入生成,但是GAN的随机数的特征是固定的,VAE的随机数的特征(均值和方差),会保留与输入的相关性。


variational_autoencoder.py对VAE的实现

  1. encoder:输入(batch_size, original_dim);用Dense将其转化为intermediate_dim数量的中间层,激活函数采用ReLU;z_mean和z_log_var均为shape:(batch_size, latent_dim)的矩阵。这个例子中并没有采用model.add的方式进行,而是利用<layer>(<parameter>)(<last_layer>)的方式连接,因为z_mean和z_log_var相互是并联在中间层上的,用sequence()无法实现
  2. 之后创建采样函数,输入z_mean和z_log_var,return一个方差为z_log_var/2,均值为z_mean的shape为(batch_size, latent_dim)的随机数矩阵。
  3. decoder:输入采样过后得到的随机数矩阵,将其转化为shape=(batch_size, intermediate_dim)的中间层,再转化为与输入相同shape的输出层decoded。
  4. 建立完成模型之后,建立一个继承自Layer的类,重写layer的call方法(每次调用),其中输入为原始数据以及最终生成的数据。loss由两部分组成,第一部分是z_mean和z_log_var,另一部分是输入和最终输出(decoded)的binary cross-entropy,这个继承至layer的类,将以模型的输入和输出作为参数,使模型增添loss函数用以训练

在创建了完整的VAE模型并增添loss函数之后,将喂入手写数据集用于训练(注意这里只会喂入image,没有任何label,因为是非监督的);训练完成之后将输入和z_mean连在一起构成新的模型,喂入手写数据集进行可视化;之后将(latent_dim, )作为输入节点,最终生成的图片作为输出节点,构建decoder模型,喂入随机数生成图片
1. 构建完整训练模型:首先将输入节点,即原始数据x,和输出节点,即生成的结果,一起作为参数传递到4.的layer中,用以定义损失函数。之后创建训练模型,将原始输入和损失函数作为节点连接(此时损失函数中就包含了运算图的信息)vae = Model(x, y),之后创建优化函数,喂入手写数字数据集进行训练
2. 构建encoder模型:用encoder = Model(x, z_mean)连接输入和z_mean输出节点,之后喂入手写数据进行预测,作图
3. 构建generator模型:采用generator = Model(decoder_input, _x_decoder_mean)


variational_autoencoder_deconv.py

这个例子将上个例子的全连接隐藏层变为了卷积和反卷积层,也是采用<layer>(<parameter>)(<last_layer>)的方式构建完整VAE网络以及encoder和decoder,具体完整VAE模型结构如下

layer shape
input (None, 28, 28, 1)
conv2d (None, 28, 28, 1)
conv2d (None, 14, 14, 64)
conv2d (None, 14, 14, 64)
conv2d (None, 14, 14, 64)
flatten (None, 12544)
dense_out (None, 128)
以下z_mean和z_log_var是并列关系 直接连接到dense_out
z_mean (None, 2)
z_log_var (None, 2)
dense_decoder_in (None, 128)
dense (None, 12544)
reshape (None, 14, 14, 64)
deconv2d (None, 14, 14, 64)
deconv2d (None, 14, 14, 64)
deconv2d (None, 29, 29, 64)
conv2d (None, 28, 28, 1)
自建层 (None, 28, 28, 1)和(None, 28, 28, 1),求取loss

训练完成之后,用于特征提取的encoder的完整结构如下:

layer shape
input (None, 28, 28, 1)
conv2d (None, 28, 28, 1)
conv2d (None, 14, 14, 64)
conv2d (None, 14, 14, 64)
conv2d (None, 14, 14, 64)
flatten (None, 12544)
dense (None, 128)
dense (None, 2)

decoder的结构如下:

layer shape
input (None, 2)
dense (None, 128)
dense (None, 12544)
reshape (None, 14, 14, 64)
deconv2d (None, 14, 14, 64)
deconv2d (None, 14, 14, 64)
deconv2d (None, 29, 29, 64)
conv2d (None, 28, 28, 1)

完整的VAE模型相当于将encoder和decoder连接在一起,并增加loss函数用于训练

体会与心得

  • 神经网络中,每个参数都有自己的功能,虽然机器并不会知道这些功能是什么,但是人类创建的模型的逻辑结构,决定了某些参数最适合做什么功能,而机器为了去达到更小的损失,就会学着将这些参数按照人类的意志使用。比如自编码器,人类的意图是用一个同等的输入输出,加上一个更小的隐藏层,达到数据的特征提取压缩,以及解压和重现;但是在机器眼中,就只有较小的隐藏层,和同样大小的输入输出层。然而在训练过程中,为了降低损失,机器采用的最佳的方式,就是像人类希望地那样,将隐藏层作为特征提取,于是就学会了完成人类的意图
  • 在GAN中也是如此,真实数据代表1,虚假数据代表0,机器不懂这是为了区别真假,但是它会这样去做。之后尝试将随机数生成判别为1的图片,人类是为了生成以假乱真的图片,但是机器不了解这个目的,不过也是会按照人类的意志去做
  • 所以深度学习,就像是人类给机器构建了一个自然选择的环境,而机器就自我进化。但是与大自然不同,人类需要去制定一些规则,这些规则实际上是不必要的,但是为了更快收敛是需要的。比如,如果采用全连接而不是卷积处理图像数据,机器最终也会在权重上反映出来某些间隔数据点是具有相关性的,这些数据点也就是二维中间隔相近的点,但是为了快速收敛,人类用卷积给了机器一个思考方式
  • 另外一例是在LSTM中的“遗忘门”,没有人告诉机器这个东西是来控制遗忘的,但是这个值能够在某些情况下抑制特定的神经元,机器在逐渐优化的过程中,就会为了减少loss而逐渐意识到这个东西是用来干什么的。
  • 也就是说,人类构建的模型应当是十分优化的,基于目的的,机器能够通过减少loss来迎合这种人类的目的。不过有时候,当构建模型时,人类将模型放得很开,此时就会反而不理解到底机器做了什么,比如卷积网络,人类的目的就是提取特征,连接高阶特征,保留二维数据的空间关系,不像LSTM的遗忘门那样在设计时就有明确的功能,于是这样当机器训练学成之后,人类也都无法理解机器进行了怎样的提取

不过有时候我也这样想,当我们睁开眼睛,辨别物体的时候,是不是就像机器那样开始读取图像数据集,并训练成标签。而当我们闭上眼睛的时候,是不是就看到了那些训练好的卷积网络的隐藏层权重图像?而当我们做梦,是不是就相当于将训练好的标签经过decoder又返回变成图形?这样一想,突然感觉深度学习非常值得深入讨论,如何结合我们的大脑,构建强大的机器?
也许有一天,如果我们构建了一个和大脑完全相同的神经网络结构,加上传感器,也许就能够完完全全创造一个人类的复制品,但是,人类研究这样久之后得到的东西,自然界又是怎样发展而来,达到如此小的功耗的呢?

猜你喜欢

转载自blog.csdn.net/B_C_Wang/article/details/74908408