动手学深度学习6.2 图像卷积-条记&练习(PyTorch)

[复制链接]
发表于 2026-2-26 00:41:40 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
以下内容为联合李沐老师的课程和课本增补的学习条记,以及对课后练习的一些思考,自留回顾,也供同砚之人交换参考。

本节课程地点:卷积层_哔哩哔哩_bilibili 代码_哔哩哔哩_bilibili
本节课本地点:6.2. 图像卷积 — 动手学深度学习 2.0.0 documentation (d2l.ai)
本节开源代码:...>d2l-zh>pytorch>chapter_multilayer-perceptrons>conv-layer.ipynb

图像卷积

上节我们剖析了卷积层的原理,现在我们看看它的实际应用。由于卷积神经网络的筹划是用于探索图像数据,本节我们将以图像为例。
相互干运算

严格来说,卷积层是个错误的叫法,由于它所表达的运算实在是相互干运算(cross-correlation),而不是卷积运算。 根据 6.1节 中的形貌,在卷积层中,输入张量和核张量通过(相互干运算)产生输出张量。
起首,我们暂时忽略通道(第三维)这一情况,看看怎样处理处罚二维图像数据和埋伏体现。在 下图 中,输入是高度为3、宽度为3的二维张量(即形状为3 × 3)。卷积核的高度和宽度都是2,而卷积核窗口(或卷积窗口)的形状由内核的高度和宽度决定(即2 × 2)。


在二维相互干运算中,卷积窗口从输入张量的左上角开始,从左到右、从上到下滑动。 当卷积窗口滑动到新一个位置时,包罗在该窗口中的部分张量与卷积核张量举行按元素相乘,得到的张量再求和得到一个单一的标量值,由此我们得出了这一位置的输出张量值。 在如上例子中,输出张量的四个元素由二维相互干运算得到,这个输出高度为2、宽度为2,如下所示:
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.
留意,输出巨细略小于输入巨细。这是由于卷积核的宽度和高度大于1, 而卷积核只与图像中每个巨细完全得当的位置举行相互干运算。 以是,输出巨细即是输入巨细 
 减去卷积核巨细 
 ,即:

这是由于我们必要富足的空间在图像上“移动”卷积核。稍后,我们将看到怎样通过在图像界限四周添补零来包管有富足的空间移动卷积核,从而保持输出巨细稳固。 接下来,我们在corr2d函数中实现如上过程,该函数继续输入张量X和卷积核张量K,并返回输出张量Y。
二维卷积层:


  • 输入 

  • 核 

  • 毛病 

  • 输出 c


  •  和 
     是可学习的参数
  1. import torch
  2. from torch import nn
  3. from d2l import torch as d2l
  4. def corr2d(X, K):  #@save
  5.     """计算二维互相关运算"""
  6.     h, w = K.shape #获取卷积核的长&宽
  7.     Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
  8.     for i in range(Y.shape[0]):
  9.         for j in range(Y.shape[1]):
  10.             Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
  11.     return Y
复制代码
通过 上图 的输入张量X和卷积核张量K,我们来[验证上述二维相互干运算的输出]。
  1. X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
  2. K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
  3. corr2d(X, K)
复制代码
输出结果:
tensor([[19., 25.],
[37., 43.]])
卷积层

卷积层对输入和卷积核权重举行相互干运算,并在添加标量偏置之后产生输出。 以是,卷积层中的两个被练习的参数是卷积核权重和标量偏置。 就像我们之前随机初始化全毗连层一样,在练习基于卷积层的模子时,我们也随机初始化卷积核权重。
基于上面界说的corr2d函数[实现二维卷积层]。在__init__构造函数中,将weight和bias声明为两个模子参数。前向流传函数调用corr2d函数并添加偏置。
  1. class Conv2D(nn.Module):
  2.     def __init__(self, kernel_size):
  3.         super().__init__()
  4.         self.weight = nn.Parameter(torch.rand(kernel_size))
  5.         self.bias = nn.Parameter(torch.zeros(1))
  6.     def forward(self, x):
  7.         return corr2d(x, self.weight) + self.bias
