卷积神经⽹络(convolutional neural network)是含有卷积层(convolutional layer)的神经⽹络。 本章中介绍的卷积神经⽹络均使⽤最常⻅的⼆维卷积层。它有⾼和宽两个空间维度,常⽤来处理图像数 据。本节中,我们将介绍简单形式的⼆维卷积层的⼯作原理。
卷积层得名于卷积(convolution)运算,但通常在卷积层中使⽤更加直观的互相关(crosscorrelation)运算。在⼆维卷积层中,⼀个⼆维输⼊数组和⼀个⼆维核(kernel)数组通过互相关运算 输出⼀个⼆维数组。
下面用一个具体的例子来介绍,首先输入时一个3×3的二维数组,核数组的⾼和宽分别为2。该数组 在卷积计算中⼜称卷积核或过滤器(filter)。卷积核窗⼝(⼜称卷积窗⼝)的形状取决于卷积核的⾼和 宽,即2×2 。图中阴影部分为第⼀个输出元素及其计算所使⽤的输⼊和核数组元素:0×0+1×1+3×2+4×3=19 。
在⼆维互相关运算中,卷积窗⼝从输⼊数组的最左上⽅开始,按从左往右、从上往下的顺序,依次在输 ⼊数组上滑动。当卷积窗⼝滑动到某⼀位置时,窗⼝中的输⼊⼦数组与核数组按元素相乘并求和,得到 输出数组中相应位置的元素。上图中的输出数组⾼和宽分别为2,其中的4个元素由⼆维互相关运算得 出:
下⾯我们将上述过程实现在 corr2d 函数⾥。它接受输⼊数组 X 与核数组 K ,并输出数组 Y 。
import torch
from torch import nn
#卷积核的计算,它接受输⼊数组 X 与核数组 K ,并输出数组 Y
def corr2d(X,K):#此函数已经保存在d2lzh_pytorch包中方便以后使用
h, w = K.shape
Y = torch.zeros((X.shape[0]-h+1, X.shape[1]-w+1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i+h,j:j+w]*K).sum()
return Y
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])#上图中的实例
K = torch.tensor([[0, 1], [2, 3]])
corr2d(X, K)
⼆维卷积层将输⼊和卷积核做互相关运算,并加上⼀个标量偏差来得到输出。卷积层的模型参数包括了 卷积核和标量偏差。在训练模型的时候,通常我们先对卷积核随机初始化,然后不断迭代卷积核和偏 差。
下⾯基于 corr2d 函数来实现⼀个⾃定义的⼆维卷积层。在构造函数 __init__ ⾥我们声明 weight 和 bias 这两个模型参数。前向计算函数 forward 则是直接调⽤ corr2d 函数再加上偏差。
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super(Conv2D, self).__init__()
self.weight = nn.Parameter(torch.randn(kernel_size))
self.bias = nn.Parameter(torch.randn(1))
def forword(self, x):
return corr2d(x, self.weight) + self.bias
卷积窗⼝形状为 p×q的卷积层称为 p×q卷积层。同样, p×q卷积或p×q 卷积核说明卷积核的⾼和宽 分别为 p和q 。
卷积层的简单应⽤:检测图像中物体的边缘,即找到像素变化的位置。⾸先我们构造 ⼀张 的图像(即⾼和宽分别为6像素和8像素的图像)。它中间4列为⿊(0),其余为⽩(1)。
X = torch.ones(6, 8)
X[:, 2:6] = 0
X
输出:
tensor([[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.]])
然后构造⼀个⾼和宽分别为1和2的卷积核 K 。当它与输⼊做互相关运算时,如果横向相邻元素相 同,输出为0;否则输出为⾮0。
K = torch.tensor([[1, -1]])
#下⾯将输⼊ X 和我们设计的卷积核 K 做互相关运算。可以看出,我们将从⽩到⿊的边缘和从⿊到⽩的
#边缘分别检测成了1和-1。其余部分的输出全是0。
Y = corr2d(X, K)
Y
tensor([[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.]])
由此可以看出,卷积层可通过重复使⽤卷积核有效地表征局部空间。
实际上,卷积运算与互相关运算类似。为了得到卷积运算的输出,我们只需将核数组左右翻转并上下翻 转,再与输⼊数组做互相关运算。可⻅,卷积运算和互相关运算虽然类似,但如果它们使⽤相同的核数 组,对于同⼀个输⼊,输出往往并不相同。
⼆维卷积层输出的⼆维数组可以看作是输⼊在空间维度(宽和⾼)上某⼀级的表征,也叫特征图 (feature map)。影响元素的前向计算的所有可能输⼊区域(可能⼤于输⼊的实际尺⼨)叫做 的 感受野(receptive field)。上图中输⼊中阴影部分的四个元素是输出中阴影部分元素的感受野。
在上⼀节的例⼦⾥,我们使⽤⾼和宽为3的输⼊与⾼和宽为2的卷积核得到⾼和宽为2的输出。⼀般来 说,假设输⼊形状是 ,卷积核窗⼝形状是 ,那么输出形状将会是
所以卷积层的输出形状由输⼊形状和卷积核窗⼝形状决定。
填充和步幅可以对给定形状的输⼊和卷积核改变输出形状。
填充(padding)是指在输⼊⾼和宽的两侧填充元素(通常是0元素)。
下图中我们在原输⼊⾼和宽的两侧分别添加了值为0的元素,使得输⼊⾼和宽从3变成了5,并导致输出⾼和宽由2增加到4。图中的阴影部分为第⼀个输出元素及其计算所使⽤的输⼊和核数组元素:0×0+0×1+0×2+0×3=0。
⼀般来说,如果在⾼的两侧⼀共填充 ⾏,在宽的两侧⼀共填充 列,那么输出形状将会是也就是说,输出的⾼和宽会分别增加 和 。
卷积神经⽹络经常使⽤奇数⾼宽的卷积核,如1、3、5和7,所以两端上的填充个数相等。
卷积窗⼝从输⼊数组的最左上⽅开始,按从左往右、从上往下 的顺序,依次在输⼊数组上滑动。我们将每次滑动的⾏数和列数称为步幅(stride)。
在上面的例子中,⾼和宽两个⽅向上步幅均为1。也可以使⽤更⼤步幅。下图展示了在⾼上步幅为3、在宽上步幅为2的⼆维互相关运算。可以看到,输出第⼀列第⼆个元素时,卷积窗⼝向下滑动了3⾏,⽽在输出第⼀⾏第⼆个元素时卷积窗⼝向右滑动了2列。当卷积窗⼝在输⼊上再向右滑动2列 时,由于输⼊元素⽆法填满窗⼝,⽆结果输出。图中的阴影部分为输出元素及其计算所使⽤的输⼊和核数组元素:0×0+0×1+1×2+2×3=8,0×0+6×1+0×2+0×3=6 。
⼀般来说,当⾼上步幅为 ,宽上步幅为 时,输出形状为
前⾯⽤到的输⼊和输出都是⼆维数组,但真实数据的维度经常更⾼。例如,彩⾊图像在⾼和宽2个维度外还有RGB(红、绿、蓝)3个颜⾊通道。假设彩⾊图像的⾼和宽分别是 h和 w(像素),那 么它可以表示为⼀个3×h×w 的多维数组。我们将⼤⼩为3的这⼀维称为通道(channel)维。
当输⼊数据含多个通道时,我们需要构造⼀个输⼊通道数与输⼊数据的通道数相同的卷积核,从⽽能够 与含多通道的输⼊数据做互相关运算。下面时一个含有两个通道的计算。在每个通道上,⼆维输⼊数组与⼆维核数组做互相关运算,再按通道相加即得到输出。图中阴影部分为第⼀个输出元素及其计算所使⽤的输⼊和核 数组元素:(1×1+2×2+4×3+5×4)+(0×0+1×1+3×2+4×3)=56
接下来实现含多个输⼊通道的互相关运算。我们只需要对每个通道做互相关运算,然后通过 add_n 函数来进⾏累加。
import torch
from torch import nn
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
def corr2d_multi_in(X, K):
# 沿着X和K的第0维(通道维)分别计算再相加
res = d2l.corr2d(X[0, :, :], K[0, :, :])
for i in range(1, X.shape[0]):
res += d2l.corr2d(X[i, :, :], K[i, :, :])
return res
当输⼊通道有多个时,因为我们对各个通道的结果做了累加,所以不论输⼊通道数是多少,输出通道数总是为1。设卷积核输⼊通道数和输出通道数分别为 和 ,⾼和宽分别为 和 。如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为 的核数组。将它们在输出通道维上连结,卷积核的形状即 。在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输⼊数组计算⽽来。
def corr2d_multi_in_out(X, K):
# 对K的第0维遍历,每次同输⼊X做互相关计算。所有结果使⽤stack函数合并在⼀起
return torch.stack([corr2d_multi_in(X, k) for k in K])