动手学深度学习6.1 实现二维交叉运算 和 卷积层

参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战


只看干巴巴的代码肯定看不懂我在干嘛,建议配合理论食用。

动手学深度学习6.1 为什么需要卷积层 | 卷积公式推导 - 掘金 (juejin.cn)

二维交叉

import torch
from torch import nn
from d2l import torch as d2l
复制代码
# 实现二维交叉运算
def corr2d(x,k):
    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
复制代码

实现的是这个公式:

h i , j = a , b v a , b x i + a , j + b h_{i, j}=\sum_{a, b} v_{a, b} x_{i+a, j+b}
# 验证一下我们的二维交叉运算

X = torch.arange(40).reshape(5,8)
K = torch.ones(3,3)
K[:,1] = -1

print(X)
print(K)
复制代码
>>
tensor([[ 0,  1,  2,  3,  4,  5,  6,  7],
        [ 8,  9, 10, 11, 12, 13, 14, 15],
        [16, 17, 18, 19, 20, 21, 22, 23],
        [24, 25, 26, 27, 28, 29, 30, 31],
        [32, 33, 34, 35, 36, 37, 38, 39]])
tensor([[ 1., -1.,  1.],
        [ 1., -1.,  1.],
        [ 1., -1.,  1.]])
复制代码

用的是和上一篇文章一样的数据,手动生成一个测试数据进行计算。看一下我们生成的“图片”和“卷积核”。

# 验证一下我们的二维交叉运算

X = torch.arange(40).reshape(5,8)
K = torch.ones(3,3)
K[:,1] = -1

print(X)
print(K)
复制代码
>>
tensor([[ 0,  1,  2,  3,  4,  5,  6,  7],
        [ 8,  9, 10, 11, 12, 13, 14, 15],
        [16, 17, 18, 19, 20, 21, 22, 23],
        [24, 25, 26, 27, 28, 29, 30, 31],
        [32, 33, 34, 35, 36, 37, 38, 39]])
tensor([[ 1., -1.,  1.],
        [ 1., -1.,  1.],
        [ 1., -1.,  1.]])
复制代码
>>
tensor([[27., 30., 33., 36., 39., 42.],
        [51., 54., 57., 60., 63., 66.],
        [75., 78., 81., 84., 87., 90.]])
复制代码

验证一下结果。

[ 0 1 2 8 9 10 16 17 18 ] [ 1 1 1 1 1 1 1 1 1 ] 元素按位置相乘 = [ 0 1 2 8 9 18 16 17 18 ] \left[\begin{array}{ccc}0 & 1 & 2 \\ 8 & 9 & 10 \\ 16 & 17 & 18\end{array}\right]\left[\begin{array}{ccc}1 & -1 & 1 \\ 1 & -1 & 1 \\ 1 & -1 & 1\end{array}\right]元素按位置相乘=\left[\begin{array}{ccc}0 & -1 & 2 \\ 8 & -9 & 18 \\ 16 & -17 & 18\end{array}\right]

[ 0 1 2 8 9 18 16 17 18 ] . s u m ( ) = 27 \left[\begin{array}{ccc}0 & -1 & 2 \\ 8 & -9 & 18 \\ 16 & -17 & 18\end{array}\right]. sum()=27

卷积

# 实现一个卷积
class Conv2D(nn.Module):
    def __init__(self,kernel_size):
        super().__init__()
        self.weight = nn.Parameter(torch.rand(kernel_size))
        self.bias = nn.Parameter(torch.zeros(1))
    def forward(self,x):
        return corr2d(x,self.weight) + self.bias
复制代码

生成一个测试数据:

X = torch.ones(5,8)
X[:,2:6]=0
print(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.]])
复制代码

输出之后X长这样,假装他是一个图片:

image.png


K = torch.tensor([[1,-1]])
# 检测图像中垂直边缘:
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.]])
复制代码

看一下输出就知道这个卷积核K是干嘛的了,进行垂直边缘检测的。

