带我入门卷积神经网络的论文主要是LeCun的两篇文章。
1990年,LeCun建立了CNN的现代框架1。
1998年,LeCun改进CNN,开发了一个名为LeNet-5的多层人工神经网络,可以对手写数字进行分类2。
小记一下:AlexNet3
下面也就从这两篇文章出发,对卷积神经网络中的部分知识点进行梳理。
CNN的现代框架#

上图就是LeCun大神的第一篇文章里的网络结构图。
输入层INPUT 是单通道的 28x28 灰度图像,MNIST手写数字数据集。
隐藏层H1、H3 是shared weights feature extractor(共享权重特征提取器),俗称卷积核或卷积层。
隐藏层H2、H4 是averaging/subsampling layer(平均下采样层),俗称池化层。
输出层OUTPUT 是fullly connected layer(全连接层)。
下面是对每层网络的详细介绍:
H1是卷积层,初始化时生成 4@5x5 的权重矩阵(即4个5x5卷积核),通过这些卷积核分别对该层的输入矩阵进行卷积(步长为1,没有填充),得到 4@24x24 的卷积矩阵,再将这个输出矩阵加上偏置矩阵(4@24x24),得到最总的输出矩阵(4@24x24)。
在这个过程中卷积核就相当于BP网络中的权重矩阵,只是对于同一通道上的不同输入采用的是相同权重矩阵(共享权重),具体的前向传播过程如下:

H2是池化层,初始化时生成 2x2 的固定池化核,平均池化,所以池化核是0.250.250.250.25,然后对输入矩阵按通道逐个进行池化,得到最总的输出矩阵(4@12x12)。
H3是卷积层,这个卷积层相比 H1 就比较复杂,初始化时将生成4个 5x5 和8个 2@5x5 的卷积核。H2 与 H3 的连接关系参考下图:

- 使用4个 5x5 的卷积核对 H2 的4个通道进行单独卷积,生成1、4、7、10这4个通道的结果图像
- 使用4个 2@5x5 的卷积核对 H2 的1、2输入通道进行卷积,分别生成2、3、5、6这4个通道的结果图像
- 使用4个 2@5x5 的卷积核对 H2 的3、4输入通道进行卷积,分别生成8、9、11、12这4个通道的结果图像
H4是池化层,参考H2就可以了。
在这篇文章中,其实还并没有 LeNet 和 AlexNet 中那种比较明确的多层全连接层,唯一的全连接层只是用来最后的标签输出,并且由于文章中并没有介绍输出层的详细信息,所以我无法判断这里的输出层是否和我之前分类鸢尾花一样通过判断输出是否符合预期输出 1 4,来实现分类,还是已经和 LeNet 中一样,通过 RBF 来判断具体的分类。
我曾经有过猜想,就是固定卷积核然后将卷积结果作为每层的输入进行全连接的网络是怎样的?这篇文章里有所描述:
A fully connected network with enough discriminative power for the task would have far too many parameters to be able to generalize correctly.
所以在卷积神经网络中,卷积层和池化层就是一个特征提取与数据压缩的一个过程,而目标分类还是依靠后面的全连接层。
然后就是我关于该网络的一些猜想、该网络和现在流行网络的一些区别。
输入层,很显然这篇1990年的文章还没有强化学习的概念,没有旋转、翻转、批处理、组合分割、马赛克等方法。
卷积层,卷积的方法比较简单,还没有之后填充、扩张等复杂变化。
池化层,和现在的方法没啥使用上的区别,和卷积层差不多就是缺少复杂变化。
激活函数,在这篇文章里我并没有找到关于激活函数内容,但根据BP神经网络出现的时间,感觉差不多也是这个时候?可能还是太早了,在LeCun大神之后的文章里就有出现在池化层的后面接激活函数的网络。
全连接层/输出层,这里的全连接层还是比较简单,只有最后输出层的时候才通过卷积的方法实现了一次全连接,按现在网络的习惯,一般都会有至少2、3层全连接层保证输出。同时在输出层还是使用的比较简单的期望输出1,还没有实现 LeNet 里的 RBF 和现在常用的 Softmax。
LeNet-5#

