DL_8——卷积层

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=1hb=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=1hb=1wwa,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=1hk=1wc=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) (nhkh+ph+1)×(nwkw+pw+1)
  • 通常取 p h = k h − 1 , p w = k w − 1 p_{h}=k_{h}-1, \quad p_{w}=k_{w}-1 ph=kh1,pw=kw1
    • 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 (nhkh+ph+sh)/sh×(nwkw+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=kh1,pw=kw1
    ⌊ ( 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+sh1)/sh×(nw+sw1)/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=0ciXi,:,: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,:,:=XWi,:,:,: 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))

猜你喜欢

转载自blog.csdn.net/CesareBorgia/article/details/120295796