目录
上期回顾
【李沐 动手学深度学习】视频课程笔记与重点总结 01-18-优快云博客https://blog.youkuaiyun.com/qq_52589927/article/details/141156454?spm=1001.2014.3001.5502还是提醒一下,文章中代码仅作展示。使用的是pytorch框架,在运行代码的时候,不要忘了先导包
import torch
from torch import nn
from d2l import torch as d2l
from torch.nn import functional as F
卷积神经网络
相信大家对卷积都稍微有些了解,就是一个卷积核(权重矩阵)在图片上不断扫描求值,如下动图就是一个3*3的卷积核在5*5的图片上做卷积。
下面会从底层逻辑开始讲起,卷积是怎么出现的
从全连接到卷积
对全连接层(输入和输出为一维向量)使用如下两种性质就会变成卷积层(输入和输出为二维矩阵)
通俗来讲就是:
平移不变性
- 确定卷积核的个数唯一
- 要识别图片上的猫,不管猫在图片的哪个位置,只需要同一个卷积核(权重矩阵)来扫描即可,即卷积核的参数不变,从图中提取到的特征就不变
局限性
- 确定卷积核的大小适当
- 无需整张图一起看,只需要看某一个位置点附近的地方
通过改变卷积核中的参数,可以实现不同目的,如边缘检测,锐化,模糊........
也可以使用一维,三维的数据来做卷积。要注意,输入输出变化的同时,卷积核也会改变维度哦
一维:文本,语言,时序序列
三维:视频,医学图像
卷积的简单公式:
- 输入 X 和 权重矩阵 W(卷积核)交叉相乘,再加上偏移 b,得到输出 Y
- 可学习的参数:W 和 b
- 超参数:W 的大小
图像卷积
实现二维卷积层
class Conv2D(nn.Module): # 继承nn.Module
def __init__(self, kernel_size): # W 的大小
super().__init__()
self.weight = nn.Parameter(torch.rand(kernel_size)) # W 可学习参数
self.bias = nn.Parameter(torch.zeros(1)) # b 可学习参数
def forward(self, x):
return corr2d(x, self.weight) + self.bias # Y = X * W + b
学习卷积核
学习由 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)
l = (Y_hat - Y) ** 2 # 均方误差作为loss
conv2d.zero_grad() # conv2d的梯度设为0
l.sum().backward()
# 迭代卷积核,梯度下降的方式
conv2d.weight.data[:] -= lr * conv2d.weight.grad
if (i + 1) % 2 == 0:
print(f'epoch {i+1}, loss {l.sum():.3f}')
# 查看这个卷积核的权重tensor
conv2d.weight.data.reshape((1, 2))
填充和步幅
卷积的超参数:填充padding、步幅stride
先讲填充,假设如下情况:
形状减小到 ( 32 - 5 + 1 ) * ( 32 - 5 + 1 ),即第一层输出大小 28 * 28
如此往复,输出结果越来越小,为了防止这一现象的出现,可以采取如下措施:
在输入的周围添加额外的行列,这也就是为什么文章开头的动图中,蓝色方框外还有一圈虚线,那个就是额外添加的padding
如果添加 ph 行填充和 pw 列填充,则输出形状将为:
p:填充padding 、k:卷积核kernel 、n:输入
h:高 、 w:宽
这样选取的话,在经过不断卷积后,输出的形状不会发生变化,和输入时大小一样(Pw同理)
代码实现:
在所有侧边填充 1 个像素(填一圈)
# 为了方便起见,我们定义了一个计算卷积层的函数。
# 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数
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)) # 输入为 8*8
comp_conv2d(conv2d, X).shape
放入之前的公式就是:
Ph=(3-1)=2,Pw=(3-1)=2,因为Kh为奇数,故在上下两侧都填充Ph/2=1行,即共填充2行
Pw同理,填充左右两侧。所以说,其实就是填充了一圈
填充不同的高度和宽度
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
接下来讲步幅,假设希望 输出的大小 比 输入小
那么就需要很多层的计算才能得到较小输出,为解决这一问题,采取如下措施:
高度3,宽度2 的步幅(行/列的滑动步长)
如果给定 高度Sh 和 宽度Sw 的步幅,则输出形状将为:
代码实现:
高度和宽度的步幅设置为 2
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
此时输入为 8*8,输出为 4*4
多输入多输出通道
卷积的超参数:输出的通道数
例如,彩色图像都有RGB三个通道,红绿蓝,这时就是多输入
多个输入通道
每个通道都有一个独立的二维卷积核 (长宽) ,所有通道卷积结果相加 得到一个输出结果
多个输出通道
可以有多个3维卷积核 (长 宽 通道) ,每个核对应生成一个输出通道
此时,核变成4维,输出变成3维
其实这里也就是说,我们可以用不同的3维卷积核,实现不同的目标,对应不同的通道
多输出的特定:每个输出可以识别特定模式
多输入的特定:识别并组合
1 * 1卷积
作用:通道融合
输入通道为3,长宽为3,