图像卷积|卷积神经网络|动手学深度学习

195 阅读8分钟

1. 构建一个具有对角线边缘的图像 X

  1. 如果将本节中举例的卷积核K应用于X,会发生什么情况?
  2. 如果转置X会发生什么?
  3. 如果转置K会发生什么?
X = torch.zeros((8, 8))
X.fill_diagonal_(1)
K = torch.tensor([[1.0, -1.0]])

U = corr2d(X, K)
V = corr2d(X.T, K)
W = corr2d(X, K.T)

X.shape, K.shape, U.shape, V.shape, W.shape, X, K, U, V, W
(torch.Size([8, 8]),
 torch.Size([1, 2]),
 torch.Size([8, 7]),
 torch.Size([8, 7]),
 torch.Size([7, 8]),
 tensor([[1., 0., 0., 0., 0., 0., 0., 0.],
         [0., 1., 0., 0., 0., 0., 0., 0.],
         [0., 0., 1., 0., 0., 0., 0., 0.],
         [0., 0., 0., 1., 0., 0., 0., 0.],
         [0., 0., 0., 0., 1., 0., 0., 0.],
         [0., 0., 0., 0., 0., 1., 0., 0.],
         [0., 0., 0., 0., 0., 0., 1., 0.],
         [0., 0., 0., 0., 0., 0., 0., 1.]]),
 tensor([[ 1., -1.]]),
 tensor([[ 1.,  0.,  0.,  0.,  0.,  0.,  0.],
         [-1.,  1.,  0.,  0.,  0.,  0.,  0.],
         [ 0., -1.,  1.,  0.,  0.,  0.,  0.],
         [ 0.,  0., -1.,  1.,  0.,  0.,  0.],
         [ 0.,  0.,  0., -1.,  1.,  0.,  0.],
         [ 0.,  0.,  0.,  0., -1.,  1.,  0.],
         [ 0.,  0.,  0.,  0.,  0., -1.,  1.],
         [ 0.,  0.,  0.,  0.,  0.,  0., -1.]]),
 tensor([[ 1.,  0.,  0.,  0.,  0.,  0.,  0.],
         [-1.,  1.,  0.,  0.,  0.,  0.,  0.],
         [ 0., -1.,  1.,  0.,  0.,  0.,  0.],
         [ 0.,  0., -1.,  1.,  0.,  0.,  0.],
         [ 0.,  0.,  0., -1.,  1.,  0.,  0.],
         [ 0.,  0.,  0.,  0., -1.,  1.,  0.],
         [ 0.,  0.,  0.,  0.,  0., -1.,  1.],
         [ 0.,  0.,  0.,  0.,  0.,  0., -1.]]),
 tensor([[ 1., -1.,  0.,  0.,  0.,  0.,  0.,  0.],
         [ 0.,  1., -1.,  0.,  0.,  0.,  0.,  0.],
         [ 0.,  0.,  1., -1.,  0.,  0.,  0.,  0.],
         [ 0.,  0.,  0.,  1., -1.,  0.,  0.,  0.],
         [ 0.,  0.,  0.,  0.,  1., -1.,  0.,  0.],
         [ 0.,  0.,  0.,  0.,  0.,  1., -1.,  0.],
         [ 0.,  0.,  0.,  0.,  0.,  0.,  1., -1.]]))

1. 在我们创建的Conv2D自动求导时,有什么错误消息?

在使用 PyTorch 的 nn.Conv2d 层进行自动求导时,如果出现错误消息,通常是由于以下原因之一引起的:

  1. 输入维度不匹配nn.Conv2d 期望输入是四维张量,格式为 (batch_size, channels, height, width)。如果输入的维度不正确,可能会导致错误。

  2. 目标输出维度不匹配:在计算损失时,输入 Y_hat 和目标 Y 的维度必须匹配。如果维度不匹配,会引发广播错误或维度不匹配错误。

  3. 数据类型不匹配:确保输入数据和模型参数的数据类型一致(通常是浮点数)。

