全连接层后,卷积层出现的意义?
一个足够充分的照片数据集,输入,全连接层参数,GPU成本,训练时间是巨大的。
(convolutional neural networks,CNN)是机器学习利用自然图像中一些已知结构的创造性方法,需要更少的参数,在处理图像和其他类型的结构化数据上各类成本,效果,可行性普遍优于全连接层。
卷积层做了什么?
将输入和核矩阵进行互相关运算,加上偏移后得到输出。
图片中找模式的原则
对全连接层使用如上原则得到卷积层。
(详细待补充)
二维卷积层
:
图中,h:高、w:宽、输入大小 n = 3。
:
图中,卷积核大小 k = 2,超参数。
:
图中 (3-2 +1)*(3-2 +1) = 4 ,计算的是 Y 的形状。
卷积效果举例
(1)实现互相关运算
卷积运算 ≠ 互相关运算
import torch
from torch import nn
from d2l import torch as d2l
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
验证运算结果
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)
result:
tensor([[19., 25.],
[37., 43.]])
实现二维卷积层
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(sekf, x):
return corr2d(x,self.weight) + self.bias
(2)学习由X生成Y卷积核
#一个输入通道、一个输出通道,不使用偏置
conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias =False)
X = X.reshape((1,1,6,8))
Y = Y.reshape((1,1,6,7))
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
if(i + 1)% 2 == 0:
print(f'batch{i + 1}, loss {l.sum():.3f}')
所学卷积核权重
conv2d.weight.data.reshape((1,2))
tensor([[ 1.0084, -0.9816]])
利用卷积层检测 图像中的不同边缘
输入
X = torch.ones((6,8))
X[:, 2:6] =0
X
tensor([[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.]])
核矩阵
K = torch.tensor([[1,-1]])
输出
Y = corr2d(X,K)
Y
tensor([[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.],
[ 0., 1., 0., 0., 0., -1., 0.]])
只能检测垂直边缘
Y = corr2d(X.t(),K)
Y
tensor([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]])
将核矩阵一起转置
Y = corr2d(X.t(),K.t())
Y
水平边缘检测可行。
tensor([[ 0., 0., 0., 0., 0., 0.],
[ 1., 1., 1., 1., 1., 1.],
[ 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0.],
[-1., -1., -1., -1., -1., -1.],
[ 0., 0., 0., 0., 0., 0.]])
填充操作
更大的卷积核可以更快地减小输出大小。
如果不想结果太小,也可以通过填充实现输出更大尺寸的X,实现控制输出形状的减少量。
填充
行
列,输出形状:
通常取
奇数:上下两侧填充
偶数:上侧填充
下侧填充
步幅
步幅指行/列滑动步长。
设置步幅的效果? 成倍减少输出形状。
下图为高3宽2步幅示意图:
(图片来自 《DIVE INTO DEEP LEARNING》)
给定步幅,高度
宽度
,输出形状:
如果输入高度宽度可被步幅整除,形状为:
填充、步幅是卷积层超参数
所有侧边填充一个像素
import torch
from torch import nn
def comp_conv2d(conv2d, X):
X = X.reshape((1,1) + X.shape)
Y =conv2d(X)
return Y.reshape(Y.shape[2:])
conv2d = nn.Conv2d(1,1,kernel_size=3,padding=1)
X= torch.rand(size=(8,8))
comp_conv2d(conv2d,X).shape
填充相同高度宽度
import torch
from torch import nn
def comp_conv2d(conv2d, X):
X = X.reshape((1,1) + X.shape)
#执行一次卷积操作
Y =conv2d(X)
return Y.reshape(Y.shape[2:])
#padding=1 在输入数据的边界填充一行和一列的零值
conv2d = nn.Conv2d(1,1,kernel_size=3,padding=1)
X= torch.rand(size=(8,8))
comp_conv2d(conv2d,X).shape
torch.Size([8, 8])
不同高度宽度
conv2d = nn.Conv2d(1,1,kernel_size=(5,3),padding=(2,1))
comp_conv2d(conv2d,X).shape
torch.Size([8, 8])
增设步幅,其宽高为2
conv2d = nn.Conv2d(1,1,kernel_size=3,padding=1,stride =2)
comp_conv2d(conv2d,X).shape
torch.Size([4, 4])
成倍缩小。
彩色RGB图片,是三通道输入数据。
每个通道都有一个卷积核,结果为各通道卷积的和。
1×1卷积层
不识别空间,用途是融合通道。
二维卷积层(多通道)
:
输入通道数、h高、w宽、输入大小 n。
:
输出通道数、卷积核大小 k。其中,
是卷积层的超参数。
:
一共有
个卷积核 每个卷积核都有一个偏差
:
大小与 填充p、核大小k有关。
怎么理解每个输出通道有独立的三维卷积核?
具有三个维度:高度、宽度和通道数。
(1)实现多通道互相关运算
定义多通道输入
import torch
from d2l import torch as d2l
#先遍历“X”和“K”的第0个维度(通道维度),再把它们加在一起
def corr2d_multi_in(X,K):
return sum(d2l.corr2d(x,k) for x,k in zip(X,K))
多通道第零维度的几何意义?
图中X第零维度有两组,几何上就是通道数。 X :
(tensor([[[0., 1., 2.],
[3., 4., 5.],
[6., 7., 8.]],
[[1., 2., 3.],
[4., 5., 6.],
[7., 8., 9.]]]),
定义X,K
# X 6*3
X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
#K 4*2
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
X,K,corr2d_multi_in(X, K)
(tensor([[[0., 1., 2.],
[3., 4., 5.],
[6., 7., 8.]],
[[1., 2., 3.],
[4., 5., 6.],
[7., 8., 9.]]]),
tensor([[[0., 1.],
[2., 3.]],
[[1., 2.],
[3., 4.]]]),
tensor([[ 56., 72.],
[104., 120.]]))
定义多通道输出
def corr2d_multi_in_out(X,K):
# 使用 PyTorch 的 torch.stack 函数,它将一组张量沿着指定的维度(这里是维度0)进行堆叠,生成一个新的张量。
return torch.stack([corr2d_multi_in(X,k) for k in K],0)
# K+1 K的每个值加一,K规模扩成了原来3倍。
K = torch.stack((K,K+1,K+2),0)
K,K.shape
(tensor([[[[0., 1.],
[2., 3.]],
[[1., 2.],
[3., 4.]]],
[[[1., 2.],
[3., 4.]],
[[2., 3.],
[4., 5.]]],
[[[2., 3.],
[4., 5.]],
[[3., 4.],
[5., 6.]]]]),
torch.Size([3, 2, 2, 2]))
返回值那一行为什么用小k对应X,多通道输入那里不是用的大K对应X,然后第零维度展开,抽出x,k对应计算吗?
K扩了三倍,所以用小k规模和原来的K相当,因此X 对应扩充前的K,扩充后的小k。
corr2d_multi_in_out(X,K)
tensor([[[ 56., 72.],
[104., 120.]],
[[ 76., 100.],
[148., 172.]],
[[ 96., 128.],
[192., 224.]]])
(2)实现1*1卷积核
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))
X = torch.normal(0, 1, (3, 3, 3))
K = torch.normal(0, 1, (2, 3, 1, 1))
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
# 进行断言,验证使用 1x1 卷积操作得到的输出 Y1 与多通道卷积操作得到的输出 Y2 是否非常接近,以确保两种方法的结果一致
assert float(torch.abs(Y1 - Y2).sum()) < 1e-6
最大池化,每个窗口最强的模式信号,它针对卷积对空间位置敏感(边缘检测案例),允许输入有一定的偏移。
也有平均池化层。
特点
池化层向前传播
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
验证最大池化层
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
pool2d(X, (2, 2))
tensor([[4., 5.],
[7., 8.]])
验证平均池化层
pool2d(X, (2,2), 'avg')
tensor([[2., 3.],
[5., 6.]])
使用内置的最大池化层
X = torch.arange(16, dtype=torch.float32).reshape((1, 1, 4, 4))
X
tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]]]])
pool2d = nn.MaxPool2d(3, padding=1, stride=2)#等价于nn.MaxPool2d((3,3), padding=(1,1), stride=(2,2))
pool2d(X)
tensor([[[[ 5., 7.],
[13., 15.]]]])
pool2d = nn.MaxPool2d((2, 3), stride=(2, 3), padding=(0, 1))
pool2d(X)
tensor([[[[ 5., 7.],
[13., 15.]]]])
验证多通道
汇聚层在每个输入通道上单独运算,输出通道数与输入通道数相同。
# 将两个张量 X, X + 1 进行拼接
X = torch.cat((X, X + 1), 1)
X
tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]],
[[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[13., 14., 15., 16.]]]])
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)
tensor([[[[ 5., 7.],
[13., 15.]],
[[ 6., 8.],
[14., 16.]]]])