深度学习 金字塔模型

2017-08-14 13:35:56 jcjx0315 阅读数 10715

深度学习基本模型浅析

  1. 前言

台湾李宏毅的深度学习课程说实话讲得还是比较不错的,有需要的话还是比较推荐学习,这篇也是基于它的深度学习基本结构的讲解的总结。

 

  1. 深度学习的三个步骤

深度学习首先是我们要构建一个网络,这个网络也就是我们所说的深度学习神经网络模型。深度学习一般可以归纳为下图所示的3个步骤:

第一个步骤, 神经网络模型是一个有简单函数组成的复杂的函数,通常我们设计一个神经网络模型(结构),然后用计算机从给定的训练数据中训练得到一些参数,这些参数保证我们的模型能够在测试集中达到设计预期的效果,并且具有泛化能力。

第二个步骤,根据训练数据定义一个代价函数,通过代价函数可以评估什么参数是有效的什么参数不是很有效的,模型中什么样的函数是好的什么样的函数是不好的,怎样定义一个代价函数则是根据你的具体任务和实际的训练数据来设计。

第三个步骤,根据前面两步骤的结果找出最佳的函数,例如用梯度下降的方法找出这个最佳的函数。

 

  1. 全连接层

  1. 神经元向量

    右边的输出神经元a的上标l表示第l层的神经元,i则表示在这一层上的第i个神经元,通常用一个向量方式表示所有第l层的的所有神经元。

  2. 权值向量W

        给这两层神经元加入权值连接成一个网络后,用上标l表示从第L-1层到第L层的权值,下标ij表示从神经元j到神经元i,细心的话可以看出有点问题,为什么不是定义为输入在左边输出在右边,而是定义为输入在右边输出在左边,这明显不符合一般的理解,但是这样表示是有一定的深意的,后面我们将讲到这样做有什么好处。

        然后我们看到右下角的权值矩阵,他表示第L层相对于L-1层的权值矩阵,上图中只是两列神经元节点的两两连接就有ij个权值,例如作为输出的右边第一个神经元对应左边每个神经元都有一个权值,左边有j个神经元对应右边的第一个神经元就会有j个权值,这样右边的第一个神经元就有j个权值(得到了一行权值向量),同样作为输出的右边的有i个神经元,就会有i行这样的权值矩阵。这样第L层的神经元相对于第l-1层的神经元的权值就组成了一个权值矩阵W。这样就解释了为什么Wij将输出i卸载左边,而输入j写在右边的道理。

    当然进一步的,如果是一个有n层的神经网络,就会有n组这样的W权值矩阵。

  3. 偏置矩阵向量

    不要忘了还有bias,但是往往论文中常常不会将bias写出来,因为我们通常可以将bias认为是权值w的一部分而一同写在w里,上图中我们可以看到第L层上的第i个神经元上的bias,同样i个神经元的bias可以组成一个列矩阵。

  4. 激活函数的输入Z

    在介绍了全连接层权值和bias后我们还有激活函数的输入,Z加入了下标的i的就表示第L层的输入i神经元的激活函数的输入,当然我们也用Z加上一个上标l表示所有输入神经元的激活函数的输入。我们可以看到每一条神经元连接线上的权值w和a的乘积之和就为Z加上一个上标,也就事表示所有的激活函数的输入。

    注意:Wij一定是从j指向i的,例如Wi1表示1指向i的连接之间的权值向量w.

  5. 激活函数的输入Z与L-1层神经元之间的关系

    我们前面知道如何计算z也就是第L层级的激活函数输入,现在我们计算所有层i的Z, 看上图我们可以很清楚的看到如果有i层,则Z可以写成权值矩阵w和L-1层输入举证乘积再加上bias。这里值得注意的是,如果前面我们的i(输出)和j(输入)的定义相反的话,我们这里的W就必须写成W的装置。所以这也是为什么有些文献写成W的装置而有些问些就直接写成我们这里的W。这也进一步解释了我们为什么前面要将Wij不是定义为输入在左边输出在右边,而是定义为输入在右边输出在左边。

  6. 激活函数的输入Z与L层神经元之间的关系

     

        我们再看激活函数的输入z和输出a之间的关系,假设神经元的激活函数是Sigma,则将z带入到Sigma中去就得到输出某一个输出神经元a,所有的a要也可写成矩阵得表达式,也可以简便的写成右下角的式子,表示L层上每一个a

  7. L层输出与激活函数、权值向量、偏执之间的关系

     

    在得到前面的各个层次的所有参数之间的关系之后我们就可以很自然的知道全连接网络的第L层神经元输出与激活函数等之间的关系。如上图。

    1. Recurrent结构(RNN)

    Recurrent的结构有一种说法就是它使得神经网络有了记忆,这是一个比较潮的说法,但是更确切一点的说就是同一个结构(网络)被反复的应用,Recurrent的优势在于输入是一个比较复杂的序列的时候,由于它是反复应用同一个结构,所以无论我们的输入的序列有多长,我们的网络所需要的参数量都一样,不会随着输入序列的长度的变化而变化。

    关于RNN,我们可以参考教科书<<Supervised Sequence Labelling with Recurrent Neural Networks>>

  8. RNN

    假设我们有一个给定的函数f,这个f具体是一系列的矩阵运算,也就使我们的神经网络结构组成的一个函数,通过这个函数我们会得到一些输出。

    我们看到上面的示意图,一个单独的f有两个输入和两个输出,如第一个,输入为h0和x1输出则为h1和y1,RNN有一个特点就是f会被反复的使用,所以我们看到如果有一个新的输入x2给定,同样的f会以h1和x2为输入经过f后得到输出h2和y2,以此类推下去。需要强调的因为每一级的h都做为输入给f所以设计模型的时候h和h'的维数必须一致,所以h0和h1等等后面的h的维数都是一致的。

    前面我们说过RNN的精髓是反复应用同一个结构,无论我们输入的序列有多长都不会影响我们的参数量,因为我们用的都是同样一个f。但是处理序列数据的时候是否只有RNN可以处理呢,当然不是,普通的前馈神经网络同样也可以,但是为什么我们选择RNN呢,因为加入序列很长,它的确可以很长,这时候如果其他的网络在输入层就会有一个很大的向量,并且参数一多就容易过拟合,即使你在训练集上拿到一个比较好的结果。但是如果你用RNN就不一样,它的参数比较少,虽然比较难在训练集上拿到一个比较好结果,但是一旦你训练好了,就比较不容易过拟合,比较容易在测试集的到好的结果。

     

  9. Deep RNN

    RNN当然也可以做Deep,如上图,只要保证输入到f的维数一样即可。

  10. Bidirectional RNN

    双向RNN其实也一样,下面的是前向的部分,上面的是反向的部分,中间通过一个f3把上下串联起来,其中每一级的c和a作为它的输入。F可以根据实际的需要进行设计。

  11. Pyramidal RNN

    我们现在有一个很长的输入序列,可以看到这是一个双向的RNN,上图是谷歌的W.Chan做的一个测试,它原先要做的是语音识别,他要用序列到序列的模型做语音识别,序列到序列就是说,输入一个序列然后就输出一个序列。

    最开始的时候W.Chan用的是一般的RNN,训练了几个月都没有收敛,到最后他用了Pyamidal RNN,用PyamidalRNN训练速度就会快很多,因为我们知道序列越长你需要运算量就越大,而这种金字塔型的RNN,有一个很长的序列作为输入,并且是一个bidirectional RNN也就是双向的RNN,然后用每一层的两个或是多个输出喂给下一层作为输入,这样做有一个好处是每一个层级的序列都在变短,但是反过来每一层f的输入也变多(相对单一序列),这样运算量似乎也没有减少多少。我们知道每一个层的f的运算是可以并行的,而一个序列的运算则不能够并行,因为他必须是一个输入到f输出然后喂给下一个输入,从这个角度来看,运算量还是可以接受的,特别是在原始输入序列较短的时候还是有优势的。

  12. Naïve RNN

    Naïve RNN也非常简单,我们可以把h和x想象成L-1到L层的输入,每个输入和他对应的权值矩阵相乘,得到的乘积相加的结果和sigmoid激活函数相乘得输出h',激活函数可以其他函数,同理可以算得到y,但是这里的y是通过h'算出来的,注意:这里是忽略了bias,但是它是必须的,只是没有写在公式里。

    另外如果y是最后一层,并且希望他的输出是一个机率的话,激活函数就可以用一个softmax函数。

    以上是一个最简单的Naïve RNN.

  13. LSTM详解

     

    上图我们看到Naïve的RNN和LSTM在结构上的区别,似乎LSTM只是在输入端多了一个输入Ct-1,输出也只是相应的多了一个Ct,为什么不可以将输入端的Ct-1和Ht-1合并在一起看成一个整体呢?

    因为c和h有比较大的不同,Ct-1和Ct的关系是Ct-1加上一些东西后得到Ct,并且C改变的很慢。而H则不同,它的变化非常快,并且Ht和Ht-1有很大的不同。

    上面的意思就是说LSTM在将信息传递到下一层级的时候有两条路径,一条路径比较快,另一条路径则比较快,慢的路径可以记住比较久的一些信息。

  14. 计算LSTM四个z

    LSTM的输出是如何计算得来的呢,看到上图应该比较好理解了:

    LSTM中我们前面讲到的我们有3个输入t-1上标的c、h和t上标的x,经过LSTM后得到3个t做为上标的c、t、y,将t上标x和t-1上标的h分别乘上四个不同的权值向量w然后乘上激活函数分别得到四个不同的向量t。

    另外,这四个计算是可以并行运算的,当然现在的一些LSTM的库已经都给做好了,但是我们不要忽略了这个细节,往后深入看到实现代码的时候我们可以看到它是如何实现的。

     

    通常也可以将t-1下标的c和其他两个输入接在一起,如上图所示,这种形式叫做"peephole",也就是窥视孔,如果接在一起,我们可以看到右上角的计算z的时候w的也相应的变长,但是在实际操作中会将w其中的一部分强制的矩阵变成diagonal,也就是对角线的。

    其他的几个输出z的和上面的描述一样。

     

    上面的步骤我们通过计算得到了四个不同的向量z,我们先将中间的两个向量上标为i的z和z相乘,当然这两个向量的维数必须是一样的。然后将c和f上标的z相乘的到结果与前面的结果相加,得到下一个时间点的Ct,注意相乘的操作(圆圈中间加点)也都是element wise operation。

    其中上标为i的z就是input gate,它决定z的信息能否流入。

    上标为f的z是forget gate,它决定c的memory能否被传入到下一个时间点。

    ht是ct取tanh后与Z0进行element wise 操作得到的结果。

    Yt则是ht乘上激活函数sigmoid。

    于是我们就得到ct和ht与新加入的xt+1作为为输入喂到下一个LSTM模型,反复利用。看下图:

    如上面所描述的ct和ht与新加入的xt+1作为为输入喂到下一个LSTM模型,然后在得到一组新的ct和ht在喂给下一个LSTM, 如此反复,和普通的RNN是一样的。

  15. GRU

    GRU是gate recurrent unit的缩写,它现在有取代LSTM的趋势,就大的架构来说GRU和Naïve RNN 很像,它没有LSTM那一样的变化很慢的和变化很快的输入,在外观上和一般的RNN是一样的,但是在它的内部就有很大的不同。

  16. GRU结构

    如下图:

    Ht-1和xt并在一起乘上一个蓝色箭头表示的矩阵再通过激活函数的到r,r为reset gate,另一边通过乘以另外一个矩阵在激活函数激活之后得到z,z为update gate。

    Ht-1和r做element wise operation的到一个新的向量后与xt并在一起乘以一个黄色箭头表示的矩阵的到h'。

    Ht-1与z做element wise operation的结果在与h'做element wise相乘得到ht。

    相对于LSTM而言,我们可以把上图中的下面这一路中的输出看成是forget gate

    把下图中的这一路的输出看成input gate

    把input gate 和forget gate的输出加起来就得到新的memory ht,然后让它在下一个时间

    点再算一次output gate。

  17. GRU的优势

    上边的每一个颜色的箭头表示一个矩阵,我们可以看到LSTM有四个不同颜色的箭头,而GRU只有3个不同颜色的箭头,也就是说LSTM的输入要乘上四个不同的向量矩阵,而GRU的每个输入只要成上三个不同的向量矩阵,所以说GRU相对于LSTM,运算量会比较小,进一步的GRU所用的参数就会较少,也就更加不容易过拟合。

  18. Stack RNN

    Stack RNN的特点是输入可以很大,甚至是无穷大,一般的RNN例如LSTM随着输入变大模型训练的参数也随之变多,很容易导致过拟合。但是Stack RNN就不一样,它需要的参数量和输入的大小是不相关的,所以就算输入是无穷大也无所谓。

    看上图,Stack RNN的输入是一个叫做stack的序列,左边红色方框中一列输入只有3个,也就是说Stack RNN不会用所有的stack作为输入,而只是取其中的一部分,取几个stack做为输入可以由模型的设计者自己自行决定,例子里是序列的前几个。

    Stack RNN会将输入的stack喂入一个f的函数,然后函数会输出要放到stack里面的信息,另外还会输出push,pop,nothing。

    Push就是说将经过f后得到的信息(绿色),push到stack里面最前面。

    Pop就是说将stack最上面的一个元素弹出,这里将stack中最上面的一个深蓝的stack弹出了。

    Nothing就是什么都不做。

    给push、pop和nothing这三个运算添加一个权值,也就是说对stack分别做push、pop和nothing这三个操作,然后分别得到三个stack,然后分别乘以三个操作的比重值分别是0.7、02和0.1最后相加得到新的stack,在将新的stack传到下一个时间点。

    1. 卷积神经网络CNN

    CNN首先得目标同样是为了简化神经网络,所以CNN所需要的参数量比全连接的神经网络更少。

  19. 稀疏连接

     

     

    上面1~5是前一层的输出,1~4是下一层的输出,如果是全连接层的话,就会要考虑前一层所有的输出以后才决定下一层一个神经元,这一层上的每一个神经元都需要考虑前一层的所有的神经元。如果是CNN,就是稀疏连接,也就是每一个神经元值会连接到前一层的部分输出,如上图所示的红色的这一层只是连接了前一层的前面3个输出,至于这个部分是多少,是3个还是多个,就有神经网络模型的设计者自己决定。

    这个被连接的前一层的部分神经元叫做"感受野",就好皮肤上的一歌神经元只管皮肤的一小部分。

  20. 参数共享

    参数共享是指不同神经元可以由不同的参数,当然这里的不同是指不同的感受野的神经元有不同的参数,如果是让同一个感受野的神经元有同一样的参数,那就没有什么意义。

    这里假设第一个和第三个神经元共享一个同样的参数,第二个和第四个神经元贡献一个同样的参数,这样我们就可以看出稀疏连接和参数贡献特性的神经网络比全连接的神经网络拥有更少的参数。

  21. Filter

    如果现在我们有不同的神经元但是他们的参数是共享的,这组参数叫做"filter",也叫做kernel。

    Filter(kernel) size的意思是说一个神经元的感受野的大小,这里1~3是kernel1的感受野,所以filter1(kernel1) 大小就是3,同理filter2(kernel2) 大小也是3。

    Stride的意思是感受野和感受野之间的间距。

  22. 一维单通道CNN例子

    上图这个例子1维单通道的例子,是一个声音信号,x1~ x5就是声音信号的取样点。股票的上下波动也可以看成是1维单通道的例子。

    如果将CNN用在这个例子上,感受野就可以是如例子上的x1~x3以及x3~x4等时间的区间,不同的感受野对应不同的filter,filter可以再接其他的CNN或是全连接的神经网络,就可以做预测、分类等不同的事情。

  23. 一维多通道CNN例子

     

    如上图,multiple chanel的意思就是说在这个维度上的每一个时间点,并不是用一个value去描述它,而前面声音的例子中一个时间点我们则是用一个value取描述它。在这里我们用一个向量去描述它(word),这个向量的每一维代表了不同的通道。

  24. 二维单通道CNN例子

     

    这个例子我们看到是一个6*6的黑白图片图像,感受野是3*3大小,stride是1,也就是说以1的跨度滑动。

  25. 二维多通道CNN例子

    上图也就是如果彩色图片,我们就可以做二维多通道的模型,其中每一个通道就表示一个颜色,而每一个像素是RGB三种颜色组成的,RGB分别就是一个通道。

    二维多通道的感受野是一3*3*3的立方体,所以每个神经元是连接到27个像素。

    同样如果要处理的是一个视频图像,那感受野就是思维的,每个神经元连接到一个思维的感受野有更多的像素。

  26. Padding

    如果没有做padding(填充),那就会少考虑边缘的地方,所以你的图像就会越做越小,例如图中上部分。

    也可以做zero padding,但是zero padding有很多中,你可以尝试一下看哪种效果比较好。假如我们现在有一个filter可以考虑前面的5个输入,图中的下半部分没有涂黑圈的地方就是输入,黑圈就是padding(填充)。

    如果只填充两个,让每一次做卷积以后输入和输出的图像一样大。

    如果感受野可以考虑5个输入,要填充4个是没有问题的,最边缘的filter还是会产生输出,如果要填充5个则不行,最边缘的输入都是0,所以最多能填充4个。

  27. Pooling Layer

    Pooling layer要做的事情是把L层的前一层L-1层的k个输出组成一群,然后L层每一个输出代表前一层k个输出的特性(也可以理解为对前面k个输出的一个概括-summarize),这就叫做pooing,如下图:

  28. Pooling的层次结构

    如上图,假设L层的前一层L-1层的N个节点分成k做,则L层就有N/k个节点,也就是说L层的每一个节点代表L-1层k组节点。

    那么如何从L-1层中的K组节点中的每一组中选择一个节点来电表这一组节点呢?这就很多种方法,例如右边的公式给出集中方法。

    首先是取每组中的一个平均值,代表这一组。

    然后是取每组中的一个最大值,代表这一组。

    再次是L2 pooling,也就是所有值都平方相加然后开根号再除以k。

    或者上面的都不满足需求的话,还可以是上面几个方法的一个组合,着个比较灵活,可以做各式各样的尝试。

  29. 那些输出可以组合在一起?

    首先如果神经元属于同一个filter,就可以将这些神经元组合在一起,也就是说同一个filter的输出组合在一起,例如上图的1和3,2和4属于同一个filter,我们就将他们组合在一起,这也是比较常见的做法,例如subsampling 即二次抽样。

    还有一些不同的方法,如上图,把不同的filter但是对应到同一个感受野的神经元组合在一起作为输出。例如上图我们的123三个神经元应用到1234四个filter,然后把12两个组合在一起,34组合在一起作为pooling输出,这也是可以的,这其实就是我们通常所说的Maxout network。

    这样分组的好处是,模型可以将长得不像的东西,但是是属于同一类的东西归类在一起,例如一辆火车从不同角度看他是不一样的,在正面看它,它是一个长方形,从侧面看它它就是一个长方形。再如我们本例子中1的两种不同写法,但是我们有不同的filter来检测它,第一种filter来检测上一种1的写法(模式),另外一种filter2来检测它的另一种写法。所以不管输入的东西是什么形式,只要他属于同一类,我们就可以将它归类起来。

    1. 如何绑定不同神经网络结构

     

     

     

     

2017-05-26 15:34:23 csuzhaoqinghui 阅读数 4563
  1. 数据增强(Data Augmentation)
    人工智能,或者说计算机视觉的一个最终目标在于构建一个真正可适用于真实世界复杂环境的系统。而就目前所应用的机器学习系统而言,大部分采用了有监督的学习方法,也必然导致了需要广泛收集图像样本,并进行对应的图像标注的工作。而人力时有穷尽,高质量的样本集图片又是构建一个优秀的机器学习系统的关键因素。使用适当的数据增强方法可以将数据集的数量增大十倍以上,从而极大化利用小样本集中的每个样本,使之也可以训练得到一个较好的机器学习模型。数据增强方法也可以提高模型的鲁棒性,防止其易在训练中出现过拟合的现象。
    常用的数据增强方法主要来自数字图像处理中的几何变换,但不改变图像中的像素值,而是改变了其空间位置,借此希望卷积神经网络学习到图像中更多的不变性特征,也有利于提高其泛化性能。
    以下将对各种方法做简单的列举与说明。
    平移(Shift)变换:对原始图片在图像平面内以某种方式(预先定义或者随机方式确定平移的步长、范围及其方向)进行平移。
    翻转(Flip)变换:沿竖直或者水平方向对原始图片进行翻转。
    随机裁剪(Random Crop):随机定义感兴趣区域以裁剪图像,相当于增加随机扰动。
    噪声扰动(Noise):对图像随机添加高斯噪声或者椒盐噪声等。
    对比度变换(Contrast):改变图像对比度,相当于在HSV空间中,保持色调分量H不变,而改变亮度分量V和饱和度S,用于模拟现实环境的光照变化。
    缩放变换(Zoom):以设定的比例缩小或者放大图像。
    尺度变换(Scale):与缩放变换有点类似,不过尺度变换的对象是图像内容而非图像本身(可以参考SIFT特征提取方法),构建图像金字塔以得到不同大小、模糊程度的图像。
  2. 权重初始化(weight initialization)
    传统神经网络之所以一直没办法加深网络的深度,一个很重要的原因在于使用随机梯度下降法训练时,随着训练迭代过程的深入会出现梯度弥散的现象,也就导致了神经网络无法有效地从样本中进行学习。而造成梯度弥散的一部分原因应该归结于传统神经网络使用了权重随机初始化。
    而现代深度神经网络抛弃了随机初始化的方法,转而使用高斯初始化、 Xavier算法等来解决这一问题。
    所谓的高斯初始化方法是指随机选取一个固定均值(如0等)和固定方差(如0.01等)的高斯分布中的值作为网络各个神经元的权重参数的初值,从而保证权重参数训练开始时符合高斯分布,以期获得更好的结果。这是一种常用的权重初始化方法。
    Xavier算法是Glorot于2010年提出的权值初始化方法。相比与随机初始化或者高斯初始化方法中的随机选取,Xavier算法为每一个深度神经网络的初始权值进行了个性定制,因为他是根据输入输出神经元的个数来进行权重的初始化。该算法假设对于一个网络有n个线性神经元,其初始权值为W,网络输出为Y,即:
    Y=W_1 X_1+W_2 X_2+⋯+ W_n X_n (1-1)
    计算每个神经元输出的方差如下:
    Var(W_i X_i )=E[X_i ]^2 Var(W_i )+E[W_i ]^2 Var(X_i ) + Var(W_i )Var(X_i ) (1-2)
    假设所有输入权重的均值为0,上述公式可以简化如下:
    Var(W_i X_i )= Var(W_i )Var(X_i ) (1-3)
    同时假设Xi和Wi都是独立同分布的,可得输出Y的方差:
    Var(Y)=Var(W_1 X_1+W_2 X_2+⋯+ W_n X_n )=nVar(W_i )Var(X_i ) (1-4)
    故如果我们要求输出分布要与输入的分布一样,则nVar(Wi)的值应为1。
    尽管算法中使用了诸多假设,可是在实际应用过程中取得了良好的效果。直观上来讲,对原始的Xavier算法来说,假设输入的均值为0对于传统的神经网络是合理的。因为其初始化层之后是非线性激活层(tanh,sigmoid等)中的激活函数在0值附近的梯度值最大。而对于现代深度神经网络而言,由于其线性整流单元(ReLU)对于输入的响应有一半为0,故He等提出以下公式作为替代。
    Var(W_i )=2/n_in (1-8)
  3. 随机梯度下降法(Stochastic Gradient descent)
    梯度下降法(Gradient descent)是当前最为受欢迎的优化算法之一,也是最常用的深度神经网络优化方法。它通过沿着当前函数点所对应的梯度的反方向进行迭代搜索,从而找到目标函数的极值点。许多学者对其提出了改进算法。
    标准梯度下降法可以表征为:
    θ(t+1)=θ_t- η∇θ E(L(θ_t) (1-9)
    其中η为每次迭代的学习率,L(θ)为目标函数。E[L(θ)]整个数据集的数学期望。
    标准梯度下降法要求在每次迭代的时候都要计算整个数据集的梯度方向。它可以保证凸目标函数可以收敛到全局最小点,而对于非凸目标函数至少可以收敛到局部极值点。近几十年来,样本数据的增长速度远远大于处理器的计算能力的增长。从这个角度上来说,统计机器学习方法的局限在于计算能力而不在于样本量的大小。对于小样本学习中标准梯度下降法是适用的,但是应用于大数据集时是极其缓慢的,整个数据集无法一次性装载进内存中的情况越来越普遍。因此标准梯度下降法无法胜任于现代大规模数据的优化任务,此时随机梯度下降法 (stochastic gradient descent, SGD)的应用效果要远远好于标准梯度下降法。
    相比于计算整个数据集的梯度方向,随机梯度下降法(SGD)以从训练集中随机选取的单个样本来估计目标梯度,以迭代更新参数θ。
    θ(t+1)=θ_t- η_t ∇θ L(θ_t;x^((t) ),y^((t) )) (1-10)
    在实际应用过程中,每次的参数更新并不是根据单个样本计算的,而是由所定义的最小批量样本(mini-batch)来计算的。这可以降低单个样本参数更新的剧烈波动并使收敛过程更加稳定。由于采用批量样本并不能完全代表数据集的梯度方向,其参数更新的波动也是无可避免的。但是这种波动一定程度上使其能够跳转到潜在的更好的局部极小点,并且最终到达全局最小点。尽管理论上,随机梯度下降法(SGD)可以渐进收敛,但是在实际应用过程中仍有许多挑战。
    首先,收敛的速度取决于学习率η,一个合适的学习率并不是那么容易确定的。如果学习率太小会导致收敛过程极其缓慢,而过大的学习率η将会阻碍模型收敛并导致目标函数在最小值附近波动甚至发散。其次,对于神经网络这种高度非线性任务而言,其目标函数通常都有大量的次优局部极小点,为保证收敛到全局最小点需要避免训练过程陷入局部极小点中。对于学习率或者学习策略的选择,可以参考以下方法:
  4. 选取一个足够小的,在第一次迭代中可以收敛的常数值(或选取两个取其均值),如果收敛速度有所下降,取η/2作为新的学习率;
  5. 在每次迭代之后引入评估机制,当两次迭代间的变化小于某个设定阈值时减小学习率。
    为了避免训练陷入局部极小点,Qian等提出了基于动量(momentum)的梯度下降学习算法,通过增加历史参数更新的权重项,加速当前参数更新的过程并减少振荡。
    ν(t+1)=〖γν〗_t- η_t ∇θ L(θ_t;x^((t) ),y^((t) )) (1-11)
    θ(t+1)=θ_t+ν(t+1) (1-12)
    其中ν_(t+1)是当前的更新向量,γ即为动量,通常设为0.9或者其他常数。
    2013年,Sutskever等对传统动量算法改进动量更新过程使得训练更加稳定。而Recht等提出并行SGD以适应现代海量数据集的训练任务。
  6. 批规范化(Batch normalization)
    当训练一个深度神经网络时,由于各层参数不断变化,所以各层输入的概率分布也在不断的变化,也导致了非线性激活函数(如sigmoid等)正负两边都会饱和,使得训练迭代的过程变得极其缓慢甚至无法进行。这时候,通常需要采用较小的学习率以及精细进行权重初始化。loffe等将这种现象称为“内部协方差偏移”(internal covariate shift),并提出了批规范化(Batch normalization,BN)解决此类问题。
    该论文中提出在每次随机梯度下降迭代时,在最小批量样本(mini-batch)内做规范化操作,使得输出结果各个维度的均值为0,方差为1,有效地避免了非线性激活函数的饱和问题(以sigmoid为例,观察sigmoid函数即可知,该函数输入集中在0附近时,其输出梯度较大);同时也让梯度变化受参数及其初值的影响减小。
    假设对当前隐含层执行规范化,其输入有d个,分别为 x=〖[x_1,x_1,…,x_d]〗^T,则其第k维输出如下:
    x ̂k = (x_k-μβ)/√(δ_β^2+ϵ) (1-13)
    其中,μβ和δβ^2分别为最小批量样本的均值和方差。
    为了加强其表示能力,原作中引入了一对可学习参数 γ,β,使得:
    y_k=〖BN〗_(γ,β) (x_k )= γx ̂_k+β (1-14)
    总体而言,批规范化(Batch normalization)作用可以归纳如下:
    (1) 可以使用更高的学习率,加快了训练速度;
    (2) 可以移除或使用较低的dropout(下一小节将作介绍)。dropout是常用的防止过拟合的方法,较低的dropout也可以提高训练速度。
    (3) 减少图像预处理的使用。由于现在训练迭代次数降低,所以可以对样本数据少做一些图像扭曲,让神经网络多从真实的数据中学习。
    当然,对于上述问题的解决办法并不是唯一的,Razvan等提出的自然梯度(Natural Gradient)也是对解决这一问题的一种思路。
  7. Dropout
    众所周知,过拟合问题一直是机器学习的算法的致力于解决的问题。对于大型神经网络来说,其参数规模相比于浅层学习算法有了跃进式的增加,所以相对来说也更加容易发生过拟合的现象。而为了解决过拟合问题,一般会采用集成方法,即同时训练多个模型进行组合,然而这种方法费时费力也不能保证取得预期效果。
    2012年,Hinton等提出Dropout用于解决神经网络训练易过拟合的问题。通过在其训练过程中,按照事先规定的概率暂时屏蔽掉其中的某些神经元。由于这种屏蔽是暂时而是随机的,对于随机梯度下降来说,相当于每一次批量样本的训练均是在训练一个新的网络。极端情况下,对于一个n节点的网络,相当于同时在训练2^n个不同网络模型, 但此时需要优化的参数数量确实不变的,这也一定程度上符合传统的集成方法的思想而同时解决了其费时的问题。
    其数学表达如下:
    y= εx (1-15)
    其中,ε以一定的概率p等于0,使得该神经元节点x暂时被屏蔽。
2018-07-16 14:04:41 jackkang01 阅读数 21350

神经网络有时可以与乐高积木进行比较,在那里你可以构建几乎任何简单到复杂的结构,你的想象力可以帮助你构建。

计算机视觉任务类型

目标识别:物体识别是得到一个原始的图像,任务是给出目标位置和识别物体属于哪个类,另外还有人脸识别,行为识别等

三维重建:摄像机标定,立体匹配

图像描述:根据给定图像,给出描述文字等

深度相机:RGB-D相机,例如手势识别、骨骼跟踪、增强现实等

视觉导航:视觉SLAM,例如ORB-SLAM

图像分割:图像分割是一项复杂的任务,目标是将每个像素映射到它的合法类,例如语义分割、实例分割等

深度学习模型

现在我们已经了解了什么是高级体系结构,并探索了计算机视觉的任务,下面列出最重要的体系结构及其描述:

AlexNet

AlexNet是深度学习的先驱之一——Geoffrey Hinton和他的同事们引入的第一个深层架构。它是一个简单而又强大的网络架构,它为深度学习的突破性研究铺平了道路。下面是作者提出的体系结构的表示。

AlexNet看起来是一个包含卷积层和池化层的简单架构,顶部是全连接层。区分这个模型的是它执行任务的规模和使用GPU进行训练。在20世纪80年代,CPU被用于训练神经网络。而AlexNet仅通过使用GPU提升了10倍的训练速度。

虽然现在有点过时了,但是AlexNet仍然在所有任务上应用深度神经网络的起点,无论是计算机视觉还是语音识别。

Paper     Code

VGG Net

VGG网络是由牛津大学视觉图形组的研究人员引入的(因此有了VGG这个名字)。这个网络的特点是它的金字塔形状,最接近图像的底层是宽的,而最上层是深的。

如图所示,VGG包含随后的卷积层和池化层。池层负责使层更窄。在他们的论文中,他们提出了多种这种类型的网络,随着架构深度的变化。

VGG的优势是:
对于特定任务的基准测试来说,这是一个非常好的体系结构。
此外,VGG的预培训网络在互联网上是免费的,所以它通常用于各种应用程序的开箱即用。

另一方面,它的主要缺点是,如果从头开始训练,训练速度非常慢。即使是在一个像样的GPU上,它也需要一个多星期才能投入使用。

Paper     Code

GoogleNet

GoogleNet(或先启网络)是谷歌研究人员设计的一类架构。谷歌是2014年ImageNet的赢家,在那里它被证明是一个强大的模型。

在这种体系结构中,随着深入(它包含22层,而VGG有19层),研究人员还提出了一种叫做初始模块的新方法。

如上所述,它与我们前面看到的顺序体系结构相比发生了巨大的变化。在单层中,存在多种类型的“特征提取器”。这间接地帮助网络更好地执行任务,因为训练网络本身在解决任务时有很多选择。它既可以选择对输入进行卷积,也可以直接将其池化。

最终的架构包含了这些叠在一起的初始模块的多个。甚至在GoogleNet中,训练也略有不同,因为大多数最顶层都有自己的输出层。这种细微差别有助于模型更快地收敛,因为对于层本身有联合训练和并行训练。

GoogleNet的优点是:
谷歌的火车比VGG快。
一个预先训练好的GoogleNet的大小比VGG要小。VGG模型可以有>500 MB,而GoogleNet只有96 MB

GoogleNet本身并没有直接的缺点,但是它提出了对体系结构的进一步修改,从而使模型执行得更好。其中一个变化被称为Xception网络,在这个变化中,inception模块发散的极限(如上图所示,是GoogleNet中的4)增大了。它现在理论上可以是无限的(因此被称为极端的开端!)

Paper     Code

ResNet

ResNet是真正定义了深度学习体系结构的深度的怪物架构之一。剩余网络(简称ResNet)由多个后续剩余模块组成,这些模块是ResNet体系结构的基本构件。残差模的表示如下

简单地说,一个残馀模块有两个选项,要么它可以对输入执行一组函数,要么它可以跳过这一步。

现在类似于GoogleNet,这些残馀模块相互堆叠,形成一个完整的端到端网络。

ResNet还介绍了一些新技术:
使用标准的SGD而不是花哨的自适应学习技术。这是与一个合理的初始化函数,保持训练完整
对输入进行预处理的更改,其中输入首先被划分为补丁,然后进入网络

ResNet的主要优点是,可以使用数百甚至数千个剩余层来创建网络并进行训练。这与通常的顺序网络稍有不同,在顺序网络中,随着层数的增加,性能升级会减少。

Paper     Code

ResNeXt

ResNeXt据说是目前最先进的对象识别技术。它构建在inception和resnet的概念之上,以实现一个新的和改进的体系结构。下图总结了ResNeXt模块的剩余模块的外观。

Paper     Code

R-CNN

基于区域的CNN架构被认为是应用于目标检测问题的所有深度学习架构中最有影响力的一个。为了解决检测问题,RCNN做的是尝试在图像中出现的所有对象上绘制一个边界框,然后识别图像中的对象。它的工作原理如下:

Faster-RCNN结构

Paper     Code

YOLO

YOLO是目前最先进的实时系统,建立在深度学习的基础上,以解决图像检测问题。如下图所示,首先将图像划分为定义好的边界框,然后并行运行所有这些框的识别算法,以识别它们属于哪个对象类。在识别了这些类之后,它继续聪明地合并这些框,以形成围绕对象的最佳边界框。

所有这些都是平行进行的,所以它可以实时运行;每秒处理多达40幅图像。

尽管与RCNN相比,它的性能有所下降,但它仍然具有实时性的优势,可以在日常问题中使用。下面是YOLO的架构表示

Paper     Code

SqueezeNet

snizenet体系结构是一个更强大的体系结构,在移动平台等低带宽场景中非常有用。这个架构只占用了4.9MB的空间,而《盗梦空间》占用了~100MB的空间!这种剧烈的变化是由一种叫做消防模块的特殊结构引起的。下图是消防模块的表示。

snizenet的最终架构如下:

Paper     Code

SegNet

SegNet是一种用于解决图像分割问题的深度学习架构。它由一系列的处理层(编码器)和相应的一组解码器组成,按像素分类。下面的图片总结了赛格网的工作。

SegNet的一个关键特征是它保留了分割图像中的高频细节,因为编码器网络的合用索引连接到解码器网络的合用索引。简而言之,信息传递是直接的而不是卷积的。SegNet是处理图像分割问题的最佳模型之一

Paper     Code

GAN

GAN是一种完全不同的神经网络体系结构,其中一个神经网络用来生成一个全新的图像,这个新的图像没有出现在训练数据集中,但足够真实地出现在数据集中。例如,下图是对甘斯的分解。在本文中,我已经介绍了GANs是如何工作的。如果你感到好奇,请仔细阅读。

Paper     Code

 

2015-12-05 17:40:41 hjimce 阅读数 22795

基于空间金字塔池化的卷积神经网络物体检测

原文地址:http://blog.csdn.net/hjimce/article/details/50187655

作者:hjimce

一、相关理论

   本篇博文主要讲解大神何凯明2014年的paper:《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》,这篇paper主要的创新点在于提出了空间金字塔池化。paper主页:http://research.microsoft.com/en-us/um/people/kahe/eccv14sppnet/index.html  这个算法比R-CNN算法的速度快了n多倍。

    我们知道在现有的CNN中,对于结构已经确定的网络,需要输入一张固定大小的图片,比如224*224,32*32,96*96等。这样对于我们希望检测各种大小的图片的时候,需要经过裁剪,或者缩放等一系列操作,这样往往会降低识别检测的精度,于是paper提出了“空间金字塔池化”方法,这个算法的牛逼之处,在于使得我们构建的网络,可以输入任意大小的图片,不需要经过裁剪缩放等操作,只要你喜欢,任意大小的图片都可以。不仅如此,这个算法用了以后,精度也会有所提高,总之一句话:牛逼哄哄。

    空间金字塔池化,又称之为“SPP-Net”,记住这个名字,因为在以后的外文文献中,你会经常遇到,特别是物体检测方面的paper。这个就像什么:OverFeat、GoogleNet、R-CNN、AlexNet……为了方便,学完这篇paper之后,你就需要记住SPP-Net是什么东西了。空间金子塔以前在特征学习、特征表达的相关文献中,看到过几次这个算法。

   既然之前的CNN要求输入固定大小的图片,那么我们首先需要知道为什么CNN需要输入固定大小的图片?CNN大体包含3部分,卷积、池化、全连接。

首先是卷积,卷积操作对图片输入的大小会有要求吗?比如一个5*5的卷积核,我输入的图片是30*81的大小,可以得到(26,77)大小的图片,并不会影响卷积操作。我输入600*500,它还是照样可以进行卷积,也就是卷积对图片输入大小没有要求,只要你喜欢,任意大小的图片进入,都可以进行卷积。

池化:池化对图片大小会有要求吗?比如我池化大小为(2,2)我输入一张30*40的,那么经过池化后可以得到15*20的图片。输入一张53*22大小的图片,经过池化后,我可以得到26*11大小的图片。因此池化这一步也没对图片大小有要求。只要你喜欢,输入任意大小的图片,都可以进行池化。

全连接层:既然池化和卷积都对输入图片大小没有要求,那么就只有全连接层对图片结果又要求了。因为全连接层我们的连接劝值矩阵的大小W,经过训练后,就是固定的大小了,比如我们从卷积到全连层,输入和输出的大小,分别是50、30个神经元,那么我们的权值矩阵(50,30)大小的矩阵了。因此空间金字塔池化,要解决的就是从卷积层到全连接层之间的一个过度。

也就是说在以后的文献中,一般空间金子塔池化层,都是放在卷积层到全连接层之间的一个网络层。

二、算法概述

OK,接着我们即将要讲解什么是空间金字塔池化。我们先从空间金字塔特征提取说起(这边先不考虑“池化”),空间金字塔是很久以前的一种特征提取方法,跟Sift、Hog等特征息息相关。为了简单起见,我们假设一个很简单两层网络:

输入层:一张任意大小的图片,假设其大小为(w,h)。

输出层:21个神经元。

也就是我们输入一张任意大小的特征图的时候,我们希望提取出21个特征。空间金字塔特征提取的过程如下:


图片尺度划分

如上图所示,当我们输入一张图片的时候,我们利用不同大小的刻度,对一张图片进行了划分。上面示意图中,利用了三种不同大小的刻度,对一张输入的图片进行了划分,最后总共可以得到16+4+1=21个块,我们即将从这21个块中,每个块提取出一个特征,这样刚好就是我们要提取的21维特征向量。

第一张图片,我们把一张完整的图片,分成了16个块,也就是每个块的大小就是(w/4,h/4);

第二张图片,划分了4个块,每个块的大小就是(w/2,h/2);

第三张图片,把一整张图片作为了一个块,也就是块的大小为(w,h)

空间金字塔最大池化的过程,其实就是从这21个图片块中,分别计算每个块的最大值,从而得到一个输出神经元。最后把一张任意大小的图片转换成了一个固定大小的21维特征(当然你可以设计其它维数的输出,增加金字塔的层数,或者改变划分网格的大小)。上面的三种不同刻度的划分,每一种刻度我们称之为:金字塔的一层,每一个图片块大小我们称之为:windows size了。如果你希望,金字塔的某一层输出n*n个特征,那么你就要用windows size大小为:(w/n,h/n)进行池化了。

当我们有很多层网络的时候,当网络输入的是一张任意大小的图片,这个时候我们可以一直进行卷积、池化,直到网络的倒数几层的时候,也就是我们即将与全连接层连接的时候,就要使用金字塔池化,使得任意大小的特征图都能够转换成固定大小的特征向量,这就是空间金字塔池化的奥义(多尺度特征提取出固定大小的特征向量)。具体的流程图如下:


三、算法源码实现

 理论学的再多,终归要实践,实践是检验理论的唯一标准,caffe中有关于空间金字塔池化的源码,我这边就直接把它贴出来,以供学习使用,源码来自https://github.com/BVLC/caffe

//1、输入参数pyramid_level:表示金字塔的第几层。我们将对这一层,进行划分为2^n个图片块。金字塔从第0层开始算起,0层就是一整张图片
//第1层就是把图片划分为2*2个块,第2层把图片划分为4*4个块,以此类推……,也就是说我们块的大小就是[w/(2^n),h/(2^n)]
//2、参数bottom_w、bottom_h是我们要输入这一层网络的特征图的大小
//3、参数spp_param是设置我们要进行池化的方法,比如最大池化、均值池化、概率池化……
LayerParameter SPPLayer<Dtype>::GetPoolingParam(const int pyramid_level,
      const int bottom_h, const int bottom_w, const SPPParameter spp_param)
{
  LayerParameter pooling_param;
  int num_bins = pow(2, pyramid_level);//计算可以划分多少个刻度,最后我们图片块的个数就是num_bins*num_bins
   //计算垂直方向上可以划分多少个刻度,不足的用pad补齐。然后我们最后每个图片块的大小就是(kernel_w,kernel_h)
  int kernel_h = ceil(bottom_h / static_cast<double>(num_bins));//向上取整。采用pad补齐,pad的像素都是0
  int remainder_h = kernel_h * num_bins - bottom_h;
  int pad_h = (remainder_h + 1) / 2;//上下两边分摊pad
//计算水平方向的刻度大小,不足的用pad补齐
  int kernel_w = ceil(bottom_w / static_cast<double>(num_bins));
  int remainder_w = kernel_w * num_bins - bottom_w;
  int pad_w = (remainder_w + 1) / 2;

  
  pooling_param.mutable_pooling_param()->set_pad_h(pad_h);
  pooling_param.mutable_pooling_param()->set_pad_w(pad_w);
  pooling_param.mutable_pooling_param()->set_kernel_h(kernel_h);
  pooling_param.mutable_pooling_param()->set_kernel_w(kernel_w);
  pooling_param.mutable_pooling_param()->set_stride_h(kernel_h);
  pooling_param.mutable_pooling_param()->set_stride_w(kernel_w);

  switch (spp_param.pool()) {
  case SPPParameter_PoolMethod_MAX://窗口最大池化
    pooling_param.mutable_pooling_param()->set_pool(
        PoolingParameter_PoolMethod_MAX);
    break;
  case SPPParameter_PoolMethod_AVE://平均池化
    pooling_param.mutable_pooling_param()->set_pool(
        PoolingParameter_PoolMethod_AVE);
    break;
  case SPPParameter_PoolMethod_STOCHASTIC://随机概率池化
    pooling_param.mutable_pooling_param()->set_pool(
        PoolingParameter_PoolMethod_STOCHASTIC);
    break;
  default:
    LOG(FATAL) << "Unknown pooling method.";
  }

  return pooling_param;
}

template <typename Dtype>
//这个函数是为了获取我们本层网络的输入特征图、输出相关参数,然后设置相关变量,比如输入特征图的图片的大小、个数
void SPPLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  SPPParameter spp_param = this->layer_param_.spp_param();

  num_ = bottom[0]->num();//batch size 大小
  channels_ = bottom[0]->channels();//特征图个数
  bottom_h_ = bottom[0]->height();//特征图宽高
  bottom_w_ = bottom[0]->width();
  reshaped_first_time_ = false;
  CHECK_GT(bottom_h_, 0) << "Input dimensions cannot be zero.";
  CHECK_GT(bottom_w_, 0) << "Input dimensions cannot be zero.";

  pyramid_height_ = spp_param.pyramid_height();//金子塔有多少层
  split_top_vec_.clear();//清空相关数据
  pooling_bottom_vecs_.clear();
  pooling_layers_.clear();
  pooling_top_vecs_.clear();
  pooling_outputs_.clear();
  flatten_layers_.clear();
  flatten_top_vecs_.clear();
  flatten_outputs_.clear();
  concat_bottom_vec_.clear();
  //如果金字塔只有一层,那么我们其实是对一整张图片进行pooling,也就是文献所提到的:global pooling
  if (pyramid_height_ == 1) {
    // pooling layer setup
    LayerParameter pooling_param = GetPoolingParam(0, bottom_h_, bottom_w_,spp_param);
    pooling_layers_.push_back(shared_ptr<PoolingLayer<Dtype> > (new PoolingLayer<Dtype>(pooling_param)));
    pooling_layers_[0]->SetUp(bottom, top);
    return;
  }
  //这个将用于保存金子塔每一层
  for (int i = 0; i < pyramid_height_; i++) {
    split_top_vec_.push_back(new Blob<Dtype>());
  }

  // split layer setup
  LayerParameter split_param;
  split_layer_.reset(new SplitLayer<Dtype>(split_param));
  split_layer_->SetUp(bottom, split_top_vec_);

  for (int i = 0; i < pyramid_height_; i++) {
    // pooling layer input holders setup
    pooling_bottom_vecs_.push_back(new vector<Blob<Dtype>*>);
    pooling_bottom_vecs_[i]->push_back(split_top_vec_[i]);

    
    pooling_outputs_.push_back(new Blob<Dtype>());
    pooling_top_vecs_.push_back(new vector<Blob<Dtype>*>);
    pooling_top_vecs_[i]->push_back(pooling_outputs_[i]);

    // 获取金字塔每一层相关参数
    LayerParameter pooling_param = GetPoolingParam(i, bottom_h_, bottom_w_, spp_param);

    pooling_layers_.push_back(shared_ptr<PoolingLayer<Dtype> > (new PoolingLayer<Dtype>(pooling_param)));
    pooling_layers_[i]->SetUp(*pooling_bottom_vecs_[i], *pooling_top_vecs_[i]);

    //每一层金字塔输出向量
    flatten_outputs_.push_back(new Blob<Dtype>());
    flatten_top_vecs_.push_back(new vector<Blob<Dtype>*>);
    flatten_top_vecs_[i]->push_back(flatten_outputs_[i]);

    // flatten layer setup
    LayerParameter flatten_param;
    flatten_layers_.push_back(new FlattenLayer<Dtype>(flatten_param));
    flatten_layers_[i]->SetUp(*pooling_top_vecs_[i], *flatten_top_vecs_[i]);

    // concat layer input holders setup
    concat_bottom_vec_.push_back(flatten_outputs_[i]);
  }

  // 把所有金字塔层的输出,串联成一个特征向量
  LayerParameter concat_param;
  concat_layer_.reset(new ConcatLayer<Dtype>(concat_param));
  concat_layer_->SetUp(concat_bottom_vec_, top);
}
函数GetPoolingParam是我们需要细读的函数,里面设置了金子塔每一层窗口大小的计算,其它的函数就不贴了,对caffe底层实现感兴趣的,可以自己慢慢细读。

