Deep Learning论文笔记

转自:http://blog.csdn.net/zouxy09/article/details/9982495    2013-08-15

 

(一)K-means特征学习

 

         自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。

 

本文的论文来自:

Learning Feature Representations with K-means, Adam Coates and Andrew Y. Ng. In Neural Networks: Tricks of the Trade, Reloaded, Springer LNCS, 2012

下面是自己对其中的一些知识点的理解:

 

《Learning Feature Representations with K-means》

         自从Deep Learning之风盛起之时到现在,江湖上诞生了很多都可以从无标签数据中学习到深度的分级的特征的算法。大部分情况,这些算法都涉及到一个多层网络,而训练和调整这个网络需要很多tricks。最近,我们发现K-means聚类算法也可以被作为一个非常快的训练方法。它的优点是快!容易实现!当然了,K-means也不是万能神丹,它也存在自身的局限性。在本文中,我们就关注K-means的方方面面。总结了最近的K-means算法的效果和介绍使用k-means来有效地学习图像的特征的一些技巧。

 

一、概述

非监督学习的一般流程是:先从一组无标签数据中学习特征,然后用学习到的特征提取函数去提取有标签数据特征,然后再进行分类器的训练和分类。之前说到,一般的非监督学习算法都存在很多hyper-parameters需要调整。而,最近我们发现对于上面同样的非监督学习流程中,用K-means聚类算法来实现特征学习,也可以达到非常好的效果,有时候还能达到state-of-the-art的效果。亮瞎了凡人之俗眼。

托“bag of features ”的福,K-means其实在特征学习领域也已经略有名气。今天我们就不要花时间迷失在其往日的光芒中了。在这里,我们只关注,如果要K-means算法在一个特征学习系统中发挥良好的性能需要考虑哪些因素。这里的特征学习系统和其他的Deep Learning算法一样:直接从原始的输入(像素灰度值)中学习并构建多层的分级的特征。另外,我们还分析了K-means算法与江湖中其他知名的特征学习算法的千丝万缕的联系(天下武功出少林,哈哈)。

经典的K-means聚类算法通过最小化数据点和最近邻中心的距离来寻找各个类中心。江湖中还有个别名,叫“矢量量化vector quantization”(这个在我的博客上也有提到)。我们可以把K-means当成是在构建一个字典D∊Rnxk,通过最小化重构误差,一个数据样本x(i) ∊Rn可以通过这个字典映射为一个k维的码矢量。所以K-means实际上就是寻找D的一个过程:

       这里,s(i)就是一个与输入x(i)对应的码矢量。D(j)是字典D的第j列。K-means毕生的目标就是寻找满足上面这些条件的一个字典D和每个样本x(i)对应的码矢量s(i)。我们一起来分析下这些条件。首先,给定字典D和码矢量s(i),我们需要能很好的重构原始的输入x(i)。数学的表达是最小化x(i)和它的重构D s(i)。这个目标函数的优化需要满足两个约束。首先,|| s(i)||0<=1,意味着每个码矢量s(i)被约束为最多只有一个非零元素。所以我们寻找一个x(i)对应的新的表达,这个新的表达不仅需要更好的保留x(i)的信息,还需要尽可能的简单。第二个约束要求字典的每列都是单位长度,防止字典中的元素或者特征变得任意大或者任意小。否则,我们就可以随意的放缩D(j)和对应的码矢量,这样一点用都木有。

这个算法从精神层面与其他学习有效编码的算法很相似,例如sparse coding:

Sparse coding也是优化同样类型的重构。但对于编码复杂度的约束是通过在代价函数中增加一个惩罚项λ|| s(i)||1,以限制s(i)是稀疏的。这个约束和K-means的差不多,但它允许多于一个非零值。在保证s(i)简单的基础上,可以更准确的描述x(i)

虽然Sparse coding比K-means性能要好,但是Sparse coding需要对每个s(i)重复的求解一个凸优化问题,当拓展到大规模数据的时候,这个优化问题是非常昂贵的。但对于K-means来说,对s(i)的优化求解就简单很多了:

这个求解是很快的,而且给定s求解D也很容易,所以我们可以通过交互的优化D和s(可以了解下我博客上的EM算法的博文)来快速的训练一个非常大的字典。另外,K-means还有一个让人青睐的地方是,它只有一个参数需要调整,也就是要聚类的中心的个数k。

 

二、数据、预处理和初始化

这里我们采用的是包含很多小的图像patches的数据集,每个patch是16×16的灰度图,对每个patch样本我们将其拉成一个256维的列向量。这些patches可以在无标签图像中随机的裁剪得到。为了建立一个“完备complete”的字典(至少有256个类中心的字典),我们需要保证有足够的用以训练的patches,这样每个聚类才会包含一定合理数量的输入样本。对于16×16的灰度patch来说,样本数m=100,000个是足够的。实际上,训练一个k-means字典比其他的算法(例如sparse coding)需要的训练样本个数要多,因为在k-means中,一个样本数据只会贡献于一个聚类的中心,换句话说一个样本只能属于一个类,与其他类就毫无瓜葛了,该样本的信息全部倾注给了它的归宿(对应类的中心)。

 

2.1、预处理 Pre-processing

在训练之前,我们需要对所有的训练样本patches先进行亮度和对比度的归一化。具体做法是,对每个样本,我们减去灰度的均值和除以标准差。另外,在除以标准差的时候,为了避免分母为0和压制噪声,我们给标准差增加一个小的常数。对于[0, 255]范围的灰度图,给方差加10一般是ok的:

         对训练数据进行归一化后,我们就可以在上面运行k-means算法以获得相应的聚类中心了(字典D的每一列),可视化在图a中,可以看到,k-means趋向于学习到低频的类边缘的聚类中心。但不幸的是,这样的特征不会有比较好的识别效果,因为邻域像素的相关性(图像的低频变化)会很强。这样K-means就会产生很多高度相关的聚类中心。而不是分散开聚类中心以更均匀的展开训练数据。所以我们需要先用白化来去除数据的相关性,以驱使K-means在正交方向上分配更多的聚类中心。

图a是由k-means从没有经过白化处理的自然图像中学习到的聚类中心。图b展示了没有和有白化的效果。左边是没有经过白化的,因为数据存在相关性,所以聚类中心会跑偏。右边是经过白化后的,可以看到聚类中心是更加正交的,这样学习到的特征才会不一样。图c展示的是从经过白化后的图像patches学习到的聚类中心。

实现whitening白化一个比较简单的方法是ZCA白化(可以参考UFLDL)。我们先对数据点x的协方差矩阵进行特征值分解cov(x)=VDVT。然后白化后的点可以表示为:

        ɛzca是一个很小的常数。对于对比度归一化后的数据,对16×16的patch,可以设ɛzca=0.01,对8×8的patch,可以设ɛzca=0.1 。需要注意的一点是,这个常数不能太小,如果太小会加强数据的高频噪声,会使特征学习更加困难。另外,因为数据的旋转对K-means没有影响,所以可以使用其他的白化变换方法,例如PCA白化(与ZCA不同只在于其旋转了一个角度)。

在白化后的数据中运行k-means可以得到清晰的边缘特征。这些特征和sparse coding啊,ICA啊等等方法学到的初级特征差不多。如图c所示。另外,对于新的数据,可能需要调整归一化的参数ɛ和白化的参数ɛzca,以满足好的效果(例如,图像patches具有高对比度,低噪声和少低频波动)。

 

2.2、初始化 Initialization

一般的K-means聚类算法存在一个比较常见的问题,就是会出现空的聚类。通俗的讲,就是它聚类后的一个类里面居然没有样本(接近于没有)。那么很明显,这个类就一点意义都没有,留着反而还会危害人间。这我们当然得做些努力来避免这种情况的发生了。那就得找原因了吧。其实这一定情况下可以认为是中心的初始化的不恰当导致的。常用的中心初始化方法就是随机地从样本中挑k个出来作为k个初始的聚类中心。但这不是个明智的选择。它有可能会导致图像趋于稠密聚集某些区域,因为如果随机选择的patches,也就是训练样本本身就在某个区域分布非常密,那么我们随机去选择聚类中心的时候,就会出现就在这个数据分布密集的地方被选出了很多的聚类中心(因为原数据分布密集,所以被选中的概率会大)。这是不好的,因为本身这个密集的区域,一个聚类中心才是好的,你硬搞出几个聚类中心,好好的队伍就会被强拆了,还搞得其他比较分散的数据点找不到归宿,因为聚类中心离它好遥远,大部分的聚类中心都让富二代给占有了,社会资源分配不当啊,哈哈。这样还会出现一种情况,就是如果样本分布密集的话,它们都几乎从属于一个聚类中心,那么其他聚类中心就几乎没有样本属于它。这样也不好。所以,一个比较好的初始化方式就是从一个正态分布中随机初始化聚类中心,然后归一化他们到单位长度。另外,因为经过了白化的阶段,我们期望我们的数据的主要成分已经在一定程度上被组织为球面spherical分布了,而在一个球面上随机地选取一些向量结果就显得温柔多了。

另外,还存在一些启发性的方法去改善k-means的这个问题。例如启发性得重新初始化空的聚类中心。实践中证明,这种方法对图像数据的效果比较好。当空类出现的时候,我们再随机挑选样本来替代这个空类中心,一般都是足够有效的。但好像一般也没啥必要。事实上,对于一个足够规模的实现来说,我们会训练很多的聚类中心,如果一个聚类中心包含的数据点太少了,我们直接抛弃它就好了,不要这个聚类中心了,因为俺们的聚类中心够多,多你一个不多,少你一个不少的。

还有一种改善的方法叫做聚类中心的抑制更新damped updates。它在每次的迭代中,都会根据以下方式更新聚类中心:

需要注意的是,这种形式的抑制并不会对“大”的聚类有很大的影响(因为XST的第j列会比Dold的第j列要大得多),它只能防止小的聚类在一次的迭代过程中被拉得太远(就像滤波的方法一样,新的值不仅受当前值的影响,还受历史值的影响,这样就会防止这个值出现突变。例如a_old=1,新的值为5,那么a_new=0.5*5+0.5*a_old=3,也就是a_new会跳到3而不是一下子跳到5,当然还取决于前面的权值0.5,看我们更信任新值还是历史值。但如果新值很大,那就没有特别大的效果了。例如新值为99,那么a_new=50,它还是比a_old=1要大多了)。我们将这个k-means训练算法概括如下:

 

三、与稀疏特征学习的比较

上面我们提到,K-means能像ICA,sparse coding一样学习到方向性的边缘特征。那我们会怀疑,这是偶然吗(因为边缘太常见了,所以一般的学习模型都可以发现它们)?!还是说这隐含着K-means本身就具有和ICA相似的稀疏分解的能力。因为每个s(i)只能包含一个非零元素(相应的聚类中心),所以k-means会尝试去学习一个可以很好的描述整个输入图像的聚类中心。因此,它也无法保证一定可以学到和ICA或者sparse coding学到的滤波器一样。这些算法可以学习到真正的“分布式”描述,因为一张图像可以通过一个字典的几列来联合描述,而不是只通过一列。尽管如此,k-means在天时地利人和的时候依然能发现数据的稀疏投影,虽然作为一个聚类方法它好像没有明确的通过公式告诉我们它还能做这个。所以我们看到的结果也不是幻觉或者偶然。

虽然k-means和ICA和sparse coding有着经验性的相似,但它有一个很大的缺点:它发现数据稀疏投影方向的能力很大程度上取决于输入数据的维数和数量。如果数据的维数增加,我们就需要增加大量的样本,才能得到好的结果。这点就比较难伺候了。例如,在实验中,对64×64的patch,我们用了500,000个样本来训练k-means,结果还是很烂。我们只学习到了很少的边缘。在这种情况下,就会存在很多的接近空数据点的聚类,所以就需要非常多的数据才能让k-means发挥光与热。所以,这就存在一个折衷了,是否要采用k-means,就要看我们的数据有多少维,我们能提供多少数据(一般要达到相同的结果,比sparse coding要多很多)。对于适当的维度(例如100维),使用k-means的训练速度比其他算法快那么多,那么我情愿花点力气去提供更多的样本给它。但对于很高维的数据,采用其他算法例如sparse coding会比k-means性能要好,甚至速度也要更快。

 

四、在图像识别上的应用

在这里没什么好说的,k-means就作为一种学习特征的方法,取代一般的非监督学习过程里面的特征学习那块就好了。可以参考UFLDL中的“卷积特征提取”和“池化”。另外,文中也介绍了一些选择参数的方法,很多论文也可以看到,这里也不说了。这里比较有趣的一点是,文中说到,根据观测的结果,如果我们的识别系统设计有大量有效的有标签训练数据的话,那么选择一个简单的快速的前向编码器(特征映射函数)的话,效果都可以挺好。但如果我们的有标签训练数据不多,那么采用一个复杂的编码函数会更好。例如,在大规模情况下,采用软阈值非线性函数:

