从全连接到卷积
MLP的缺陷,假设有如下的场景:
分类猫和狗的图片
- 使用一个还不错的相机采集图片(12M像素)
- RGB图片有 36M元素
- 使用100大小的单隐藏层MLP,模型有 3.6B元素
- 远多于世界上所有猫和狗总数(900M狗,600M猫)
- 远多于世界上所有猫和狗总数(900M狗,600M猫)
在图片里面找模式的原则:1、平移不变性 2、局部性
重新考察全连接层
-
将输入和输出变形为矩阵(宽度,高度)
-
将权重变形为4-D张量(h, w)到(h’, w’)
h i , j = ∑ k , l w i , j , k , l x k , l = ∑ a , b v i , j , a , b x i + a , j + b h_{i,j} = \sum_{k,l} w_{i,j,k,l} x_{k,l} = \sum_{a,b} v_{i,j,a,b} x_{i+a,j+b} hi,j=k,l∑wi,j,k,lxk,l=a,b∑vi,j,a,bxi+a,j+b -
V 是 W 的重新索引 v i , j , a , b = w i , j , i + a , j + b v_{i,j,a,b} = w_{i,j,i+a,j+b} vi,j,a,b=wi,j,i+a,j+b
原则 #1 - 平移不变性
- x 的平移导致 h 的平移 h i , j = ∑ a , b v i , j , a , b x i + a , j + b h_{i,j} = \sum_{a,b} v_{i,j,a,b} x_{i+a,j+b} hi,j=∑a,bvi,j,a,bxi+a,j+b
- v 不应该依赖于 (i, j)
- 解决方案: v i , j , a , b = v a , b v_{i,j,a,b} = v_{a,b} vi,j,a,b=va,b
h i , j = ∑ a , b v a , b x i + a , j + b h_{i,j} = \sum_{a,b} v_{a,b} x_{i+a,j+b} hi,j=a,b∑va,bxi+a,j+b这就是 2 维卷积(数学上叫 2 维交叉相关)
原则 #2 - 局部性
h i , j = ∑ a , b v a , b x i + a , j + b h_{i,j} = \sum_{a,b} v_{a,b} x_{i+a,j+b} hi,j=a,b∑va,bxi+a,j+b
- 当评估 h i , j h_{i,j} hi,j 时,我们不应该用远离 x i , j x_{i,j} xi,j 的参数
- 解决方案:当 ∣ a ∣ , ∣ b ∣ > Δ |a|, |b| > \Delta ∣a∣,∣b∣>Δ 时,使得 v a , b = 0 v_{a,b} = 0 va,b=0 h i , j = ∑ a = − Δ Δ ∑ b = − Δ Δ v a , b x i + a , j + b h_{i,j} = \sum_{a=-\Delta}^{\Delta} \sum_{b=-\Delta}^{\Delta} v_{a,b} x_{i+a,j+b} hi,j=a=−Δ∑Δb=−Δ∑Δva,bxi+a,j+b
总结
- 对全连接层使用平移不变性和局部性得到卷积层
h i , j = ∑ a , b v i , j , a , b x i + a , j + b h_{i,j} = \sum_{a,b} v_{i,j,a,b} x_{i+a,j+b} hi,j=∑a,bvi,j,a,bxi+a,j+b -> h i , j = ∑ a = − Δ Δ ∑ b = − Δ Δ v a , b x i + a , j + b h_{i,j} = \sum_{a=-\Delta}^{\Delta} \sum_{b=-\Delta}^{\Delta} v_{a,b} x_{i+a,j+b} hi,j=∑a=−ΔΔ∑b=−ΔΔva,bxi+a,j+b
卷积层
二维交叉相关
二维卷积层
输入和输出的维度
- 输入 X \mathbf{X} X: n h × n w n_h \times n_w nh×nw
- 核 W \mathbf{W} W: k h × k w k_h \times k_w kh×kw
- 偏差 b ∈ R b \in \mathbb{R} b∈R
- 输出 Y \mathbf{Y} Y: ( 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) Y = X ⋆ W + b \mathbf{Y} = \mathbf{X} \star \mathbf{W} + b Y=X⋆W+b
- W \mathbf{W} W 和 b b b 是可学习的参数
交叉相关 vs 卷积
-
二维交叉相关
y i , j = ∑ a = 1 h ∑ b = 1 w w a , b x i + a , j + b y_{i,j} = \sum_{a=1}^{h} \sum_{b=1}^{w} w_{a,b} x_{i+a,j+b} yi,j=a=1∑hb=1∑wwa,bxi+a,j+b -
二维卷积
y i , j = ∑ a = 1 h ∑ b = 1 w w − a , − b x i + a , j + b y_{i,j} = \sum_{a=1}^{h} \sum_{b=1}^{w} w_{-a,-b} x_{i+a,j+b} yi,j=a=1∑hb=1∑ww−a,−bxi+a,j+b -
由于对称性,在实际使用中没有区别
一维和三维交叉相关
一维
y i = ∑ a = 1 h w a x i + a y_i = \sum_{a=1}^{h} w_a x_{i+a} yi=a=1∑hwaxi+a
- 文本
- 语言
- 时序序列
三维
y i , j , k = ∑ a = 1 h ∑ b = 1 w ∑ c = 1 d w a , b , c x i + a , j + b , k + c y_{i,j,k} = \sum_{a=1}^{h} \sum_{b=1}^{w} \sum_{c=1}^{d} w_{a,b,c} x_{i+a,j+b,k+c} yi,j,k=a=1∑hb=1∑wc=1∑dwa,b,cxi+a,j+b,k+c
- 视频
- 医学图像
- 气象地图
总结
- 卷积层将输入和核矩阵进行交叉相关,加上偏移后得到输出
- 核矩阵和偏移是可学习的参数
- 核矩阵的大小是超参数
代码
首先定义一个函数计算二维互相关运算:
import torch
from torch import nn
from d2l import torch as d2ldef 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)
下面就实现二维卷积层:
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
图像中目标的边缘检测
假设黑色为 0 ,白色为 1 :
X = torch.ones((6, 8))
X[:, 2:6] = 0
X
接下来,我们构造一个高度为 1 1 1、宽度为 2 2 2的卷积核 K
。当进行互相关运算时,如果水平相邻的两元素相同,则输出为零,否则输出为非零。
K = torch.tensor([[1.0, -1.0]])
# 这个卷积核k只可以检测垂直的边缘
Y = corr2d(X, K)
Y
但是上述实现的卷积核 K 只可以检测垂直边缘:
下图可以发现,将矩阵转置后,就检测不出来了
是否可以学习由 X 生成 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) # 通过卷积层 conv2d 对输入张量 X 进行卷积操作得到的预测值。l = (Y_hat - Y) ** 2 #使用一个均方误差conv2d.zero_grad() # 将梯度设为0 l.sum().backward()# 迭代卷积核:梯度下降conv2d.weight.data[:] -= lr * conv2d.weight.gradif (i + 1) % 2 == 0:print(f'epoch {i+1}, loss {l.sum():.3f}')
下图可以发现,和构造出来的[-1,1]很接近了:
QA 思考
Q1:为什么不应该看那么远?感受野不是越大越好吗?
A1:这就类似于全连接层,我把层做的很浅很胖,效果没有我把层做的很深很瘦的好,尽管数学上两者是等价的。这里做一个小的 Kernel ,做的比较深 ,实践表明是最好的。
Q2:二维卷积层,有没有可能同时使用两个不同尺寸的Kernel进行计算,然后再计算出一个更合适的Kernel,从而提高特征提取的性能。
A2:GoogleNet 论文的设计思路。