李沐《动手学深度学习》预备知识 张量操作及数据处理
李沐《动手学深度学习》预备知识 线性代数及微积分
李沐《动手学深度学习》线性神经网络 线性回归
李沐《动手学深度学习》线性神经网络 softmax回归
李沐《动手学深度学习》多层感知机 模型概念和代码实现
李沐《动手学深度学习》多层感知机 深度学习相关概念
李沐《动手学深度学习》深度学习计算
教材:李沐《动手学深度学习》
卷积神经网络(convolutional neural network,CNN)是一类强大的、为处理图像数据而设计的神经网络,是机器学习利用自然图像中一些已知结构的创造性方法。
全连接层网络的缺点:
全连接层的形式化表示:( 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少很多,因为前者不再依赖于图像中的位置。
局部性: 神经网络的前面几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系,这就是“局部性”原则。最终,可以聚合这些局部特征,以在整个图像级别进行预测。(在 ∣ a ∣ > Δ |a| > \Delta ∣a∣>Δ或 ∣ b ∣ > Δ |b| > \Delta ∣b∣>Δ的范围之外,可以设置 [ V ] a , b = 0 [V]_{a,b}=0 [V]a,b=0)
[ 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是该卷积层的权重。
二维互相关运算的实现:
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
二维卷积层的实现:
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
先构造一个卷积层,并将其卷积核初始化为随机张量。接下来,在每次迭代中,我们比较Y与卷积层输出的平方误差,然后计算梯度来更新卷积核。
# 构造一个二维卷积层,它具有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}')
填充和步幅可用于有效地调整数据的维度
卷积层高度和宽度均为3,所有侧边填充1个像素(padding=1)
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))
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
将高度和宽度的步幅设置为2(stride=2):
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
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:卷积核的宽度)
def corr2d_multi_in_out(X, K):
# 迭代“K”的第0个维度,每次都对输入“X”执行互相关运算。
# 最后将所有结果都叠加在一起
return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
当以每像素为基础应用时,可以将1x1卷积层看作是在每个像素位置应用的全连接层,用 c i c_i ci个输入值转换 c 0 c_0 c0个输出值;下图是使用1x1卷积核与3个输入通道和2个输出通道的互相关计算,这里的输入和输出具有相同的高度和宽度,输出中的每个元素都是从输入图像中同一位置的线性组合。
使用全连接层实现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))
当我们处理图像时,我们希望逐渐降低隐藏表示的空间分辨率、聚集信息,这样随着我们在神经网络中层叠的上升,每个神经元对其敏感的感受野(输入)就越大。通过逐渐聚合信息,生成越来越粗糙的映射,最终实现学习全局表示的目标,同时将卷积图层的所有优势保留在中间层。
实现汇聚层的前向传播:
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