就可以工作的比较好。其中,ɑ是个可调的常数。相反,复杂的sigmoid非线性函数效果就没那么好了:

 

五、构建深度网络

在上面一个章节中,我们是先从一些无标签数据中随机提取一些patches,然后用k-means来学习其中的特征,得到一个字典。然后对于一个大的图像,我们用这个字典(特征提取器)去卷积图像的每个局部感受野,从而提取整幅图像的特征。然后我们再通过一个pooling步骤去对提取到的特征进行绛维。当然了,如果我们可以通过上面这些提取到的特征去学习更高层的特征,那就更好了。最简单的方法就是,对无标签数据集X中,我们通过上面的方法得到pooling后的特征后,然后得到新的数据集Z,我们再在这个数据库Z中用同样的特征学习过程去学习新的特征。从而得到第二层的特征。但这有个问题,就是这个Z一般都是非常高维的,所以需要用非常大的特征字典来学习(例如k=10000甚至更多)。但这样,对k-means的要求也高了,我们需要提供更多的样本来训练。

该文提出了一种pair-wise “dependency test”的方法来改善这个问题。我们通过一个“能量相关”来定义数据集Z中两个特征zj和zk的依赖性。我们需要在Z的特征表达之上学习更高级的特征。使用“dependency test”,我们可以以一种相对简单的方式来选择合理的感受野:我们挑了一个特征z0,然后使用“dependency test”来寻找和z0具有很强依赖性的R特征。然后只用这R特征作为k-means算法的输入。如果我们选取的R足够小(例如100或者200),那么归一化和白化过后,再用k-means来训练一般都可以达到好的效果。因为输入的维数很小,所以我们只需要训练一百个左右的聚类中心,这样使用少量的训练样本就可以运行k-means了。

详细的算法介绍和效果见原论文。

 

六、总结

本文中,我们讨论了用k-means来搭建特征学习系统的一些关键点,现在总结如下:

1、需要先对数据进行均值和对比度的归一化。

2、使用白化来消去数据的相关性。注意白化参数的选择。

3、通过高斯噪声和归一化来初始化k-means的聚类中心。

4、使用抑制更新来避免空聚类和增加稳定性。

5、注意数据的维数和稀疏的影响。K-means通过寻找数据分布的”heavy-tailed”方向来找到输入数据的稀疏投影。但如果数据没有经过适当的白化,或者数据维数太高了,或者存在无效的数据,那么效果往往是不好的。

6、对于高维数据,k-means需要增加大量的样本来能得到好的效果。

7、一些外在的参数(例如pooling,编码方法的选择等等)对于系统性能的影响比学习算法本身要大。所以最好先化时间和成本在通过更多的交叉检验的方法来挑选好的参数比花更多的成本在学习算法上要明智的多。

8、对于图像识别来说,更多的聚类中心往往是有帮助的。当然,前提是我们要有足够的训练样本。

9、当有标签数据多的时候,采用一个简单的编码器。如果标签数据有限(例如每类只有百来个训练样本),那么采用一个复杂的编码器会更好。

10、尽可能地使用局部感受野。K-means是否成功,其瓶颈在于输入数据的维数。越低越好。如果无法手动的选择好的局部感受野,那么尝试去通过一个自动的依赖性测试dependency test来帮助从数据中挑选出一组低维的数据。这对于深度网络来说是很有必要的。

 

 

(二)Sparse Filtering稀疏滤波

 

         自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。

 

本文的论文来自:

Sparse filtering , J. Ngiam, P. Koh, Z. Chen, S. Bhaskar, A.Y. Ng. NIPS2011。在其论文的支撑材料中有相应的Matlab代码,代码很简介。不过我还没读。

下面是自己对其中的一些知识点的理解:

 

《Sparse Filtering》

       本文还是聚焦在非监督学习Unsupervised feature learning算法。因为一般的非监督算法需要调整很多额外的参数hyperparameter。本文提出一个简单的算法:sparse filtering。它只有一个hyperparameter(需要学习的特征数目)需要调整。但它很有效。与其他的特征学习方法不同,sparse filtering并没有明确的构建输入数据的分布的模型。它只优化一个简单的代价函数(L2范数稀疏约束的特征),优化过程可以通过几行简单的Matlab代码就可以实现。而且,sparse filtering可以轻松有效的处理高维的输入,并能拓展为多层堆叠。

sparse filtering方法的核心思想就是避免对数据分布的显式建模,而是优化特征分布的稀疏性从而得到好的特征表达。

 

一、非监督特征学习

一般来说,大部分的特征学习方法都是试图去建模给定训练数据的真实分布。换句话说,特征学习就是学习一个模型,这个模型描述的就是数据真实分布的一种近似。这些方法包括denoising autoencoders,restricted Boltzmann machines (RBMs), independent component analysis (ICA) 和sparse coding等等。

这些方法效果都不错,但烦人的一点就是,他们都需要调节很多参数。比如说学习速率learning rates、动量momentum(好像rbm中需要用到)、稀疏度惩罚系数sparsity penalties和权值衰减系数weight decay等。而这些参数最终的确定需要通过交叉验证获得,本身这样的结构训练起来所用时间就长,这么多参数要用交叉验证来获取时间就更多了。我们花了大力气去调节得到一组好的参数,但是换一个任务,我们又得调节换另一组好的参数,这样就会花了俺们太多的时间了。虽然ICA只需要调节一个参数,但它对于高维输入或者很大的特征集来说,拓展能力较弱。

本文中,我们的目标是研究一种简单并且有效的特征学习算法,它只需要最少的参数调节。虽然学习数据分布的模型是可取的,而且效果也不错,但是它往往会使学习的算法复杂化,例如:RBMs需要近似对数划分log-partition函数的梯度,这样才可能优化数据的似然函数。Sparse coding需要在每次的迭代过程中寻找活跃的基的系数,这是比较耗时的。而且,稀疏因子也是一个需要调整的参数。本文方法主要是绕过对数据分布的估计,直接分析优化特征的分布。那怎样的特征的分布才是优的呢?到这里,我们需要先关注特征的一些主要属性:population sparsity,lifetime sparsity和 high dispersal。怎样的特征才是好的特征,才是对分类或者其他任务好的特征。我们的学习算法就应该学会去提取这种特征。

 

二、特征分布

上面讨论的特征学习算法都可以认为是生成特定的特征分布Feature distributions。例如,sparse coding只用少许的非零系数(特征)来描述每个样本。而一个面向特征分布的方法可以认为是直接优化特征分布的一些属性,使其可以更好的描述样本。

我们引入一个特征分布矩阵,矩阵的每一行是一个特征,每一列是一个样本。每个元素表示第i个样本的第j个特征的激活值。由上面的分析可以知道,这是一个由输入到特征的映射函数(特征提取函数)来得到。

下面我们就来讨论下什么样的特征分布才是好的:

1)每个样本的特征应该是稀疏的(Population Sparsity)

每个样本都只用很少的激活(非零)特征来描述。具体来说,对于特征矩阵的每一列(一个样本)f(i),只有很少的非零元素。其他的都是0 。例如,一幅图像可以由里面包含的一些目标来描述,如果里面存在很多可能的目标,那么在某一时刻,也只可能出现一些。我们称之为population sparsity(种群稀疏)。

 

2)样本间的特征应该是稀疏的(Lifetime Sparsity)

好的特征应该是具有区分性的,这样才可以区分样本。例如需要区分人脸和人手,那么很明显,肤色不是区分性的特征,因为人脸和人手都有肤色。但如果看有没有眼睛,那么就很容易区分是人脸还是人手了,所以眼睛就是一个区分性的特征。所以要区分样本,就要选择样本独有的,而不是大家都有的特征。稍微学术点的表达就是,每个特征只允许在少量的样本内被激活。也就是说,在特征矩阵中,每一行(一种特征)应该只有少量的非零元素。这个特征的属性被称为lifetime sparsity(存在稀疏)。

 

3)特征的分布应该是均匀的(High Dispersal)

对每一行(一种特征在不同样本的时候的不同取值)的特征的分布,应该和其他行的特征的分布相似,或者说每种特征都应该具有相似的统计特性。具体来说,对矩阵的每一行,我们取该行所有元素(一种特征在不同样本的时候的不同取值)的平方后的均值作为其统计特性的描述。每一行都存在一个均值,那么每行的均值都应该是一样的,这样就可以认为所有的特征都具有相似的分布。这种属性我们称之为high dispersal(高分散性)。但对于一个好的特征描述来说,这个属性并不是必要的。但它可以防止特征的退化,也就是可以防止提取到相同的特征(如果提取到相同的特征,那么特征既冗余,又没有增加信息量,所以一般都要求提取到的特征是正交的)。对于过完备的特征表达。high dispersal可以理解为只有很少的inactive不活跃的特征。例如,PCA编码一般不会满足high dispersal,因为大的特征值对应的特征向量(也就是特征code)大部分总是活跃active的。

 

很多特征学习方法其实有包含上面的这些约束的。例如sparse RBM会约束一个特征的激活值靠近一个目标的值(lifetime sparsity)。ICA会归一化每个特征,还会优化特征的lifetime sparsity。Sparse autoencoder也会显式的优化lifetime sparsity。

另外,基于聚类的算法,例如K-means,是population sparsity约束的一种极端形式,它的一个聚类中心只对应一个特征,对每个样本来说,只有一个特征是激活的(只有一个值是1,其他全是0)。Triangle三角激活函数,本质上也会保证population sparsity。Sparse coding实际上也可以被视为存在population sparsity。

本文中,我们从特征分布的角度去推导得到一种简单的特征学习算法。它仅仅需要优化high dispersal和population sparsity。因为在我们的实验中,我们发现,实现这两种特征的属性对学习一个过完备的特征表达来说已经足够了。在后面,我们会说明,这两种属性的结合实际上已经蕴含着保证特征的lifetime sparsity。

 

三、Sparse filtering

下面我们就说明下,sparse filtering是如何捕捉到上面说的那些特性的。我们先考虑下从每个样本中计算线性特征。具体来说,我们用来表示第i个样本(特征矩阵中第i列)的第j个特征值(特征矩阵中第j行)。因为是线性特征,所以。第一步,我们先简单的对特征矩阵的行进行归一化,然后再对列进行归一化,然后再将矩阵中所有元素的绝对值求和。

具体来说,我们先归一化每个特征为相等的激活值。具体做法是将每一个特征除以其在所有样本的二范数:。然后我们再归一化每一个样本的特征。这样,他们就会落在二范数的单位球体unit L2-ball上面了。具体做法是:。这时候,我们就可以对这些归一化过的特征进行优化了。我们使用L1范数惩罚来约束稀疏性。对于一个有M个样本的数据集, sparse filtering的目标函数表示为:

 

3.1、Optimizing for population sparsity

其中这一项度量的就是第i个样本的特征的population sparsity,也就是限制每个样本只有很少的非零值。因为归一化的特征被约束只能落在二范数的单位球体上面,所以当这些特征是稀疏的时候,也就是样本接近特征坐标轴的时候,上面的目标函数才会最小化。反之,如果一个样本的每个特征值都差不多,那么就会导致一个很高的惩罚。可能有点难理解,我们看下图:

左图:假设我们的特征维数是两维(f1, f2),我们有两个样本,绿色和褐色的。每个样本都会先投影到二范数的球体上面(二维的话就是单位圆),再进行稀疏性的优化。可以看到,当样本落在坐标轴的时候,特征具有最大的稀疏性(例如,一个样本落在f2轴上,那么这个样本的表示就是(0, 1),一个特征值为1,其他的为0,那么很明显它具有最大的稀疏性)。右图:因为归一化,特征之间会存在竞争。上面有一个只在f1特征上增加的样本。可以看到,尽管它只在f1方向上增加(绿色三角型转移到蓝色三角型),经过列归一化后(投影到单位圆上),可以看到第二个特征f2会减少(绿色圆圈转移到蓝色圆圈)。也就是说特征之间存在竞争,我变大,你就得变小。

对特征进行归一化的一个属性就是它会隐含的进行特征间的竞争。归一化会使得如果只有一个特征分量f(i)增大,那么其他所有的特征分量的值将会减小。相似的,如果只有一个特征分量f(i)减小,那么其他所有的特征分量的值将会增大。因此,我们最小化,将会驱使归一化的特征趋于稀疏和大部分接近于0 。也就是,一些特征会比较大,其他的特征值都很小(接近于0)。因此,这个目标函数会优化特征的population sparsity。

上面的公式与Treves-Rolls的population/life-time sparsity的度量非常相似:

        这里F是特征的个数。这个度量常用来衡量大脑神经元激活的稀疏度。我们提出的公式可以看成是这个度量的平方根,再乘以一个尺度因子。

 

3.2、Optimizing for high dispersal

上面说到,特征的high dispersal属性要求每个特征被恒等激活。在这里,我们粗鲁地强制每个特征的激活值平方后的均值相等。在上面sparse filtering的公式中,我们首先通过将每个特征除以它在所有样本上面的二范数来归一化每个特征,使他们具有相同的激活值:。实际上,它和约束每个特征具有相同的平方期望值有一样的效果。,因此,它已经隐含的优化了high dispersal属性。

 