复制代码
高度和宽度分别为 
 和 
 的卷积核可以被称为 
 卷积或 
 卷积核。 我们也将带有 
 卷积核的卷积层称为 
 卷积层。
图像中目的的边沿检测

如下是[卷积层的一个简朴应用:]通过找到像素厘革的位置,来(检测图像中差别颜色的边沿)。 起首,我们构造一个 6 × 8像素的优劣图像。中心四列为玄色(0),别的像素为白色(1)。
  1. X = torch.ones((6, 8))
  2. X[:, 2:6] = 0
  3. 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。当举行相互干运算时,假如程度相邻的两元素雷同,则输出为零,否则输出为非零。
  1. K = torch.tensor([[1.0, -1.0]])
复制代码
现在,我们对参数X(输入)和K(卷积核)实行相互干运算。 如下所示,[输出Y中的1代表从白色到玄色的边沿,-1代表从玄色到白色的边沿],其他情况的输出为0。
  1. Y = corr2d(X, K)
  2. 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.]])
现在我们将输入的二维图像转置,再举行如上的相互干运算。 其输出如下,之前检测到的垂直边沿消散了。 不出所料,这个[卷积核K只可以检测垂直边沿],无法检测程度边沿。
  1. corr2d(X.t(), K)
复制代码
输出结果:
tensor([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]])
增补:


学习卷积核

假如我们只需探求优劣边沿,那么以上[1, -1]的边沿检测器足以。然而,当有了更复杂数值的卷积核,大概一连的卷积层时,我们不大概手动筹划滤波器。那么我们是否可以[学习由X天生Y的卷积核]呢?
现在让我们看看是否可以通过仅查察“输入-输出”对来学习由X天生Y的卷积核。 我们先构造一个卷积层,并将其卷积核初始化为随机张量。接下来,在每次迭代中,我们比力Y与卷积层输出的平方毛病,然后盘算梯度来更新卷积核。为了简朴起见,我们在此利用内置的二维卷积层,并忽略偏置。
  1. # 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
  2. conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)
  3. # 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),
  4. # 其中批量大小和通道数都为1
  5. X = X.reshape((1, 1, 6, 8))
  6. Y = Y.reshape((1, 1, 6, 7))
  7. lr = 3e-2  # 学习率
  8. for i in range(10):
  9.     Y_hat = conv2d(X)
  10.     l = (Y_hat - Y) ** 2
  11.     conv2d.zero_grad()
  12.     l.sum().backward()
  13.     # 迭代卷积核
  14.     conv2d.weight.data[:] -= lr * conv2d.weight.grad
  15.     if (i + 1) % 2 == 0:
  16.         print(f'epoch {i+1}, loss {l.sum():.3f}')
复制代码
输出结果:
epoch 2, loss 4.114
epoch 4, loss 0.798
epoch 6, loss 0.178
epoch 8, loss 0.048
epoch 10, loss 0.016
在10 次迭代之后,毛病已经降到富足低。现在我们来看看我们[所学的卷积核的权重张量]。
  1. conv2d.weight.data.reshape((1, 2))
复制代码
输出结果:
tensor([[ 1.0016, -0.9785]])
过细的读者肯定会发现,我们学习到的卷积核权重非常靠近我们之前界说的卷积核K。
相互干和卷积

追念一下我们在 6.1节 中观察到的相互干和卷积运算之间的对应关系。 为了得到正式的卷积运算输出,我们必要实行 (6.1.6)中界说的严格卷积运算,而不是相互干运算。 荣幸的是,它们差异不大,我们只需程度和垂直翻转二维卷积核张量,然后对输入张量实行相互干运算。
值得留意的是,由于卷积核是从数据中学习到的,因此无论这些层实行严格的卷积运算还是相互干运算,卷积层的输出都不会受到影响。 为了阐明这一点,假设卷积层实行相互干运算并学习 上图 中的卷积核,该卷积核在这里由矩阵 
 体现。 假设其他条件稳固,当这个层实行严格的卷积时,学习的卷积核 
 在程度和垂直翻转之后将与 
 雷同。 也就是说,当卷积层对 上图 中的输入和 
 实行严格卷积运算时,将得到与相互干运算 上图中雷同的输出。
