8.2 卷积神经网络的基本结构

> 卷积神经网络的提出

1962年,Hubel和Wiesel通过对猫视觉皮层细胞的研究,提出了感受野(receptive field)的概念。通俗地说,就是他们发现视觉皮层的神经元是局部接受信息的,只受某些特定区域刺激的响应,而不是对全局图像进行感知。

1984年,日本学者Fukushima基于感受野概念提出神经认知机(neocognitron)。这个神经认知机是感受野概念在人工神经网络领域的首次应用。简单地说,神经认知机将一个视觉模式分解为许多子模式,它试图将视觉系统模型化。所以卷积神经网络可以说是全连接网络和感受野概念的结合应用。

卷积神经网络最近几年大热,但是大家不要误以为卷积神经网络是近几年才提出的。它既不是06年也不是12年提出的,早在1989年的时候,就已经提出了卷积神经网络,并在1995年应用于支票上的手写数字识别,获得了非常好的应用效果。

首先,我们来大致看一下一个完整的卷积神经网络的结构图,了解它的各个组件和流程。在对卷积神经网络有了整体的把握之后,我们再来对其中的关键部分进行重点讲解。

从图中我们可以清楚地看到,卷积神经网络是一个多层的神经网络,每一层是由多个二维平面组成的,其中每一个平面又是由多个独立神经元组成的。这和我们之前所接触过的全连接神经网络有什么异同呢?我们可以通过对比两种结构来介绍卷积神经网络的结构。

虽然全连接神经网络和卷积神经网在直观上看差异比较大,但它们的整体架构是比较相似的。

在结构方面,两者的输入层、全连接层和Softmax层都是一样的。

除了在结构上相似,全连接神经网络的损失函数、参数的优化过程等都同样适用于卷积神经网络。

两者唯一的区别就在于:神经网络中,相邻两层的连接方式不同。体现在网络结构上就是卷积层和降采样层。

第一个特别的地方就是卷积层。卷积运算的主要目的是使原信号特征增强,并降低噪音。

另一个特别的地方就是降采样层。什么是降采样呢?通俗地说,就是把分辨率高的图像变成分辨率较低的图像。降采样层有时候也叫池化层,但大家要清楚的一点是,我们可以通过池化进行降采样,但池化并不是唯一的降采样方式,通过步长大于1的卷积也可以达到降维的目的。

> 卷积

在介绍卷积神经网络的基本概念之前,我们先做一个求点积的运算。

(1)求点积:将5×5输入矩阵中3×3深蓝色区域中每个元素分别与其对应位置的权值(右下角的红色数字)相乘,然后再相加,所得到的值作为3×3输出矩阵(绿色)的元素。

那么3×3输出矩阵(绿色)中第一个元素12是怎么来的呢?

3×0+3×1+2×2+0×2+0×2+1×0+3×0+1×1+2×2=12

(2)滑动窗口:将3×3权值矩阵(深蓝色区域)向右移动一个格(即步长为1)

(3)重复操作:同样地,将此时深蓝色区域内每个元素分别与对应的权值相乘然后再相加,所得到的值作为输出矩阵的第二个元素。重复上述“求点积-滑动窗口”操作,直至输出矩阵所有值被填满。

卷积核在二维输入数据上“滑动”,对当前输入部分的元素进行矩阵乘法,然后将结果汇为单个输出的像素值,重复这个过程直到遍历整张图像,这个过程就叫做卷积。权值矩阵(红色的数字)就是卷积核。卷积操作后的图像(绿色矩阵)称为特征图(feature map)

从上面的介绍我们可以看出,卷积实际上是一个提供权重的模版,用邻域的点按一定的权重去重新定义这一点的运算,简单地说,就是矩阵的点积运算。我们来看一个更加直观的图:

所以卷积就是这样一个过程,用一个卷积核滑动地与图像相应位置的像素点相乘,汇总之后得到一个值,填充到新的位置上。

我们在介绍卷积神经网络之前,讲到了全连接神经网络最大的缺点就是参数太多,那么卷积神经网络是怎样有效地减少参数的呢?答案就是用到了局部连接权值共享

局部连接:每个输出特性不用查看每个输入特征,而只需查看部分输入特征。

权值共享:卷积核在图像上滑动过程中保持不变。

在上面的例子中,我们输入的特征是5×5=25,输出的大小是3×3=9,如果我们使用标准的全连接层,就会产生一个25×9=225个参数的权值矩阵,因为后面一层中每个节点都是前面一层中所有节点输入的加权求和。