3.3、Optimizing for lifetime sparsity

我们发现,对population sparsity和 high dispersal的优化就已经隐含的优化了特征的lifetime sparsity。这其中的缘由是什么呢。首先,一个具有population sparsity的特征分布在特征矩阵里会存在很多非激活的元素(为0的元素)。而且,因为满足high dispersal,这些零元素会近似均匀的分布在所有的特征里。因此,每一个特征必然会有一定数量的零元素,从而保证了lifetime sparsity。所以,对于population sparsity和 high dispersal的优化就已经足够得到一个好的特征描述了。

 

四、Deep sparse filtering

因为sparse filtering的目标函数是不可知的,我们可以自由地选择一个前向网络来计算这些特征。一般来说,我们都使用比较复杂的非线性函数,例如:

或者多层网络来计算这些特征。这样,sparse filtering也算是训练深度网络的一种自然的框架了。

有sparse filtering的深度网络可以使用权威的逐层贪婪算法来训练。我们可以先用sparse filtering来训练得到一个单层的归一化的特征,然后用它当成第二层的输入去训练第二层,其他层一样。实验中,我们发现,它能学习到很有意义的特征表达。

 

五、实验与分析

在我们的实验中,我们采用软绝对值函数作为我们的激活函数:

       其中ɛ=10^-8,然后用现有的L-BFGS算法来优化sparse filtering的目标函数直至收敛。

         其他的实验结果看原文。

 

六、总结与讨论

6.1、与divisive normalization的联系

sparse filtering的population sparsity和divisive normalization有着紧密的联系。divisive normalization是一种视觉处理过程,一个神经元的响应会除以其邻域所有神经元的响应的和(或加权和)。divisive normalization在多级目标识别中是很有效的。然而,它被当成一个预处理阶段引入,而不是属于非监督学习(预训练)的一部分。实际上,sparse filtering把divisive normalization结合到了特征学习过程中,通过让特征间竞争,学习到满足population sparse的特征表达。

 

6.2、与ICA 和sparse coding的联系

sparse filtering的目标函数可以看出ICA目标函数的归一化版本。在ICA中,目标函数是最小化线性滤波器组的响应,例如||Wx||1,但需要满足滤波器间相互正交的约束。正交约束保证我们学习到的特征是不同的。在sparse filtering中,我们用一个归一化的稀疏惩罚来替代这个约束,每个滤波器的响应都会除以所有滤波器响应的二范数||Wx||1/||Wx||2,在这里,就给滤波器引入了竞争,而不需要添加正交化。

同样,我们也可以将归一化的思想运用到sparse coding框架。在sparse coding中,类似于L1/L2的稀疏惩罚已经被使用了。我们用一个归一化的惩罚,例如||s||1/||s||2来替换一般的L1范数的惩罚,例如||s||1。归一化惩罚具有尺度不变性,对于数据的变化可以更加鲁棒。

 

(三)单层非监督学习网络分析

 

         自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。

 

本文的论文来自:

An Analysis of Single-Layer Networks in Unsupervised Feature Learning, Adam Coates, Honglak Lee, and Andrew Y. Ng. In AISTATS 14, 2011。在其论文的demo_code。不过我还没读。

下面是自己对其中的一些知识点的理解:

 

《An Analysis of Single-Layer Networks in Unsupervised Feature Learning》

       最近,很多的研究都聚焦在从无标签数据中学习特征的算法,采用逐渐复杂的非监督算法和深度模型在一些基准的数据库中还得到了很好的效果。本文说明了一些简单的因素,例如模型隐层的节点数,对要达到高的性能,要比学习算法的选择或者模型的深度更重要。本文主要是针对一个隐含层的网络结构进行分析的,分别对比了4种网络结构,sparse auto-encoders、sparse RBMs 、 K-means clustering和Gaussian mixtures。本文还分析了4个影响网络性能的因素:感受野大小receptive field size、隐层节点数(或者要提取的特征数)、卷积步长(stride)和白化whitening。

最后作者得出了下面几个结论:

1、网络中隐含层神经元节点的个数(需要学习的特征数目),采集的密度(也就是convolution时的移动步伐,也就是在什么地方计算特征)和感知区域大小对最终特征提取效果的影响很大,甚至比网络的层次数,deep learning学习算法本身还要重要。

2、Whitening在预处理过程中还是很有必要的。

3、如果不考虑非监督学习算法的选择的话,whitening、 large numbers of features和small stride都会得到更好的性能。

4、在以上4种实验算法中,k-means效果竟然最好。因此在最后作者给出结论时的建议是,尽量使用whitening对数据进行预处理,每一层训练更多的特征数,采用更密集的方法对数据进行采样。

 

一、概述

很多特征学习系统的一个主要缺点就是他们的复杂度和开销。另外,还需要调整很多参数(hyper-parameters)。比如说学习速率learning rates、动量momentum(好像rbm中需要用到)、稀疏度惩罚系数sparsity penalties和权值衰减系数weight decay等。而这些参数最终的确定需要通过交叉验证获得,本身这样的结构训练起来所用时间就长,这么多参数要用交叉验证来获取时间就更多了。所以本文得出的结论用kmeans效果那么好,且无需有这些参数要考虑。(对于k-means的分析,见“Deep Learning论文笔记之(一)K-means特征学习”)。

 

二、非监督特征学习框架:

1、通过以下步骤去学习一个特征表达:

1)从无便签的训练图像中随机提取一些小的patches;

2)对这些patches做预处理(每个patch都减去均值,也就是减去直流分量,并且除以标准差,以归一化。对于图像来说,分别相当于局部亮度和对比度归一化。然后还需要经过白化);

3)用非监督学习算法来学习特征映射,也就是输入到特征的映射函数。

2、学习到特征映射函数后,给定一个有标签的训练图像集,我们用学习到的特征映射函数,对其进行特征提取,然后用来训练分类器:

1)对一个图像,用上面学习到的特征来卷积图像的每一个sub-patches,得到该输入图像的特征;

2)将上面得到的卷积特征图进行pooling,减少特征数,同时得到平移等不变性;

3)用上面得到的特征,和对应的标签来训练一个线性分类器,然后在给定新的输入时,预测器标签。

 

三、特征学习:

对数据进行预处理后,就可以通过非监督学习算法去学习特征了。我们可以把非监督学习算法看成一个黑盒子。它接受输入,然后产生一个输出。可以表示为一个函数f:RN->RK,把一个N维的输入向量x(i)映射为一个K维的特征向量。在这里,我们对比分析了四种不同的非监督学习算法:

1)sparse auto-encoders

我们用BP去训练一个K个隐藏节点的自动编码机,代价函数是重构均方误差,并存在一个惩罚项。主要限制隐藏节点,使其保持一个低的激活值。算法输出一个权值矩阵W(KxN维)和一组基B(K维),特征映射函数为:f(x)=g(Wx+b)。这里g(z)=1/(1+exp(-z))是sigmoid函数,对向量z的每个元素求值。

在这里,存在很多参数需要调整,例如权值衰减系数和目标激活值。针对特定的感受野大小,这些参数需要通过交叉验证方法来选择。

 

2)sparse restricted Boltzmann machine

RBM是一个无向图模型,包含K个二值隐层随机变量。稀疏RBMs可以通过contrastive divergence approximation(对比分歧近似)方法来训练。其中的稀疏惩罚项和自动编码机一样。训练模型还是输入权值矩阵W和基b,我们也可以使用和上面的自动编码机同样的特征映射函数。但它的训练方法和自动编码机是完全不一样的。

 

3)K-means clustering

我们用K-means聚类算法来从输入数据中学习K个聚类中心c(k)。当学习到这K个聚类中心后,我们可以有两种特征映射f的方法。第一种是标准的1-of-K,属于硬分配编码:

这个fk(x)表示样本x的特征向量f的第k个元素,也就是特征分量。为什么叫硬分配呢,因为一个样本只能属于某类。也就是说每个样本x对应的特征向量里面只有一个1,其他的全是0。K个类有K个中心,样本x与哪个中心欧式距离最近,其对应的位就是1,其他位全是0,属于稀疏编码的极端情况,高稀疏度啊。

第二种是采用非线性映射。属于软编码。

这里zk=||x-c(k)||2,u(z)是向量z的均值。也就是先计算出对应样本与k个类中心点的平均距离d,然后如果那些样本与类别中心点的距离大于d的话都设置为0,小于d的则用d与该距离之间的差来表示。这样基本能够保证一半以上的特征都变成0了,也是具有稀疏性的,且考虑了更多那些距类别中心距离比较近的值。为什么叫软分配呢。软表示这个样本以一定的概率属于这个类,它还以一定的概率属于其他的类,有点模糊聚类的感觉。而硬则表示这个样本与哪个类中心距离最近,这个样本就只属于这个类。与其他类没有任何关系。

 

4)Gaussian mixtures

高斯混合模型GMM用K个高斯分布的混合来描述了输入数据的密度分布。GMMs可以通过EM算法来训练。一般来说需要先运行一次K-means算法来初始化GMM的K个高斯分量。这样可以避免其陷入较差的局部最小值。这里的特征映射f把每个输入样本x映射为一个后验概率:

k是对角协方差矩阵,Φk是由EM学习得到的每个类的先验概率。其实这里和上面k-means的软分配有点像。对每个样本x都要计算其属于每一个类别j的概率。然后用这些后验概率来编码对应x的特征向量。

 

四、特征提取和分类

通过上面的步骤,我们就可以得到了将一个输入patch x(N维)映射到一个新的描述y=f(x)(K维)的函数f。这时候,我们就可以用这个特征提取器来提取有标签图像数据的特征来训练分类器了。

这里的描述到处都是,就不啰嗦了,可以参考UFLDL中的“卷积特征提取”和“池化”。

 

五、关于白化

对稀疏自动编码机和RBMs来说,有没有白化就显得有点随意了。当要提取的特征数较少的时候(隐层节点数少),对稀疏RBMs,白化会有点用。但如果特征数目很多,白化就显得不那么有用了。但对于聚类的算法来说,白化是一个关键的必不可少的预处理步骤,因为聚类算法对数据的相关性是盲目的。所以我们需要先通过白化来消去数据的相关性再聚类。可以看出whitening后学习到更多的细节,且whitening后几种算法都能学到类似gabor滤波器的效果,因此并不一定是deep learning的结构才可以学到这些特性。

 

本文还参考了:Deep learning:二十(无监督特征学习中关于单层网络的分析)

 

(四)CNN卷积神经网络推导和实现

 

         自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。

 

本文的论文来自:

Notes on Convolutional Neural Networks, Jake Bouvrie。

这个主要是CNN的推导和实现的一些笔记,再看懂这个笔记之前,最好具有CNN的一些基础。这里也先列出一个资料供参考:

[1] Deep Learning(深度学习)学习笔记整理系列之(七)

[2] LeNet-5, convolutional neural networks

[3]卷积神经网络

[4] Neural Network for Recognition of Handwritten Digits

[5] Deep learning:三十八(Stacked CNN简单介绍)

[6] Gradient-based learning applied to document recognition.

[7]Imagenet classification with deep convolutional neural networks.

[8] UFLDL中的“卷积特征提取”和“池化”。

另外,这里有个matlab的Deep Learning的toolbox,里面包含了CNN的代码,在下一个博文中,我将会详细注释这个代码。这个笔记对这个代码的理解非常重要。

下面是自己对其中的一些知识点的理解:

 

《Notes on Convolutional Neural Networks》

一、介绍

这个文档讨论的是CNNs的推导和实现。CNN架构的连接比权值要多很多,这实际上就隐含着实现了某种形式的规则化。这种特别的网络假定了我们希望通过数据驱动的方式学习到一些滤波器,作为提取输入的特征的一种方法。

本文中,我们先对训练全连接网络的经典BP算法做一个描述,然后推导2D CNN网络的卷积层和子采样层的BP权值更新方法。在推导过程中,我们更强调实现的效率,所以会给出一些Matlab代码。最后,我们转向讨论如何自动地学习组合前一层的特征maps,特别地,我们还学习特征maps的稀疏组合。

 

二、全连接的反向传播算法

典型的CNN中,开始几层都是卷积和下采样的交替,然后在最后一些层(靠近输出层的),都是全连接的一维网络。这时候我们已经将所有两维2D的特征maps转化为全连接的一维网络的输入。这样,当你准备好将最终的2D特征maps输入到1D网络中时,一个非常方便的方法就是把所有输出的特征maps连接成一个长的输入向量。然后我们回到BP算法的讨论。(更详细的基础推导可以参考UFLDL中“反向传导算法”)。

2.1、Feedforward Pass前向传播

在下面的推导中,我们采用平方误差代价函数。我们讨论的是多类问题,共c类,共N个训练样本。

         这里表示第n个样本对应的标签的第k维。表示第n个样本对应的网络输出的第k个输出。对于多类问题,输出一般组织为“one-of-c”的形式,也就是只有该输入对应的类的输出节点输出为正,其他类的位或者节点为0或者负数,这个取决于你输出层的激活函数。sigmoid就是0,tanh就是-1.

因为在全部训练集上的误差只是每个训练样本的误差的总和,所以这里我们先考虑对于一个样本的BP。对于第n个样本的误差,表示为:

       传统的全连接神经网络中,我们需要根据BP规则计算代价函数E关于网络每一个权值的偏导数。我们用l来表示当前层,那么当前层的输出可以表示为:

       输出激活函数f(.)可以有很多种,一般是sigmoid函数或者双曲线正切函数。sigmoid将输出压缩到[0, 1],所以最后的输出平均值一般趋于0 。所以如果将我们的训练数据归一化为零均值和方差为1,可以在梯度下降的过程中增加收敛性。对于归一化的数据集来说,双曲线正切函数也是不错的选择。

 

2.2、Backpropagation Pass反向传播

反向传播回来的误差可以看做是每个神经元的基的灵敏度sensitivities(灵敏度的意思就是我们的基b变化多少,误差会变化多少,也就是误差对基的变化率,也就是导数了),定义如下:(第二个等号是根据求导的链式法则得到的)

         因为∂u/∂b=1,所以∂E/∂b=∂E/∂u=δ,也就是说bias基的灵敏度∂E/∂b=δ和误差E对一个节点全部输入u的导数∂E/∂u是相等的。这个导数就是让高层误差反向传播到底层的神来之笔。反向传播就是用下面这条关系式:(下面这条式子表达的就是第l层的灵敏度,就是)

公式(1)

这里的“◦”表示每个元素相乘。输出层的神经元的灵敏度是不一样的:

         最后,对每个神经元运用delta(即δ)规则进行权值更新。具体来说就是,对一个给定的神经元,得到它的输入,然后用这个神经元的delta(即δ)来进行缩放。用向量的形式表述就是,对于第l层,误差对于该层每一个权值(组合为矩阵)的导数是该层的输入(等于上一层的输出)与该层的灵敏度(该层每个神经元的δ组合成一个向量的形式)的叉乘。然后得到的偏导数乘以一个负学习率就是该层的神经元的权值的更新了:

公式(2)

对于bias基的更新表达式差不多。实际上,对于每一个权值(W)ij都有一个特定的学习率ηIj

 

三、Convolutional Neural Networks 卷积神经网络

3.1、Convolution Layers 卷积层

我们现在关注网络中卷积层的BP更新。在一个卷积层,上一层的特征maps被一个可学习的卷积核进行卷积,然后通过一个激活函数,就可以得到输出特征map。每一个输出map可能是组合卷积多个输入maps的值:

       这里Mj表示选择的输入maps的集合,那么到底选择哪些输入maps呢?有选择一对的或者三个的。但下面我们会讨论如何去自动选择需要组合的特征maps。每一个输出map会给一个额外的偏置b,但是对于一个特定的输出map,卷积每个输入maps的卷积核是不一样的。也就是说,如果输出特征map j和输出特征map k都是从输入map i中卷积求和得到,那么对应的卷积核是不一样的。

3.1.1、Computing the Gradients梯度计算

我们假定每个卷积层l都会接一个下采样层l+1 。对于BP来说,根据上文我们知道,要想求得层l的每个神经元对应的权值的权值更新,就需要先求层l的每一个神经节点的灵敏度δ(也就是权值更新的公式(2))。为了求这个灵敏度我们就需要先对下一层的节点(连接到当前层l的感兴趣节点的第l+1层的节点)的灵敏度求和(得到δl+1),然后乘以这些连接对应的权值(连接第l层感兴趣节点和第l+1层节点的权值)W。再乘以当前层l的该神经元节点的输入u的激活函数f的导数值(也就是那个灵敏度反向传播的公式(1)的δl的求解),这样就可以得到当前层l每个神经节点对应的灵敏度δl了。

然而,因为下采样的存在,采样层的一个像素(神经元节点)对应的灵敏度δ对应于卷积层(上一层)的输出map的一块像素(采样窗口大小)。因此,层l中的一个map的每个节点只与l+1层中相应map的一个节点连接。

为了有效计算层l的灵敏度,我们需要上采样upsample 这个下采样downsample层对应的灵敏度map(特征map中每个像素对应一个灵敏度,所以也组成一个map),这样才使得这个灵敏度map大小与卷积层的map大小一致,然后再将层l的map的激活值的偏导数与从第l+1层的上采样得到的灵敏度map逐元素相乘(也就是公式(1))。

在下采样层map的权值都取一个相同值β,而且是一个常数。所以我们只需要将上一个步骤得到的结果乘以一个β就可以完成第l层灵敏度δ的计算。

我们可以对卷积层中每一个特征map j重复相同的计算过程。但很明显需要匹配相应的子采样层的map(参考公式(1)):

        up(.)表示一个上采样操作。如果下采样的采样因子是n的话,它简单的将每个像素水平和垂直方向上拷贝n次。这样就可以恢复原来的大小了。实际上,这个函数可以用Kronecker乘积来实现:

       好,到这里,对于一个给定的map,我们就可以计算得到其灵敏度map了。然后我们就可以通过简单的对层l中的灵敏度map中所有节点进行求和快速的计算bias基的梯度了:

公式(3)

最后,对卷积核的权值的梯度就可以用BP算法来计算了(公式(2))。另外,很多连接的权值是共享的,因此,对于一个给定的权值,我们需要对所有与该权值有联系(权值共享的连接)的连接对该点求梯度,然后对这些梯度进行求和,就像上面对bias基的梯度计算一样:

       这里,中的在卷积的时候与逐元素相乘的patch,输出卷积map的(u, v)位置的值是由上一层的(u, v)位置的patch与卷积核k_ij逐元素相乘的结果。

咋一看,好像我们需要煞费苦心地记住输出map(和对应的灵敏度map)每个像素对应于输入map的哪个patch。但实际上,在Matlab中,可以通过一个代码就实现。对于上面的公式,可以用Matlab的卷积函数来实现:

我们先对delta灵敏度map进行旋转,这样就可以进行互相关计算,而不是卷积(在卷积的数学定义中,特征矩阵(卷积核)在传递给conv2时需要先翻转(flipped)一下。也就是颠倒下特征矩阵的行和列)。然后把输出反旋转回来,这样我们在前向传播进行卷积的时候,卷积核才是我们想要的方向。

 

3.2、Sub-sampling Layers 子采样层

对于子采样层来说,有N个输入maps,就有N个输出maps,只是每个输出map都变小了。

        down(.)表示一个下采样函数。典型的操作一般是对输入图像的不同nxn的块的所有像素进行求和。这样输出图像在两个维度上都缩小了n倍。每个输出map都对应一个属于自己的乘性偏置β和一个加性偏置b。

 

3.2.1、Computing the Gradients 梯度计算

这里最困难的是计算灵敏度map。一旦我们得到这个了,那我们唯一需要更新的偏置参数β和b就可以轻而易举了(公式(3))。如果下一个卷积层与这个子采样层是全连接的,那么就可以通过BP来计算子采样层的灵敏度maps。

我们需要计算卷积核的梯度,所以我们必须找到输入map中哪个patch对应输出map的哪个像素。这里,就是必须找到当前层的灵敏度map中哪个patch对应与下一层的灵敏度map的给定像素,这样才可以利用公式(1)那样的δ递推,也就是灵敏度反向传播回来。另外,需要乘以输入patch与输出像素之间连接的权值,这个权值实际上就是卷积核的权值(已旋转的)。

      在这之前,我们需要先将核旋转一下,让卷积函数可以实施互相关计算。另外,我们需要对卷积边界进行处理,但在Matlab里面,就比较容易处理。Matlab中全卷积会对缺少的输入像素补0 。

到这里,我们就可以对b和β计算梯度了。首先,加性基b的计算和上面卷积层的一样,对灵敏度map中所有元素加起来就可以了:

       而对于乘性偏置β,因为涉及到了在前向传播过程中下采样map的计算,所以我们最好在前向的过程中保存好这些maps,这样在反向的计算中就不用重新计算了。我们定义:

这样,对β的梯度就可以用下面的方式计算:

 

3.3、Learning Combinations of Feature Maps 学习特征map的组合

大部分时候,通过卷积多个输入maps,然后再对这些卷积值求和得到一个输出map,这样的效果往往是比较好的。在一些文献中,一般是人工选择哪些输入maps去组合得到一个输出map。但我们这里尝试去让CNN在训练的过程中学习这些组合,也就是让网络自己学习挑选哪些输入maps来计算得到输出map才是最好的。我们用αij表示在得到第j个输出map的其中第i个输入map的权值或者贡献。这样,第j个输出map可以表示为:

         需要满足约束:

         这些对变量αij的约束可以通过将变量αij表示为一个组无约束的隐含权值cij的softmax函数来加强。(因为softmax的因变量是自变量的指数函数,他们的变化率会不同)。

         因为对于一个固定的j来说,每组权值cij都是和其他组的权值独立的,所以为了方面描述,我们把下标j去掉,只考虑一个map的更新,其他map的更新是一样的过程,只是map的索引j不同而已。

Softmax函数的导数表示为:

        这里的δ是Kronecker delta。对于误差对于第l层变量αi的导数为:

         最后就可以通过链式规则去求得代价函数关于权值ci的偏导数了:

 

3.3.1、Enforcing Sparse Combinations 加强稀疏性组合

为了限制αi是稀疏的,也就是限制一个输出map只与某些而不是全部的输入maps相连。我们在整体代价函数里增加稀疏约束项Ω(α)。对于单个样本,重写代价函数为:

然后寻找这个规则化约束项对权值ci求导的贡献。规则化项Ω(α)对αi求导是:

         然后,通过链式法则,对ci的求导是:

         所以,权值ci最后的梯度是:

 

3.4、Making it Fast with MATLAB

CNN的训练主要是在卷积层和子采样层的交互上,其主要的计算瓶颈是:

1)前向传播过程:下采样每个卷积层的maps;

2)反向传播过程:上采样高层子采样层的灵敏度map,以匹配底层的卷积层输出maps的大小;

3)sigmoid的运用和求导。

对于第一和第二个问题,我们考虑的是如何用Matlab内置的图像处理函数去实现上采样和下采样的操作。对于上采样,imresize函数可以搞定,但需要很大的开销。一个比较快速的版本是使用Kronecker乘积函数kron。通过一个全一矩阵ones来和我们需要上采样的矩阵进行Kronecker乘积,就可以实现上采样的效果。对于前向传播过程中的下采样,imresize并没有提供在缩小图像的过程中还计算nxn块内像素的和的功能,所以没法用。一个比较好和快速的方法是用一个全一的卷积核来卷积图像,然后简单的通过标准的索引方法来采样最后卷积结果。例如,如果下采样的域是2×2的,那么我们可以用2×2的元素全是1的卷积核来卷积图像。然后再卷积后的图像中,我们每个2个点采集一次数据,y=x(1:2:end,1:2:end),这样就可以得到了两倍下采样,同时执行求和的效果。

对于第三个问题,实际上有些人以为Matlab中对sigmoid函数进行inline的定义会更快,其实不然,Matlab与C/C++等等语言不一样,Matlab的inline反而比普通的函数定义更非时间。所以,我们可以直接在代码中使用计算sigmoid函数及其导数的真实代码。

(五)CNN卷积神经网络代码理解

 

         自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。

 

本文的代码来自githup的Deep Learning的toolbox,(在这里,先感谢该toolbox的作者)里面包含了很多Deep Learning方法的代码。是用Matlab编写的(另外,有人翻译成了C++和Python的版本了)。本文中我们主要解读下CNN的代码。详细的注释见代码。

在读代码之前,最好先阅读下我的上一个博文:

Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现

http://blog.csdn.net/zouxy09/article/details/9993371

里面包含的是我对一个作者的CNN笔记的翻译性的理解,对CNN的推导和实现做了详细的介绍,看明白这个笔记对代码的理解非常重要,所以强烈建议先看懂上面这篇文章。

 

下面是自己对代码的注释:

cnnexamples.m

clear all; close all; clc;  
addpath('../data');  
addpath('../util');  
load mnist_uint8;  
  
