# 图像卷积代码
import torch
from torch import nn
from d2l import torch as d2l
def corr2d(X, K): # X 是输入数据,通常是一个二维张量(例如,图像),K 是卷积核(滤波器)。
"""计算二维互相关运算"""
h, w = K.shape # 首先获取卷积核 K 的高度 h 和宽度 w
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1)) # 创建一个输出张量 Y,其形状由输入 X 的形状和卷积核的大小决定,但不包括边缘像素。
for i in range(Y.shape[0]): # 使用两个嵌套循环遍历输入 X 中的所有位置,计算互相关运算的结果并将结果存储在 Y 中。
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
return Y
# 简单来说Y就是输出矩阵
class Conv2D(nn.Module):
def __init__(self, kernel_size): # kernel_size 参数表示卷积核(滤波器)的大小。它是一个整数或元组,指定了卷积核的高度和宽度。
super().__init__()
self.weight = nn.Parameter(torch.rand(kernel_size)) # 这一行代码创建了卷积层的权重参数 self.weight。权重参数是一个可训练的 PyTorch 参数,它使用随机初始化的值(从均匀分布中随机抽样),其形状由 kernel_size 决定。
self.bias = nn.Parameter(torch.zeros(1)) # 这一行代码创建了卷积层的偏置参数 self.bias,初始化为零。
def forward(self, x):
return corr2d(x, self.weight) + self.bias
学习卷积核
# 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False) # 1, 1:表示该卷积层有一个输入通道和一个输出通道。kernel_size=(1, 2):指定了卷积核的大小,为 1x2,这意味着卷积核的高度为1,宽度为2。bias=False:表示不使用偏置项。
X = X.reshape((1, 1, 6, 8)) #形状为(批量大小,通道数,高度,宽度)。在这里,X 和 Y 分别被调整为(1,1,6,8)和(1,1,6,7)的形状。
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 # 更新卷积核的权重,使用梯度下降方法。学习率 lr 控制了每次更新的步长。
if (i + 1) % 2 == 0:
print(f'epoch {i+1}, loss {l.sum():.3f}')
# 填充和步幅
# 为了方便起见,我们定义了一个计算卷积层的函数。
# 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数
def comp_conv2d(conv2d, X):
# 这里的(1,1)表示批量大小和通道数都是1
X = X.reshape((1, 1) + X.shape) # X 是输入数据,这里将其形状调整为 (1, 1, 8, 8) 的四维张量,其中 (1, 1) 表示批量大小和通道数都为 1,(8, 8) 表示高度和宽度为 8 的图像。
Y = conv2d(X) # 计算了卷积层 conv2d 对输入 X 的卷积结果,并将结果存储在 Y 中。
# 省略前两个维度:批量大小和通道
return Y.reshape(Y.shape[2:]) # 为了简化结果,这行代码将 Y 的形状从 (1, 1, 8, 8) 调整为 (8, 8),省略了批量大小和通道维度。
# 请注意,这里每边都填充了1行或1列,因此总共添加了2行或2列
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1) # 1, 1 表示输入通道数和输出通道数都为 1。kernel_size=3 表示卷积核的大小为 3x3。表示在图像的每一侧都填充了一行或一列的零,总共添加了2行或2列的填充。
X = torch.rand(size=(8, 8))
comp_conv2d(conv2d, X).shape
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4)) # padding=(0, 1):在水平方向上不填充,而在垂直方向上填充了1行的零;stride=(3, 4):在水平方向上的步幅为3,而在垂直方向上的步幅为4。
comp_conv2d(conv2d, X).shape # 输出的形状为 (2, 2)。