二维卷积层
在二维互相关运算中,卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当卷积窗口滑动到某一位置时,窗口中的输入子数组与核数组按元素相乘并求和,得到输出数组中相应位置的元素。
例如:
输入为:input:
[012345678]\begin{bmatrix} 0 & 1 & 2 \\ 3 & 4 &5 \\ 6 & 7 & 8 \end{bmatrix}⎣⎡036147258⎦⎤
核为:kernel:
[0123]\begin{bmatrix} 0 & 1 \\ 2 & 3 \end{bmatrix}[0213]
则输出为:output
output=input∗kernel=[012345678]∗[0123]=[19253743]output = input * kernel = \begin{bmatrix} 0 & 1 & 2 \\ 3 & 4 &5 \\ 6 & 7 & 8 \end{bmatrix} * \begin{bmatrix} 0 & 1 \\ 2 & 3 \end{bmatrix} = \begin{bmatrix} 19 & 25 \\ 37 & 43 \end{bmatrix}output=input∗kernel=⎣⎡036147258⎦⎤∗[0213]=[19372543]
输出数组高和宽分别为2,其中的4个元素由二维互相关运算得出:
0×0+1×1+3×2+4×3=19,1×0+2×1+4×2+5×3=25,3×0+4×1+6×2+7×3=37,4×0+5×1+7×2+8×3=43.0\times0+1\times1+3\times2+4\times3=19,\\ 1\times0+2\times1+4\times2+5\times3=25,\\ 3\times0+4\times1+6\times2+7\times3=37,\\ 4\times0+5\times1+7\times2+8\times3=43. 0×0+1×1+3×2+4×3=19,1×0+2×1+4×2+5×3=25,3×0+4×1+6×2+7×3=37,4×0+5×1+7×2+8×3=43.
设计函数实现卷积层的计算:
import torch
from torch import nndef corr2d(X, K): h, w = K.shapeY = 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)
输出:
tensor([[19., 25.],[37., 43.]])
自定义二维卷积层
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 forward(self, x):return corr2d(x, self.weight) + self.bias
这里我们使用了corr2d函数来实现一个自定义的二维卷积层,这个网络层有weight、bias两个参数,前向计算函数是直接调用corr2d函数再加上偏差。
使用卷积层做图像中物体边缘的检测
检测图像中物体的边缘,即找到像素变化的位置。首先我们构造一张6×86\times 86×8的图像(即高和宽分别为6像素和8像素的图像)。它中间4列为黑(0),其余为白(1)。
X = torch.ones(6, 8)
X[:, 2:6] = 0
构造一个高和宽分别为1和2的卷积核K。当它与输入做互相关运算时,如果横向相邻元素相同,输出为0;否则输出为非0。
K = torch.tensor([[1, -1]])
将输入X和我们设计的卷积核K做互相关运算。计算 将从白到黑的边缘和从黑到白的边缘分别检测成了1和-1。其余部分的输出全是0。
Y = corr2d(X, K)
print(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.]])
通过这个例子我们能看出,卷积层可以通过重复使用卷积核有效的表示局部空间。
通过数据学习出卷积核
使用物体边缘检测中的输入数据X和输出数据Y来学习我们构造的核数组K。
我们首先构造一个卷积层,其卷积核将被初始化成随机数组。接下来在每一次迭代中,我们使用平方误差来比较Y和卷积层的输出,然后计算梯度来更新权重。
model = conv2d(kernel_size=(1, 2))
step = 20 #训练步数
lr = 0.01 #学习率
for i in range(step):y_hat= model(X)l = ((y_hat - y)**2).sum()l.backward()#进行梯度下降model.weight.data -= lr * model.wight.gradmodel.bias.data -= lr * model.bias.gradi+1# 梯度清0conv2d.weight.grad.fill_(0)conv2d.bias.grad.fill_(0)if (i+1)%5 == 0:print('step: %d , loss: %.3f' %(i+1, l.item()))