train_x = double(reshape(train_x',28,28,60000))/255;  
test_x = double(reshape(test_x',28,28,10000))/255;  
train_y = double(train_y');  
test_y = double(test_y');  
  
%% ex1   
%will run 1 epoch in about 200 second and get around 11% error.   
%With 100 epochs you'll get around 1.2% error  
  
cnn.layers = {  
    struct('type', 'i') %input layer  
    struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer  
    struct('type', 's', 'scale', 2) %sub sampling layer  
    struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer  
    struct('type', 's', 'scale', 2) %subsampling layer  
};  
  
% 这里把cnn的设置给cnnsetup,它会据此构建一个完整的CNN网络,并返回  
cnn = cnnsetup(cnn, train_x, train_y);  
  
% 学习率  
opts.alpha = 1;  
% 每次挑出一个batchsize的batch来训练,也就是每用batchsize个样本就调整一次权值,而不是  
% 把所有样本都输入了,计算所有样本的误差了才调整一次权值  
opts.batchsize = 50;   
% 训练次数,用同样的样本集。我训练的时候:  
% 1的时候 11.41% error  
% 5的时候 4.2% error  
% 10的时候 2.73% error  
opts.numepochs = 10;  
  
% 然后开始把训练样本给它,开始训练这个CNN网络  
cnn = cnntrain(cnn, train_x, train_y, opts);  
  
% 然后就用测试样本来测试  
[er, bad] = cnntest(cnn, test_x, test_y);  
  
%plot mean squared error  
plot(cnn.rL);  
%show test error  
disp([num2str(er*100) '% error']);  

cnnsetup.m

function net = cnnsetup(net, x, y)  
    inputmaps = 1;  
    % B=squeeze(A) 返回和矩阵A相同元素但所有单一维都移除的矩阵B,单一维是满足size(A,dim)=1的维。  
    % train_x中图像的存放方式是三维的reshape(train_x',28,28,60000),前面两维表示图像的行与列,  
    % 第三维就表示有多少个图像。这样squeeze(x(:, :, 1))就相当于取第一个图像样本后,再把第三维  
    % 移除,就变成了28x28的矩阵,也就是得到一幅图像,再size一下就得到了训练样本图像的行数与列数了  
    mapsize = size(squeeze(x(:, :, 1)));  
  
    % 下面通过传入net这个结构体来逐层构建CNN网络  
    % n = numel(A)返回数组A中元素个数  
    % net.layers中有五个struct类型的元素,实际上就表示CNN共有五层,这里范围的是5  
    for l = 1 : numel(net.layers)   %  layer  
        if strcmp(net.layers{l}.type, 's') % 如果这层是 子采样层  
            % subsampling层的mapsize,最开始mapsize是每张图的大小28*28  
            % 这里除以scale=2,就是pooling之后图的大小,pooling域之间没有重叠,所以pooling后的图像为14*14  
            % 注意这里的右边的mapsize保存的都是上一层每张特征map的大小,它会随着循环进行不断更新  
            mapsize = floor(mapsize / net.layers{l}.scale);  
            for j = 1 : inputmaps % inputmap就是上一层有多少张特征图  
                net.layers{l}.b{j} = 0; % 将偏置初始化为0  
            end  
        end  
        if strcmp(net.layers{l}.type, 'c') % 如果这层是 卷积层  
            % 旧的mapsize保存的是上一层的特征map的大小,那么如果卷积核的移动步长是1,那用  
            % kernelsize*kernelsize大小的卷积核卷积上一层的特征map后,得到的新的map的大小就是下面这样  
            mapsize = mapsize - net.layers{l}.kernelsize + 1;  
            % 该层需要学习的参数个数。每张特征map是一个(后层特征图数量)*(用来卷积的patch图的大小)  
            % 因为是通过用一个核窗口在上一个特征map层中移动(核窗口每次移动1个像素),遍历上一个特征map  
            % 层的每个神经元。核窗口由kernelsize*kernelsize个元素组成,每个元素是一个独立的权值,所以  
            % 就有kernelsize*kernelsize个需要学习的权值,再加一个偏置值。另外,由于是权值共享,也就是  
            % 说同一个特征map层是用同一个具有相同权值元素的kernelsize*kernelsize的核窗口去感受输入上一  
            % 个特征map层的每个神经元得到的,所以同一个特征map,它的权值是一样的,共享的,权值只取决于  
            % 核窗口。然后,不同的特征map提取输入上一个特征map层不同的特征,所以采用的核窗口不一样,也  
            % 就是权值不一样,所以outputmaps个特征map就有(kernelsize*kernelsize+1)* outputmaps那么多的权值了  
            % 但这里fan_out只保存卷积核的权值W,偏置b在下面独立保存  
            fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;  
            for j = 1 : net.layers{l}.outputmaps  %  output map  
                % fan_out保存的是对于上一层的一张特征map,我在这一层需要对这一张特征map提取outputmaps种特征,  
                % 提取每种特征用到的卷积核不同,所以fan_out保存的是这一层输出新的特征需要学习的参数个数  
                % 而,fan_in保存的是,我在这一层,要连接到上一层中所有的特征map,然后用fan_out保存的提取特征  
                % 的权值来提取他们的特征。也即是对于每一个当前层特征图,有多少个参数链到前层  
                fan_in = inputmaps * net.layers{l}.kernelsize ^ 2;  
                for i = 1 : inputmaps  %  input map  
                    % 随机初始化权值,也就是共有outputmaps个卷积核,对上层的每个特征map,都需要用这么多个卷积核  
                    % 去卷积提取特征。  
                    % rand(n)是产生n×n的 0-1之间均匀取值的数值的矩阵,再减去0.5就相当于产生-0.5到0.5之间的随机数  
                    % 再 *2 就放大到 [-1, 1]。然后再乘以后面那一数,why?  
                    % 反正就是将卷积核每个元素初始化为[-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in + fan_out))]  
                    % 之间的随机数。因为这里是权值共享的,也就是对于一张特征map,所有感受野位置的卷积核都是一样的  
                    % 所以只需要保存的是 inputmaps * outputmaps 个卷积核。  
                    net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));  
                end  
                net.layers{l}.b{j} = 0; % 将偏置初始化为0  
            end  
            % 只有在卷积层的时候才会改变特征map的个数,pooling的时候不会改变个数。这层输出的特征map个数就是  
            % 输入到下一层的特征map个数  
            inputmaps = net.layers{l}.outputmaps;   
        end  
    end  
      
    % fvnum 是输出层的前面一层的神经元个数。  
    % 这一层的上一层是经过pooling后的层,包含有inputmaps个特征map。每个特征map的大小是mapsize。  
    % 所以,该层的神经元个数是 inputmaps * (每个特征map的大小)  
    % prod: Product of elements.  
    % For vectors, prod(X) is the product of the elements of X  
    % 在这里 mapsize = [特征map的行数 特征map的列数],所以prod后就是 特征map的行*列  
    fvnum = prod(mapsize) * inputmaps;  
    % onum 是标签的个数,也就是输出层神经元的个数。你要分多少个类,自然就有多少个输出神经元  
    onum = size(y, 1);  
  
    % 这里是最后一层神经网络的设定  
    % ffb 是输出层每个神经元对应的基biases  
    net.ffb = zeros(onum, 1);  
    % ffW 输出层前一层 与 输出层 连接的权值,这两层之间是全连接的  
    net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));  
end  

cnntrain.m

function net = cnntrain(net, x, y, opts)  
    m = size(x, 3); % m 保存的是 训练样本个数  
    numbatches = m / opts.batchsize;  
    % rem: Remainder after division. rem(x,y) is x - n.*y 相当于求余  
    % rem(numbatches, 1) 就相当于取其小数部分,如果为0,就是整数  
    if rem(numbatches, 1) ~= 0  
        error('numbatches not integer');  
    end  
      
    net.rL = [];  
    for i = 1 : opts.numepochs  
        % disp(X) 打印数组元素。如果X是个字符串,那就打印这个字符串  
        disp(['epoch ' num2str(i) '/' num2str(opts.numepochs)]);  
        % tic 和 toc 是用来计时的,计算这两条语句之间所耗的时间  
        tic;  
        % P = randperm(N) 返回[1, N]之间所有整数的一个随机的序列,例如  
        % randperm(6) 可能会返回 [2 4 5 6 1 3]  
        % 这样就相当于把原来的样本排列打乱,再挑出一些样本来训练  
        kk = randperm(m);  
        for l = 1 : numbatches  
            % 取出打乱顺序后的batchsize个样本和对应的标签  
            batch_x = x(:, :, kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));  
            batch_y = y(:,    kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));  
  
            % 在当前的网络权值和网络输入下计算网络的输出  
            net = cnnff(net, batch_x); % Feedforward  
            % 得到上面的网络输出后,通过对应的样本标签用bp算法来得到误差对网络权值  
            %(也就是那些卷积核的元素)的导数  
            net = cnnbp(net, batch_y); % Backpropagation  
            % 得到误差对权值的导数后,就通过权值更新方法去更新权值  
            net = cnnapplygrads(net, opts);  
            if isempty(net.rL)  
                net.rL(1) = net.L; % 代价函数值,也就是误差值  
            end  
            net.rL(end + 1) = 0.99 * net.rL(end) + 0.01 * net.L; % 保存历史的误差值,以便画图分析  
        end  
        toc;  
    end  
      
end  

cnnff.m

function net = cnnff(net, x)  
    n = numel(net.layers); % 层数  
    net.layers{1}.a{1} = x; % 网络的第一层就是输入,但这里的输入包含了多个训练图像  
    inputmaps = 1; % 输入层只有一个特征map,也就是原始的输入图像  
  
    for l = 2 : n   %  for each layer  
        if strcmp(net.layers{l}.type, 'c') % 卷积层  
            %  !!below can probably be handled by insane matrix operations  
            % 对每一个输入map,或者说我们需要用outputmaps个不同的卷积核去卷积图像  
            for j = 1 : net.layers{l}.outputmaps   %  for each output map  
                %  create temp output map  
                % 对上一层的每一张特征map,卷积后的特征map的大小就是   
                % (输入map宽 - 卷积核的宽 + 1)* (输入map高 - 卷积核高 + 1)  
                % 对于这里的层,因为每层都包含多张特征map,对应的索引保存在每层map的第三维  
                % 所以,这里的z保存的就是该层中所有的特征map了  
                z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);  
                for i = 1 : inputmaps   %  for each input map  
                    %  convolve with corresponding kernel and add to temp output map  
                    % 将上一层的每一个特征map(也就是这层的输入map)与该层的卷积核进行卷积  
                    % 然后将对上一层特征map的所有结果加起来。也就是说,当前层的一张特征map,是  
                    % 用一种卷积核去卷积上一层中所有的特征map,然后所有特征map对应位置的卷积值的和  
                    % 另外,有些论文或者实际应用中,并不是与全部的特征map链接的,有可能只与其中的某几个连接  
                    z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');  
                end  
                %  add bias, pass through nonlinearity  
                % 加上对应位置的基b,然后再用sigmoid函数算出特征map中每个位置的激活值,作为该层输出特征map  
                net.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});  
            end  
            %  set number of input maps to this layers number of outputmaps  
            inputmaps = net.layers{l}.outputmaps;  
        elseif strcmp(net.layers{l}.type, 's') % 下采样层  
            %  downsample  
            for j = 1 : inputmaps  
                %  !! replace with variable  
                % 例如我们要在scale=2的域上面执行mean pooling,那么可以卷积大小为2*2,每个元素都是1/4的卷积核  
                z = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid');   
                % 因为convn函数的默认卷积步长为1,而pooling操作的域是没有重叠的,所以对于上面的卷积结果  
                % 最终pooling的结果需要从上面得到的卷积结果中以scale=2为步长,跳着把mean pooling的值读出来  
                net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);  
            end  
        end  
    end  
  
    %  concatenate all end layer feature maps into vector  
    % 把最后一层得到的特征map拉成一条向量,作为最终提取到的特征向量  
    net.fv = [];  
    for j = 1 : numel(net.layers{n}.a) % 最后一层的特征map的个数  
        sa = size(net.layers{n}.a{j}); % 第j个特征map的大小  
        % 将所有的特征map拉成一条列向量。还有一维就是对应的样本索引。每个样本一列,每列为对应的特征向量  
        net.fv = [net.fv; reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3))];  
    end  
    %  feedforward into output perceptrons  
    % 计算网络的最终输出值。sigmoid(W*X + b),注意是同时计算了batchsize个样本的输出值  
    net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));  
  
end  

cnnbp.m

