卷积神经网络是含有卷积层的神经网络。
卷积核:可以学习的权重。
PS:这里的卷积运算与数学及信号中的不同,神经网络的卷积运算是数学上的互相关运算。
图像的平移不变性使我们可以以相同的方式处理局部图像。
局部性意味着计算相应的隐藏表示只需一小部分局部图像像素。
在图像处理中,卷积层通常比全连接层需要更少的参数。
卷积神经网络(CNN)是一类特殊的神经网络,它可以包含多个卷积层。
多个输入和输出通道使模型在每个空间位置可以获取图像的多方面特征。
输出大小等于输入大小 nh×nw 减去卷积核大小 kh×kw ,即:
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
卷积层的参数包含了卷积核和标量偏差,通常先对卷积核做随机初始化,然后不断更新卷积核和偏差
下面基于corr2d函数来实现一个自定义的二维卷积层。在构造函数__init__里我们声明weight和bias两个参数,前进计算则是直接调用corr2d再加上偏差。
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
X
OUTPUT:
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.]])
构造一个高和宽为1,2的卷积核。当它做互相关运算时,如果横向相邻元素相同,输出为0;否则输出为非0
K = torch.tensor([[1.0, -1.0]])
OUTPUT:
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.]])
现在我们将输入的二维图像转置,再进行如上的互相关运算。 其输出如下,之前检测到的垂直边缘消失了。 不出所料,这个卷积核K只可以检测垂直边缘,无法检测水平边缘。
corr2d(X.t(), K)
OUTPUT:
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个输出通道和形状为(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))
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}')
OUTPUT:
batch 2, loss 3.196
batch 4, loss 0.582
batch 6, loss 0.116
batch 8, loss 0.027
batch 10, loss 0.008
输出的卷积层有时被称为 特征映射 (Feature Map),因为它可以被视为一个输入映射到下一层的空间维度的转换器。 在CNN中,对于某一层的任意元素 x ,其 感受野 (Receptive Field)是指在前向传播期间可能影响 x 计算的所有元素(来自所有先前层)。
填充:可以增加输出的高与宽,使得输出与输入具有相同的高和宽
步幅:可以减少输出的高和宽
默认条件下,填充为0,步幅为1
用 ci 和 co 分别表示输入和输出通道的数目,并让 kh 和 kw 为卷积核的高度和宽度。为了获得多个通道的输出,我们可以为每个输出通道创建一个形状为 ci×kh×kw 的卷积核张量,这样卷积核的形状是 co×ci×kh×kw 。在互相关运算中,每个输出通道先获取所有输入通道,再以对应该输出通道的卷积核计算出结果。
池化层:缓解卷积层对位置的过度敏感性
一般分为最大池化层和平均池化层
池化层同样可以指定填充和步幅
池化层的输出通道和输入通道相同