几幅手稿讲解CNN
学习深度神经网络方面的算法已经有一段时间了,对目前比较经典的模型也有了一些了解。这种曾经一度低迷的方法现在已经吸引了很多领域的目光,在几年前仅仅存在于研究者想象中的应用,近几年也相继被深度学习方法实现了。无论是对数据的分析或是生成,无论数据形式是图像、视频、音频、文本还是其它复杂维度,也无论是下棋、玩游戏还是无人驾驶汽车导航,似乎总有人会发掘出这种强大工具的新用途。人类刚刚将仿生学运用到“如何创造智能”这个问题上,就发现了光明的前景。
我把在组里介绍深度学习(Deep Learning)基础知识时画的几幅手稿分享出来,希望能帮助新人更快的了解这种方法。我在讲解的过程中参考了李宏毅老师的PPT(网络上他的PPT也有不止一个版本,YouTube也有视频教程),推荐读者结合起来阅读。
任何一个算法都可以看作一个函数。输入
经过函数
产生输出
,我们期望获得的输出是
,所以我们的目的是找到一个函数
,使得
与
尽可能的接近。
同时,函数由一组参数
确定,所以输出
可以看作是输入
和参数
的因变量。当参数
固定时,每当输入不同的
,其对应的输出
一般也会不同。这时,
与
之间的差异称作误差(loss)。描述
,
,
与误差loss之间关系的函数叫作误差函数(loss function)。对于实际问题,一般误差函数总会存在一个最小值。寻找最优函数
的过程,实际上就是寻找一组参数
,使得无论输入
如何(当然是位于定义域内的
),其误差总是最小的。
对于分类问题,函数就是分类器,
就是需要通过训练获得的分类器参数。
在神经网络算法中,最基本的组成单位如图中左上所示,前一层神经元
,
,
(有时还有一个按层独立的叠加项
,称为偏置节点bias),经过权边
,
,
连接到下一层的节点
。权重
与节点
之间可以是简单的相乘再求和(线性网络),也可以是卷积后再求和(卷积神经网络,Convolutional Neural Networks,CNN),或者是其他的计算形式。即便是简单的线性运算,一个基本的单元可以用来表示一个线性分类器(左下),也可以用于线性拟合(右下),而且经过多层的累积计算之后还可以处理更复杂的问题。
卷积神经网络当中的“卷积”(convolution)与图像处理中的滤波器(filter)十分相似,只不过在滤波器中求和时输入元素与窗口元素是同向的,而在卷积计算中输入元素与窗口元素是反向的(注意公式中
的下标)。所以,一些依赖第三方库(比如OpenCV)的实现会直接把二维的卷积核做水平和竖直两个方向的翻转(或者旋转180度)之后直接调用一个滤波器函数(filter2D)就完成了卷积的计算。
在特征提取方面,卷积运算的作用与滤波器相同。如图中下方所示,假设在数轴上的输入数据是经过{2,-1,1}的一条曲线,与一个{1,-2,1}的核进行滤波(或者经过水平翻转后进行卷积),会得到一个比较大的输出(5),经过激活函数(actiation function)会产生一个十分接近于1的激活输出。这时,我们可以说输入符合特征
的模式(pattern)。
计算机科学中的神经网络算法只是从仿生学上部分借鉴了人类大脑的结构。上面的截图来自CrashCourse的科普视频。在大脑的神经元(Neuron)中,输入信号经过树突(dendrite)传入,然后再从轴突(Axon)传递出去。在神经元内部,信息以电信号方式传递,在神经元之间则以化学信号传递。在传递的过程中,信号的强度和速度是一定的,但频率是可变的。所以信号的强弱(比如痛感、情绪等)是以信号频率的高低来区分的。另外神经元之间的连接是多对多的,即一个神经元可以有多个输入和输出。
与之相比,神经网络算法一般没有信号频率的概念,即每个节点只向外产生一次激活(RNN递归计算的节点可以看作展开成一条节点链,但是链上每个节点依然只经过一次计算)。而且,目前的算法大多是严格按层级进行计算,并不像生物体神经元那样在三维空间中呈现纷繁复杂的结构。
在神经网络中,层与层之间的连接分为全连通(fully-connected)与局部连通(local connected)两种。一些比较古老的文献认为局部连通的计算形式可以用卷积的形式来表示,所以错误地把这种经过简化计算的局部连通网络称为卷积网络。局部连通与全连通相比,参数数量要少得多,所以在过去计算机性能不佳时是一个比较有效的性能优化有段。但与此同时,局部连通也不可避免地引入了只有相邻节点(
,
,
)的输出才与下层节点(
)有关的约束,对大多实际问题是不合理的。直到Dropout的出现,结合了二者的优点,同时还提高了网络的泛化能力,成为目前十分流行的技术。
对于一个机器学习算法来说(也可以推广到其他领域的算法),最关键的三点是模型、数据和误差函数。模型即确定输入、参数与输出之间的关系;数据即我设计的模型是针对什么样的数据,期望得到什么样的输出;误差函数是评价一个算法好坏的关键,需要用明确的表达式合理地衡量实际输出与理想输出之间的差异。前文说过,寻找一个最优函数的过程,即寻找一个使误差
最小的参数
的过程。如果我们规定的误差
是可微的,那么最优参数
必然落在误差函数的驻点处(
对
的偏导等于0)。但是稍微复杂一点的问题都无法一下子确定最优参数
,我们只能从一个猜测的
出发来寻找最优值。这就很自然地引入了梯度下降法(Gradient Descent)。
对于一个可微函数(误差函数),其上任意一点处的偏导大小代表该点处切线斜率大小,方向(正负号)代表这条切线是向上还是向下的。因为我们需要在变量的方向上寻找最低点,所以要向梯度方向的反方向(即下降方向)进行搜索。需要注意在一些问题中,误差越大并不代表错误得越离谱,而是为了对模型的纠正过程中施加一个更大的作用力。
网络上有很多关于梯度下降法的介绍,这里不再赘述。推荐阅读An overview of gradient descent optimization algorithms来了解一些常用的梯度方法。
这张图来自李宏毅老师的PPT,是为了说明可能会造成训练收敛缓慢的原因。在函数平缓的区域,由于偏导本身数值很小,会导致参数的更新量也很小,这时就需要比较大的步长。在鞍点时,某一轴上是极小点,但在其他轴上并不是极小点,但由于误差函数形式的原因(比如涉及到对误差取偶数次幂),会表现为在鞍点周围误差都大于鞍点,所以使得训练过程误“收敛”于鞍点。由于大量的局部极小点和鞍点的存在,深度神经网络训练的调参也是一个十分精细的工作。关于在鞍点方面的分析,请参考Identifying and attacking the saddle point problem in high-dimensional non-convex optimization。
深度神经网络一般用反向传播训练方法(Back Propagation)来迭代地更新参数。上图是以线性网络为例解释BP的计算过程,公式应该可以自明,我就不用文字赘述了。对于卷积网络,其实计算过程是相同的,只不过把偏导项之间的乘法替换为卷积(卷积核在水平和竖直翻转之后的卷积)。推荐阅读Backpropagation in Convolutional Neural Network了解CNN中BP算法的细节。
当训练结果不好时,可能会有两种结果,欠拟合与过拟合。欠拟合是指模型不足以对训练集产生比较高的分类精度,从误差-迭代曲线上表现为无论是训练期间还是测试期间,误差都比较高。这说明模型对特征的提取不够,不足以用来描述样本间的差异。这时一般需要优化方法来解决这个问题,比如改变激活函数、误差函数,或者换一种梯度下降方法(以及调整梯度方法的参数)。过拟合是指模型对训练集有比较高的分类精度,但对测试集表现不佳,从误差-迭代曲线上表现为在训练期间误差能够收敛到一个较小值,但测试期间误差却比较大。这说明模型过分地依赖训练样本的特征,对没有遇见过新样本不知所措,缺乏泛化能力。这时需要正则化方法来提高模型对一般性样本的适应性,比如Dropout和Batch Normalization。
误差不收敛的一个更常见的原因——尤其是在一个新模型刚刚建立时——是梯度消失或梯度爆炸。在网络中缺少比较可靠的正则化技术时,在网络不断迭代训练的过程中(甚至第二次迭代开始)会发现新样本产生的误差梯度在反向传播的过程中越来越小(或越来越大),有时呈现每一两层就减小(或增大)一个数量级。梯度趋向消失时,无论训练多久,会发现最浅层(前一两层)的参数与初始值并没有太大变化,这就使得浅层的存在失去了意义,而且这也会使训练过程变得非常缓慢。梯度爆炸时,仅仅几次迭代之后就会发现某一层所有节点的输出都变成了1(或者十分接近于1),这时网络也就失去了分类的能力。
既然已经知道网络的输入和参数会影响最终输出的误差,那么也就可以假设我们可以在一个三维坐标上画出三者的关系。如图中所示,当参数(parameter)固定时,每输入不同的样本(sample),就会产生不同的误差(loss),因为真实样本
与理想样本
相比总是存在误差的。而对于同一个样本,变化的参数一般也会产生变化的误差。所以训练网络的过程实际上是在sample和parameter两个轴上不断变化时找到loss的最低点。
在训练模型时,一般会将训练集等分为若干小集合(mini-batch),一次将一个mini-batch输入网络,计算完所有mini-batch后——如果觉得网络精度还达不到要求——将所有样本随机排序,再分割为若干mini-batch进行训练。这个过程可以看作在sample轴上随机跳跃,在parameter轴上逐步前进地搜索,能够尽可能地保证搜索到的最低点是所有样本的最低点。
特征提取是一个分类器的核心,深度学习的优势就在于它能自动从原始数据提炼出特征,并以层级的逻辑组合这些特征来描述原始样本。我在最后一幅图中用一个简单的例子来说明CNN的层级结构是如何解决图像分类问题的。
假设我们需要用机器视觉方法对图A(两个三角形构成松树的形状)和图B(两个三角形构成钻石的形状)进行区分。在神经网络方法出现之前,一种比较可行的方法是通过图像处理中的直线检测方法找到图像中所有直线,然后通过直线参数之间的关系来确定如下判断规则:如果下面的三角形尖角朝上,即为松树;如果尖角朝下,即为钻石。经过细致的调参,这个算法应该已经可以完美解决区分图A与图B的问题了。如果现在又来了一副图C(也许是两个三角形水平排列构成小山的形状,也可能根本不包含三角形),需要用之前的算法来同时区分这三幅图片,怎么办?
好在我们可以用CNN来解决这个问题。首先需要注意,我在这一小节所指“卷积”实际上是滤波操作,因为卷积涉及翻转,不利用直观理解。假设我们训练好的网络有两层隐层,第一层包含两个节点(图中第二列蓝色图形,分别为一条左斜线与一条右斜线),第二层包含四个节点(图中第四列蓝色图形,分别为一条水平线,一条竖直线,一条左斜线与一条右斜线)。图A经过第一隐层,得到图中第三列黑色的图形。黑色的圆点代表原始图像中对某个卷积核激活值高的区域,白色代表激活值低的区域。图A松树左侧的两条斜边经过“左斜线”卷积核计算得到位于图像左侧的两个黑色圆点,其他区域都不符合“左斜线”这个特征,所以输出值全部忽略为0。同时,只有松树右侧的两条斜边会对“右斜线”卷积核产生高激活(得到两个位于右侧的黑色圆点),其他区域产生的激活都为0。同理,图B钻石图像经过“左斜线”与“右斜线”卷积核产生两幅不同的图像(一副在左上和右下有黑点,一副在右上和左下有黑点)。这时,第一层的计算就完成了。
与一般的CNN模型一样,我们把第一层的结果(图中第三列)输入第二隐层之前要缩小一下图像的尺度。经过缩小之后(你可以眯起眼睛离屏幕稍远些观察),第三列的四个图形分别变成了一条在左侧的竖线,一条在右侧的竖线,一条右斜线和一条左斜线。现在,我们拿第二层的四个卷积核(第四列蓝色图形)来对这四个结果进行卷积再求和。为了简化,如果在图像中存在一个区域使其与某卷积核的激活输出值较高,就将该卷积核的对应输出记为1;如果不存在这样的一个区域即记为0。这样,图中第三列第一个图像对四个卷积核分别产生,第二个图像产生
,所以图A的最终结果是这两个向量的和,为
。而图B的结果为
。虽然图A与图B有相似之处,但经过两次卷积得到的向量是完全不同的,通过这两个向量,我们就能唯一地确定图A与图B。
如果有新的样本加入,我们只需要改变一下图例中的卷积核数目和形状(或者甚至不对网络做任何修改)也能够轻松地实现分类。当然,CNN方法在实际运用时是不需要人为地设计卷积核的,而是依靠对样本的训练逐渐构造的。
其实,作为一个尚未成熟的工具,深度学习的优点与缺点同样明显。其优点毋庸置疑,是推动其在各领域蔓延的高分类精度,确切地说是它能够自主归纳特征,免去了过去慢慢手工筛选特征来提高精度的过程。深度学习方法的缺点也十分致命,即训练结果完全的不可预测性。在训练完成、进行测试之前,即便是有经验的工程师也难以给出其精度的界,更无法预知训练后的参数会变成什么样。虽然这种说法对于一个机器学习方法来说有些苛刻,毕竟在数值计算领域有很多迭代算法也会因为微小的参数变化而难以收敛。但是作为一个参数量级惊人的算法,人类对其参数含义的解读也是困难的。其在多个领域的广泛运用产生的一个结果是,参数的不可预测性会带来其行为的不可预测性。虽然技术的推广者经常会如何保证甚至如何吹嘘他们训练出来的模型在很多条件下都有很高的精度(或者说,执行设计者所期望的行为),但更有可能的是,吹牛的人并不知道模型究竟为什么会在特定条件下产生特定的结果。作为一个“黑盒”算法,其对外的表征是高分通过所有测试,但对于测试内容没有考虑到的情况才是值得担心的。一个廉价而高效的工具在市场上的阻力是比较小的,那么比市场上现有算法更复杂更强大的算法势必会被不断推出。如果我们能够假定“智能”只是一个不断收集和处理数据的过程,那么我们也可以说《终结者》所描述的世界也并纯粹的想象了。
原文来自:http://johnhany.net/2017/08/deep-learning-intro-hand-scripts-for-cnn/