function net = cnnbp(net, y)  
    n = numel(net.layers); % 网络层数  
  
    %  error  
    net.e = net.o - y;   
    %  loss function  
    % 代价函数是 均方误差  
    net.L = 1/2* sum(net.e(:) .^ 2) / size(net.e, 2);  
  
    %%  backprop deltas  
    % 这里可以参考 UFLDL 的 反向传导算法 的说明  
    % 输出层的 灵敏度 或者 残差  
    net.od = net.e .* (net.o .* (1 - net.o));   %  output delta  
    % 残差 反向传播回 前一层  
    net.fvd = (net.ffW' * net.od);              %  feature vector delta  
    if strcmp(net.layers{n}.type, 'c')         %  only conv layers has sigm function  
        net.fvd = net.fvd .* (net.fv .* (1 - net.fv));  
    end  
  
    %  reshape feature vector deltas into output map style  
    sa = size(net.layers{n}.a{1}); % 最后一层特征map的大小。这里的最后一层都是指输出层的前一层  
    fvnum = sa(1) * sa(2); % 因为是将最后一层特征map拉成一条向量,所以对于一个样本来说,特征维数是这样  
    for j = 1 : numel(net.layers{n}.a) % 最后一层的特征map的个数  
        % 在fvd里面保存的是所有样本的特征向量(在cnnff.m函数中用特征map拉成的),所以这里需要重新  
        % 变换回来特征map的形式。d 保存的是 delta,也就是 灵敏度 或者 残差  
        net.layers{n}.d{j} = reshape(net.fvd(((j - 1) * fvnum + 1) : j * fvnum, :), sa(1), sa(2), sa(3));  
    end  
  
    % 对于 输出层前面的层(与输出层计算残差的方式不同)  
    for l = (n - 1) : -1 : 1  
        if strcmp(net.layers{l}.type, 'c')  
            for j = 1 : numel(net.layers{l}.a) % 该层特征map的个数  
                % net.layers{l}.d{j} 保存的是 第l层 的 第j个 map 的 灵敏度map。 也就是每个神经元节点的delta的值  
                % expand的操作相当于对l+1层的灵敏度map进行上采样。然后前面的操作相当于对该层的输入a进行sigmoid求导  
                % 这条公式请参考 Notes on Convolutional Neural Networks  
                % for k = 1:size(net.layers{l + 1}.d{j}, 3)  
                    % net.layers{l}.d{j}(:,:,k) = net.layers{l}.a{j}(:,:,k) .* (1 - net.layers{l}.a{j}(:,:,k)) .*  kron(net.layers{l + 1}.d{j}(:,:,k), ones(net.layers{l + 1}.scale)) / net.layers{l + 1}.scale ^ 2;  
                % end  
                net.layers{l}.d{j} = net.layers{l}.a{j} .* (1 - net.layers{l}.a{j}) .* (expand(net.layers{l + 1}.d{j}, [net.layers{l + 1}.scale net.layers{l + 1}.scale 1]) / net.layers{l + 1}.scale ^ 2);  
            end  
        elseif strcmp(net.layers{l}.type, 's')  
            for i = 1 : numel(net.layers{l}.a) % 第l层特征map的个数  
                z = zeros(size(net.layers{l}.a{1}));  
                for j = 1 : numel(net.layers{l + 1}.a) % 第l+1层特征map的个数  
                     z = z + convn(net.layers{l + 1}.d{j}, rot180(net.layers{l + 1}.k{i}{j}), 'full');  
                end  
                net.layers{l}.d{i} = z;  
            end  
        end  
    end  
  
    %%  calc gradients  
    % 这里与 Notes on Convolutional Neural Networks 中不同,这里的 子采样 层没有参数,也没有  
    % 激活函数,所以在子采样层是没有需要求解的参数的  
    for l = 2 : n  
        if strcmp(net.layers{l}.type, 'c')  
            for j = 1 : numel(net.layers{l}.a)  
                for i = 1 : numel(net.layers{l - 1}.a)  
                    % dk 保存的是 误差对卷积核 的导数  
                    net.layers{l}.dk{i}{j} = convn(flipall(net.layers{l - 1}.a{i}), net.layers{l}.d{j}, 'valid') / size(net.layers{l}.d{j}, 3);  
                end  
                % db 保存的是 误差对于bias基 的导数  
                net.layers{l}.db{j} = sum(net.layers{l}.d{j}(:)) / size(net.layers{l}.d{j}, 3);  
            end  
        end  
    end  
    % 最后一层perceptron的gradient的计算  
    net.dffW = net.od * (net.fv)' / size(net.od, 2);  
    net.dffb = mean(net.od, 2);  
  
    function X = rot180(X)  
        X = flipdim(flipdim(X, 1), 2);  
    end  
end  

cnnapplygrads.m

function net = cnnapplygrads(net, opts)  
    for l = 2 : numel(net.layers)  
        if strcmp(net.layers{l}.type, 'c')  
            for j = 1 : numel(net.layers{l}.a)  
                for ii = 1 : numel(net.layers{l - 1}.a)  
                    % 这里没什么好说的,就是普通的权值更新的公式:W_new = W_old - alpha * de/dW(误差对权值导数)  
                    net.layers{l}.k{ii}{j} = net.layers{l}.k{ii}{j} - opts.alpha * net.layers{l}.dk{ii}{j};  
                end  
            end  
            net.layers{l}.b{j} = net.layers{l}.b{j} - opts.alpha * net.layers{l}.db{j};  
        end  
    end  
  
    net.ffW = net.ffW - opts.alpha * net.dffW;  
    net.ffb = net.ffb - opts.alpha * net.dffb;  
end  

cnntest.m

function [er, bad] = cnntest(net, x, y)  
    %  feedforward  
    net = cnnff(net, x); % 前向传播得到输出  
    % [Y,I] = max(X) returns the indices of the maximum values in vector I  
    [~, h] = max(net.o); % 找到最大的输出对应的标签  
    [~, a] = max(y);     % 找到最大的期望输出对应的索引  
    bad = find(h ~= a);  % 找到他们不相同的个数,也就是错误的次数  
  
    er = numel(bad) / size(y, 2); % 计算错误率  
end  

 

(六)Multi-Stage多级架构分析

 

         自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。

 

本文的论文来自:

Kevin Jarrett, Koray Kavukcuoglu, Marc’Aurelio Ranzato, and Yann LeCun, ”What is the Best Multi-Stage Architecture for Object Recognition?”, in Proc. International Conference on Computer Vision (ICCV’09), 2009

这里面还提供了一个精简版的Matlab代码。实现的是random convolutional filters 和linear logistic regression classifier的两级目标识别系统。

下面是自己对其中的一些知识点的理解:

 

《What is the Best Multi-Stage Architecture for Object Recognition?》

         对目标识别怎样的多级架构Multi-Stage Architecture还是最好的?在当前的很多目标识别系统中,特征提取阶段一般由一组滤波器,再进行非线性变换和一些类型的特征pooling层组成。大部分系统都使用一级特征提取,这时候的滤波器是hard-wired(人工选择的,硬连线的,参数不可学习调整)的,或者使用两级,这时候其中的一级或者两级的滤波器都可以通过监督或者非监督的方式学习得到。

本文关注三个问题:

1)滤波器组后面接的non-linearities非线性算法是如何影响识别的准确性的?

2)通过监督或者非监督方式学习到的滤波器组是否比随机的滤波器组或者人工指定的滤波器要好?

3)与仅有一级的特征提取对比,两级的特征提取是否还有其他优点?

我们证明了:

1)使用包含校正和局部对比度归一化的非线性算子对增加目标识别的准确性来说有很大帮助。

2)两级的特征提取比一级的要好。准确率更高。

3)惊喜的是,用随机初始化的滤波器组的两级系统却可以在Caltech这个数据中达到63%的识别率。当然,这里面包含了合适的非线性算子和pooling层。

4)经过监督微调,系统在NORB数据库上达到当前领先水平。而且非监督预训练后再加监督微调可以在Caltech这个数据库中达到更好的准确率(大于63%)。然后在没有处理过的MNIST数据库中,可以达到目前我们知道的最低的0.53%的错误率。

 

一、概述

在过去这几年,对于目标识别来说,出现了很多不错的特征描述子。很多方法都是把输入图像划分为一个个规律排列的密集的patch,然后提取这些patch的特征。再以某种方式组合这些patch的特征作为这个输入图像的特征。概括的来说,这些系统的很大一部分都是这样的一个特征提取过程:输入经过一个滤波器组filter bank(一般是基于方向性的边缘检测器),再经过一个非线性算子non-linear operation(quantization, winner-take-all, sparsification, normalization, and/or point-wise saturation),然后用一个pooling操作(把实空间或者特征空间邻域的值通过一个max, average, or histogramming operator)来绛维和得到一定的不变性。例如俺们熟知的SIFT特征,它先通过对每个小patch经过方向性边缘检测器,然后用winner-take-all算子来获取最显著的方向。最后,在更大块的patch上面统计局部方向的直方图,pooling成一个稀疏向量。

对于一层特征提取的系统,也就是提取到了上面的这个特征后,例如SIFT,HOG等,然后直接接一个监督学习的分类器,就构成了一个目标识别系统。还有一些模型会使用两级或者更多级的特征提取器,然后再接一个监督学习分类器来构成一个比较复杂的目标识别系统。这些系统本质的差别在于:有一个或者多个特征提取层、滤波器组后使用的非线性算子、滤波器组的得到(人工选择、非监督学习还是监督学习)和顶层的分类器的选择(线性分类器还是更复杂的分类器)。

一般对滤波器组的选择是Gabor小波,还有人选择一些简单的方向性检测滤波器组,也就是梯度算子,例如SIFT和HOG。还有一些直接通过非监督学习方法直接从训练数据中学习这些滤波器组。当在自然图像中训练的时候,学到的滤波器也是类似于Gabor边缘检测的东西。特征学习方法的一个好处就是它可以分级的学习特征。因为我们具有一定的先验知识,觉得第一级的特征就应该是边缘检测器,但第二层特征又应该是什么呢?人就没有这个类似的先验知识了。所以就比较难人工设计一个比较好二级特征提取器。所以说,二级或者多级特征必须让系统自己学习。现在出现的方法也很多了,有监督的,非监督的,或者两者联合的。

咋一看,用像Caltech-101这些非常少的训练数据库(这个数据库要识别101类的物体,但每类只提供了很少的有标签训练数据)只用监督学习算法来训练一个完整的系统显得有点天真和不被看好,因为模型参数的个数比训练样本的个数都要多很多。所以很多人觉得只有非常认真的训练或者人工挑选好的滤波器组才可以有好的识别性能,然后再考虑非线性算子的选择。其实,这些观点,都是wrong的。

 

二、模型架构

这部分讲述如何去构建一个分级的特征提取和分类系统。分级通过堆叠一个或者多个特征提取阶段,每个阶段包括一个滤波器组合层、非线性变换层和一个pooling层,pooling层通过组合(取平均或者最大的)局部邻域的滤波器响应,因而达到对微小变形的不变性。

1、滤波器组层Filter Bank Layer-FCSG

FCSG一般包括三部分:一组卷积滤波器(C)、再接一个sigmoid/tanh非线性变换函数(S),然后是一个可训练的增益系数(G)。分别对应下面的三个运算:

2、校正层Rectification Layer-Rabs

只是简单的一个取绝对值的操作(如果是tanh,则存在负的值,但在图像中负值是不表示的,而对于卷积来说,里面的都是绝对值越大,非线性函数输出的绝对值最大,实际意义是一样的。卷积是越相似,输出值越大)。除了绝对值算子外,我们还试过了其他的非线性算子,产生的效果差不多。

3、局部对比度归一化层Local Contrast Normalization Layer-N:

该模块主要进行的是局部做减和做除归一化,它会迫使在特征map中的相邻特征进行局部竞争,还会迫使在不同特征maps的同一空间位置的特征进行竞争。在一个给定的位置进行减法归一化操作,实际上就是该位置的值减去邻域各像素的加权后的值,权值是为了区分与该位置距离不同影响不同,权值可以由一个高斯加权窗来确定。除法归一化实际上先计算每一个特征maps在同一个空间位置的邻域的加权和的值,然后取所有特征maps这个值的均值,然后每个特征map该位置的值被重新计算为该点的值除以max(那个均值,该点在该map的邻域的加权和的值)。分母表示的是在所有特征maps的同一个空间邻域的加权标准差。哦哦,实际上如果对于一个图像的话,就是均值和方差归一化,也就是特征归一化。这个实际上是由计算神经科学模型启发得到的。(这里自己有点理解,请见本文的第四节)

4、平均池化和子采样层Average Pooling and Subsampling Layer -PA

该层的作用是使得提取的特征对微小变形鲁棒,和视觉感知中的复杂细胞的角色差不多。采样窗口所有值取平均得到下采样层的值。

5、最大值池化和子采样层Max-Pooling and Subsampling Layer -PM

可以用任何一种对称的pooling操作实现对提取的特征的平移不变性。最大池与平均池相似,只是最大取代了平均。一般来说,池化窗口是不重叠的。

 

三、实验与结论

该文做了很多实验来验证不同的模型架构的性能(组合上面的不同的层)。这里就不列举实验结果了,可以回原文查看。这里就直接回答一开始的那几个问题吧:

1)滤波器组后面接的non-linearities非线性算法是如何影响识别的准确性的?

俺们的实验结论是,简单的矫正过的非线性算子会提高识别性能。原因可能有二。a)特征的对立polarity(也就是负值的特征)和目标的识别是无关的。b)在采用平均池化的时候,矫正层的添加会消去邻近的滤波器输出之间的cancellations,因为如果没有矫正,平均下采样只会传播输入的噪声。另外,局部归一化层也会增加性能,因为它可以使监督学习算法更快,也许是因为所有的变量都具有相似的方差了(与其他白化和去相关的方法的优点一样),这样会加快收敛速度。

2)通过监督或者非监督方式学习到的滤波器组是否比随机的滤波器组或者人工指定的滤波器要好?