根据你提供的代码片段和任务描述,这里是一个完整的代码示例,包含了可能导致错误的检查以及修复:

# 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(1, 1, kernel_size=(1, 2), bias=False)

# 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),
# 其中批量大小和通道数都为1
X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2  # 学习率

X = X.float()
Y = Y.float()

for i in range(50):
    Y_hat = conv2d(X)

    assert Y_hat.shape == Y.shape, f"Output shape {Y_hat.shape} does not match target shape {Y.shape}"

    l = (Y_hat - Y) ** 2
    conv2d.zero_grad()
    l.sum().backward()

    # 迭代卷积核
    with torch.no_grad():
        conv2d.weight.data[:] -= lr * conv2d.weight.grad

    if (i + 1) % 2 == 0:
        print(f'epoch {i+1}, loss {l.sum():.10f}')

关键修复和检查

  1. 确保输入和目标输出的形状匹配:在前向传播之后,使用断言语句 assert Y_hat.shape == Y.shape 检查 Y_hatY 的形状是否匹配。
  2. 确保数据类型一致:在训练之前,将输入数据和目标数据转换为浮点类型 float
  3. 使用上下文管理器更新权重:在更新权重时使用 with torch.no_grad(),以确保权重更新不会被跟踪计算图。

这些修复和检查将有助于确保卷积层的自动求导过程顺利进行,并避免常见的错误。

1. 如何通过改变输入张量和卷积核张量,将互相关运算表示为矩阵乘法?

在卷积神经网络中,卷积操作可以通过将输入张量和卷积核张量重新排列成矩阵形式来表示为矩阵乘法。这种转换有时称为“展开”(im2col)操作。下面是如何实现这一转换的详细步骤:

1. 了解卷积操作

假设我们有一个二维卷积层,其卷积核的形状为 (kH, kW),输入张量的形状为 (C, H, W),输出张量的形状为 (C_out, H_out, W_out)。其中:

  • C 是输入通道数。
  • H 和 W 分别是输入张量的高度和宽度。
  • C_out 是输出通道数。
  • H_out 和 W_out 分别是输出张量的高度和宽度。
  • kH 和 kW 分别是卷积核的高度和宽度。

2. 将输入张量重新排列成矩阵

我们使用 im2col 方法将输入张量重新排列成矩阵形式。这个方法将输入张量的每个局部区域(与卷积核大小相同)展开成一个列向量。对于每个局部区域,我们得到一个列向量,所有这些列向量组成一个大的矩阵。

假设输入张量 X 的形状为 (C, H, W),我们将其转换成形状为 (C * kH * kW, H_out * W_out) 的矩阵。

3. 将卷积核张量重新排列成矩阵

卷积核张量的形状为 (C_out, C, kH, kW),我们将其转换成形状为 (C_out, C * kH * kW) 的矩阵。

4. 进行矩阵乘法

将重新排列后的输入矩阵与卷积核矩阵进行矩阵乘法,得到形状为 (C_out, H_out * W_out) 的矩阵。最后,将这个矩阵重新排列成 (C_out, H_out, W_out) 形状的输出张量。

具体实现步骤

import numpy as np

# 定义输入张量和卷积核张量
X = np.random.randn(1, 3, 5, 5)  # 输入张量 (batch_size, in_channels, height, width)
K = np.random.randn(2, 3, 3, 3)  # 卷积核张量 (out_channels, in_channels, kernel_height, kernel_width)

# 展开输入张量
def im2col(X, K_height, K_width, stride=1, padding=0):
    batch_size, in_channels, height, width = X.shape
    out_height = (height + 2 * padding - K_height) // stride + 1
    out_width = (width + 2 * padding - K_width) // stride + 1
    col = np.zeros((batch_size, in_channels, K_height, K_width, out_height, out_width))

    for y in range(K_height):
        y_max = y + stride * out_height
        for x in range(K_width):
            x_max = x + stride * out_width
            col[:, :, y, x, :, :] = X[:, :, y:y_max:stride, x:x_max:stride]

    col = col.transpose(1, 2, 3, 0, 4, 5).reshape(in_channels * K_height * K_width, -1)
    return col