从结果中可以看出原图第二列到第三列的时候发生了一下变化,倒数第三列到倒数第二列的时候发生了一次变化。

为什么呢?

因为卷积核是1*2的,卷到下图两个位置的时候生成的恰好是计算结果的第二列和倒数第二列。而这两个计算位置就是原图的第二列到第三列 和 倒数第三列到倒数第二列。

image.png

下边我们来测试一下卷积层

代码1

这是李沐老师书上的源代码:

# 用一下pytorch的卷积层:
conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias=False)

X = X.reshape((1,1,5,8))
Y = Y.reshape((1,1,5,7))

epoch = 20
learning_rate = 0.03

for i in range(epoch):
    Y_hat = conv2d(X)
    l = ((Y_hat-Y)**2).sum()
    conv2d.zero_grad()
    l.backward()
    conv2d.weight.data[:] -= learning_rate * conv2d.weight.grad
    if (i+1)%5 == 0:
        print(f"batch {i+1},loss = {l.sum():.5f}")
        
print(conv2d.weight.data.reshape(1,2))
复制代码
>>
batch 5,loss = 0.30810
batch 10,loss = 0.00828
batch 15,loss = 0.00023
batch 20,loss = 0.00001
复制代码

可以看到此时我们的loss已经很低了。

上边代码是用pytorch自己的二维卷积实现的。

先是把我们测试数据进行reshape编程四维的,第一个代表有几个通道,第二个代表批量,第三第四维度就是正常的原来的二维矩阵啦。

之后就是手写了一个梯度下降。

print(conv2d.weight.data.reshape(1,2))
复制代码
>>
tensor([[ 0.9994, -0.9994]])
复制代码

看一下训练之后的结果,已经很接近[1,-1]了。

代码2

# 用一下pytorch的卷积层:
conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias=False)

X = X.reshape((1,1,5,8))
Y = Y.reshape((1,1,5,7))
loss = nn.MSELoss(reduction='sum')
trainer = torch.optim.SGD(conv2d.parameters(),learning_rate)

epoch = 20
learning_rate = 0.03

for i in range(epoch):
    Y_hat = conv2d(X)
    l = loss(Y_hat,Y)
    trainer.zero_grad()
    l.backward()
#     conv2d.weight.data[:] -= learning_rate*conv2d.weight.grad
    trainer.step()
    if (i+1)%5 == 0:
        print(f"batch {i+1},loss = {l:.5f}")
        

print(conv2d.weight.data.reshape(1,2))
复制代码

既然都用人家的卷积层了,那还手写什么梯度下降,直接用人家的不香吗。

注意loss = nn.MSELoss(reduction='sum')这里reduction参数记得修改为sum,因为默认是返回mean,如果你用mean,那learning rete要改为0.8左右才能在20步内达到同样的效果。

代码3

# 再用一下我们自己写的卷积层
conv2d = Conv2D(kernel_size=(1,2))

epoch = 20
learning_rate = 0.03

for i in range(epoch):
    Y_hat = conv2d(X)
    l = (Y_hat-Y)**2
    conv2d.zero_grad()
    l.sum().backward()
    conv2d.weight.data[:] -= learning_rate * conv2d.weight.grad
    if (i+1)%5 == 0:
        print(f"batch {i+1},loss = {l.sum():.5f}")
        

print(conv2d.weight.data.reshape(1,2))
复制代码
>>
batch 5,loss = 0.66233
batch 10,loss = 0.01583
batch 15,loss = 0.00044
batch 20,loss = 0.00001
tensor([[ 0.9992, -0.9992]])
复制代码

当然了,只是因为前边我们自己写了个卷积层,所以在这里用一下了。实际中还是用人家的更快嗷。


  1. 《动手学深度学习》系列更多可以看这里:《动手学深度学习》专栏(juejin.cn)

  2. 笔记Github地址:DeepLearningNotes/d2l(github.com)

还在更新中…………

猜你喜欢

转载自juejin.im/post/7033378119373455391