实验结果很惊喜,在两级系统中采样随机滤波器组在Caltech-101中居然达到了挺高的62.9%的识别率,但在NORB数据库中就显得有点低调了,可能这种情况只会在训练样本集较少的时候才出现。另外,非监督预训练接个监督微调会有最好的效果,尽管比单纯的全部使用监督会差点。

3)与仅有一级的特征提取对比,两级的特征提取是否还有其他优点?

实验证明,两级比一级好。我们这里,两级系统的性能和最好的一级系统的性能:SIFT特征+PMK-SVM分类器相媲美,也许PM Kernel还隐藏着实现了我们的第二级特征提取的功能。

 

四、关于local contract normalization

这里对这个东西再啰嗦一下。local contract normalization 这个归一化包括两个部分:局部做减和局部做除(local subtractive and divisive normalizations)。我的理解:自然图像存在低阶和高阶的统计特征,低阶(例如二阶)的统计特征是满足高斯分布的,但高阶的统计特性是非高斯分布。图像中,空间上相邻的像素点有着很强的相关性。而对于PCA来说,因为它是对协方差矩阵操作,所以可以去掉输入图像的二阶相关性,但是却无法去掉高阶相关性。而有人证明了除以一个隐含的变量就可以去除高阶相关性。你可以理解为一张图像x的像素值是一个随机变量,它由两个独立的随机变量相乘得到,分别是二阶量和高阶量相乘,二阶量的相关性可以由PCA去掉,然后高阶量(这个是隐含的,需要通过MAP最大后验估计等方法估计出来)直接用x除掉就好了。

有论文的操作是这样:

对输入图像的每一个像素,我们计算其邻域(例如3×3窗口)的均值,然后每个像素先减去这个均值,再除以这个邻域窗口(例如3×3窗口)拉成的9维向量的欧几里德范数(如果这个范数大于1的时候才除:这个约束是为了保证归一化只作用于减少响应(除以大于1的数值变小),而不会加强响应(除以小于1的数值变大))。也有论文在计算均值和范数的时候,都加入了距离的影响,也就是距离离该窗口中心越远,影响越小,例如加个高斯权重窗口(空间上相邻的像素点的相关性随着距离变大而变小)。

其实在这里,很多自己也还不清楚,所以上面的不一定正确,仅供参考。还望明白的人也指点一下。谢谢。

关于local contract normalization可以参考以下两篇文章:

S. Lyu 等:Nonlinear image representation using divisive normalization.

N. Pinto等: Why is real-world visual object recognition hard?

 

(七)深度网络高层特征可视化

 

         自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。

 

本文的论文来自:

Dumitru Erhan, Aaron Courville, Yoshua Bengio, and Pascal Vincent. Visualizing Higher Layer Features of a Deep Network. Spotlight presentation and poster at the ICML 2009 Workshop on Learning Feature Hierarchies, Montréal, Canada

下面是自己对其中的一些知识点的理解:

 

《Visualizing Higher-Layer Features of a Deep Network》

         Deep Learning很吸引人,也很玄乎的一个点就是大家都说它可以提取到分级的逐层抽象的特征。但对我们来说,总是耳听为虚,眼见为实。所以,每当我们训练完一个深度模型后,我们还特别想把这个深度模型学到的东西给可视化出来,好弄明白它到底学到了什么东西,是不是有意义的,是不是像传说中的那样神奇。那怎样有意义的可视化呢?对了,我们用deep net来做什么的了?来提取特征的。那它提取什么特征呢?如果它像我们所说的提取的是底层到高层的特征,例如边缘,到形状,到目标等等,那就证明我们的目的是达到的。

另外,在对深度模型定量的分析上,我们还需要一个定性的分析方法去比较不同的深度架构学习到的特征。本文的目的就是寻找深度模型所提取到的高级特征的比较好的定性解释。我们通过在几个视觉数据库中训练堆叠降噪自动编码器和DBN深信网络,并比较几种不同的高级特征可视化的方法。虽然这些特征的显示是在单元级别上面的,也许有违于直观理解,但它很容易实现,而且在不同方法上面得到的结果也是一致的。我们希望这些方法可以让研究者更清楚的理解深度学习是如何工作和为什么工作的。本文中,介绍三种可视化的方法:激活最大化、采样和线性组合法。

 

一、概述

一些深度架构(例如DBNs)与generative procedure生成过程存在密切的联系,所以我们可以利用这个生成过程来瞥见一个单独的隐层神经元所表示的是什么。在这里,我们研究其中一个这样的采样方法。然而,一方面有时候我们很难去获得可以完全覆盖波尔兹曼或者RBM分布的样本,另一方面这种基于采样的可视化方法没办法运用到其他基于自动编码器的深度架构模型或者在每个层嵌入保留相似性的半监督学习模型上。

一个典型的对深度架构第一层所提取的特征的定性分析方法是通过观察由模型所学习到的这些滤波器。这些滤波器是输入层到第一层的权值矩阵的权值。他们是由输入空间来表示。这样就非常方便了,因为输入是图像或者小波,他们是可以被可视化的。

一般来说,当在数字的数据集中训练的时候,这些滤波器可以被可视化为一些不同的数字的笔画的检测器。当在自然图像中训练时,这些滤波器就相当于不同的边缘检测器了(小波滤波器)。

本文的目标是研究一种可以可视化深度架构中任意层中的任意神经元所计算或者提取的特征的一种方法。为了达到这个目的,我们需要在输入空间(图像)中实现可视化,并且需要找到一种有效地计算方法去计算,然后使他具有通用性,也就是在不同的深度网络模型中都可以使用。在这里我们探究了几种方法。然后我们在两个数据集中对他们进行了定性的对比,研究他们之间的联系性。

在实验过程中,一个非常让人惊喜的地方是,每一个隐层的节点对输入图像的响应,也就是输入空间的函数,居然是单峰的,也就是说,不管你随机地在什么地方初始化,最终都可以可靠地找到这个最大值,这对于迭代寻优来说是非常爽的,而且它可以将每个节点做了什么公开于天下。一览无余。

 

二、模型

我们这里讨论两个模型,这两个模型都是在深度架构中比较常见的。第一个是DBNs,它是通过贪婪的堆叠多个RBM层得到的。我们先通过CD算法训练一个RBM,然后固定它,再将其输出做了另一个RBM的输入,去训练这一个隐层的RBM。这个过程可以通过不断重复,然后得到一个服从训练分布的非监督模型的深度架构。需要注意的一点是,它是一个数据的生成模型,可以很容易的通过一个已经训练好的模型去采集样本。

我们要讨论的第二个模型是降噪自动编码器,它是传统编码器的随机变种,它具有更强的能力。它不会学习到恒等函数(也就是h(x)=x,这个函数满足了零重构误差,但这是没有意义的)。它被强制去学习输入的本质表达。

它训练的关键是需要不断的提高生成模型的似然函数的下界。它比传统的编码机要牛逼,如果堆叠成一个深度监督架构的话,与RBMs的性能相当,甚至更牛。另一个在隐层单元比输入单元要多的情况下避免学习恒等函数的方式是对隐层code增加稀疏约束。

我们在这里概括下Stacked Denoising Auto-Encoders的训练方法。对于一个输入x,我们给它添加随机的污染或者噪声,然后训练让降噪自动编码机学习重构原始的输入x。每个自动编码机的输出都是一个码矢h(x)。在这里和传统的神经网络一样,h(x) = sigmoid(b + W x)。这里我们用C(x)表示x的一个随机污染,我们让Ci (x) = xi或 0,换句话说,就是我们随机的在原来的x中挑选一个固定大小的子集,把他们设置为0。我们还可以添加椒盐噪声,随机的选择一定大小的子集,设置为Bernoulli(0.5)。

在实际的图像中,对于特定像素i的像素输入xi和它的重构像素xii都可以看成该像素的伯努利概率:该位置的像素被涂成黑色的概率。我们通过交叉熵来比较该像素位置i的原始输入xi和它的重构像素xii的分布的相似性。然后需要对所有像素求和。另外,只有当输入和重构的值都在[0,1]这个范围的时候,伯努利分布才有意义。另外的选择就是选择高斯分布,这时候对应的就是均方误差规则了。

 

三、Maximizing the activation 最大化激活值

第一个思想是很简单的:我们寻找使一个给定的隐层单元的激活值最大的输入模式。因为第一层的每一个节点的激活函数都是输入的线性函数,所以对第一层来说,它的输入模式和滤波器本身是成比例的。

我们回顾下诺贝尔医学奖David Hubel 和Torsten Wiesel 的那个伟大的实验。他们发现了一种被称为“方向选择性细胞(Orientation Selective Cell)”的神经元细胞。当瞳孔发现了眼前的物体的边缘,而且这个边缘指向某个方向时,这种神经元细胞就会活跃。也就是说某个“特定方向神经细胞”只对这个特定方向的图像边缘存在激励或者兴奋。通俗点说就是如果我这个神经元是提取这个特征的,那么如果你这个图像满足这个特征(可以理解为和它很相似),那么神经元的输出就很大,会兴奋。(有资料表明,人的大脑高层会存在“祖母细胞”,这类细胞的某一个细胞只对特定一个目标兴奋,例如你大脑里面有个能记忆你女朋友的细胞,然后一旦你女朋友出现在你面前,你这个细胞就会兴奋,告诉大脑,啊,这个是我的女朋友!)我们如果了解过模板卷积,那么我们知道如果某个卷积模板与图像中模块越相似,那么响应就越大。相反,如果某个图像输入使得这个神经元输出激励值最大,那么我们就有理由相信,这个神经元就是提取了和这个输入差不多的特征。所以我们寻找可以使这个神经元输入最大的那个x就是我们可以可视化并且有意义的表达这个神经元学习到的特征了。

用数学属于来表述就是,一旦完成网络训练后,参数W是确定的了,那么我们就可以寻找使得这个神经元最大化的激活值对应的x了,也就是:

        但这个优化问题通常是一个非凸优化问题,也就是是存在很多局部最小值。最简单的方法就是通过梯度下降去寻找到一个局部最小值。这会出现两种场景:一是从不同的随机值初始化开始最后都迭代得到相同的最小值,二是得到两个或者更多的局部最小值。不管是哪种情况,该神经节点提取的特征都可以通过找到的一个或者多个最小值进行描述。如果有多个最小值,那么可以寻找使激活值最大的或者将所有的进行平均,或者把所有的都显示出来。

 

四、Sampling from a unit of a Deep Belief Network 从DBN的一个节点中采样

我们这里用一个j层的Deep Belief Network来说明。这里层j和层j-1构成一个RBM,我们可以通过块Gibbs采样方法来对分布p(hj−1|hj ) 和 p(hj |hj−1)进行连续采样(这里hj表示层j的所有的二值节点构成的向量)。在这个马尔科夫链中,我们限制一个节点hij为1,其他节点都是0 。然后在DBN中,我们从层j-1一直执行top-down采样一直到输入层。这样就会产生一个分布pj(x|hij=1)。也就是说我们用分布pj(x|hij=1)来描述hij,和第三部分相似,我们可以通过从这个分布生成或者采样足够多的样本来描述这个隐层节点或者通过计算期望E[x|hij=1]来概括这个信息。这个方法只有一个参数需要确定,也就是我们要采样多少个样本来估计这个期望值。

在最大化激活值和计算期望E[x|hij=1] 两种方法之间是存在一个非常微妙的联系的。由条件期望的定义我们可以知道:

        我们考虑一个极端的情况,就是这个分布全部集中在x+这一个点,这时候pj(x|hij=1)约等于δx+ (x)。所以其期望E[x|hij=1]= x+

事实上,我们观测到的是,尽管采样得到的样本或者它们的平均可能看起来很像训练样本,但是由激活值最大化获得的图像反而看起来更像图像的部分。所以后者可能更能准确的表达一个特定的节点做了什么。

 

五、Linear combination of previous layers’ filters 上层滤波器的线性组合

Lee等(2008)在他们的论文中展示了一种可视化第二层隐层的节点的特征的方法。他们是基于一个神经元节点可以由和其强连接的上一层的滤波器的组合来描述的假设的。该层的某个节点的可视化可以通过上一层滤波器的线性加权得到,每个滤波器的权值就是该滤波器与这个节点之间的连接权值。

他们用自然图像训练了一个具有激活值稀疏约束的DBNs,然后用这种方法显示它在第二层学习到的是一个角的检测器。Lee拓展了这个方法去可视化第三层学习到的东西:通过简单对第二层的滤波器进行加权,权值是第二层滤波器到第三层该节点的连接权值,而且选择的是最大的那个权值。

这种方法是简单而有效的。但是它的一个缺点就是,对在每一层如何自动的选择合适的滤波器个数没有一个清晰的准则。另外,如果只是选择少有的几个与第一次连接最强的滤波器的话,我们有可能得到的是失望的毫无意思的混杂图像,这是因为上面这种方法本质上忽略了上一层其他没有被选择的滤波器。另一方面,这种方法还忽略了层间的非线性,这种非线性在模型中确是非常重要的一部分。