# 将输入张量和卷积核张量转换成矩阵形式
X_col = im2col(X, K.shape[2], K.shape[3])
K_col = K.reshape(K.shape[0], -1)

# 进行矩阵乘法
Y_col = K_col.dot(X_col)

# 转换回输出张量形状
batch_size = X.shape[0]
out_channels = K.shape[0]
out_height = (X.shape[2] - K.shape[2] + 1)
out_width = (X.shape[3] - K.shape[3] + 1)
Y = Y_col.reshape(out_channels, out_height, out_width, batch_size).transpose(3, 0, 1, 2)

print(Y.shape)  # 输出张量的形状

解释代码

  1. 输入张量: X 是形状为 (1, 3, 5, 5) 的输入张量,表示批量大小为1,通道数为3,高度和宽度均为5。
  2. 卷积核张量: K 是形状为 (2, 3, 3, 3) 的卷积核张量,表示输出通道数为2,输入通道数为3,高度和宽度均为3。
  3. im2col 函数: 将输入张量 X 转换为列矩阵 X_col。这个函数展开输入张量的局部区域。
  4. 卷积核转换: 将卷积核 K 重塑为矩阵 K_col,其形状为 (2, 27)。
  5. 矩阵乘法: Y_col = K_col.dot(X_col) 进行矩阵乘法,得到形状为 (2, 9) 的输出矩阵。
  6. 输出重塑: 将输出矩阵 Y_col 重塑为原始输出张量的形状 (batch_size, out_channels, out_height, out_width)

通过这种方式,我们成功地将卷积操作表示为矩阵乘法,这可以加速计算并利用线性代数库进行高效计算。

1. 手工设计一些卷积核。

  1. 二阶导数的核的形式是什么?
  2. 积分的核的形式是什么?
  3. 得到dd次导数的最小核的大小是多少?

卷积核是用于图像处理的工具,可以设计成执行各种任务,如检测边缘、平滑、微分和积分。以下是对这些任务的一些卷积核的设计:

1. 手工设计卷积核

二阶导数的卷积核

二阶导数核可以用于检测图像中的变化率。最常见的二阶导数核是拉普拉斯算子。拉普拉斯算子有多种形式,这里列出一种常见的形式:

0  1  0
1 -4  1
0  1  0

或者:

1  1  1
1 -8  1
1  1  1

这些核检测图像中的变化率,即寻找边缘。

积分的卷积核

积分操作在离散情况下相当于累加。积分核可以看作是平均滤波器的一种形式,例如,简单的积分核可以是一个全1的核,它可以累加周围的像素值。

例如,一个简单的积分核(3x3)可以是:

1  1  1
1  1  1
1  1  1

这种核会计算一个3x3区域内所有像素的和。

d次导数的最小核的大小

d次导数的最小核的大小取决于导数的阶数。以下是一些例子:

  • 一阶导数(dx 或 dy)的最小核大小为 2(如Sobel算子):

    [-1, 1]
    

    或者:

    [-1]
    [ 1]
    
  • 二阶导数(拉普拉斯算子)的最小核大小为 3(如上面提到的拉普拉斯算子)。

  • 更高阶的导数:对于d阶导数,其最小核的大小为d+1。

例如,三阶导数的最小核大小为 4:

[ 1, -3, 3, -1]

总结

  • 二阶导数的卷积核:常见的是拉普拉斯算子,用于检测图像中的变化率和边缘。
  • 积分的卷积核:一般是全1的核,用于累加邻域像素值。
  • d次导数的最小核大小:d次导数的最小核大小为d+1。

这些卷积核可以在图像处理中应用,以执行不同的任务,如边缘检测和图像平滑。设计卷积核时,需要考虑任务的具体需求和核的大小对计算复杂度的影响。