而卷积操作中,每个深绿色的格子只与前面一层中3×3大小的深蓝色区域相连,而不用查看5×5的特征矩阵中所有的数值,这就是局部连接。卷积核在图像上滑动的过程中,红色的权值矩阵是不变的,这就是权值共享。因此,卷积神经网络只需要九个参数就可以实现从5×5到3×3的变换。

卷积是图像矩阵与权值矩阵的点乘。接下来我们通过一个例子来看一下,不同的卷积核对图像进行卷积操作之后会产生什么效果。

首先,我们定义了ImgConvolve(),这是一个进行卷积操作的函数。

然后定义卷积核。我们定义了三个卷积核,便于对比。

第一个卷积核在纵向上是有数值的,它的特点是纵向的。

第二个卷积核的特点是横向的。

第三个卷积核没有明显水平或是竖直方向的特点。

在定义卷积核之后,我们对它进行卷积操作(图8-21),并对卷积后的图像进行展示(图8-22)。

以下是卷积前的原图:

不同的卷积核对图像进行卷积操作,得到的图像特征是非常不一样的。

水平方向的卷积核对图像进行卷积之后,得到的图像显示出显著的水平方向的边缘特征。

类似的,竖直方向的卷积核对图像进行卷积之后,得到的图像显示出显著的竖直方向的边缘特征。

而拉普拉斯算子没有明显竖直或水平方向的特征,它显示出的特征图就兼顾了不同方向的特征。

此外,我们还可以看到,对于图像中没有边缘的区域,比如蓝天,大部分像素值几乎是相同的,所以卷积核在这些点,输出几乎也是相同的;而对于有边缘的区域,由于边缘两侧的像素是不一样的,卷积核计算结果因此也是不同的,从而能够检测到边缘,这就是边缘检测的原理。

对图像用一个卷积核进行卷积运算,实际上是一个滤波的过程。每个卷积核都是一种特征提取方式,就像是一个筛子,将图像中符合条件的部分筛选出来。

比如下图中,通过不同的卷积核提取不同的特征,达到了卷积运算使原信号特征增强,并降低噪音的作用。

> 0填充(Padding)

观察上面的卷积示例,我们会发现一个现象:在卷积核滑动的过程中图像的边缘会被裁剪掉,5×5特征矩阵经过卷积之后转换为3×3的特征矩阵,这里免不了会丢失一部分信息。

那我们如何使得输出尺寸与输入保持一致呢?这里就用到了一个很重要的技术——0填充。

用额外的“假”像素(通常值为0)填充边缘。这样,在滑动时的卷积核可以允许原始边缘像素位于卷积核的中心,同时延伸到边缘之外的假像素,从而产生与输入(5×5蓝色)相同大小的输出(5×5绿色)。

> 多通道卷积

我们知道,每个卷积核都会将图像生成为另一幅特征映射图,即一个卷积核提取一种特征。为了使特征提取更充分,我们可以添加多个卷积核以提取不同的特征,也就是多通道卷积

下面这张图就是每个通道使用一个卷积核进行卷积操作的过程(RGB三通道)。

然后,将这些特征图相同位置上的值相加,生成另外一张特征图。

最后,进行加偏置,这个加偏置和传统神经网络中是一样的。偏置的作用是对每个特征图加一个偏置项,以便产生最终的输出特征图。

> 池化

在卷积层之后常常紧接着一个降采样层,通过减小矩阵的长和宽,从而达到减少参数的目的。而降采样是降低特定信号的采样率的过程。

在这个降采样的过程中,我们经常使用的一个技术叫做池化(pooling)。池化就是计算图像一个区域上的某个特定特征的平均值或最大值。

比如下图中,把四个点聚合成一个点,从而达到了降维的目的。

卷积层的作用是探测上一层特征的局部连接,而池化的作用是在语义上把相似的特征合并起来,从而达到降维目的。

这些概要统计特征不仅具有低得多的维度(相比使用所有提取得到的特征),同时还会改善结果(不容易过拟合)。

常用的池化方法有均值池化最大池化,均值池化和最大池化适用于不同的场景。

均值池化:对池化区域内的像素点取均值,这种方法得到的特征数据对背景信息更敏感。

最大池化:对池化区域内所有像素点取最大值,这种方法得到的特征对纹理特征信息更加敏感。

举一个最大池化的例子:

