机器学习 | 卷积神经网络详解 (二)——自己手写一个卷积神经网络
上篇文章中我们讲解了卷积神经网络的基本原理,包括几个基本层的定义、运算规则等。本文主要写卷积神经网络如何进行一次完整的训练,包括前向传播和反向传播,并自己手写一个卷积神经网络。如果不了解基本原理的,可以先看看上篇文章:机器学习 | 卷积神经网络CNN原理详解(一)——基本原理
自己手写一个卷积神经网络
一、卷积神经网络的前向传播
首先我们来看一个最简单的卷积神经网络:
1.输入层---->卷积层
以上一节的例子为例,输入是一个 4*44∗4 的 image,经过两个 2*2 的卷积核进行卷积运算后,变成两个 3*33∗3 的 feature_map
以卷积核 filter1 为例(stride = 1 ):
计算第一个卷积层神经元的输入:
神经元 o11 的输出:(此处使用 Relu 激活函数)
其他神经元计算方式相同
2.卷积层---->池化层
计算池化层 m_{11}m11 的输入(取窗口为 2 * 22∗2),池化层没有激活函数
3.池化层---->全连接层
池化层的输出到 flatten 层把所有元素“拍平”,然后到全连接层。
4.全连接层---->输出层
全连接层到输出层就是正常的神经元与神经元之间的邻接相连,通过 softmax 函数计算后输出到 output,得到不同类别的概率值,输出概率值最大的即为该图片的类别。
二、卷积神经网络的反向传播
传统的神经网络是全连接形式的,如果进行反向传播,只需要由下一层对前一层不断的求偏导,即求链式偏导就可以求出每一层的误差敏感项,然后求出权重和偏置项的梯度,即可更新权重。而卷积神经网络有两个特殊的层:卷积层和池化层。 池化层输出时不需要经过激活函数,是一个滑动窗口的最大值,一个常数,那么它的偏导是 1。池化层相当于对上层图片做了一个压缩,这个反向求误差敏感项时与传统的反向传播方式不同。从卷积后的 feature_map 反向传播到前一层时,由于前向传播时是通过卷积核做卷积运算得到的 feature_map,所以反向传播与传统的也不一样,需要更新卷积核的参数。下面我们介绍一下池化层和卷积层是如何做反向传播的。
在介绍之前,首先回顾一下传统的反向传播方法:
通过前向传播计算每一层的输入值(如卷积后的 feature_map 的第一个神经元的输入:
反向传播计算每个神经元的误差项,,其中 E 为损失函数计算得到的总体误差,可以用平方差,交叉熵等表示。
计算每个神经元权重的梯度,
更新权重(其中λ为学习率)
1. 卷积层的反向传播
由前向传播可得:
每一个神经元的值都是上一个神经元的输入作为这个神经元的输入,经过激活函数激活之后输出,作为下一个神经元的输入,在这里我用 i_{11}i11表示前一层,o_{11}o11表示 i_{11}i11的下一层。那么 net_{i_{11}}neti11就是 i_{11}i11这个神经元的输入,net_{i_{11}}neti11就是 i_{11}i11这个神经元的输出,同理,net_{o_{11}}neto11就是 o_{11}o11这个神经元的输入,out_{o_{11}}outo11就是 o_{11}o11这个神经元的输出,因为上一层神经元的输出 = 下一层神经元的输入,所以 out_{i_{11}}= net_{o_{11}}outi11=neto11,这里我为了简化,直接把 out_{i_{11}}outi11记为 i_{11}i11
net_{i_{11}}neti11表示上一层的输入,out_{i_{11}}outi11表示上一层的输出
首先计算卷积的上一层的第一个元素 i11 的误差项δ_{11}δ11:
此处我们并不清楚\frac{∂E}{∂i_{11}}∂i11∂E怎么算,那可以先把 input 层通过卷积核做完卷积运算后的输出 feature_map 写出来:
然后依次对输入元素 ii,j 求偏导
的偏导:
的偏导:
的偏导:
的偏导:
的偏导:
观察一下上面几个式子的规律,归纳一下,可以得到如下表达式:
图中的卷积核进行了 180°翻转,与这一层的误差敏感项矩阵周围补零后的矩阵做卷积运算后,就可以得到∂E∂i11,即
第一项求完后,我们来求第二项\frac{∂i_{11}}{∂net_{i_{11}}}∂neti11∂i11
此时我们的误差敏感矩阵就求完了,得到误差敏感矩阵后,即可求权重的梯度。
由于上面已经写出了卷积层的输入 net_{o_{11}}neto11与权重 h_{i,j}hi,j之间的表达式,所以可以直接求出:
推论出权重的梯度:
偏置项的梯度:
可以看出,偏置项的偏导等于这一层所有误差敏感项之和。得到了权重和偏置项的梯度后,就可以根据梯度下降法更新权重和梯度了。
2. 池化层的反向传播
池化层的反向传播就比较好求了,看着下面的图,左边是上一层的输出,也就是卷积层的输出 feature_map,右边是池化层的输入,还是先根据前向传播,把式子都写出来,方便计算:
假设上一层这个滑动窗口的最大值是 out_{o_{11}}outo11
这样就求出了池化层的误差敏感项矩阵。同理可以求出每个神经元的梯度并更新权重。
三、手写一个卷积神经网络
1.定义一个卷积层
首先我们通过 ConvLayer 来实现一个卷积层,定义卷积层的超参数
其中 calculate_output_size 用来计算通过卷积运算后输出的 feature_map 大小
2.构造一个激活函数
此处用的是 RELU 激活函数,因此我们在 activators.py 里定义,forward 是前向计算,backforward 是计算公式的导数:
其他常见的激活函数我们也可以放到 activators 里,如 sigmoid 函数,我们可以做如下定义:
如果我们需要自动以其他的激活函数,都可以在 activator.py 定义一个类即可。
3.定义一个类,保存卷积层的参数和梯度
4.卷积层的前向传播
1).获取卷积区域
2).进行卷积运算
3).增加 zero_padding
4).进行前向传播
其中 element_wise_op 函数是将每个组的元素对应相乘
5.卷积层的反向传播
1).将误差传递到上一层
2).保存传递到上一层的 sensitivity map 的数组
3).计算代码梯度
4).按照梯度下降法更新参数
6.MaxPooling 层的训练
1).定义 MaxPooling 类
2).前向传播计算
3).反向传播计算
完整代码公众号迈微电子研发社,后台回复"手写字识别"获取,请见:[cnn.py]
最后,我们用之前的 4 * 44∗4 的 image 数据检验一下通过一次卷积神经网络进行前向传播和反向传播后的输出结果:
运行一下:
运行结果:
四、PaddlePaddle 卷积神经网络源码解析
卷积层
在上篇文章中,我们对 paddlepaddle 实现卷积神经网络的的函数简单介绍了一下。在手写数字识别中,我们设计 CNN 的网络结构时,调用了一个函数 simple_img_conv_pool(上篇文章的链接已失效,因为已经把 framework—>fluid,更新速度太快了 = =)使用方式如下:
这个函数把卷积层和池化层两个部分封装在一起,只用调用一个函数就可以搞定,非常方便。如果只需要单独使用卷积层,可以调用这个函数 img_conv_layer,使用方式如下:
我们来看一下这个函数具体有哪些参数(注释写明了参数的含义和怎么使用)
我们了解这些参数的含义后,对比我们之前自己手写的 CNN,可以看出 paddlepaddle 有几个优点:
支持长方形和正方形的图片尺寸
支持滑动步长 stride、补零 zero_padding、扩展 dilation 在水平和垂直方向上设置不同的值
支持偏置项卷积核中能够共享
自动适配 cpu 和 gpu 的卷积网络
在我们自己写的 CNN 中,只支持正方形的图片长度,如果是长方形会报错。滑动步长,补零的维度等也只支持水平和垂直方向上的维度相同。了解卷积层的参数含义后,我们来看一下底层的源码是如何实现的:ConvBaseLayer.py 有兴趣的同学可以在这个链接下看看底层是如何用 C++写的 ConvLayer
池化层同理,可以按照之前的思路分析,有兴趣的可以一直顺延看到底层的实现,下次有机会再详细分析。(占坑明天补一下 tensorflow 的源码实现)
总结
本文主要讲解了卷积神经网络中反向传播的一些技巧,包括卷积层和池化层的反向传播与传统的反向传播的区别,并实现了一个完整的 CNN,后续大家可以自己修改一些代码,譬如当水平滑动长度与垂直滑动长度不同时需要怎么调整等等,最后研究了一下 paddlepaddle 中 CNN 中的卷积层的实现过程,对比自己写的 CNN,总结了 4 个优点,底层是 C++实现的,有兴趣的可以自己再去深入研究。写的比较粗糙,如果有问题欢迎留言:)
参考文章:
1.https://www.cnblogs.com/pinard/p/6494810.html
2.https://www.zybuluo.com/hanbingtao/note/476663
CNN 应用案例:
[1] 表情识别FER | 基于深度学习的人脸表情识别系统(Keras)
[3] 机器学习 | 卷积神经网络详解(二)——自己手写一个卷积神经网络
推荐文章
[3] 卷积神经网络中十大拍案叫绝的操作
[4] 机器学习算法之——梯度提升(Gradient Boosting) 算法讲解及Python实现
[5] 机器学习算法之——逻辑回归(Logistic Regression)
[6] 机器学习算法之——决策树模型(Decision Tree Model)算法讲解及Python实现
[7] 机器学习算法之——K最近邻(k-Nearest Neighbor,KNN)分类算法原理讲解
[8] 机器学习算法之——K最近邻(k-Nearest Neighbor,KNN)算法Python实现
传送门
关注微信公众号:迈微电子研发社,回复 “深度学习实用教程” 获取 Github 开源项目,回复“手写字识别”获取本文的完整代码。
△微信扫一扫关注「迈微电子研发社」公众号
知识星球:社群旨在分享 AI 算法岗的秋招/春招准备攻略(含刷题)、面经和内推机会、学习路线、知识题库等。
△扫码加入「迈微电子研发社」学习辅导群
版权声明: 本文为 InfoQ 作者【迈微AI研发社】的原创文章。
原文链接:【http://xie.infoq.cn/article/7e6f615cfc861bd606750b2f9】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论