动手学习深度学习(10)卷积层和池化层

茴香豆 Lv5

卷积神经网络(convolutional neural networks,CNN)是机器学习利用自然图像中一些已知结构的创造性方法。

两个原则:平移不变性,局部性

详细讲解了为什么要使用卷积,以及如何从全连接层推理至卷积层。建议完整观看视频。

19 卷积层【动手学深度学习v2】_bilibili

  • 卷积层将输入和核矩阵进行交叉相关,加上偏移后得到输出
  • 核矩阵和偏移是可学习的参数
  • 核矩阵的大小是超参数

图像卷积

互相关运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch
from torch import nn
from d2l import torch as d2l

def corr2d(X, K): #@save
"""计算二维互相关运算"""
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

X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
corr2d(X, K)
# output
tensor([[19., 25.],
[37., 43.]])

实现二维卷积层

1
2
3
4
5
6
7
8
9
10
# 在__init__构造函数中,将weight和bias声明为两个模型参数。
# 前向传播函数调用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

填充与步幅

填充:在输入的周围添加额外的行/列

步幅:卷积核的滑动步长

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
import torch
from torch import nn

# 为了方便起见,我们定义了一个计算卷积层的函数。
# 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数
def comp_conv2d(conv2d, X):
# 这里的(1,1)表示批量大小和通道数都是1
X = X.reshape((1, 1) + X.shape)
Y = conv2d(X)
# 省略前两个维度:批量大小和通道
return Y.reshape(Y.shape[2:])

# 请注意,这里每边都填充了1行或1列,因此总共添加了2行或2列
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
X = torch.rand(size=(8, 8))
print(comp_conv2d(conv2d, X).shape)
# output
torch.Size([8, 8])

# 当卷积核的高度和宽度不同时,我们可以填充不同的高度和宽度,
# 使输出和输入具有相同的高度和宽度。在如下示例中,我们使用高度为5,宽度为3的卷积核,
# 高度和宽度两边的填充分别为2和1。
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
print(comp_conv2d(conv2d, X).shape)
# output
torch.Size([8, 8])

# 我们将高度和宽度的步幅设置为2,从而将输入的高度和宽度减半。
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
print(comp_conv2d(conv2d, X).shape)
# output
torch.Size([4, 4])

# 稍微复杂的例子
conv2d = nn.Conv2D(1, kernel_size=(3, 5), padding=(0, 1), strides=(3, 4))
print(comp_conv2d(conv2d, X).shape)
# output
(2, 2)

多个输入和输出通道

多个输入通道:

  • 每个通道都有一个卷积核,结果是所有通道卷积结果的和。

多个输出通道:

  • 我们可以有多个三维卷积核,每个核生成一个输出通道。

多个输入输出通道:

  • 每个输出通道可以识别特定的模式
  • 输入通道核始别并组合输入中的模式

1x1卷积层:不识别空间模式,只是融合通道。

假设输入为 c_i×h×w ,卷积核大小为 c_o×c_i×k_h×k_w ,填充为 (p_h, p_w) ,步幅为 (s_h, s_w)

  1. 前向传播的计算成本(乘法和加法):
    MATHJAX-SSR-0
  2. 参数内存占用:

c_i×h×w+c_o×c_i×k_h×k_w+c_o×⌊(ℎ−𝑘_ℎ+𝑝_ℎ+𝑠_ℎ)/𝑠_ℎ⌋×⌊(𝑤−𝑘_𝑤+𝑝_𝑤+𝑠_𝑤)/𝑠_𝑤⌋

实现

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
40
41
42
43
44
45
46
47
48
49
50
import torch
from d2l import torch as d2l
# 多输入通道互相关运算
def corr2d_multi_in(X, K):
return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
# 验证互相关运算的输出
X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])

print(corr2d_multi_in(X, K))
# output
tensor([[ 56., 72.],
[104., 120.]])

# 多输出通道互相关运算
def corr2d_multi_in_out(X, K):
# 迭代“K”的第0个维度,每次都对输入“X”执行互相关运算。
# 最后将所有结果都叠加在一起
return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
K = torch.stack((K, K + 1, K + 2), 0)
print(K.shape)
# output
torch.Size([3, 2, 2, 2])
print(corr2d_multi_in_out(X, K))
# output
tensor([[[ 56., 72.],
[104., 120.]],

[[ 76., 100.],
[148., 172.]],

[[ 96., 128.],
[192., 224.]]])

# 使用全连接层实现1x1卷积
def corr2d_multi_in_out_1x1(X, K):
c_i, h, w = X.shape
c_o = K.shape[0]
X = X.reshape((c_i, h * w))
K = K.reshape((c_o, c_i))
# 全连接层中的矩阵乘法
Y = torch.matmul(K, X)
return Y.reshape((c_o, h, w))
X = torch.normal(0, 1, (3, 3, 3))
K = torch.normal(0, 1, (2, 3, 1, 1))

Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(torch.abs(Y1 - Y2).sum()) < 1e-6

池化层

我们的机器学习任务通常会跟全局图像的问题有关(例如,“图像是否包含一只猫呢?”),所以我们最后一层的神经元应该对整个输入的全局敏感。通过逐渐聚合信息,生成越来越粗糙的映射,最终实现学习全局表示的目标,同时将卷积图层的所有优势保留在中间层。

本节将介绍汇聚(pooling)层,它具有双重目的:降低卷积层对位置的敏感性,同时降低对空间降采样表示的敏感性。

  • 池化层与卷积层类似,都具有填充和步幅
  • 没有可学习参数
  • 在每个输入通道应用池化层以获得相应的输出通道
  • 输出通道数 = 输入通道数

最大池化层:每个窗口中最强的模式信号

平均池化层:将最大池化层中的“最大”操作替换为“平均”

实现

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import torch
from torch import nn
from d2l import torch as d2l

def pool2d(X, pool_size, mode='max'):
p_h, p_w = pool_size
Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
return Y
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
print(pool2d(X, (2, 2)))
# output
tensor([[4., 5.],
[7., 8.]])
print(pool2d(X, (2, 2), 'avg'))
# output
tensor([[2., 3.],
[5., 6.]])

# 填充和步幅
X = torch.arange(16, dtype=torch.float32).reshape((1, 1, 4, 4))
print(X)
# output
tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]]]])
# 默认情况下,深度学习框架中的步幅与汇聚窗口的大小相同。
pool2d = nn.MaxPool2d(3)
print(pool2d(X))
# output
tensor([[[[10.]]]])

# 填充和步幅可以手动设定。
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
print(pool2d(X))
# output
tensor([[[[ 5., 7.],
[13., 15.]]]])

# 当然,我们可以设定一个任意大小的矩形汇聚窗口,
# 并分别设定填充和步幅的高度和宽度。
pool2d = nn.MaxPool2d((2, 3), stride=(2, 3), padding=(0, 1))
print(pool2d(X))
# output
tensor([[[[ 5., 7.],
[13., 15.]]]])

# 多个通道
X = torch.cat((X, X + 1), 1)
print(X)
# output
tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]],

[[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[13., 14., 15., 16.]]]])
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
print(pool2d(X))
# output
tensor([[[[ 5., 7.],
[13., 15.]],

[[ 6., 8.],
[14., 16.]]]])
  • Title: 动手学习深度学习(10)卷积层和池化层
  • Author: 茴香豆
  • Created at : 2022-10-30 16:34:07
  • Updated at : 2022-10-31 16:14:51
  • Link: https://hxiangdou.github.io/2022/10/30/DL_10/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments