文章目录
1 卷积
1.1 交叉相关和卷积
-
二维交叉相关
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=1h∑b=1wwa,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=1h∑b=1ww−a,−bxi+a,j+b -
由于对称性,在实际使用中二者没有区别。
1.2 一维和三维交叉相关
- 一维
y i = ∑ a = 1 h w a x i + a y_{i}=\sum_{a=1}^{h} w_{a} x_{i+a} yi=∑a=1hwaxi+a- 文本
- 语言
- 时间序列
- 三维
y i , j , k = ∑ a = 1 h ∑ k = 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_{k=1}^{w} \sum_{c=1}^{d} w_{a, b, c} x_{i+a, j+b, k+c} yi,j,k=∑a=1h∑k=1w∑c=1dwa,b,cxi+a,j+b,k+c- 视频
- 医学图像
- 气象地图
1.3 总结
- 卷积层将输入和核矩阵进行交叉相关,加上偏移后得到输出
- 核矩阵和偏移是可学习的参数
- 核矩阵的大小是超参数
1.4 从零实现卷积操作和卷积层
# -*- coding: utf-8 -*-
# @Time : 2021/9/14 21:23
# @Author : Amonologue
# @software : pycharm
# @File : conv2d_from_zero.py
import torch
from torch import nn
from torch.nn import functional as F
def corr2d(X, k):
'''卷积操作,准确来说应该是二维互相关运算'''
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
if __name__ == '__main__':
X = torch.ones((6, 8))
X[:, 2:6] = 0
print(X) # 原始图像
k1 = torch.tensor([[1., -1.]])
y1 = corr2d(X, k1)
print(y1) # 提取边缘特征
conv2d = Conv2d(kernel_size=(1, 2))
X = X.reshape((6, 8))
Y = y1
Y = Y.reshape((6, 7))
# 训练10个epoch
for i in range(10):
Y_hat = conv2d(X) # 预测值
l = (Y_hat - Y) ** 2
conv2d.zero_grad() # 梯度清零
l.sum().backward() # 反向传播
conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad # 手动sgd
print(f'batch {i}, loss {l.sum():.3f}') # 输出训练过程
2 填充&步幅
2.1 填充 padding
在原数据周围添加 n n n 圈为0的数据
- 填充
p
h
p_h
ph 行和
p
w
p_w
pw 列,输出形状为
( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) \left(n_{h}-k_{h}+p_{h}+1\right) \times\left(n_{w}-k_{w}+p_{w}+1\right) (nh−kh+ph+1)×(nw−kw+pw+1) - 通常取
p
h
=
k
h
−
1
,
p
w
=
k
w
−
1
p_{h}=k_{h}-1, \quad p_{w}=k_{w}-1
ph=kh−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 ⌉ \left\lceil\frac{p_{h}}{2}\right\rceil ⌈2ph⌉, 在下侧填充 ⌊ p h 2 ⌋ \left\lfloor\frac{p_{h}}{2}\right\rfloor ⌊2ph⌋
- 注:此处的 p h p_h ph是指总共添加的行数,即在原始数据上方添加 p h 2 \frac{p_h}{2} 2ph行,在原始数据下方添加 p h 2 \frac{p_h}{2} 2ph
在torch中,在创建卷积层时指定padding大小即可
# 在原始数据周围添加一圈0
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
# 在原始数据周围添加两圈0
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=2)
# 在原始数据上方添加两行0,下方添加两行0,左侧添加一列0,右侧添加一列0
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=(2, 1))
2.2 步幅 stride
kernel的移动跨度
- 给定高度
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 ⌋ \left\lfloor\left(n_{h}-k_{h}+p_{h}+s_{h}\right) / s_{h}\right\rfloor \times\left\lfloor\left(n_{w}-k_{w}+p_{w}+s_{w}\right) / s_{w}\right\rfloor ⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋ - 如果
p
h
=
k
h
−
1
,
p
w
=
k
w
−
1
p_{h}=k_{h}-1, \quad p_{w}=k_{w}-1
ph=kh−1,pw=kw−1
⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ \left\lfloor\left(n_{h}+s_{h}-1\right) / s_{h}\right\rfloor \times\left\lfloor\left(n_{w}+s_{w}-1\right) / s_{w}\right\rfloor ⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋ - 如果输入高度和宽度可以被步幅整除
( n h / s h ) × ( n w / s w ) \left(n_{h} / s_{h}\right) \times\left(n_{w} / s_{w}\right) (nh/sh)×(nw/sw)
同样的,在torch中指定stride大小即可设置步幅
# 将高度和宽度的步幅都设置为2
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
# 将高度步幅设置为3,宽度步幅设置为4
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=(3, 4))
2.3 总结
- 填充和步幅是卷积层的超参数
- 填充在输入周围添加额外的列/行,来控制输出形的减少量
- 步幅是每次滑动核窗口时的行/列的步长,可以成倍的减少输出形状
3 多输入多输出通道
对于灰度图只需要一个channel,但是对于彩色的图就需要用上rgb三个channel
3.1 多个输入通道
每个通道都有一个卷积核,结果是所有通道卷积结果的和
- 输入 X : c i × n h × n w \mathbf{X}: c_{i} \times n_{h} \times n_{w} X:ci×nh×nw
- 核 W : c i × k h × k w \mathbf{W}: c_{i} \times k_{h} \times k_{w} W:ci×kh×kw
- 输出
Y
:
m
h
×
m
w
\mathbf{Y}: m_{h} \times m_{w}
Y:mh×mw
Y = ∑ i = 0 c i X i , : , : ∗ ⋆ W i , : , : \mathbf{Y}=\sum_{i=0}^{c_{i}} \mathbf{X}_{i,:,:}^{*} \star \mathbf{W}_{i,:,:} Y=i=0∑ciXi,:,:∗⋆Wi,:,:
其中 c i c_i ci 表示输入通道数
def corr2d_multi_in(X, K):
'''实现多输入通道'''
return sum(corr2d(x, k) for x, k in zip(X, K))
3.2 多个输出通道
可以有多个三维卷积核,每个核生成一个输出通道
- 输入 X : c i × n h × n w \mathbf{X}: c_{i} \times n_{h} \times n_{w} X:ci×nh×nw
- 核 W : c o × c i × k h × k w \mathbf{W}: c_{o} \times c_{i} \times k_{h} \times k_{w} W:co×ci×kh×kw
- 输出
Y
:
c
o
×
m
h
×
m
w
\mathbf{Y}: c_{o} \times m_{h} \times m_{w}
Y:co×mh×mw
Y i , : , : = X ⋆ W i , : , : , : for i = 1 , … , c o \mathbf{Y}_{i,:,:}=\mathbf{X} \star \mathbf{W}_{i,:,:,:} \quad \text { for } i=1, \ldots, c_{o} Yi,:,:=X⋆Wi,:,:,: for i=1,…,co
其中 c o c_o co 表示输出通道数
def corr2d_multi_in_out(X, K):
'''实现多输入多输出通道'''
return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
3.3 1 × 1 1\times1 1×1卷积
1 × 1 1\times1 1×1卷积等价于全连接
def corr2d_multi_in_out_1x1(X, K):
'''实现 1 x 1卷积'''
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(x, k)
return Y.reshape((c_o, h, w))