Autoencoder自动编码器的发展
算是一个综述性质的东西。
动机是实验室有缺陷检测的项目,找了几篇最新的文章,其中用到了自动编码器,有项目的原因也有自己好奇的原因,一路追溯到1985年的文章,再加上我能找到的很多博客都没挖到这里,就有了写这篇博客的想法。
文章都在参考文献里,有小飞机可以直达,没有的话不能保证。另外,这些文章只是我个人觉得比较有代表性的而已,中间还有很多研究没有放进来,这十几篇文章仅供参考。
0、玻尔兹曼机中的测试实验——编码问题(1985)
0.1、玻尔兹曼机
玻尔兹曼机[13]最早由Hinton与Sejnowski在1985年发明,大概长这样:
它是最早能够学习内部表达,并能表达和(给定充足的时间)解决复杂的组合优化问题的神经网络。但是,没有特定限制连接方式的玻尔兹曼机目前为止并未被证明对机器学习的实际问题有什么用。所以它目前只在理论上显得有趣。
0.2、受限的玻尔兹曼机
受限的玻尔兹曼机(restricted Boltzmann machine, RBM)[14]在1986年由Paul Smolensky发明,最早被命名为簧风琴(Harmonium),直到Hinton及其合作者在2000年代中叶发明快速学习算法后,受限玻尔兹曼机才变得知名,它大概长这样:
与原始的玻尔兹曼机相比,区别在于这是一个对称的二分图,可见层与隐藏层可以相互转换,它们共享W权重,但是拥有属于自己的偏置b与c,相比一般玻尔兹曼机,这样的限定使得更高效的训练算法成为可能。
0.3、编码问题——自动编码器雏形
为了解决玻尔兹曼机学习困难的问题,还是Hinton与Sejnowski,两人又发明了一种针对玻尔兹曼机的学习算法[1],从文章的名字来看就很明显:《A Learning Algorithm for Boltzmann Machines*》。
就是在这篇文章里,为了测试这个针对玻尔兹曼机的学习算法,自动编码器的雏形被提出来了,在这篇文章的第4章,它被称为The encoder problem:
左边是4-2-4编码器,右边是4-3-4编码器。问题被设计为,从上到下分别被称为V1层,H层,V2层,V1、V2层都与H层连接,但是它们自己相互之间没有连接,这时,希望两个可见层V1与V2能够沟通彼此的状态,每个阴影盒子被称为一个单元,单元中的黑色方块为负权重,白色方块为正权重,而这个问题的学习是从所有权重均为0的情况下开始学习的,没有任何先验知识。这两幅图都是经过一定数量的循环学习后得到的。
我看了很久也觉得没有完全参透这几个图,下面是我的一些观察。
直观来看,如左图下方的V2可见层,从左到右4个单元中,中间权重的颜色分别是(白,白)、(黑,黑)、(白,黑)、(黑,白),可以把它们视为不同的状态,接着它们被压缩成中间的H层,再解压到V1层,最后可以看到,V1与V2对应的单元中,对应位置的权重基本是相同的。
同时,以左图H层中左边的单元为例,顶部与底部的4个权重可以看成(1,0,1,0),中间的两个权重可以看成(0.3,0),这就很有我们所知道的自编码器的味道了。
这个算法学习的是黑白方块的权重,每个阴影盒子本身即可视为一个网络,但是这里又把每个阴影盒子作为一个状态,组成了一个更大的网络,这小网络叠成大网络是怎么操作的,实在是没看出来。
如果要把玻尔兹曼机系列参透,完全可以开另一个专题,由于这里的主题是自编码器,而且限于限于能力与精力,这个问题我就看到这里了。
所以,自动编码器的雏形,是在一个玻尔兹曼机的测试实验中产生的。那为什么测试玻尔兹曼机会用到这样的结构呢?让我们再看一眼这个4-2-4编码器,如果我们把它在隐藏层上下折叠起来,是不是就变成了上面的受限的玻尔兹曼机(RBM),又因为RBM是一个特殊的玻尔兹曼机,所以就有了玻尔兹曼机(对称、二分)→受限的玻尔兹曼机(较小的层展开)→自动编码器雏形。
作者应该是想表达:如果这个算法能够将编码问题学习出比较好的结果,那么它也能学习玻尔兹曼机。
1、反向传播中的仿真——单层自动编码器(1986)
还是1986年,还是Hinton,在这一年,发表有两篇文章。
一篇是《Learning representations by back-propagating errors(1986a)》[2],发表在《Nature》上,至今被引用了17908次。
另一篇是《Learning internal representations by back propagation(1986b)》[3],发表在一本叫《Neurocomputing: foundations of research》的书上,至今被引用了24602次。
第一篇描述了反向传播算法在神经网络中的应用,短短4页开启了一个时代。
第二篇文章相当于第一篇文章的一个补充,它旨在证明一件事:反向传播算法可以让神经网络发现自身数据的内部表示。
在这里要讲的是第二篇文章[3]——利用反向传播学习内部表征。
在这篇文章里,为了证明反向传播算法可以让神经网络发现自身数据的内部表示,Hinton他们做了好几个问题的仿真,比如异或门问题,对称问题,否定问题等,我们的编码问题也在其中:
这个模型是从第0节提到的那篇文章中提炼出来的——一组正交输入通过一个更小的隐藏层映射到另外一组正交输出。而输入等于输出,仅仅是这里的一个设定,为了看看利用反向传播训练稳定后,隐藏层会表现出一个怎样的情况,是否能表达其内部表征。
经过学习得到的是右图的Table5,这表示这个输出等于输入的系统是利用了中间值(intermediate values)来处理这个问题的。原文中还有更多的论述,有兴趣的小伙伴可以读一读原文。在我看来,它出现在这里的目的仅仅是为了给论点——反向传播算法能够学习内部表征——提供论据而已:你们看,对这个编码问题,我们学习出了这样有趣的内部结构,巴拉巴拉(一通分析),所以,我们的论点是成立的。(像这样的仿真在文章里还有7个。)
尽管在这个时候,编码问题仍然只是一个测试,还没有实际的应用,但是最早提出这样简单明确的“大-小-大”的编码-解码网络结构,加上“输出等于输入”这个设定,“自动编码器”算是真正出生了。
2、利用神经网络进行数据降维——深度自动编码器(2006)
时间来到2006年,还是Hinton,这次是和他的一位学生Salakhutdinov,发表了一篇《reduce the dimensionality of data with neural networks》[4]——神经网络的数据降维,在这里用到的网络结构正是深度自编码器。
这篇文章主要做了两件事情:
1、从单层自编码器变成深度自编码器,做到了有效的数据降维,功能类似于PCA,但是比PCA灵活,不仅能表征线性变换,也能表征非线性变换。
2、从1986年到2006年,虽然有反向传播算法,但是这段时间里对深度网络的训练仍然存在两个问题,一个是梯度消失,另一个是当时的权重W都是随机初始化的——这是一个坏主意,给大了容易陷入局部极值,给小了训练困难(慢)。这篇文章提出了“无监督预训练+有监督微调”的方法,先把权重W预训练到一个比较好的数值上,使梯度下降能良好地工作,再整体微调,解决了W初始化的问题,同时也初步解决了梯度消失的问题。
上图就是这篇文章的关键步骤展示:
Pretrainning(预训练):
无监督预训练用到的正是上面提到的受限制的玻尔兹曼机RBM(见0.2,图就不再贴了)。上图左起第一列的pretraining,最下面一组图表示,把一张人脸图(假设是100*100,展开成10000维的向量)压缩到2000维的向量,权重是w1,这时候可以把人脸的10000维向量看成0.2图中的V层(可见层),把2000维视为H层(隐藏层),再注意到w1旁边的箭头是双向的,即可见层与隐藏层是可以相互转换的,训练在这两层间不断进行。
RBM这里的训练目标是最小化它的能量:
bi、bj是偏置,vi、hj是可见层、隐藏层的向量,wij是它们共同的权重。
RBM是一个能量模型也是一个概率模型,最小化这个函数其中也还有很多细节,其中的门道在这里不细讲,直接讲一下结论:系统越有序或者概率分布越集中,系统的能量越小。反之,系统越无序或者概率分布越趋于均匀分布,则系统的能量越大。
能量函数的最小值,对应于系统的最稳定状态,则此时的2000维特征向量能最稳定地表达这10000维像素向量,故此时的w1是一个比较良好的值(而为什么只是“良好”,因为这里连带偏置bi、bj也训练出来了,我们却只需要权重wij,而且这只是深度网络中的其中一层)。
当2000维特征训练好后,从2000维到1000维,从1000维到500维,从500维到30维,同理依次训练出w2、w3、w4,这样,深度自编码器编码部分的每层都有了较好的初始权重(解码部分的初始权重为其转置)。
Unrolling(展开):
在上图的第二列,很明显,这里只是把预训练得到的初始权重放在各层之间,搭起了一个编码-解码的网络,注意到这里encoder与decoder的虚线框是不对齐的,因为这里只取了预训练的权重,没有取偏置,所以最后一步就是微调偏置。
Fine-tuning(微调):
最后,全局微调阶段通过确定性实值(即原图展开的向量)替换随机活动,并通过整个自动编码器使用反向传播来微调权重以进行最佳重建。
到这里,我忍不住吐槽一下,从1985年为了找到内部表征的玻尔兹曼机,到1986年反向传播算法解决了非线性分类与学习的问题(但是也出现了梯度消失的问题),到2006年“无监督预训练+有监督微调”初步解决梯度消失的问题,自动编码器发展到这里,看起来就像是深度学习发展中的副产物。(ps:另外,2011年relu函数基本解决了梯度消失问题,12年后基本就放弃了“预训练+微调”的方法,再到15年的残差网络,网络已经变得很深,然而后续也发现,深度网络训练不好不完全是梯度消失的问题。To be continue…)
但是从06年的这个版本之后,自动编码器有了种自立门户的感觉。下文是几种编码器的发展以及它与其他一些东西的结合,有了自己的应用,而且,与其说它是一种结构,它更倾向于是一种思想。
3、去噪自编码器(2008)
去噪自编码器最早是在2008年由Pascal Vincent(终于不是Hinton了)的团队提出:《Extracting and Composing Robust Features with Denoising Autoencoders》[5],这时候还是一个单层的去噪编码器,还是这个团队的同一拨人,2010年发表了深度去噪编码器:《Stacked Denoising Autoencoders: Learning Useful Representations in a Deep Network with a Local Denoising Criterion》[6]。两篇文章对去噪编码器本身的论述基本是一样的,只是10年的文章做成了深度的,而且增加了很多介绍、证明、实验。
去噪自编码器:顾名思义,在训练时加入噪声,在噪声中重建。
去噪自编码器的观点:我们的目的不是去噪本身,而是通过去噪来建立一个训练准则学习如何找出有用的特征,噪声中可以重建出更鲁棒更稳定的结果。
下面介绍深层去噪自编码器是怎么堆叠起来的。
单层:
x是输入向量(待编码向量),z是输出向量(解码向量),y是压缩向量, x ^ \hat{x} x^ 是受污染的向量(维数与x相同)。
qD是污染数据的过程,fθ是编码过程,gθ’是解码码过程,LH(x,z)是损失函数。
常用污染方式:二项式噪声(翻转黑白)、高斯噪声等。
将x污染(大概污染30%)成 x ^ \hat{x} x^,把 x ^ \hat{x} x^编码成y,再把y解码成z,损失函数为未受污染的初始输入x与解码输出z之间的误差。
多层:
左边第一层使用上图单层的方式训练完毕后,其输出z作为下一层的输入,在训练的时候再污染输入,得到输出,层层训练(每一层都污染输入)。
整体:
直观的例子:
4、稀疏自编码器(2011)
《Sparse autoencoder》[7],Ng给CS294A写的讲义(是熟悉的味道),比较令我惊讶的是,这份讲义居然有500多次的引用(Ng:妹想到吧)。
稀疏编码器的观点:稀疏编码器可以学习出比人工选择更好的特征,稀疏“过完备”也是一种压缩方式。
稀疏“过完备”:常规来说,编码器隐藏层中神经元节点的数量小于可见层,但是在这个稀疏编码器里,隐藏层的神经元节点是“过完备”的,它表面上的数量可以超过可见层,但是,在稀疏操作下,实际被激活的极少,这可以视为一种压缩。
借用这份讲义中的图,这是一个自动编码器(在稀疏编码器中,你可以认为LayerL2层有100个神经元节点,而不止图中的3个):
好,现在这里有一个稀疏自编码器的损失函数,读懂了这个损失函数,我们就知道稀疏自编码器在干什么了:
加号前后的两个式子我在下面分别称之为J(W,b)与KL散度,这个KL散度在这里被称为稀疏惩罚项,β用来控制稀疏惩罚的力度。
首先,J(W,b)展开的形式为:
这里,前半项为一个常见的平均平方和误差项,后半项是一个减小权重大小的正则项(防止过拟合),其中λ是一个权重衰减参数,用来控制权重惩罚的力度,而后者的三个连加,表示将这个网络中出现的每一个权重项,自平方再累加。
什么意思呢:我希望这个编码器的输出和输入尽量相似(最好相等),同时我还希望网络中的权重尽可能小。
这里看起来还是一个正常的自编码器,那稀疏是怎么做到的呢,我们来看Jsparse(W,b)的后一项, ρ ^ \hat{\rho} ρ^ j j j与 ρ \rho ρ的KL散度,它展开来是这样的:
其中:
s2表示隐藏层中神经元的个数,ρ是一个设定的稀疏参数(假设ρ=0.05),m是样本个数,aj(2)(x(i))表示样本x(i)的隐藏层LayerL2中的节点j是否被激活(激活函数为sigmiod函数)。
故 ρ ^ \hat{\rho} ρ^ j j j表示:在样本集合中,隐藏层的节点j被激活的样本数,占总样本的多少,即节点j在训练数据集上的平均激活率。
KL散度在这里则是在衡量 ρ ^ \hat{\rho} ρ^ j j j与 ρ \rho ρ的相似程度 ρ \rho ρ已经假设为0.05, ρ ^ \hat{\rho} ρ^ j j j与0.05越接近,其惩罚越小:
这是 ρ \rho ρ为0.2时的情况,可以看到,只有当 ρ ^ \hat{\rho} ρ^ j j j也为0.2, ρ ^ \hat{\rho} ρ^ j j j与 ρ \rho ρ的KL散度值才为0(最小),在其他情况下都会出现正值,加在损失函数里即变成了惩罚。
那么,这KL散度在这里是什么意思呢:我希望隐藏层的每个节点的激活率都与我设定的稀疏参数 ρ \rho ρ相同(如这里的0.05),这样,隐藏层里实际工作的节点就很少(100个里面只有5个被激活了),从而实现了稀疏。
下面是稀疏自编码器的技术总结。
可以先假设这个自编码器的隐藏层中,神经元节点数比可见层要多得多,然后进行训练的时候保证三点:
1、输出尽量等于输入
2、所有神经元的权重都尽可能地小
3、隐藏层中被激活的神经元的比率尽量跟设定的稀疏参数(非常小)相等
这里其实有个启发,上面的第一点是常规的均方差误差,第二和第三点都是特殊的约束,只不过在这里,加完相应的约束之后,它是一个稀疏自编码器。也有人把这里的约束改成了其他的东西,于是有了不同的功能,然后发了文章,比如收缩自编码器等,在这里我觉得它们是同一个类型的东西,就不再看这类相似的文章了。
5、卷积自编码器(2011)
《Stacked Convolutional Auto-Encoders for Hierarchical Feature Extraction》[8],我能找到的最早的卷积自编码器,作者是Jonathan Masci,至今有800多次的引用。在今天看来,这篇文章有些索然无味,但是结合当时的情况来看,还是比较合理了。
文章的贡献:我们用卷积自编码器来做特征提取,并且,我们不需要使用任何规则项(约束)就能做到稀疏,因为我们使用了maxpooling(当时,我的脑子里缓缓打出了一个问号)。
卷积(编码):
反卷积(解码):
损失函数(均方差函数,2n的n是为了求导消去):
损失函数对权重的偏导(反向传播更新权重):
这个偏导里面的δh跟δy,文章里面只有一句:δh and δy are the deltas of the hidden states and the reconstruction, respectively.
我就不参悟了。
上面这些式子都是文章里的内容,咋一看,就是一个普普通通的卷积自编码器,但是结合当时的历史情况,这篇文章还是有几个有意思的地方。
首先,2012年Hinton(Hello?)课题组才首次参加ImageNet图像识别比赛,其通过构建的CNN网络AlexNet一举夺得冠军,且碾压第二名(SVM方法)的分类性能,自此,CNN才吸引到了众多研究者的注意。因此,卷积自编码器发表的2011年,卷积网络本身就还不太受关注,而且自编码器也才刚开始发展,作者在这种情形下能想到把两者结合起来,也是比较厉害了。
第二,在上面的稀疏自编码器有提到,当时流行的是在损失函数里加正则项来进行各种约束,而作者可能就是看到了卷积一般都自带maxpooling,正好有稀疏的功能,而且不用大费周章地去设计、调试正则项,所以才有了这篇文章(个人猜测)。
第三,2011年还没有relu激活函数,所以还是“预训练-微调”的训练方法,而这里预训练用的不是06年的那个RBM预训练方法,而是10年的去噪编码器——加入噪声后预训练每一层的权重,上一层的输出是下一层的输入。
作者在2011年把上述要素结合起来,是一篇不错的文章了。
回到现在,不管是卷积自编码器还是去噪自编码器,在各种强大的框架之下,都是小几十行就能实现的东西,而且不用像以前那样预训练,只需要框架搭好数据一填一把梭跑起来等结果。它们现在都是很基础很简单的东西了,在很多博文里,它们经常作为baseline一样的东西来介绍自编码器。
总之吧,在今天看来,这篇文章确实有些乏善可陈,卷积(编码)-反卷积(解码),over。
6、变分自编码器(2013)
变分自编码器(Variational auto-encoder,VAE)是一类重要的生成模型(generative model),文章《Auto-Encoding Variational Bayes》[9]于2013年底(2013年12月20)由Diederik P.Kingma和Max Welling发表。
变分自编码器我看了挺久,因为本身比较难懂,网上的很多中文资料也显得比较混乱,最后我是在《Tutorial on Variational Autoencoders》[15](2016年Carl Doersch写了一篇VAEs的tutorial,对VAEs做了更详细的介绍,更好理解)这篇文章里大概地把推理过程理顺的。
下面我也要为混乱的变分自编码器解读教程添砖加瓦了。
6.1、模型
变分自编码器的提出动机是:我们希望学习出一个模型,能产生训练样本中没有,但与训练集相似的数据。
变分自编码器的最终实现的模型比较简单,借用Tutorial里的一张图就能理解:
先看左图, X X X是数据集,首先通过编码器学习出数据的均值 μ ( X ) \mu(X) μ(X)与方差 Σ ( X ) \Sigma(X) Σ(X),同时希望这个 μ ( X ) \mu(X) μ(X)与 Σ ( X ) \Sigma(X) Σ(X)构成的正态分布与标准正态分布尽可能接近(用KL散度衡量),学习出这个正态分布之后,再从这个分布里面取样 z z z(注意,这个 z z z在 X X X中没有,但它又是符合 X X X的分布的),再把 z z z放进解码器中解码,同时希望解码出来的样本与原始样本 X X X尽可能相同。
这样, z z z相当于从 X X X的数据分布中采样而来,再经解码器还原,就做到了“产生训练样本中没有,但与训练集相似的数据”。
右图是“重参数技巧”,因为“采样”这个动作本身不连续不可导(这个数据你取还是不取?那一个呢?),但是采样的结果是连续可导的,因此这里从标准正态分布 N ( 0 , 1 ) N(0,1) N(0,1)中采样出 ϵ \epsilon ϵ,这个 ϵ \epsilon ϵ就是标准正态分布中的一个值,如0.9856,再令 z = μ ( X ) + Σ 1 / 2 ( X ) ∗ ϵ z=\mu(X)+\Sigma^{1/2}(X)*\epsilon z=μ(X)+Σ1/2(X)∗ϵ, z z z在 N ( μ ( X ) , Σ ( X ) ) N(\mu(X),\Sigma(X)) N(μ(X),Σ(X))中的位置相当于 ϵ \epsilon ϵ在 N ( 0 , 1 ) N(0,1) N(0,1)中的位置,这就巧妙地把采样不连续不可导的问题解决了,其他与左图相同。
好了,下面是推理部分,我试着讲一讲,有兴趣的同学试着看一看。
6.2、推理过程
目标
首先我们有一批数据 { X 1 , X 2 , X 3 , . . . , X n } \{X1,X2,X3,...,Xn\} { X1,X2,X3,...,Xn},整体用 X X X来表示,如果我们能从 X X X中得到它的分布 P ( X ) P(X) P(X),那么我们就能得到包括 { X 1 , X 2 , X 3 , . . . , X n } \{X1,X2,X3,...,Xn\} { X1,X2,X3,...,Xn}以外的所有 X X X,比如我有标准正态分布的100个数,我算出了它的分布,那么我可以得到这100个数以外的所有数值。
但是实际上这很难做到,因为这需要极大的样本量,同时我们不知道这批数据服从什么分布,而且这批数据中可能有离群点,如手写数字图片里有个样本一半是0另一半是8。因此预先定义好要生成什么东西是很有帮助的,如在手写数字识别里预先定义只生成0到9的数字。
这样的预定义/假设/先验知识,可以称之为隐变量。
变分自编码器是想生成训练样本中没有,但与训练集相似的数据,现在我们假设所有生成的 X X X,都能由隐变量 z z z映射得到: X = f ( z ; θ ) X=f(z;\theta) X=f(z;θ)。然后用一个分布 P ( X ∣ z ; θ ) P(X|z;\theta) P(X∣z;θ)代替 X = f ( z ; θ ) X=f(z;\theta) X=f(z;θ),前者表示:在某分布下(在这里, P P P分布与 f f f函数是同一个东西),参数 θ \theta θ固定的情况下, z z z生成 X X X的概率。然后写出我们的生成过程:
P ( X ) = ∫ P ( X ∣ z ; θ ) P ( z ) d z P(X) = \int P(X|z;\theta) P(z)dz P(X)=∫P(X∣z;θ)P(z)dz
需要提示一下的是,看一眼上面变分自编码器的模型图,Decoder下写着一个P,Decoder的作用正好是把 z z z还原成 X X X,所以,在这里 P ( X ∣ z ) P(X|z) P(X∣z)可以理解成由 z z z生成 X X X的解码器。
其中,解码器的分布选择为正态分布: P ( X ∣ z ; θ ) = N ( X ∣ f ( z ; θ ) , σ 2 ∗ I ) P(X|z;\theta)=N(X|f(z;\theta),\sigma^2*I) P(X∣z;θ)=N(X∣f(z;θ),σ2∗I), f ( z ; θ ) f(z;\theta) f(z;θ)是均值, σ 2 ∗ I \sigma^2*I σ2</