在完成对LeCun大神第一篇文章的学习后,再反过来看 LeNet-5 就相对简单很多,但40页的文章还是过长,所以只挑部分网络内容讲,后面字符分割的先忽略。
INPUT是输入层,在这篇文章中使用的数据集依旧是 28x28 的手写字符数据集,比MNIST更加复杂,但这里以原始图像作为基础通过 0填充 的方法扩充到了32x32。
图像应该是使用的某种标准化方法,还没细看,需要补充。
LeNet-5 的卷积层和池化层相比第一篇有所差别(共享权重和偏置):
- 卷积层中,图像卷积后,单个输出通道中加上的偏置是相同的,而不是每个输出像素都有独立偏置,即每个卷积核有1个偏置
- 池化层中,池化并不是使用平均或最大池化,而是设立可调的池化核,池化核中的每个权重参数相同
- 池化层中,图像池化后,单个输出通道中加上的偏置是相同的,即每个池化核有1个偏置
INPUT -> C1 是卷积(层),卷积核尺寸是 6@5x5,步长1,每个输出通道有1个偏置,共计6x(5x5+1)=156个可调参数,输出 6@28x28。
C1 -> S2 是池化(层),池化核尺寸是 6@2x2,步长2,每个输出通道有1个偏置,共计6x(1+1)=12个可调参数,输出出 6@14x14,输出后通过 Sigmoid 进行激活。
S2 -> C3 是卷积(层),和之前的 H3 类似,C3 卷积层也比较复杂,S2 与 C3 的关系如下图所示:

- 使用6个 3@5x5 的卷积核依次对 S2 的6个通道中相邻的3个通道进行卷积,得到输出通道0、1、2、3、4、5
- 使用6个 4@5x5 的卷积核依次对 S2 的6个通道中相邻的4个通道进行卷积,得到输出通道6、7、8、9、10、11
- 使用3个 4@5x5 的卷积核对 S2 的6个通道中的4个通道进行卷积,挑选方法如图所示(相邻2个-空开-相邻2个),得到输出通道12、13、14
- 使用1个 6@5x5 的卷积核对 S2 的6个通道同时进行卷积,得到输出通道15
- 完成图像卷积后,每个输出通道加上1个偏置
C3 -> S4是池化(层),池化核尺寸是 16@2x2,步长2,每个输出通道有1个偏置,共计9x(1+1)=18个可调参数,输出 16@5x5,输出后通过 Sigmoid 进行激活。
S4 -> C5是卷积(层),使用120个 16@5x5 的卷积核,对 S4 的所以输出通道进行卷积,相当与有120个节点的全连接,共计 (16x5x5+1)x120=48120。
C5 -> F6是全连接(层),84个节点的全连接层,输出结果需要通过 Sigmoid 进行激活。
F6 -> OUPUT是全连接/输出(层),10个节点的全连接层
输出分类,这里在进行分类时,期望输出是字符的 ASCII 编码,并且计算具体分类时采用的的时 RBF(欧式径向基函数)。
似乎是还使用到了 Softmax+argmax 进行分类,还没有仔细看。
LeNet-5 已经具备现代神经网络中的大部分要素了,不像LeCun大神的第一篇文章里的网络那么简单了,毕竟时隔8年。
测试网络结构#
尝试构建了一个CNN,在CNN和LeNet-5的基础上,增加了部分AlexNet的方法,先上网络结构图:

输入层,将 MNIST数据集 的手写数组图片(28x28)通过 Zero-Padding(零填充) 到 32x32。
new_img = np.pad(array=img, pad_width=(2, 2), mode='constant')
输入层 ⇨ 卷积层C1,通过4个 5x5 的卷积核分别对输入矩阵进行卷积,得到 4@28x28 的净输入矩阵,然后通过 ReLU 激活。
LeNet-5 使用 Sigmoid 激活池化层的净输入
AlexNet 使用 ReLU 激活卷积层的净输入
卷积层C1 ⇨ 池化层S2,对 C1 的输出进行平均池化,得到 4@14x14 的净输入。
池化层S2 ⇨ 卷积层C3,对 S2 的输出进行卷积,卷积方法如下表所示:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|
0 | x | | | x | x | | x | x | x |
1 | x | x | | | x | x | | x | x |
2 | | x | x | | x | x | x | | x |
3 | | | x | x | | x | x | x | x |
类似于 LeNet-5 的卷积方法:
- 使用4个 2@5x5 的卷积核依次对 S2 的输出中相邻的2个通道进行卷积,得到净输入的 0、1、2、3 通道。
- 使用4个 3@5x5 的卷积核依次对 S2 的输出中相邻的3个通道进行卷积,得到净输入的 4、5、6、7 通道。
- 使用1个 4@5x5 的卷积核对 S2 的所有输出通道进行卷积,得到净输入的 8 通道。
通过以上步骤,得到 9@10x10 的净输入,然后通过 ReLU 激活,得到该层输出。
卷积层C3 ⇨ 池化层S4,对 C3 的输出进行平均池化,得到 9@5x5 的净输入(输出)。
比较一下 LeNet-5 和 AlexNet 是如何让数据从多维降到一维的:
LeNet-5 使用120个 9@5x5 的卷积核对池化层的多维输出矩阵进行卷积,得到120个节点的输出,然后与下一层进行全连接
AlexNet 将池化层的多维输出矩阵进行扁平化处理,得到一维矩阵,然后与下一层进行全连接
现在用到比较多的就是 AlexNet 这种扁平化处理,在代码实现以及逻辑上更加简单
池化层S4 ⇨ 全连接层F5,先将 S4 的输出(9@5x5)进行 扁平化(按行进行),得到225个节点的输出(120x1),然后F5 的120个节点,对这225个节点进行全连接,得到净输入,然后通过 ReLU 进行激活,得到输出。
全连接层F5 ⇨ 全连接层F6,F6 的84个节点,对 F5 的120个节点进行全连接,得到净输入,然后通过 ReLU 进行激活,得到输出。
全连接层F6 ⇨ 输出层,输出层的10个节点,对 F6 的84个节点进行全连接,得到预测值,然后通过 RBF 计算预测值到各个目标分类的距离,将距离最近的的目标分类作为预测出的分类。
模型训练历史:

各层的输出结果:
初始化后的输出,实际训练结果由于权重没保存,莫得了,下次先要完成权重的保存的读取😂
训练时间太长了!!

定义一下:
w 是权重矩阵、卷积核、池化核
b 是偏置
z 是通过权重和偏置计算得到的某一层的输出(净输入)
a 是在某一层的净输入的基础上通过激活得到的输出,是下一层的输入
前向传播#
卷积层#
单通道的输入,通过单个卷积核,得到对应的卷积结果。
- 需要1个卷积核w,初始化时,卷积核所有元素的累加值为1
- 需要1个偏置b,初始化时,偏置值为0
a11a21a31a12a22a32a13a23a33∗w11w21w12w22+b=z11z21z12z22
z11z12z21z22=a11w11+a12w12+a21w21+a22w22+b=a12w11+a13w12+a22w21+a23w22+b=a21w11+a22w12+a31w21+a32w22+b=a22w11+a23w12+a32w21+a33w22+b
输入多通道,通过多维卷积核,输出单通道,方法类似:∑aw+b=z
初始化时,多维卷积核的累加值依旧为1,同时依旧只有1个偏置,不随维度变化
∗ 表示卷积
池化层#
池化层的操作方法和卷积差不多,就是通过权重矩阵在输入上进行滑动,并将每次滑动所取的矩阵和权重矩阵相乘并累加获得最后结果。只是池化层的滑动步长一般与池化核的尺寸一致,而卷积层的滑动步长一般要小于卷积核(常取1)。
平均池化 就是池化核累加值为1,并且池化核内所有元素相等。
最大池化 就是池化核在输入上滑动时,当前窗口中最大的值。
池化层本身是可以进行反向传播并更新权重的,但平均池化和最大池化都属于其中特殊的方法,无需更新权重。
该种类型的池化层是取运算窗口中的所有值的均值作为最后运算的结果,可以表示为:

最大池化#
该种类型的池化层是取运算窗口中的最大值作为最后运算的结果,可以表示为:

张量扁平化6#
张量扁平化 是将输出矩阵视作多维张量,然后按一定的规律重新排列为一维张量,常见 CNN 的输入有4维,分别是 批处理、通道、高度、宽度,扁平化的排列顺序也如此。
a11a21a12a22=>(a11,a12,a21,a22)
反向传播#
CNN 的反向传播78910和之前的全连接网络没啥差别,就是在搞 卷积层3 的时候通道之间互相交错,在代码实现时就很混乱…
全连接层 ⇦ 全连接层#
回顾一下 ANN/DNN 的反向传播,在我所搭建的测试网络中,最后3层网络都是通过全连接实现的。
首先是确认 CNN 的预测值与实际值之间的损失(误差),这也是 F7/OUTPUT 与实际值之间的损失,使用L2Loss获取最终损失:
δ=∂y^∂e=y^−y
利用链式求导和数学归纳法,使用 δl+1 逐步计算出上一层l对实际值的损失δl:
δl=∂zl∂δ=∂zl+1∂δ×∂zl∂zl+1=(∂zl∂zl+1)Tδl+1=(wl+1)Tδl+1
如果有激活函数σ则是:
δl=(wl+1)Tδl+1⊙σ′(al)
其中:zl是当前层输出(净输入),zl+1是下一层输出,al是当前层经过激活的输出
有了当前层的输出损失δl后,开始计算w、b的梯度表达式:
δwl=∂wl∂δ=∂zl∂δ×∂wl∂zl=δl(al−1)T
δbl=∂bl∂δ=∂zl∂δ×∂bl∂zl=δl
其中:zl是当前层输出(净输入),al−1是上一层经过激活的输出(当前层的输入)
有了w、b梯度表达式,就可以用梯度下降法来优化w、b,求出最终的所有w、b的值。
卷积/池化层 ⇦ 全连接层#
逆扁平化,在我搭建的测试网络中的前向传播中,S4(9@5x5)到F5(120)之间通过扁平化,将三维张量扁平化为一维张量。
在 F5 到 S4 的反向传播过程中,求得的 S4 的输出损失是长度为225的一维张量,需要按照扁平化的顺序,将这225个梯度值转到三维张量(9@5x5)。
由于这里使用了 张量扁平化,δ、w、b的梯度表达式按 全连接层 ⇦ 全连接层 进行求取即可。
隐藏层 ⇦ 池化层,求隐藏层输出的损失#
平均池化#

