Introduction

        卷积神经网络CNN,虽然它听起来就像是生物学、数学和计算机的奇怪混杂产物,但在近些年的机器视觉领域,它是最具影响力的创新结果。随着Alex Krizhevsky开始使用神经网络,将分类错误率由26%降到15%并赢得2012年度ImageNet竞赛(相当于机器视觉界的奥林匹克),它就开始声名大噪了。从那时起,一票公司开始在它们的核心服务中使用深度学习技术。例如Facebook用它进行自动的图像标签,google用它做照片检索,amazon用它做产品推荐,Pinterest用它做个性化家庭定制推送(?),Instagram用它搭建他们的搜索架构。

        基于CNN最经典也是最流行的应用应当是图像处理领域。那么,就让我们看看怎样使用CNN技术来设计图像分类算法。

The Problem Space

        图像分类主要指将输入图像进行硬分类或模糊分类(例如猫图、狗图等)。对于人类来说,这是出生后就应当学会的第一个技能,并在成人后能够做到非常轻松自然地做到这一点。我们能够不假思索地连续快速地分辨所处的环境,周边事物等。无论我们看到图片还是真实景象,都能够马上对其进行判断并打上标签,有时候这种行为就是下意识的。这种识别技术主要基于人们的先验知识与环境,而这些是我们的机器所无法拥有知识的。

Inputs and Outputs

        当我们的电脑看到一副图片时,机器只是看到一个由像素值组成的矩阵,例如说32*32*3,其中32表示其分辨率或图像大小,3表示RGB三原色。为了把问题阐述清楚,我们这里定义一个JPG格式的彩色图像,大小为480*480,那么表示的矩阵就是480*480*3。矩阵里每一点取值范围0-255,表示为该点的像素强度(灰度值)。在我们人类进行图像识别的时候,这些像素点一点儿意义也没有,它们只是作为机器进行图像识别的输入而已。机器的输出呢,可以是一组概率值,这组概率值表明了当前的图像是某一类图像的可能性有多大。(比如0.8可能性是猫,0.15可能性是狗,0.05可能性是鸟等)

What We Want the Computer to Do

        我们知道了问题的输入和输出,现在要考虑该怎么解决它。我们想要计算机做的是分辨出所有给出的图中所具有独特特征,例如说狗图或猫图的独特特征,这些特征是在某一分类图中一致,而跟其它类型图不同。这件事在我们自己的脑中同样也是自动完成的。例如,当我们看一副狗图时,我们能够根据图中物体的爪子或4条腿分辨其是小狗。类似地,计算机也可以通过寻找一些低等级特征,例如边缘或纹理等,并由此通过一系列卷积层来建立更抽象的概念,来实现分类识别。大体上这就是CNN所做的事。接下来让我们更具体地展开说。

Biological Connection

        先让我们从一些背景知识开始吧。当你第一次听到卷积神经网络(CNN)这个短语时,也许你会想起生物神经科学领域的一些东西;可以说,某种程度上你是对的。CNN是从神经生物学中视觉皮层这个概念上获取了灵感。在大脑的视觉皮层中,有着许多由细胞组成的,对特定的视觉领域敏感的微小区域。这个理论由Hubel以及Wiesel在1962年通过实验获得了证实。(Video) 在实验中,大脑内一些独立的神经元细胞仅对给实验者展示的某些特征放电或有反应,例如特定方向的边缘。例如,在展示垂直边缘、水平边缘或对角线边时,一些神经元开始放电。Hubel和Wiesel发现,这些神经元聚集并被组合成为纵列或柱形的结构,从而产生视觉。在一个系统整体中,不同的部分负责不同的任务(例如视觉皮层的神经元细胞负责感知视觉),这样的构造,正是CNN的基础。

Structure

        回到细节里来。针对CNN的具体行为,一个更加细化的视角是,用户将图像经过一系列卷积、非线性、池化(下采样)、以及全连通层后,获得了输出结果。就像之前说的,这种输出结果也许是一个用于描述该图像的分类结果或是一个分类的可能性。现在的难点在于,如何理解每一层都做了些什么。那么,让我们进入这个最重要的部分。