左边矩阵中粉红色的四个格子的最大值是6,我们取最大值6填充到右边的矩阵中。同理,我们取剩下三种颜色中分别的最大值一一填充到右边的矩阵中。

如果是均值池化,那么右边矩阵中粉红色格子的值就是左边矩阵中四个粉红色格子数值相加除以格子数,其他的颜色也是同理。

我们回到之前介绍过的卷积网络的结构中,可以看到,特征图的数量在逐层增加,层和层之间的分辨率递减,递减的原因是为了去除冗余信息,因为相邻像素点之间往往表示相似的特征。

所以,我们可以通过池化操作把四个点平均成一个点,通过相似语义的聚合,达到降维的目的,同时不丢失过多的有效信息。但另一方面,为了检测更多的特征信息、形成更多不同通道特征的组合,从而形成更复杂的特征,需要逐渐增加每层所含的平面数(也就是特征图的数量)。

> 步长

步长是卷积操作的重要概念,它表示卷积核在图片上移动的格数。

通过步长的变换,可以得到不同尺寸的卷积输出结果。

步长大于1的卷积操作也是降维的一种方式。也就是说,在进行降采样的时候,我们既可以使用池化,也可以用步长大于1的卷积,来使得图像的分辨率降低。

卷积后图片尺寸可以这么计算:假如步长为S,原始图片尺寸为[N1,N1],卷积核大小为[N2,N2],那么卷积之后图像大小为[(N1-N2)/S+1, (N1-N2)/S+1]。

在卷积神经网络中,第一层是输入层,第二层是经过卷积操作之后的特征图,在卷积操作之后再进行降采样。

描述整个过程就是:输入图像通过若干个“卷积→降采样”后,连接成一个向量,输入到传统的分类器层中,最终得到输出。

如果用一个正则表达式来表示,就是:输入层→(卷积层+→池化层?)+→全连接层+。

> 示例代码

定义卷积操作函数.py
import numpy as np
from PIL import Image

def ImgConvolve(image_array,kernel):
    ''' 参数说明:
        image_array:原灰度图像矩阵
        kernel :卷积核
        返回值: 原图像与算子进行卷积后的结果
    '''
    image_arr = image_array.copy()
    img_dim1,img_dim2 = image_arr.shape
    k_dim1,k_dim2 = kernel.shape
    AddW = int((k_dim1-1)/2)
    AddH = int((k_dim2-1)/2)

    # padding填充
    temp = np.zeros([img_dim1 + AddW*2,img_dim2 + AddH*2])
    #将原图拷贝到临时图片的中央
    temp[AddW:AddW+img_dim1,AddH:AddH+img_dim2] = image_arr[:,:]
    #初始化一张同样大小的图片作为输出图片
    output = np.zeros_like(a=temp)
    #将扩充后的图和卷积核进行卷积
    for i in range(AddW,AddW+img_dim1):
        for j in range(AddH,AddH+img_dim2):
            output[i][j] = int(np.sum(temp[i-AddW:i+AddW+1,j-AddW:j+AddW+1]*kernel))

    return output[AddW:AddW+img_dim1,AddH:AddH+img_dim2]
定义卷积核.py
# 提取竖直方向特征
# sobel_x
kernel_1 = np.array(
                    [[-1, 0, 1],
                    [ -2, 0, 2],
                    [ -1, 0, 1]])

# 提取水平方向特征
# sobel_y
kernel_2 = np.array(
                    [[-1,-2,-1],
                     [ 0, 0, 0],
                     [ 1, 2, 1]])

# Laplace扩展算子
# 二阶微分算子
kernel_3 = np.array(
                    [[1, 1, 1],
                    [1,-8, 1],
                    [1, 1, 1]])
卷积操作并显示.py
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm

# 打开图像并转化成灰度图像
image = Image.open("img_066.png").convert("L")

# 将图像转化成数组
image_array = np.array(image)

# 卷积操作
sobel_x = ImgConvolve(image_array,kernel_1)
sobel_y = ImgConvolve(image_array,kernel_2)
laplace = ImgConvolve(image_array,kernel_3)

# 显示图像


plt.imshow(image_array,cmap=cm.gray)
plt.axis("off")
plt.show()

plt.imshow(sobel_x,cmap=cm.gray)
plt.axis("off")
plt.show()

plt.imshow(sobel_y,cmap=cm.gray)
plt.axis("off")
plt.show()

plt.imshow(laplace,cmap=cm.gray)
plt.axis("off")
plt.show()

Last updated