但值得注意的是,实际上最大化一个节点的激活值的梯度更新方法与线性加权组合之前也存在微妙的联系。例如,对于第2层的第i个节点hi2= v’ sigmoid(W x),这里v是该节点的权值,W是第一层的权值矩阵。然后∂hi2/∂x = v’diag(sigmoid(W x) ∗ (1 −sigmoid(W x)))W。这里*是逐元素相乘。diag是一个从一个向量创建一个对角矩阵的算子。1是个全一向量。如果第一层的节点没有饱和saturate,那么∂hi2/∂x就会大体的指向v’W的方向,可以用vi中的元素的绝对值的最大的那个来近似。(我也还没懂)

 

六、实验

6.1、Data and setup

         我们在MINST手写体数据库和自然图像数据库中分别训练DBN和DSAE两种模型。然后再用三种方法来可视化其中一些层的一些节点所提取到的特征。

6.2、Activation Maximization

在MNIST手写体数据库中用Activation maximization得到的可视化效果。左:分别是第一层(第一列)、第二层(第二列)和第三层(第三列)的36个节点所提取的特征,然后第一行是DBN训练得到的,第二行是SDAE训练得到的。右:对于DBN的第三层的一个节点,然后从9个随机初始化的值开始迭代,都可以得到同一个结果。

一般来说,在第三层的激活函数应该是一个关于它的输入的高度非凸的函数,但是不知道是我们一直很幸运还是说恰好我们在MNIST或者自然图像中训练网络是一种很特殊的情况,我们惊喜的发现,这些节点的激活函数既然还是趋向于更加的“单峰化”。

 

6.3、Sampling a unit

左:从MNIST数据库中训练DBN得到的,然后采样得到第二层的其中6个节点的可视化效果。右:从自然图像中训练的。每一行的样本是每一个节点的分布采样得到的,然后每行的均值在第一列。

值得注意的是,采样法和激活值最大化方法的结果不同,采样得到的(或者说分布生成的)样本更有可能是训练样本(数字或者patches)的潜在分布。激活值最大化方法是产生很多特征,然后要我们去决定哪些样本会匹配或者符合这些特征;采样方法是产生很多样本,然后由我们决定这些样本存在哪些共同的特征。从这个层面上面讲,这两种方法是互补的。

 

6.4、Comparison of methods

在这里,我们对比可视化的三种方法:激活最大化、采样和线性组合法。

我们这里展示了三种方法:左:采样;中:上次滤波器的线性组合;右:最大化节点激活值。在MINST数据库(top)和自然图像(down)上训练DBN模型。然后可视化第二层的36个节点。

在这三种方法中,线性组合方法是以前提出来的,其他两种是我们以现有的知识吗,集当前的智慧得到的。

 

       附:原论文中,提到了更多的内容,深入了解请参考原论文。另外,自己还没来得及去实现一些,等以后可以了再放上来和大家交流下。也希望大家已经实现了的也可以共享下,谢谢。

 

(八)Deep Learning最新综述

 

自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。

 

本文的论文来自:

Bengio, Y., Courville, A., & Vincent, P. (2012). Representation Learning: A Review and New Perspectives

这是一篇Deep Learning比较新的综述。但是好长啊,读完了也好多不懂,之前边读边翻译了前面两节,先摆上来。后面有时间再更新后续的了。另外,因为水平有限,有些地方翻译和理解可能有错误,还望大家指正。谢谢。

另外,对于Deep Learning这里有个reading-list,感觉很不错。大家可以参考里面的list来学习。

http://deeplearning.net/reading-list/

 

下面是自己对其中的一些知识点的理解:

 

《Representation Learning: A Review and New Perspectives》

摘要

机器学习算法的成功主要取决于数据的表达data representation。我们一般猜测,不同的表达会混淆或者隐藏或多或少的可以解释数据不同变化的因素。尽管特定的领域知识可以有助于设计或者选择数据的表达,但通过一般的先验知识来学习表达也是有效的。而且,人工智能AI的要求也迫使我们去寻找更强大的特征学习算法去实现这些先验知识。

本文回顾非监督特征学习和深度学习领域的一些近期工作,包括概率模型的发展、自动编码机、流行学习和深度网络。通过这些分析,可以激发我们去思考一些长久以来尚未解决的问题,例如如何学习好的表达?如何选择适合的目标函数以便于计算表达?还有表达学习、密度估计和流行学习他们之间是否具有一定的几何联系?

 

1、介绍

众所周知,机器学习方法的性能很大程度上取决于数据表达(或者特征)的选择。也正是因为这个原因,为了使得机器学习算法有效,我们一般需要在数据的预处理和变换中倾注大部分的心血。这种特征工程的工作非常重要,但它费时费力,属于劳动密集型产业。这种弊端揭露了目前的学习算法的缺点:在提取和组织数据的区分性信息中显得无能为力。特征工程是一种利用人的智慧和先验知识来弥补上述缺点的方法。为了拓展机器学习的适用范围,我们需要降低学习算法对特征工程的依赖性。这样,就可以更快的构建新的应用,更重要的是,在人工智能AI领域迈出了一大步。人工智能最基本的能力就是能理解这个世界(understand the world around us)。我们觉得,只有当它能学会如何辨别和解开在观测到的低级感知数据中隐含的解释性因素时才能达到这个目标。

这篇文章主要讲述表达学习representation learning的,或者说学习一种数据的表达使得提取对构建分类器或者预测器有用的信息更加容易。以概率模型为例,一个好的表达总能捕捉观测输入数据的隐含解释性因素的后验概率分布。一个好的表达作为监督预测器的输入也是有用的。在表达学习的那么多不同的方法中,本文主要聚焦在深度学习方法:通过组合多个非线性变换,以得到更抽象和最终更有效的表达。这里,我们综述这个快速发展的领域,其中还会强调当前进展中的特定问题。我们认为,一些基本问题正在驱动该领域的研究。特别的,是什么导致一种表达优于另一种表达?我们应该怎样去计算它的表达,换句话来说就是,我们应该如何进行特征提取?还有就是为了学习好的表达,怎样的目标函数才是适合的?

 

2、我们为什么要关心表达学习?

表达学习(亦被江湖称作深度学习或者特征学习)已经在机器学习社区开辟了自己的江山,成为学术界的一个新宠。在一些顶尖会议例如NIPS和ICML中都有了自己的正规军(研究它的workshops),今年(2013)还专门为它搞了一个新的会议,叫ICLR(International Conference on Learning Representations),可见它在学术界得到的宠爱招人红眼。尽管depth(深度)是这个神话的一个主要部分,但其他的先验也不能被忽视,因为有时候,先验知识会为表达的学习献上一臂之力,画上点睛之笔,更容易地学习更好的表达,这在下一章节中将会详细讨论。在表达学习有关的学术活动中最迅速的进展就是它在学术界和工业界都得到了经验性的显著性的成功。下面我们简单的聚焦几点。

 

2.1、Speech Recognition and Signal Processing语音识别与信号处理

语音也是神经网络诞生时其最早的一个应用之一,例如卷积(或者时延)神经网络(Bengio在1993年的工作)。当然,在HMM在语音识别成功之后,神经网络也相对沉寂了不少。到现在,神经网络的复活、深度学习和表达学习的运用在语音识别领域可谓大展拳脚,重展雄风,在一些学术派和工业派人士(Dahlet al., 2010; Deng et al., 2010; Seide et al., 2011a; Mohamedet al., 2012; Dahl et al., 2012; Hinton et al., 2012)的努力下取得了突破性的成果,使得这些算法得到更大范围的应用,并且实现了产品化。例如,微软在2012年发布了它们的语音识别MAVIS (Microsoft Audio Video Indexing Service)系统的一个新版本,这个版本是基于深度学习的(Seide et al., 2011a)。对比现有的一直保持领先位置的高斯混合模型的声学建模方法,他们在四个主要的基准测试集中把错误率降低了30%左右(例如在RT03S数据库中从 27.4%的错误率降到18.5%)。在2012年,Dahl等人再次书学神话,他在一个小的大词汇量语音识别基准测试集中(Bing移动商业搜索数据库,语音长40小时)的错误率降到16%与23%之间。

表达学习算法还被应用的音乐方面上,在四个基准测试集中,比当前领先的polyphonic transcription (Boulanger-Lewandowskiet al., 2012)在错误率上取得了5%到30%之间的提升。深度学习还赢得了MIREX (Music Information Retrieval)音乐信息检索竞赛。例如2011年的音频标注audio tagging上(Hamelet al., 2011)。

 

2.2、Object Recognition目标识别

在2006年,深度学习的开始,主要聚焦在MNIST手写体图像分类问题上(Hinton et al.,2006; Bengioet al., 2007),它冲击了SVMs在这个数据集的霸主地位(1.4%的错误率)。最新的记录仍被深度网络占据着:Ciresanet al.(2012)声称他在这个任务的无约束版本(例如,使用卷积架构)的错误率是0.27%,为state-of-the-art。而Rifaiet al.(2011c)在MNIST的knowledge-free版本中保持着0.81%的错误率,为state-of-the-art。

在最近几年,深度学习将其目光从数字识别移到自然图像的目标识别,而最新的突破是在ImageNet数据库中把领先的26.1%的错误率拉低到15.3% (Krizhevskyet al., 2012)。

 

2.3、Natural Language Processing自然语言处理

除了语音识别,深度学习在自然语言处理中也有很多应用。symbolic 数据的分布式表达由Hinton在1986年引入,在2003年由Bengio等人在统计语言模型中得到第一次的发展,称为神经网络语言模型neural net language models (Bengio,2008)。它们都是基于学习一个关于每个单词的分布式表达,叫做word embedding。增加一个卷积架构,Collobertet al.(2011)开发了一个SENNA系统,它在语言建模、部分语音标记、chunking(节点识别)、语义角色标记和句法分解中共享表达。SENNA接近或者超于目前的在这些任务中的当前领先方法。但它比传统的预测器要简单和快速。学习word embeddings可以以某种方式与学习图像表达结合,这样就可以联系文本和图像。这个方法被成功运用到谷歌的图像搜索上,利用大量的数据来建立同一空间中图像与问题之间的映射(Weston et al.,2010)。在2012年,Srivastava等将其拓展到更深的多模表达。

神经网络语言模型也被通过在隐层中增加recurrence来改进(Mikolovet al., 2011)。改进效果比当下领先的平滑n-gram语言模型不仅在复杂度上降低,还降低了语音识别的错误率(因为语言模型是语音识别系统的一个重要组成部分)。这个模型还被应用到统计机器翻译上面 (Schwenk et al., 2012; Leet al., 2013),改进了复杂度和BLEU分数。递归自动编码机Recursive auto-encoders(产生recurrent网络)在全句释义检测full sentenceparaphrase detection上也达到了现有的领先水平,是以前技术的两倍F1分数(Socheret al., 2011a) 。表达学习还用到了单词歧义消除word sense disambiguation上 (Bordeset al., 2012),取得了准确率从67.8% 到 70.2%的提升。最后,它还被成功运用到sentimentanalysis (Glorotet al., 2011b; Socher et al., 2011b)上,并超越现有技术。

 

2.4、Multi-Task and Transfer Learning, Domain Adaptation多任务和迁移学习,域自适应

迁移学习(传统的机器学习假设训练数据与测试数据服从相同的数据分布。如果我们有了大量的、在不同分布下的训练数据,完全丢弃这些数据也是非常浪费的。如何合理的利用这些数据就是迁移学习主要解决的问题。迁移学习可以从现有的数据中迁移知识,用来帮助将来的学习。迁移学习(Transfer Learning)的目标是将从一个环境中学到的知识用来帮助新环境中的学习任务。)是指一个学习算法可以利用不同学习任务之间的共性来共享统计的优点和在任务间迁移知识。如下面的讨论,我们假设表达学习算法具有这样的能力,因为它可以学习到能捕捉隐含因素的子集的表达,这个子集是对每个特定的任务相关的。如图1所示。这个假设被很多的经验性结果所验证,并且展现了表达学习在迁移学习场合中同样具有优异的能力。

        图1:表达学习发现了隐含的解释性因素(中间隐层红色的点)的示意图。一些解释了输入(半监督设置),一些解释了每个任务的目标。因为这些子集间会重叠,所以会贡献统计的优点,利于generalization泛化。

给人印象深刻的是在2011年的两个迁移学习的挑战赛,都被表达学习算法夺魁。首先在由ICML2011一个workshop举办的Transfer Learning Challenge中,由无监督逐层预训练方法unsuper-vised layer-wise pre-training (Bengio, 2011; Mesnil et al.,2011)夺得。第二个挑战赛同年举办,被Goodfellow et al. (2011)夺得。在相关的domain adaptation方面,目标保持不变,但输入分布会改变(Glorot et al., 2011b; Chen et al., 2012)。在多任务学习方面multi-task learning,表达学习同样表现出了其独特的优越性(Krizhevskyet al.(2012); Collobertet al.(2011)),因为它可以在任务间共享因素。

 

发表评论

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