First Layer – Math Part

        CNN的第一层通常都是卷积层。你必须记住的第一件事应当是卷积层(conv layer)的输入是什么。就像之前说的,输入是一个32*32*3的像素数列。要解释这个卷积层,最好的办法就是想象一下下面场景:你举着手电筒将光束打在一幅图像的左上角。我们假定这个光束覆盖的范围是5*5。那么现在,想象光束逐渐滑动平移,经过整幅图像。在机器学习的语言里,这个手电筒就被称作滤波器filter(或神经元neuron,或内核kernel),而它照过的区域叫做感知区(receptive field)。这个滤波器也可以表示为一个数组(数组中的数字可称为加权weight或参数parameter)。一个重要的点在于,滤波器的深度必须跟输入图像深度一致(才能保证计算不出错)。那么,这个滤波器的维度就变成了:5*5*3。现在,以这个滤波器最初的位置为例,它应当处于图像的左上角。随着滤波器的平移(或称卷积convolving),经过整幅图像。它将自身数组中的值与图像对应位置的像素值进行相乘(即点乘),将点乘结果加起来(数学上一共有75次乘加),就有了一个数。记住,这个数只代表滤波器在图像左上角初始位置时的卷积值。现在,我们一边移动滤波器一边重复这个计算(顺序是从左到右,然后从上到下。下一步是往右移一个像素)。这样,图像上每个单独的像素位置都会产生一个滤波(卷积)后的数值。在遍历整个图像后,你会得到一个28*28*1的数列,我们称之为激活图activation map或特征图feature map。28的原因是32-5+1=28。这样一共就有784个值。

 

        (注:上图使用的图片,是从这本特别棒的书中引用的 "Neural Networks and Deep Learning" by Michael Nielsen. 强烈推荐它)

        如果现在我们有两个5*5*3的滤波器,而不是一个,那么输出结果就会变成28*28*2,随着使用越来越多的滤波器,我们能够获取到越来越多的空间维度信息。从数学的角度,这就是卷积层所做的事。

First Layer – High Level Perspective

        让我们讨论一下,从更高的层面上,该如何看待这个卷积层所做的事。事实上,这些滤波器可以看做是特征识别器。这里的特征,指的是那些直线边缘、颜色、纹理等。考虑所有图像中都共通的一些最简单的特征。让我们把第一个滤波器改成大小为7*7*3的纹理识别器。(在这个章节,为了降低复杂度,我们忽略滤波器与图像的深度,仅考虑其顶层的灰度数列及其计算)。作为一个纹理识别器,它的像素结构将会在纹理形状的对应区域内具有较高的数值。(记住,所有的滤波器都只是数值和数值的组合)

        现在,让我们回到数学上。当我们把滤波器放到图像左上角时,它开始进行7*7范围的内积计算。这里我们用给出的特征提取滤波器举一个例子,见下图。    

        记住,我们做的事是矩阵对应的像素值相乘再相加。


        基本上,在输入图像中,如果当前形状跟我们的滤波器大体相像,卷积后的计算结果将会是一个很大的值!那么让我们看看如果移动滤波器到其它位置会发生什么。

        计算结果很小!这是因为滤波器和当前形状完全不同。记住,这个卷积层的输出是一个激活图层(activation map)。那么,针对这个单边缘滤波器卷积的简单例子来说,它的激活图层(activation map)将会展示出当前图像中最有可能是边缘特征的区域。在上图中,左上区域的卷积结果为6600,这个大数值表明图中对应位置有这样的边缘特征使得滤波器被激活了。而右上区域的值为0,表示当前区域没有任何能够激活滤波器的特征。(或更简单地说,图中该区域没有对应的图像特征)。记住,这只是一个滤波器。这只是一个检测竖直向右偏线状特征的滤波器。我们可以再添加其它滤波器用于检测竖直左偏或垂直特征等。滤波器越多,激活图层activation map的深度就越深,对于输入图像我们就能够获取到越多的信息。

        声明:文中所述的滤波器是为了卷积计算上的简化而定义的。下图给出了一些用于第一卷积层的实际滤波器示例,但主要参数(原理)还是类似的。第一层的滤波器通过对输入图像的卷积和“激活”(或计算极值点),来寻找输入图像中特定的特征。

        (注:上图来自于斯坦福Andrej Karpathy以及Justin Johnson CS 231N course 。推荐给需要深入理解CNN的朋友们)

