6.2 图像卷积

发布时间 2023-07-27 16:20:42作者: Ann-

我们定义一个函数,实现图像的卷积操作。

 这里X[i:i+h,j:j+w]的用法是把X这个大矩阵中,行数从i到i+h-1,列数从j到j+w-1的一小块给拿出来了,例子如下:

A = torch.tensor([
    [1,1,1,1],
    [2,2,2,2],
    [3,3,3,3],
    [4,4,4,4]
])
print(A[0:3,1:4])

输出结果:

 

1.卷积层

卷积层对输入和卷积核权重进行互相关运算,并在添加标量偏置之后产生输出。 所以,卷积层中的两个被训练的参数是卷积核权重和标量偏置。 就像我们之前随机初始化全连接层一样,在训练基于卷积层的模型时,我们也随机初始化卷积核权重。

基于上面定义的corr2d函数实现二维卷积层。在__init__构造函数中,将weightbias声明为两个模型参数。前向传播函数调用corr2d函数并添加偏置。

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

 

2.学习卷积核

就像torch.nn中有全连接层nn.Linear()一样,torch.nn也有卷积层nn.Conv2d()。nn.Conv2d()有四个参数,分别是输入通道数、输出通道数、卷积核尺寸kernel_size,以及偏置bias。

我们学习一个横向的边缘检测卷积核。首先定义一个矩阵X:

 再定义我们希望的输出矩阵Y,Y的值是X经过1*2卷积核[1,-1]得到的:

 我们简单实现一个训练代码,来训练卷积核参数:

# 使用nn的内置卷积层,前两个参数分别表示输入、输出通道数
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)

# 四个参数分别是(批量大小、通道、高度、宽度)
X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2  # 学习率

for i in range(10):
    Y_hat = conv2d(X)
    l = (Y_hat - Y) ** 2
    conv2d.zero_grad()
    l.sum().backward()
    # 迭代卷积核
    conv2d.weight.data[:] -= lr * conv2d.weight.grad
    if (i + 1) % 2 == 0:
        print(f'epoch {i+1}, loss {l.sum():.3f}')

这里面,梯度清零使用了conv2d.zero_grad(),conv2d是Conv2d类的对象,是nn.Module的子类的一个对象,应该是nn.Module都有这个.zero_grad()方法。

 

在3.2节,线性回归的从零开始实现中我们自己实现了sgd优化算法,如下:

def sgd(params, lr, batch_size):  #@save
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

这里参数梯度的更新是使用参数本身的.grad.zero_()更新的。