CNN是一类强大的、为处理图像数据而设计的神经网络
- 图像的平移不变性使我们以相同的方始处理局部图像,而不在乎它的位置
- 局部性意味着计算相应的隐藏表示只需一小部分局部图像像素
- 在图像处理中,卷积层通常比全连接层需要更少的参数,但仍是高效的模型
- CNN是一类特殊的神经网络,它可以包含多个卷积层
- 多个输入和输出通道使模型在每个空间位置可以获得图像的多方面特征
图像卷积
互相关运算:输出大小=输入大小-卷积核大小
计算二维互相关运算
import torch
from torch import nn
from d2l import torch as d2l
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]])
#计算二维互相关运算
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
print(corr2d(X,K))
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(self, x):
return corr2d(x, self.weight) + self.bias
图像中目标的边缘检测
#构造一个6*8像素的黑白图像,0黑色,1白色
X= torch.ones((6,8))
X[:,2:6] = 0
print(X)
#卷积核
K = torch.tensor([[1.0,-1.0]])
Y = corr2d(X,K)
print(Y)
#X的转置
print(corr2d(X.t(),K))
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.]])
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.]])
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.]])
学习卷积核
#构造一个二维卷积层,它具有一个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(in_channels=1,out_channels=1,kernel_size=(1,2),bias=False)
#此二维卷积层使用四维输入输出格式(批量大小,通道,高度,宽度)
X = X.reshape(1,1,6,8)
Y = Y.reshape(1,1,6,7)
lr = 3e-2
for i in range(100):
Y_hat = conv2d(X)
l = (Y_hat-Y)**2
conv2d.zero_grad()
l.sum().backward()
#迭代卷积核
conv2d.weight.data[:] -= lr*conv2d.weight.grad
if(i+1)%5==0:
print('epoch:{} loss:{}'.format(i+1,l.sum()))
print(conv2d.weight.data.reshape(1,2))
epoch:5 loss:2.06238055229187
epoch:10 loss:0.17614521086215973
epoch:15 loss:0.018391011282801628
epoch:20 loss:0.0019686929881572723
epoch:25 loss:0.0002113177761202678
epoch:30 loss:2.2689247998641804e-05
epoch:35 loss:2.4366997877223184e-06
epoch:40 loss:2.616923211462563e-07
epoch:45 loss:2.8010937569433736e-08
epoch:50 loss:2.9932962775092165e-09
epoch:55 loss:3.1597124916515895e-10
epoch:60 loss:3.33173488797911e-11
epoch:65 loss:3.410605131648481e-12
epoch:70 loss:1.9610979506978765e-12
epoch:75 loss:1.9610979506978765e-12
epoch:80 loss:1.9610979506978765e-12
epoch:85 loss:1.9610979506978765e-12
epoch:90 loss:1.9610979506978765e-12
epoch:95 loss:1.9610979506978765e-12
epoch:100 loss:1.9610979506978765e-12
tensor([[ 1.0000, -1.0000]])
- 二维卷积层的核心运算是二位互相关运算。最简单的形式是对二位输入数据和卷积核执行互相关操作,然后添加一个偏置
- 可以设计一个卷积核来检测图像的边缘
- 可以从数据中学习卷积核的参数
- 学习卷积核时,无论严格卷积运算或互相关运算,卷积层的输出不会受太大的影响
- 当需要检测输入特征中更广区域时,可以构建一个更深的卷积网络
填充与步幅
填充(padding):如果添加Ph行填充(大约一半在顶部,一半在底部)和Pw列填充(大约一半在左侧,一半在右侧),输出形状为:
通常,假设Kh是奇数,将在高度的两侧填充Ph/2行。如果Kh是偶数,则一种可能性实在输入顶部填充
,在底部填充
,同理,填充左右两侧方式相同。
在CNN中卷积核的高度和宽度通常为奇数。选择奇数保持空间维度的同时可以在顶部和底部填充相同数量的行,在左右两侧添加相同数量的列
在所有侧边填充一个像素
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
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])
步幅(stride):当垂直步幅为
、水平步幅为
时,输出形状为:
下面将高度和宽度的步幅设置为2,从而将输入高度和宽度减半
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
torch.Size([4, 4])
一个稍微复杂的例子:
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape
torch.Size([2, 2])
总结:
- 填充可以增加输出的高度和宽度
- 步幅可以减小输出的高度和宽度
多输入多输出通道
多输入通道运算
import torch
from d2l import torch as d2l
def corr2d_multi_in(X, K):
return sum(d2l.corr2d(x, k) for x, k in zip(X, K))
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 = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
corr2d_multi_in(X, K)
tensor([[ 56., 72.],
[104., 120.]])
计算多输出通道
def corr2d_multi_in_out(X, K):
return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
K = torch.stack((K, K + 1, K + 2), 0)
K.shape
torch.Size([3, 2, 2, 2])
corr2d_multi_in_out(X, K)
tensor([[[ 56., 72.],
[104., 120.]],
[[ 76., 100.],
[148., 172.]],
[[ 96., 128.],
[192., 224.]]])
总结:
- 多输入多输出通道可以用来扩展卷积层的模型
- 当以每像素为基础应用时,1*1卷积层相当于全连接层
- 1*1卷积层模型通常用于调整网络层的通道数量和控制,模型复杂度
汇聚层
- 对于给定输入元素,最大汇聚层会输出该窗口内的最大值,平均汇聚层会输出该窗口内的平均值
- 汇聚层的优点之一是减轻卷积层对位置的过度敏感
- 可以指定汇聚层的填充和步幅
- 使用最大汇聚层以及大于1的步幅,可以减少空间维度
- 汇聚层的输入通道数与输出通道数相同