Going Deeper Through the Network

        在传统的CNN结构中,在这些卷积层之间还夹杂有其它类型的层。我强烈建议大家去钻研并理解它们的功能和效果,这里仅给出一个大致的介绍。这些层提供了非线性特性nonlinearities与维度保留特性preservation of dimension用于提高整个网络结构的鲁棒性以及控制过拟合(control overfitting)。一个经典的CNN结构像下图所示:

        然而,最后一层是非常重要的一层,我们稍后会介绍。现在让我们回过头来看看到目前已经学了哪些东西。我们讨论了第一卷积层的滤波器的设计与检测方法。它们用来检测边缘、纹理这类低阶特征。正如大家想象的,要想预测图像中的事物,我们需要一个网络结构用于识别像手掌、爪子、耳朵这样的高阶特征。然后让我们想一下第一卷积层的输出应当是什么:一个28*28*3的数列结构(假设我们用的是三个5*5*3的滤波器)。当我们进入第二个卷积层的时候,第一卷积层的输出就变成了第二卷积层的输入。这就比较抽象,比较难以想象了。要知道,当我们讨论第一层时,输入就是未经处理的原始图像;但是,当我们讨论第二卷积层时,输入已经变成了第一层的卷积结果,也就是一个激活图层activation map。这个输入的每一层基本上描述了某些低阶特征在原始图像中出现的位置。现在,当你把它输入给第二层,也就是再使用一系列滤波器对其卷积时,输出就应当是表示更高阶特征的激活图层,例如说半圆(结合了一个曲线和直线边缘)、方形(结合了一些直线边缘)等。当你将数据继续经过更多的卷积层时,你就会得到更加高阶特征的激活图层。在整个网络的最后,你也许会用一些滤波器用以激活图像中的手写特征handwriting、粉红色 物体pink object等。如果你想知道更多关于滤波器可视化的信息,Matt Zeiler与Rob Fergus在他们的ConvNets模型与其研究论文research paper中有精彩的阐述。同样,在YouTube上,Jason Yosinski提供了一个非常棒的讲解视频video。另外一件有趣的事是,当你的网络结构越深,滤波器的感知区域范围就越大,这意味着它们在处理时,能够将对应原始图像中更大区域的信息都揽括在内(能够对更大的像素空间进行反应和感知)。

Fully Connected Layer

        检测出高阶特征后,我们可以锦上添花地在网络结构的最后添加一个全连通层fully connected layer。全连通层输入一个数列(无论这个输入是来自卷积层conv、线性整流ReLU层还是池化层pool),输出一个N维向量,N是由程序指定的分类类别数量。例如,对于一个数字分类程序,N就应该取10(0~9共10个数字)。这个N维向量中的每一个数字表示被分到该类的几率。例如,还是针对数字分类程序的分类结果为[0 .1 .1 .75 0 0 0 0 0 .05],这就表示这个输入的图像为1的概率有10%,为2的概率10%,为3的概率75%,为9的概率5%。(注:输出的表示方法不止这一种,这里只是展示了这种小数概率表示的softmax算法)。全连通层的工作原理是,根据之前其它层的输出(表示为高阶特征的激活图层),检测哪些特征与特定的类别相匹配。例如,程序若判定图像是一只狗,那么激活图层中表示狗的高阶特征,像是爪子、四条腿等特征将会具有很高的数值。再比如,程序若判定图像是一只鸟,那么激活图层中表示鸟的高阶特征,像是翅膀、鸟喙等就会具有很高的数值。基本上,全连通层的工作就是寻找与特定类别匹配的高阶特征,并设定相应的权重值。这样当我们把之前层的结果通过权重进行计算后,就能够正确估计其属于某一类别的可能性了。

 