对于 平均池化,下一层的损失会平均分配到上一层对应区块中的所有神经元。
最大池化#

对于 最大池化,下一层的误差项的值会原封不动的传递到上一层对应区块中的最大值所对应的神经元,而其他神经元的误差项的值都是0。
隐藏层 ⇦ 卷积层,求隐藏层输出的损失#
隐藏层的输出(卷积层的输入)为al−1(3x3):
al−1=a11a21a31a12a22a32a13a23a33
卷积层输出的损失为δl−1(3x3):
δl−1=∇a11∇a21∇a31∇a12∇a22∇a32∇a13∇a23∇a33
卷积层的输出为 al(2x2):
al=z11z21z12z22
卷积核为wl(2x2):
wl=w11w21w12w22
卷积层输出的损失为δl(2x2):
δl=δ11δ21δ12δ22
这里假设卷积层的前向传播过程如下:
a11a21a31a12a22a32a13a23a33∗w11w21w12w22=z11z21z12z22
z11z12z21z22=a11w11+a12w12+a21w21+a22w22=a12w11+a13w12+a22w21+a23w22=a21w11+a22w12+a31w21+a32w22=a22w11+a23w12+a32w21+a33w22
对于a11的梯度,由于在4个等式中a11只和z11有乘积关系,从而我们有:
∇a11=δ11w11
对于a12的梯度,由于在4个等式中a12和z11、z12都有乘积关系,从而我们有:
∇a12=δ11w12+δ12w11
依次类推,得到隐藏层输出损失张量中的所有元素:
∇a13∇a21∇a22∇a23∇a31∇a32∇a33=δ12w12=δ11w21+δ21w11=δ11w22+δ12w21+δ21w12+δ22w11=δ12w22+δ22w12=δ21w21=δ21w22+δ22w21=δ22w22
这里还找到了一个简化的描述11,通过卷积的方法进行表示(该卷积层在前向传播时是激活过的):
δl−1=∂zl−1∂δ=δl∗rot180(wl)⊙σ′(al)
00000δ11δ2100δ12δ2200000∗w22w12w21w11=∇a11∇a21∇a31∇a12∇a22∇a32∇a13∇a23∇a33
为了进行梯度计算,需要对当前卷积层输出的损失进行 0填充,填充至 4x4,然后将翻转180度的卷积核与损失张量进行卷积,得到前一层输出的损失张量。
求隐藏层w、b的梯度#
沿用上面求隐藏层输出的损失的例子,在获取输出损失后,求该层权重、偏置的梯度。
注意到卷积层输出z和w、b的关系为:
zl=al−1∗wl+b
由此我们得到:
δwl=al−1∗δl
注意到此时卷积核并没有反转,主要是此时是层内的求导,而不是反向传播到上一层的求导。具体过程我们可以分析一下。
沿用上面的例子,为了方便,忽略两层间的大小关系,当前隐藏的卷积过程如下:
a11a21a31a12a22a32a13a23a33∗w11w21w12w22=z11z21z12z22
已经获得了当前隐藏层输出的损失δl,求该隐藏层输出损失对权重和偏置的导数。
假设,l是层,i、j分别是输出矩阵的行列,p、q分别是权重矩阵的行列,卷积核w的导数可以表示如下:
δwl(p,q)=∑i∑j(δijlai+p,j+ql−1)
由此得到如下等式:
δwl(1,1)δwl(1,2)δwl(2,1)δwl(2,2)=a11δ11+a12δ12+a21δ21+a22δ22=a12δ11+a13δ12+a22δ21+a23δ22=a21δ11+a22δ12+a31δ21+a32δ22=a22δ11+a23δ12+a32δ21+a33δ22
归纳总结上面的式子后,得到:
δwl=a11a21a31a12a22a32a13a23a33∗δ11δ21δ12δ22
对于卷积层的偏置δbl的求法,与 ANN/DNN 不同,无法直接使其等于 δl,但由于每个卷积核仅有1个偏置(无论卷积核的通道数量),因此可以求得对于某个卷积核的偏置δbl就等于该卷积核卷积后输出损失的累加,其中i、j分别是输出损失的行与列:
δbl=∑i∑jδijl