为了与深度学习文献中的标准术语保持同等,我们将继续把“相互干运算”称为卷积运算,只管严格地说,它们略有差别。 别的,对于卷积核张量上的权重,我们称其为元素。
增补:


  • 二维相互干



  • 二维卷积



  • 由于对称性,在实际利用中没有区别
一维和三维相互干:


  • 一维:文本、语言、时序序列(音频)



  • 三维:视频、医学图像、情况舆图

特性映射和感受野

如在 6.1.4.1节 中所述, 上图 中输出的卷积层偶然被称为特性映射(feature map),由于它可以被视为一个输入映射到下一层的空间维度的转换器。 在卷积神经网络中,对于某一层的恣意元素 
 ,其感受野(receptive field)是指在前向流传期间大概影响 
 盘算的全部元素(来自全部先前层)。
请留意,感受野大概大于输入的实际巨细。让我们用 上图 为例来表明感受野: 给定 2×2 卷积核,阴影输出元素值19的感受野是输入阴影部分的四个元素。 假设之前输出为 
 ,其巨细为 2×2,现在我们在厥后附加一个卷积层,该卷积层以 
 为输入,输出单个元素 
 。 在这种情况下,
 上的 
 的感受野包罗 
 的全部四个元素,而输入的感受野包罗最初全部九个输入元素。 因此,当一个特性图中的恣意元素必要检测更广地区的输入特性时,我们可以构建一个更深的网络。
小结



  • 二维卷积层的核心盘算是二维相互干运算。最简朴的情势是,对二维输入数据和卷积核实行相互干操纵,然后添加一个偏置。
  • 我们可以筹划一个卷积核来检测图像的边沿。
  • 我们可以从数据中学习卷积核的参数。
  • 学习卷积核时,无论用严格卷积运算或相互干运算,卷积层的输出不会受太大影响。
  • 当必要检测输入特性中更广地区时,我们可以构建一个更深的卷积网络。
练习

1. 构建一个具有对角线边沿的图像X。
1)假如将本节中举例的卷积核K应用于X,会发生什么情况?
2)假如转置X会发生什么?
3)假如转置K会发生什么?
解:
代码如下:

  1. X = torch.ones((5, 5))
  2. for i in range(X.shape[0]):
  3.     for j in range(X.shape[1]):
  4.         if i == j:
  5.             X[i,j] = 0
  6. X
复制代码
输出结果:
tensor([[0., 1., 1., 1., 1.],
[1., 0., 1., 1., 1.],
[1., 1., 0., 1., 1.],
[1., 1., 1., 0., 1.],
[1., 1., 1., 1., 0.]])

  1. K = torch.tensor([[1.0, -1.0]])
  2. corr2d(X, K)
  3. # 输出形状为(5,4),检测对角线边缘,对角线上为-1,下为1
复制代码
输出结果:
tensor([[-1., 0., 0., 0.],
[ 1., -1., 0., 0.],
[ 0., 1., -1., 0.],
[ 0., 0., 1., -1.],
[ 0., 0., 0., 1.]])

  1. corr2d(X.t(), K)
  2. # X转置后结果不变
复制代码
输出结果:
tensor([[-1., 0., 0., 0.],
[ 1., -1., 0., 0.],
[ 0., 1., -1., 0.],
[ 0., 0., 1., -1.],
[ 0., 0., 0., 1.]])

  1. corr2d(X, K.t())
  2. # 输出形状变成(4,5),检测对角线边缘,对角线上为1,下为-1
复制代码
输出结果:
tensor([[-1., 1., 0., 0., 0.],
[ 0., -1., 1., 0., 0.],
[ 0., 0., -1., 1., 0.],
[ 0., 0., 0., -1., 1.]])

2. 在我们创建的Conv2D自动求导时,有什么错误消息?
解:
报错:维度不匹配。由于输入和输特别式是四维的,包罗批量巨细、通道、高和宽,但自界说的Conv2D仅包罗二维信息,即高和宽。
将输入和输出的批量巨细和通道去掉,大概创建卷积层时增长批量巨细和通道,均可办理该题目。

  1. conv2d = Conv2D((1,2))
  2. Y_hat = conv2d(X)
  3. l = (Y_hat - Y) ** 2
  4. conv2d.zero_grad()
  5. l.sum().backward()
  6. conv2d.weight.data[:] -= lr * conv2d.weight.grad
