系列文章
李沐《动手学深度学习》预备知识 张量操作及数据处理
李沐《动手学深度学习》预备知识 线性代数及微积分
李沐《动手学深度学习》线性神经网络 线性回归
李沐《动手学深度学习》线性神经网络 softmax回归
李沐《动手学深度学习》多层感知机 模型概念和代码实现
李沐《动手学深度学习》多层感知机 深度学习相关概念
李沐《动手学深度学习》深度学习计算
目录
教材:李沐《动手学深度学习》
卷积神经网络(convolutional neural network,CNN)是一类强大的、为处理图像数据而设计的神经网络,是机器学习利用自然图像中一些已知结构的创造性方法。
一、从全连接层到卷积
(一)全连接层
-
全连接层网络的缺点:
- 不能预先假设任何与特征交互相关的先验结构;
- 全连接层往往意味着大量的参数,需要大量的GPU、分布式优化训练的经验和超乎常人的耐心。
-
全连接层的形式化表示:( X X X是输入的二维图像, H H H是其对应的隐藏表示, W W W是权重矩阵, U U U包含偏置参数)
H \] i , j = \[ U \] i , j + ∑ k ∑ l \[ W \] i , j , k , l \[ X \] k , l = \[ U \] i , j + ∑ a ∑ b \[ V \] i , j , a , b \[ X \] i + a , j + b \\begin{aligned} \[H\]_{i,j} \&=\[U\]_{i,j}+\\sum_{k}\\sum_{l}\[W\]_{i,j,k,l}\[X\]_{k,l}\\\\ \&=\[U\]_{i,j}+\\sum_{a}\\sum_{b}\[V\]_{i,j,a,b}\[X\]_{i+a,j+b} \\end{aligned} \[H\]i,j=\[U\]i,j+k∑l∑\[W\]i,j,k,l\[X\]k,l=\[U\]i,j+a∑b∑\[V\]i,j,a,b\[X\]i+a,j+b 令 k = i + a k=i+a k=i+a, l = j + b l=j+b l=j+b,则有 \[ V \] i , j , a , b = \[ W \] i , j , i + a , j + b \[V\]_{i,j,a,b}=\[W\]_{i,j,i+a,j+b} \[V\]i,j,a,b=\[W\]i,j,i+a,j+b。索引 a a a和 b b b在正偏移和负偏移之间移动覆盖了整个图像。
-
平移不变性: 不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应;(这意味着检测对象在输入 X X X中的平移仅导致隐藏表示 H H H中的平移,而 V V V和 U U U实际上不依赖于 ( i , j ) (i,j) (i,j)的值)
H \] i , j = u + ∑ a ∑ b \[ V \] a , b \[ X \] i + a , j + b \[H\]_{i,j}=u+\\sum_{a}\\sum_{b}\[V\]_{a,b}\[X\]_{i+a,j+b} \[H\]i,j=u+a∑b∑\[V\]a,b\[X\]i+a,j+b **这就是卷积:** 使用系数 \[ V \] a , b \[V\]_{a,b} \[V\]a,b对位置 ( i , j ) (i,j) (i,j)附近的像素 ( i + a , j + b ) (i+a,j+b) (i+a,j+b)进行加权得到 \[ H \] i , j \[H\]_{i,j} \[H\]i,j,而且此时 \[ V \] a , b \[V\]_{a,b} \[V\]a,b的系数比 \[ V \] i , j , a , b \[V\]_{i,j,a,b} \[V\]i,j,a,b少很多,因为前者不再依赖于图像中的位置。
H \] i , j = u + ∑ a = − Δ Δ ∑ b = − Δ Δ \[ V \] a , b \[ X \] i + a , j + b \[H\]_{i,j}=u+\\sum_{a=-\\Delta}\^\\Delta\\sum_{b=-\\Delta}\^\\Delta\[V\]_{a,b}\[X\]_{i+a,j+b} \[H\]i,j=u+a=−Δ∑Δb=−Δ∑Δ\[V\]a,b\[X\]i+a,j+b **这就是卷积层:** V V V被称为卷积核或滤波器,也就是卷积层的权重。
(三)通道
实际上,图像不是二维张量,而是一个由高度、宽度和颜色组成的三维张量。
H \] i , j , d = ∑ a = − Δ Δ ∑ b = − Δ Δ ∑ c \[ V \] a , b , c , d \[ X \] i + a , j + b , c \[H\]_{i,j,d}=\\sum_{a=-\\Delta}\^\\Delta\\sum_{b=-\\Delta}\^\\Delta\\sum_{c}\[V\]_{a,b,c,d}\[X\]_{i+a,j+b,c} \[H\]i,j,d=a=−Δ∑Δb=−Δ∑Δc∑\[V\]a,b,c,d\[X\]i+a,j+b,c 这是具有多个通道的卷积层,其中 V V V是该卷积层的权重。 ## 二、图像卷积 ### (一)互相关运算 * 在卷积层中,输入张量和核张量通过互相关运算产生输出张量; * 输出大小等于输入大小 n h × n w n_h\\times n_w nh×nw减去卷积核大小 k h × k w k_h \\times k_w kh×kw,即: ( n h − k h + 1 ) × ( n w − k w + 1 ) (n_h-k_h+1)\\times(n_w-k_w+1) (nh−kh+1)×(nw−kw+1)  二维互相关运算的实现: ```python 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 ``` ### (二)卷积层 1. 卷积层对输入和卷积核权重进行互相关运算,并在添加标量偏置之后产生输出。 所以,卷积层中的两个被训练的参数是卷积核权重和标量偏置。 在训练基于卷积层的模型时,会随机初始化卷积核权重。 二维卷积层的实现: ```python 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. 卷积层的一个简单应用:通过找到像素变化的位置,来检测图像中不同颜色的边缘,实现图像中目标的边缘检测。 ### (三)学习卷积核 先构造一个卷积层,并将其卷积核初始化为随机张量。接下来,在每次迭代中,我们比较Y与卷积层输出的平方误差,然后计算梯度来更新卷积核。 ```python # 构造一个二维卷积层,它具有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 # 学习率 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}') ``` ### (四)特征映射和感受野 * 特征映射:卷积层有时被称为特征映射,因为它可以被视为一个输入映射到下一层的空间维度的转换器; * 感受野:在卷积神经网络中,对于某一层的任意元素 x x x,其感受野是指在前向传播期间可能影响计算的所有元素(来自所有先前层)。 ## 三、填充和步幅 填充和步幅可用于有效地调整数据的维度 ### (一)填充:在输入图像的边界填充元素(通常填充元素是0) * 填充可以增加输出的高度和宽度,这常用来使输出与输入具有相同的高和宽; * 如果我们添加 p h p_h ph行填充(大约一半在顶部,一半在底部)和列填充 p w p_w pw(左侧大约一半,右侧一半),则输出形状将为 ( n h + p h − k h + 1 ) × ( n w + p w − k w + 1 ) (n_h+p_h-k_h+1)\\times(n_w+p_w-k_w+1) (nh+ph−kh+1)×(nw+pw−kw+1) 此时输出的高度和宽度将分别增加和 p h p_h ph和 p w p_w pw; * 如果要使得输入和输出具有相同的高度和宽度,则应该设置 p h = k h − 1 p_h=k_h-1 ph=kh−1和 p w = k w − 1 p_w=k_w-1 pw=kw−1,如果 k h k_h kh是奇数,则应该在高度的两侧填充 p h / 2 p_h/2 ph/2行,如果 k h k_h kh是偶数,则应该在输入的顶部填充 \[ p h / 2 \] \[p_h/2\] \[ph/2\]行,在底部填充 \[ p h / 2 \] \[p_h/2\] \[ph/2\]行。因此卷积神经网络中卷积核的高度和宽度通常为奇数,在保持空间维度的同时,可以在顶部和底部填充相同数量的行,在左侧和右侧填充相同数量的列。  卷积层高度和宽度均为3,所有侧边填充1个像素(padding=1) ```python 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)) comp_conv2d(conv2d, X).shape ``` 卷积层高度为5,宽度为3,高度和宽度两边的填充分别为2和1(padding=(2,1)) ```python conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1)) comp_conv2d(conv2d, X).shape ``` ### (二)步幅:每次滑动元素的数量 * 步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的 1 n \\frac{1}{n} n1(n是一个大于1的整数); * 当垂直步幅为 s h s_h sh、水平步幅为 s w s_w sw时,输出形状为: \[ ( n h − k h + p h + s h ) / s h \] × \[ n w − k w + p w + s w ) / s w \] \[(n_h-k_h+p_h+s_h)/s_h\]\\times\[n_w-k_w+p_w+s_w)/s_w\] \[(nh−kh+ph+sh)/sh\]×\[nw−kw+pw+sw)/sw
如果设置了 p h = k h − 1 p_h=k_h-1 ph=kh−1和 p w = k w − 1 p_w=k_w-1 pw=kw−1,输出形状将简化为:
( n h + s h − 1 ) / s h \] × \[ n w + s w − 1 ) / s w \] \[(n_h+s_h-1)/s_h\]\\times\[n_w+s_w-1)/s_w\] \[(nh+sh−1)/sh\]×\[nw+sw−1)/sw
如果输入的高度和宽度可以被垂直和步幅整除,则输出形状为:
( n h / s h ) × ( n w / s w ) (n_h/s_h)\times(n_w/s_w) (nh/sh)×(nw/sw)
将高度和宽度的步幅设置为2(stride=2):
python
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
四、多输入多输出通道
(一)多输入通道
- 卷积核:与输入数据具有相同输入通道数
- 多输入通道互相关运算: 对每个通道执行互相关操作,然后将结果相加
利用代码实现多输入通道的互相关计算:
python
import torch
from d2l import torch as d2l
def corr2d_multi_in(X, K):
# 先遍历"X"和"K"的第0个维度(通道维度),再把它们加在一起
return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
(二)多输出通道
-
每个通道可以看作是对不同特征的响应。每个通道不是独立学习的,而是为了共同使用而优化的,因此多输出通道并不仅是学习多个单通道的检测器。
-
为了获得多个通道的输出,可以为每个输出通道创建一个形状 c i × k h × k w c_i\times k_h \times k_w ci×kh×kw的卷积核张量,这样卷积核的形状是 c 0 × c i × k h × k w c_0\times c_i\times k_h \times k_w c0×ci×kh×kw。( c i c_i ci:输入通道数, c 0 c_0 c0:输出通道数, k h k_h kh:卷积核的高度, k w k_w kw:卷积核的宽度)
python
def corr2d_multi_in_out(X, K):
# 迭代"K"的第0个维度,每次都对输入"X"执行互相关运算。
# 最后将所有结果都叠加在一起
return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
(三)1x1卷积层
- 1x1卷积失去了卷积层的特有能力------在高度和宽度维度上,识别相邻元素间相互作用的能力;
- 1x1卷积层通常用于调整网络层的通道数量和控制模型复杂性
- 1x1卷积的唯一计算发生在通道上;
当以每像素为基础应用时,可以将1x1卷积层看作是在每个像素位置应用的全连接层,用 c i c_i ci个输入值转换 c 0 c_0 c0个输出值;下图是使用1x1卷积核与3个输入通道和2个输出通道的互相关计算,这里的输入和输出具有相同的高度和宽度,输出中的每个元素都是从输入图像中同一位置的线性组合。