四、算法应用之物体检测

在SPP-Net还没出来之前,物体检测效果最牛逼的应该是RCNN算法了,下面跟大家简单讲一下R-CNN的总算法流程,简单回顾一下:

1、首先通过选择性搜索,对待检测的图片进行搜索出2000个候选窗口。

2、把这2k个候选窗口的图片都缩放到227*227,然后分别输入CNN中,每个候选窗台提取出一个特征向量,也就是说利用CNN进行提取特征向量。

3、把上面每个候选窗口的对应特征向量,利用SVM算法进行分类识别。

可以看到R-CNN计算量肯定很大,因为2k个候选窗口都要输入到CNN中,分别进行特征提取,计算量肯定不是一般的大。

OK,接着回归正题,如何利用SPP-Net进行物体检测识别?具体算法的大体流程如下:

1、首先通过选择性搜索,对待检测的图片进行搜索出2000个候选窗口。这一步和R-CNN一样。

2、特征提取阶段。这一步就是和R-CNN最大的区别了,同样是用卷积神经网络进行特征提取,但是SPP-Net用的是金字塔池化。这一步骤的具体操作如下:把整张待检测的图片,输入CNN中,进行一次性特征提取,得到feature maps,然后在feature maps中找到各个候选框的区域,再对各个候选框采用金字塔空间池化,提取出固定长度的特征向量。而R-CNN输入的是每个候选框,然后在进入CNN,因为SPP-Net只需要一次对整张图片进行特征提取,速度是大大地快啊。江湖传说可一个提高100倍的速度,因为R-CNN就相当于遍历一个CNN两千次,而SPP-Net只需要遍历1次。