复制代码
输出结果:
---------------------------------------------------------------------------

RuntimeError Traceback (most recent call last)

Cell In[24], line 3
1 conv2d = Conv2D((1,2))
2 Y_hat = conv2d(X)
----> 3 l = (Y_hat - Y) ** 2
4 conv2d.zero_grad()
5 l.sum().backward()

RuntimeError: The size of tensor a (4) must match the size of tensor b (7) at non-singleton dimension 3

3. 怎样通过改变输入张量和卷积核张量,将相互干运算体现为矩阵乘法?
解:
核心头脑是,将输入张量中每个与卷积核做相互干运算的元素展成一行,

好比将本节举例的X变化为: [0134124534674578]
再与reshape成列向量的卷积核K做矩阵乘法: [0134124534674578][0123]=[19253743]
再将结果reshape成输出Y的尺寸即可。
  1. def corr2d_matmul(X, K):
  2.     h, w = K.shape
  3.     # 将输入X中每次与卷积核做互相关运算的元素展平成一行
  4.     # X_reshaped的每一行元素均为与卷积核做互相关的元素
  5.     X_reshaped = torch.zeros((X.shape[0] - h + 1)*(X.shape[1] - w + 1), h*w)
  6.     k = 0
  7.     for i in range(X.shape[0] - h + 1):
  8.         for j in range(X.shape[1] - w + 1):
  9.             X_reshaped[k,:] = X[i:i + h, j:j + w].reshape(1,-1)
  10.             k += 1
  11.     # 将卷积核K转为列向量,并与X_reshaped做矩阵乘法,再reshape成输出尺寸即可
  12.     Y = [email protected](-1)
  13.     return Y.reshape(X.shape[0] - h + 1, X.shape[1] - w + 1)
  14. X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
  15. K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
  16. Y = corr2d_matmul(X, K)
  17. Y
复制代码
输出结果:
tensor([[19., 25.],
[37., 43.]])

4.手工筹划一些卷积核。
1)二阶导数的核的情势是什么?
2)积分的核的情势是什么?
3)得到 𝑑 次导数的最小核的巨细是多少?
解:
1)


  • 对于一阶导数:

对应的一阶导数卷积核为 

扩展至二维,对应的一阶导数程度卷积核为 
 大概 

对应的一阶导数垂直卷积核为
 大概 



  • 进一步地,对于二阶导数: 

对应的二阶导数卷积核为 

扩展至二维, 

对应的二阶导数程度卷积核为 
 大概 

二阶导数垂直卷积核为 
 大概 

二阶导数叠加卷积核为 

2)


  • 一维积分核--均匀滤波器: 
     此中, 
     是 
     中的元素数量。
  • 二维积分核: 积分核可以是均匀滤波器,也称为均值滤波器,其情势如下:
    二维均匀滤波器 (也称为盒式滤波器或均质滤波器):

 此中, 
 和 
 分别是 
 的高度和宽度。
高斯积分核: 在空间域具有高斯形状,用于实现平滑结果,如下:

此中, 
 是高斯分布的标准差, 
 是滤波器中每个元素的坐标。
双边滤波器核:联合了空间域的高斯权重和强度域的高斯权重,用于在保持边沿的同时实现平滑,如下: 

此中, 
 是归一化因子,确保核的总权重为 1, 
 是空间域的标准差, 
 是强度域的标准差, 𝐼(𝑥,𝑦) 是图像在点 
 的强度, 
 是中心像素的强度。
3)联合第一小问的解答,


  • 对于一阶导数,最小核的巨细是3(一个3点中心差分近似)。
  • 对于二阶导数,在一维情况下,最小核的巨细是3(一个3点中心差分近似)。在二维情况下,一个简朴的近似大概是一个3×3的核。
  • 对于更高阶的导数,核的巨细至少应与导数的阶数d相匹配,但这通常不敷以提供良好的近似。实际利用的核会更大,以确保富足的平滑性和镌汰毛病。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表