前面的八篇学习笔记,基本上都是围绕着深度神经网络(DNN)和全连接网络(FCN)在学习。从本篇开始,将带着大家一起学习和研究深度学习的另一个主题——卷积神经网络(Convolutional Neural Network),也就是我们平常眼熟的 CNN。卷积神经网络作为当前计算机视觉领域的核心技术,发展到如今已是枝繁叶茂。笔者对于这一块的初步打算是从卷积网络的基本原理讲起,将卷积网络的前向传播和反向传播过程讲清楚,以及如何使用 numpy 和 tensorflow 实现卷积网络。然后会从深度卷积网络的发展历程出发,对主要的经典深度网络进行深度剖析,对计算机视觉的三大核心任务:图像分别、目标检测和图像分割等技术算法进行详细学习和讲解。
从前面的学习中,我们了解了深度神经网络的一般结构,它的前向传播和反向传播机制,而卷积神经网络相较于深度神经网络,其主要区别就在于卷积层,卷积层的存在使得神经网络具备更强的学习能力。除了卷积层之外,池化层(Pooling layer)的存在也使得卷积神经网络的鲁棒性更强,最后则是 DNN 中常见的全连接层(Fully Connected layer)。一个典型的卷积神经网络通常包括这三层。
卷积神经网络的结构
那到底什么是卷积?
从数学来说,卷积可以理解为一种类似于加权运算一样的操作。在图像处理中,针对图像的像素矩阵,卷积操作就是用一个卷积核来逐行逐列的扫描像素矩阵,并与像素矩阵做元素相乘,以此得到新的像素矩阵。这个过程是为卷积。其中卷积核也叫过滤器或者滤波器,滤波器在输入像素矩阵上扫过的面积称之为感受野。可能你还有点晕,让我来更详细的解释下。
卷积过程
且看上面的动图(这里感谢一下 NG 大大给我们提供这么好的教学资料),我们用一个 3x3 的滤波器去扫描一个 5x5 的像素矩阵,用滤波器中每一个元素与像素矩阵中感受野内的元素进行乘积运算,可得到了一个 3x3 的输出像素矩阵,这个输出的 3x3 像素矩阵能够较大程度的提取原始像素矩阵的图像特征,这也是卷积神经网络之所以有效的原因。为防止有同学不清楚卷积是如何计算的,笔者以输出像素矩阵中第一个元素 4 为例,演示一下计算过程:
1x1 + 1x0 + 1x1 + 0x0 +1x1 + 1x0 + 0x1 +0x0 + 1x1 = 4
当然,这里你可能会问:如何确定经过卷积后的输出矩阵的维度?我们是有计算公式的。假设原始输入像素矩阵的 shape 为 nxn,滤波器的 shape 为 fxf,那么输出像素矩阵的 shape 为 (n-f+1)x(n-f+1) 。
大体上卷积操作就是这么个过程,是不是非常简单。但这里我们也需要注意两个问题:第一个就是滤波器移动的步幅问题,上面的例子中我们的滤波器的移动步长为 1 ,即在像素矩阵上一格一格平移。但如果滤波器是以两个单位或者更多单位平移呢?这里就涉及到卷积过程中的 stride 问题。第二个问题涉及到卷积操作的两个缺点,第一个缺点在于每次做卷积,你的图像就会变小,可能做了几次卷积之后,你的图像就变成 1x1,这就不好办了。第二个缺点在于原始输入像素矩阵的边缘和角落的像素点只能被滤波器扫到一次,而靠近像素中心点的像素点则会被多次扫到进行卷积。这就使得边缘和角落里的像素特征提取不足,这就涉及到卷积过程中的 padding 问题。
针对第一个问题,也就是卷积步长问题,其实也很简单,就是按照正常的卷积过程去操作,只不过每次多走一个像素单位而已。且看卷积步幅为 2 的卷积操作示例:
我们用一个 3x3 的滤波器去对原始像素为 7x7 的图像进行卷积操作,设定卷积步长为 2,可看到输出像素矩阵的第二行第一个元素 69 的计算跨越了两个像素格点,计算过程为:
3x3 + 4x4 + 8x4 + 7x1 + 8x0 + 3x2 + 4x-1 + 2x0 + 1x3 = 69
加入步长之后我们的输出像素矩阵的 shape 的计算公式需要更新一下为:
((n-f)/s+1)x((n-f)/s+1) 。其中 s 为步长。
针对第二个问题,卷积神经网络采用一种叫做 padding 的操作,即对原始像素边缘和角落进行零填充,以期能够在卷积过程中充分利用边缘和角落的像素特征。至于填充多少 0 像素值,一般有两个选择,一是 valid 填充,也就是不填充,所以就不用管它了。我们在意的是有填充,就是第二种,same 填充方法。即填充后,输入和输出大小是一致的,对于nxn大小的输入像素,如果你用填充了 p 个像素点之后,n 就变成了 n+2p,最后输出像素的 shape 计算公式就变成了 ((n+2p-f)/s+1)x((n+2p-f)/s+1),要想让 n+2p-f+1=n 的话,输入输出大小相等,则 p=(f-1)/2。所以,一般而言,滤波器的大小 f 都会选择为奇数个。
实际操作中,padding 的编程写法如下:
defzero_pad(X, pad): X_pad = np.pad(X, ((0,0), (pad, pad), (pad, pad), (0,0)),'constant')
returnX_pad
numpy 一行代码即可搞定。测试效果如下:
np.random.seed(1)
x = np.random.randn(4,3,3,2)x_pad = zero_pad(x,2)fig, axarr = plt.subplots(1,2)axarr[0].set_title('x')axarr[0].imshow(x[0,:,:,0])axarr[1].set_title('x_pad')axarr[1].imshow(x_pad[0,:,:,0])
本节对卷积神经网络的卷积细节进行了详细的讲解和笔记。关于带有颜色通道的卷积操作我们下次笔记见。