使用全连接层实现1x1卷积:
python
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))
五、汇聚层
当我们处理图像时,我们希望逐渐降低隐藏表示的空间分辨率、聚集信息,这样随着我们在神经网络中层叠的上升,每个神经元对其敏感的感受野(输入)就越大。通过逐渐聚合信息,生成越来越粗糙的映射,最终实现学习全局表示的目标,同时将卷积图层的所有优势保留在中间层。
- 汇聚(pooling)层的双重目的:
- 降低卷积层对位置的敏感性;
- 降低对空间降采样表示的敏感性。
- 汇聚层与卷积层的比较:
- 相同点:都是由一个固定形状的窗口组成,根据步幅大小在输入区域上滑动
- 不同点:卷积层对应互相关计算是有参数的;汇聚层的池运算是确定的,汇聚层不包含参数
- 汇聚层按照计算方法可以分为最大汇聚层和平均汇聚层,最大汇聚层返回汇聚窗口中所有元素的最大值,平均汇聚层返回汇聚窗口中所有元素的平均值;
- 与卷积层一样,汇聚层也可以改变输出形状,可以通过填充和步幅以获得所需的输出形状;
- 在处理多通道输入数据时,汇聚层在每个输入通道上单独运算,而不是像卷积层一样在通道上对输入进行汇总。 这意味着汇聚层的输出通道数与输入通道数相同
实现汇聚层的前向传播:
python
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