摘要:卷积神经网络(CNN)是处理图像数据的强大工具,利用平移不变性和局部性特征有效识别图像对象。CNN由卷积层、池化层和全连接层组成,其中卷积层通过核函数进行互相关运算,并采用填充(padding)和步幅(stride)参数控制特征提取。池化层(最大池化和平均池化)用于降低位置敏感性。实验代码展示了卷积和池化操作的具体实现,包括填充、步幅等关键参数设置效果。
0简介
卷积神经网络(convolutional neural network,CNN)是一类强大的为处理图像数据而设计的神经网络。基于卷积神经网络架构的模型在计算机视觉领域中已经占据主导地位,当今图像识别、目标检测或语义分割相关的商业应用都以CNN为基础。
以图像识别为例,随着现在图片的精度越来越高,包含的像素点数量达到百万级,训练需要消耗大量的算力。但图像上识别物体并不需要对每个像素进行处理,而是能提取到对象的特征。
对象识别利用对象特征的两个特性,平移不变性和局部性。
平移不变性(translation invariance):不管检测对象出现在图像中的哪个位置,神经网络的前几层应该对相同的图像区域具有相似的反应。
局部性(locality):神经网络的前面几层应该只探索输入图像的局部区域,而不过度在意图像中相隔较远区域的关系。
1卷积层
卷积神经网络由一个或多个卷积层和顶端的全连通层(对应经典的神经网络)组成,同时也包括关联权重和池化层(pooling layer)。这一结构使得卷积神经网络能够利用输入数据的二维结构。
卷积层对输入和卷积核进行互相关运算,并在添加标量偏置之后产生输出。所以,卷积层中的两个被训练的参数是卷积核和标量偏置。
卷积核(convolution kernel)又称核函数,一般是小维度的张量元素,高度和宽度通常为奇数,1、3、5、7。选择奇数的好处是在保持空间维度的同时在顶部和底部填充相同数量的行,在左侧和右侧填充相同的列。
输入张量和核函数的运算包含了两个动作参数,填充(padding)和步幅(stride)。
1.1填充(padding)
一个240x240像素的图像,经过10层5x5的卷积后,一层减少(5-1)的宽度和高度,共减少到200x200像素,这样,图像边缘丢失了很多数据,填充就是解决该问题的方法。如下图所示,左侧原来是3x3的张量,四周各填充一组数据,和2x2的核函数计算后,得到4x4的张量结果。
1.2步幅(stride)
在核函数计算时,核函数从输入张量的左上角开始向右、向下滑动。每次滑动的步数称为步幅,不同步数得到的结果不同。如下图所示,5x5的张量与3x3的核函数计算,步幅为1的话结果时3x3的结果,步幅为2的话为2x2的结果。
1.3程序
import torch
from torch import nn
#padding填充
#为方便起见,我们定义一个计算卷积的函数
#此函数初始化卷积层权重,并对输入核输出扩大核缩减相应的维数
def comp_conv2d(conv2d,X):
#(1,1)批量大小核通道数都是1
X=X.reshape((1,1)+X.shape)
Y=conv2d(X)
#省略前2个维度:批量大小核通道
return Y.reshape(Y.shape[2:])
#注意,每侧边都填充了1行或1列,共2行2列
conv2d=nn.Conv2d(1,1,kernel_size=3,padding=1)
X=torch.rand(size=(8,8))
comp_conv2d(conv2d,X).shape
结果:torch.Size([8, 8])
#非对称张量
conv2d=nn.Conv2d(1,1,kernel_size=(5,3),padding=(2,1))
comp_conv2d(conv2d,X).shape
结果:torch.Size([8, 8])
#stride步幅
conv2d=nn.Conv2d(1,1,kernel_size=3,padding=1,stride=2)
comp_conv2d(conv2d,X).shape
结果:torch.Size([4, 4])
conv2d=nn.Conv2d(1,1,kernel_size=(3,5),padding=(0,1),stride=(3,4))
comp_conv2d(conv2d,X).shape
结果:torch.Size([2, 2])
2.池化层(pooling layer)
池化层的双重目的:降低卷积层对位置的敏感性,降低对空间降采样表示的敏感性。与卷积层类似,池化层也由一个固定形状的窗口组成。
2.1池化方式
池化的计算方法不同,常用的方法是最大汇聚(maximum pooling)和平均汇聚(average pooling)。
最大汇聚就是在窗口内取最大值为结果,平均汇聚是以窗口数据的平均值为结果。
import torch
from torch import nn
from d2l import torch as d2l
#max和mean
def pool2d(X,pool_size,mode='max'):
p_h,p_w=pool_size
Y=torch.zeros(X.shape[0]-p_h+1,X.shape[1]-p_w+1)
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode=='max':
Y[i,j]=X[i:i+p_h,j:j+p_w].max()
elif mode=='avg':
Y[i,j]=X[i:i+p_h,j:j+p_w].mean()
return Y
X=torch.tensor([[0.0,1.0,2.0],[3.0,4.0,5.0],[6.0,7.0,8.0]])
pool2d(X,(2,2))
结果:tensor([[4., 5.], [7., 8.]])
#平均聚集
pool2d(X,(2,2),'avg')
结果:tensor([[2., 3.], [5., 6.]])
2.2填充和步幅
和卷积层一样,池化方法也有填充和步幅的参数设置。
#填充和步幅
X=torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))
X
结果:tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]]]])
pool2d=nn.MaxPool2d(3)
pool2d(X)
结果:tensor([[[[10.]]]])
pool2d=nn.MaxPool2d(3,padding=1,stride=3)
pool2d(X)
结果:tensor([[[[ 5., 7.],
[13., 15.]]]])
pool2d=nn.MaxPool2d((2,3),stride=(2,3),padding=(0,1))
pool2d(X)
结果:tensor([[[[ 5., 7.],
[13., 15.]]]])