3、最后一步也是和R-CNN一样,采用SVM算法进行特征向量分类识别。

算法细节说明:看完上面的步骤二,我们会有一个疑问,那就是如何在feature maps中找到原始图片中候选框的对应区域?因为候选框是通过一整张原图片进行检测得到的,而feature maps的大小和原始图片的大小是不同的,feature maps是经过原始图片卷积、下采样等一系列操作后得到的。那么我们要如何在feature maps中找到对应的区域呢?这个答案可以在文献中的最后面附录中找到答案:APPENDIX A:Mapping a Window to Feature Maps。这个作者直接给出了一个很方便我们计算的公式:假设(x’,y’)表示特征图上的坐标点,坐标点(x,y)表示原输入图片上的点,那么它们之间有如下转换关系:

(x,y)=(S*x’,S*y’)

其中S的就是CNN中所有的strides的乘积。比如paper所用的ZF-5:

S=2*2*2*2=16

而对于Overfeat-5/7就是S=12,这个可以看一下下面的表格:

 

需要注意的是Strides包含了池化、卷积的stride。自己计算一下Overfeat-5/7(前5层)是不是等于12。

反过来,我们希望通过(x,y)坐标求解(x’,y’),那么计算公式如下:

 

因此我们输入原图片检测到的windows,可以得到每个矩形候选框的四个角点,然后我们再根据公式:

Left、Top:

 

Right、Bottom:

 

参考文献:

1、https://github.com/BVLC/caffe

2、《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》

3、http://research.microsoft.com/en-us/um/people/kahe/eccv14sppnet/index.html

4、http://caffe.berkeleyvision.org/

**********************作者:hjimce   时间:2015.12.5  联系QQ:1393852684   地址:http://blog.csdn.net/hjimce   原创文章,转载请保留原文地址、作者等信息****************


2018-11-15 10:15:09 wydbyxr 阅读数 1286

空间金字塔池化(spatial pyramid pooling,SPP)

解决的问题

  先前方法,输入图片会经过裁切(Crop)或者变形缩放(Warp),这都在一定程度上导致图片信息的丢失和变形,限制了识别精确度。
  SPP可以输入任意大小的图片(不需要经过裁剪缩放等操作,去除网络输入是固定大小的限制),输出固定长度的representation(一维特征向量)。
在这里插入图片描述

意义

  R-CNN是直接从原始图片中提取特征,它在每张原始图片上提取2000个Region Proposal,然后对每一个候选区域框进行一次卷积计算,差不多要重复2000次。
  假设最后一个卷积层的输出大小为a×a,若给定金字塔层有n×n 个bins,进行滑动窗池化,窗口尺寸为win=a/n,步长为str=a/n,使用一个网络完成一个完整epoch的训练,之后切换到另外一个网络。
  只是在训练的时候用到多尺寸,测试时直接将SPPNet应用于任意尺寸的图像。
  多尺度特征提取出固定大小的特征向量。

具体操作

  SPP层作为pooling的代替。是取max的pooling。前一层是主干网络的最后一个卷积层,然后SPP产生固定大小的输出,后一层是第一个FC层。
  具体过程,其实就是从这21个图片块中,分别计算每个块的最大值,从而得到一个输出神经元。
最后把一张任意大小的图片转换成了一个固定大小的21维特征。