Training (AKA:What Makes this Stuff Work)

        现在这个部分是我特意在之前的讨论中未提及的,很可能是关于神经网络的最重要的部分。也许你在之前的阅读时会有很多问题。比如,第一卷积层的滤波器怎么知道该如何查找边缘和纹理?而全连通层又是怎样知道该如何查找激活图层的?每一层的滤波器都是怎样设定其值的?计算机在解决这些问题时主要依靠一个训练过程training process,称作反向传播算法backpropagation(BP算法)。

        在讨论反向传播之前,我们必须先回过头来讲讲让神经网络(生物学意义上的)工作起来需要什么。当我们出生时,我们的大脑是一片空白的。我们并不知道什么是猫、狗、鸟儿。类似地,在CNN运行之前,滤波器的数值和权重等参数都是随机的。滤波器并不知道该如何寻找边缘和纹理。高阶层的滤波器同样也不知道该如何寻找爪子、鸟喙这类高阶特征。当我们逐渐长大,父母以及老师会利用照片、影像与之对应的标签来教会我们这些动物的辨别方法。这种利用标签的图片同样也是CNN所使用的训练过程。在深入研究之前,我们先简单地打个比方:我们有一个训练集,其中有几千张图片,绘制着猫、狗、鸟儿,同时每张图片上都有一个标明这是哪种动物的标签label。好的,现在回到我们的反向传播算法。

        反向传播算法可以分为4个部分:前向传播forward pass,损失函数loss function,反向传播backward pass,权重更新weight update。在前向传播的过程中,你将一个训练数据(32*32*3的图像数列,表示一个数字)输入整个网络。由于所有权重或滤波器系数都是随机生成的,输出很可能是类似这样的结果[.1 .1 .1 .1 .1 .1 .1 .1 .1 .1],基本上这样的输出难以判别是一个有效分类的。系统以目前的参数/权值是很难寻找低阶特征,并且也很难以此进行可靠的分类的。于是进入算法的损失函数阶段。记得我们刚才使用的输入是训练数据,那么除了图像数列外还附带标签。比方说这个输入的训练数据是3,那么标签就应当是[0 0 0 1 0 0 0 0 0 0]。通过其输出结果和标签的比较,就能够计算损失函数了。损失函数的定义方法很多,这里就用一个常见的均方差算法MSE(mean squared error),如下式

        这里我们使用变量L保存这个损失函数的结果。可以想象,在训练刚开始时这个值会相当的大。现在让我们用直觉去想想,我们想要找到一个“点”使得预测的标签(也就是网络的输出)和训练标签相同(这意味着我们的网络预测的结果是正确的)。那么我们需要做的就是最小化损失函数L。将其具象化,其实就是一个演算calculus优化的问题,我们想要找到哪些输入(在我们的系统中就是权值)更直接地影响到损失L,或者说误差。

    

        上图表示,针对这个问题具象化的一个方法是将其放在由一个误差(损失相关)坐标轴、两个权值坐标轴组成的3D图中。(当然大部分神经网络的权值显然大于2,这只是为了简化)。想要最小化误差值就需要不断调节权值w1和w2。用图上的话说,我们要找到这个碗状曲面的z轴最低点。要做到这一点,就需要求出误差在各个方向(权值)上的导数(斜率)。

        在数学上,这等价于针对某一层的权重W,计算导数dL/dW。那么现在,我们想要做的就是对网络进行一次反向传播backward pass,以判断哪些权重对损失L有较大的影响,并且调整这些权重以减小损失。一旦导数计算出来之后,我们就可以进行最后一个步骤:更新权重。我们将所有滤波器的权重进行更新使得它们随着其梯度方向的变化而改变(change in the direction of the gradient梯度下降法?)

        学习速率learning rate是由程序员所指定的参数。高学习速率表明在进行权重更新的跨度大,这样模型就能更快地汇集到最优的权重集上面来。但是,过高的学习速率有可能导致跨度过大而难以精确地收敛到最优解上。

 

        这整套流程:前向传递forward pass、损失函数loss function、后向传递backward pass、参数更新parameter update合起来称为一代epoch。针对每个训练图像,程序会重复进行多代epoch的训练。每次迭代结束后,我们都希望能够让网络通过良好地训练,正确地调节其每层的权重。

Testing

        最后,要想验证我们的CNN是否运作正确,我们需要准备另外一套数据和标签(不能与训练集重复!),将其送入CNN进行测试。我们会比较输出结果与金标准ground truth是否一致,以验证网络是否正确。

How Companies Use CNNs

        数据,数据,数据。这神奇的单词,那些有着大量数据的公司才能在激烈的市场竞争中获得潜在的先机。在网络训练的过程中,你的训练数据量越大,能够进行的训练迭代就越多,能够进行的权重调整也越多,调优的结果就越好。Facebook与Instagram公司拥有旗下数亿规模用户照片;Pinterest公司的网站上也有500亿张拼趣图片pin供其使用;Google坐拥海量的用户搜索数据;而Amazon则每天都在接收着几百万项用户产品购买的信息。现在你能明白隐藏在这些数据之下的魔力了吧。

 

 

Stride and Padding

        好的,让我们回忆一下我们的老朋友卷积层。还记得那些滤波器、感知区、卷积吗?现在让我们介绍两个卷积层中的重要参数。步长stride和填充padding。

        步长stride主要用于控制滤波器在输入图像上的卷积行为。如下图例子所示,滤波器在图像上的卷积是每次卷积计算后平移一定距离再次计算。这个距离就是通过步长stride来进行控制的。在这个例子中,步长stride设为1。通常来说,步长stride参数的选择需要考虑到这个步数能够被整除。让我们看一下图中的这个例子:设输入为7*7的图像,滤波器尺寸为3*3(同样忽略了第三维),设定步长为1,那么输出就应该是下图右边的5*5图像。

 

        还是老样子,不是吗?想想如果我们把步长stride设为2的情形。

        那么,感知区每次就会平移两个单位(像素),那么输出自然就相应的缩小为3*3了。如果我们想要把步长stride设为3,那么在进行卷积时就会有空间上的问题了,同时也难以保证感知区在平移时是否还处于图像内(译者:原因是步数不能整除?)。通常,在设计卷积层时,步长stride越大,那么感知区的重叠就越小,同样地输出的图像也越小。

        现在,让我们看一下填充padding参数。先想象一下这样的场景。当你使用3个5*5*3的滤波器对一个32*32*3的输入图像滤波,那么输出图像应当是28*28*3。注意空间尺寸变小了。然后我们把这个图像再次放入卷积层,会发现尺寸更小了。事实上,在整个网络的开始几层,我们并不希望尺寸缩水得这么快。我们希望能够尽量保留下这些输入图像的信息,才能够比较好地提取底层特征。也就是说,我们想要保证输出仍然是32*32*3。为了实现这个目的,我们在卷积层上引入了尺寸为2的零填充zero padding。它能够在输入图像的边界上形成一层宽度为2,值为0的边界。那么输入图像就变成了36*36*3。如下图所示。

        若设定步长stride为1,同时零填充zero padding的尺寸为:

 

 

        式中K为滤波器尺寸。那么输入和输出图像的尺寸就会永远保持一致。

        卷积层的输出图像的尺寸可以通过下面公式得出:

        式中O是输出宽/高,W是输入宽/高,K为滤波器尺寸,P为填充padding尺寸,S为步长stride。

 

Choosing Hyperparameters

         我们该怎样确定在一个系统中,卷积层的数量、滤波器的尺寸、或是步长stride、填充padding这些参数呢?这些可不是细枝末节的问题,另外这些问题也没有一个放之四海而皆准的答案。这是因为系统参数的设定主要取决于你的数据类型。数据的大小、图像的复杂度、图像处理的目的和方式等,都会随着处理目的的不同而发生变化。选择超参数的正确做法是,在检视数据集时通过抽象概念将数据/图像以合适的尺度正确组合起来。

 

ReLU (Rectified Linear Units) Layers

        按照惯例,每个卷积层之后都紧跟一个非线性层(或激活层activation layer)。由于系统在卷积层的计算主要为线性操作(像素/元素级的乘法和加法计算),因此这层的主要目的在于为系统引入非线性性。在过去,研究人员主要利用双曲正切tanh或S函数sigmoid作为非线性函数进行处理,后来大家发现线性整流层ReLU效果更佳,由于其计算效率能够大大加快整个系统训练的速度。同时它能减轻梯度消失问题vanishing gradient problem,这个问题主要出现在训练时,由于梯度呈指数下降而导致的底层训练十分缓慢的问题。线性整流层ReLU将输入图像的所有元素通过这样的一个整流函数:f(x) = max(0, x) 。从术语的角度上,本层将所有负激活negative activations都改为0,由此提高了模型与整个系统的非线性特性,而不会影响到卷积层的感知区。

        相关信息可参考Geoffrey Hinton大神(也就是深度学习之父)的论文Paper。http://www.cs.toronto.edu/~fritz/absps/reluICML.pdf

 

Pooling Layers

        通过线性整流层ReLU处理后,研究人员可能会在其后添加一个池化pooling层,也叫下采样downsampling层。这同样有许多可选的方法,其中最常用的是最大池化maxpooling方法。这种方法定义一个最大值滤波器(通常2*2)以及对应长度的步长stride,然后对输入图像进行滤波输出滤波器经过的每个子区域的最大值。如下图。

        池化层也可以用均值或L2范数(L2-norm,欧氏距离)来计算。池化层的直观意义在于,从输入图像中获取到某个特征(会有较高的激活值activation value),它与其它特征的相对位置比其自身的绝对位置更加重要。从图中不难看出,池化层大大降低了输入图像的空间维度(宽和高,不包括深度)。这就达到了两个目的。首先,参数/权重的个数降低为原来的1/4,计算量相应减少了。第二,它能够防止过拟合overfitting。过拟合表示为由于过度的调优导致模型过于执着满足样本的特征(可能有些是局部特征)而失去了泛用性,从而难以识别其它数据。过拟合overfitting的一个征兆就是,模型在训练集上能获得99%或100%的精确度但在测试数据上仅有50%。

 

 

Dropout Layers

        在神经网络中,丢包层dropout layer有一个非常特殊的函数。在上一节,我们介绍了过拟合overfitting问题,当训练完成后,由于对训练集过度调优导致的系统对新数据的识别效果不好。实际上丢包层在前向传播的过程中,故意地把一些随机的激活特征activations设为0值,这样就把它们简单地“丢包”了。那么,这样一个看似不必要、违反常理的简单操作有什么好处呢?事实上,它强行地保证了整个系统的冗余性。也就是说,系统需要满足这样的情形:当输入数据的一部分激活特征activations缺失时,系统也能够将其正确识别。丢包层dropout能够保证系统不会跟训练集过于相似,从而从一定程度上解决过拟合overfitting问题。需要注意的是,丢包层dropout仅用于训练环节。

        

 

Network in Network Layers

        网络中的网络层Network in Network(NIN)指的是一个拥有1*1大小滤波器的卷积层。初看你会奇怪网络层有什么用,它投影出来的感知区区域比待投影对象还大。但是,请记住滤波器还有一个维度:深度N。因此这是一个1*1*N的卷积操作。实际上,它进行了一次N-D逐像素乘法,其中N是输入数据的深度。(大概是用子网络结构代替原先的线性卷积)

 

Classification, Localization, Detection, Segmentation

        在我们之前的图像分类任务那个例子中,系统将输入的图像处理后输出其分类标签(或类别可能性数组)。但是,如果任务变成目标识别,那么除了需要进行分类外,还需要用一个框划出目标的具体位置。如图。

        另外在一些目标检测的任务中,图中所有目标的位置都需要确定。因此,就需要输出多个定位框和类别标签。

        更进一步地,在目标分割任务中,除开分类标签和定位之外,还需要系统能够将目标的边缘轮廓识别并描绘出来,如下图。

       

 

Transfer Learning

        许多人认为只有拥有了google级别的数据量才能训练好一个模型,这是一个常见的对深度学习的误解。诚然,数据量对于整个系统的搭建是至关重要的,然而我们还有迁移学习transfer learning的方法来缓解数据量的需求。迁移学习transfer learning是把已训练的模型(指其他研究人员利用其它大型数据集训练的模型及其参数/权重),利用自己的数据集进行参数调整的过程。方法思路在于,将已训练模型当作一个特征提取器feature extractor。我们需要把模型的最后一层(全连通层?)移除,替换为自己的分类器(取决于我们自己需要解决的问题空间)。然后,冻结其它层的权重/参数,并开展数据训练。冻结是为了防止在梯度