
目录
1、认识ResNet:引用来源:https://www.sohu.com/a/157818653_390227
第五章 卷积神经网络
几个具有代表性的深度卷积神经网络的设计思路:
1、最早提出的AlexNet
2、后来使用重复元素的网络VGG
3、网络中的网络NiN
4、含并行连结的网络GoogleNet
5、残差网络ResNet
6、稠密连接网络DenseNet
本章中:批量归一化和残差网络为训练和设计深度模型提供了两类重要思路。
一、二维卷积层
1、二维互相关运算
卷积层得名于卷积(convolution)运算,但是我们通常在卷积层中使用更加直观的互相关运算。


2、二维卷积层
二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。卷积层的模型参数包括了卷积核和标量偏差。在训练模型的时候,通常我们先对卷积核随机初始化,然后不断迭代卷积核和偏差。
自定义一个二维卷积层:
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super(Conv2D, self).__init__()
self.weight = nn.Parameter(torch.randn(kernel_size))
self.bias = nn.Parameter(torch.randn(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
3、图像中物体边缘检测
卷积层的一个简单应用:检测图像中物体的边缘,即找到像素变化的位置。
图像为0与1渐变成像,卷积核构造为高1宽2的卷积核K,这样横向相邻元素相同输出为0,否则非0。

由此可以看出卷积层通过重复使用卷积核有效地表征局部空间。
4、通过数据学习核数组
通过上面的例子中的数据,我们反推卷积核数组K。
首先我们先构造一个卷积层,其卷积核将被初始化成随机数组。接下来在每一次迭代中,我们使用平方误差来比较Y和卷积层的输出,然后计算梯度来更新权重。

学到的卷积核的权重参数与我们之前定义的核数组K较接近,而偏置参数接近0。自动初始化。无需我们再次初始化。
5、互相关运算和卷积运算
卷积运算与互相关运算类似。为了得到卷积运算的输出,我们只需将核数组左右翻转并上下翻转,再与输入数组做互相关运算。卷积层为何能使用互相关运算替代卷积运算。
其实,在深度学习中核数组都是学出来的:卷积层无论使用互相关运算或卷积运算都不影响模型预测时的输出。
6、特征图和感受野
二维卷积层输出的二维数组可以看作是输入在空间维度(宽和高)上某一级的表征,也叫特征图(feature map)。影响特征图中元素x的前向计算的所有可能输入区域(可能大于输入的实际尺寸)叫做x的感受野(receptive field)。

以图5.1为例,输入中阴影部分的四个元素是输出中阴影部分元素的感受野。我们将图5.1中形状为2×2的输出记为Y,并考虑一个更深的卷积神经网络:将Y与另一个形状为2×2的核数组做互相关运算,输出单个元素z。那么,z在Y上的感受野包括Y的全部四个元素,在输入上的感受野包括其中全部9个元素。由此可见:
可以通过更深的卷积神经网络使特征图中单个元素的感受野变得更加广阔,从而捕捉输入上更大尺寸的特征。
随着层数的叠加,每个神经元的感受野,也逐渐增大,其代表的元素也逐渐增加。过滤掉的信息也逐渐增加。(后层的神经元可以逐渐代表原来越多的前面的神经元。等级制度。)
小结
- 二维卷积层的核心计算是二维互相关运算。在最简单的形式下,它对二维输入数据和卷积核做互相关运算然后加上偏差。
- 我们可以设计卷积核来检测图像中的边缘。
- 我们可以通过数据来学习卷积核。
二、填充与步幅
卷积层的输出-特征图的形状由输入形状和卷积核窗口形状决定。( Input.shape[0] - kernel.shape[0] +1, Input.shape[1] - kernel.shape[1] +1 )。同时填充(padding)和步幅(step)也会改变输出的特征图的形状。
1、填充Padding
当卷积核的高和宽不同时,我们也可以通过设置高和宽上不同的填充数使输出和输入具有相同的高和宽。
2、步幅Stride
步幅可以减小输出的高和宽。
三、多输入通道与多输出通道
1、多输入通道:
当输入数据含多个通道时,我们需要构造一个输入通道数与输入数据的通道数相同的卷积核,从而能够与含多通道的输入数据做互相关运算。假设输入数据的通道数为,那么卷积核的输入通道数同样为
。设卷积核窗口形状为
。
当=1时,我们知道卷积核只包含一个形状为kh×kwk_h\times k_wkh×kw的二维数组。
当>1时,我们将会为每个输入通道各分配一个形状为
的核数组。把这
个数组在输入通道维上连结,即得到一个形状为
的卷积核。由于输入和卷积核各有
个通道,我们可以在各个通道上对输入的二维数组和卷积核的二维核数组做互相关运算,再将这
个互相关运算的二维输出按通道相加,得到一个二维数组。
(多维通道进卷积,最后还是变成了一个二维数组。多通道经过一个卷积核数组各个层要加和,所以最后的输出通道数,取决于卷积核的个数。)
2、多输出通道:
当输入通道有多个时,因为我们对各个通道的结果做了累加,所以不论输入通道数是多少,输出通道数总是为1。
设卷积核输入通道数和输出通道数分别为和
,高和宽分别为
和
。如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为
的核数组。(创建多个卷积核)。将它们在输出通道维上连结,卷积核的形状即
。在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输入数组计算而来。
3、1×1卷积层:
1×1卷积的主要计算发生在通道维上。我们可以通过调整网络层之间的通道数来控制模型复杂度。由卷积核的组数,控制输出的维度。卷积核的维度与输入维度一致,但是输出由卷积核的组数决定。输入维度为一组。

输入输出的尺寸是一样的,但是通道数不同了,输入通道数是3(如果是feature map的话甚至是几百个),输出通道数取决于卷积核的组数。完美的实现了,通道数的压缩,参数的消减。
假设我们将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么1 × 1卷积层的作用与全连接层等价。
始终牢记一点:输出特征图feature map通道数的通道数,取决于上一层卷积层的卷积核组数。
输出通道数取决于卷积核的组数。
四、池化层Pooling
1、最大池化和平均池化
回忆一下,在5.1节(二维卷积层)里介绍的图像物体边缘检测应用中,我们构造卷积核从而精确地找到了像素变化的位置。设任意二维数组X的i行j列的元素为X[i, j]。如果我们构造的卷积核输出Y[i, j]=1,那么说明输入中X[i, j]和X[i, j+1]数值不一样。这可能意味着物体边缘通过这两个元素之间。但实际图像里,我们感兴趣的物体不会总出现在固定位置:
即使我们连续拍摄同一个物体也极有可能出现像素位置上的偏移。这会导致同一个边缘对应的输出可能出现在卷积输出
Y中的不同位置,进而对后面的模式识别造成不便。
在本节中我们介绍池化(pooling)层,它的提出是为了缓解卷积层对位置的过度敏感性。
池化的意义在于降低卷积层对像素级位置信息的过度敏感性。
再次回到本节开始提到的物体边缘检测的例子。现在我们将卷积层的输出作为2×2最大池化的输入。设该卷积层输入是X、池化层输出为Y。无论是X[i, j]和X[i, j+1]值不同,还是X[i, j+1]和X[i, j+2]不同,池化层输出均有Y[i, j]=1。也就是说,使用2×2最大池化层时,只要卷积层识别的模式在高和宽上移动不超过一个元素,我们依然可以将它检测出来。从而降低了有像素级位置信息干扰。
2、填充和步幅
池化窗口的移动也可以采用填充和步幅策略。来改变输出形状。
3、多通道
在处理多通道输入数据时,池化层对每个输入通道分别池化,而不是像卷积层那样将各通道的输入按通道相加。这意味着池化层的输出通道数与输入通道数相等。
五、卷积神经网络(LeNet)
在3.9节(多层感知机的从零开始实现)里我们构造了一个含单隐藏层的多层感知机模型来对Fashion-MNIST数据集中的图像进行分类。每张图像高和宽均是28像素。我们将图像中的像素逐行展开,得到长度为784的向量,并输入进全连接层中。然而,这种分类方法有一定的局限性。
- 图像在同一列邻近的像素在这个向量中可能相距较远。它们构成的模式可能难以被模型识别。
- 对于大尺寸的输入图像,使用全连接层容易造成模型过大。假设输入是高和宽均为1000像素的彩色照片(含3个通道)。即使全连接层输出个数仍是256,该层权重参数的形状是3,000,000×256:它占用了大约3 GB的内存或显存。这带来过复杂的模型和过高的存储开销。
卷积神经网络解决了这两个问题:
- 卷积层保留输入形状,使图像的像素在高和宽两个方向上的相关性均可能被有效识别;
- 卷积层通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免参数尺寸过大。参数减小。
卷积神经网络就是含卷积层的网络。LeNet展示了通过梯度下降训练卷积神经网络可以达到手写数字识别在当时最先进的结果。这个奠基性的工作第一次将卷积神经网络推上舞台,为世人所知。
1、LeNet模型
LeNet分为卷积层块和全连接层块两个部分。
卷积层块里的基本单位是卷积层后接最大池化层:卷积层用来识别图像里的空间模式,如线条和物体局部,之后的最大池化层则用来降低卷积层对位置的敏感性。卷积层块由两个这样的基本单位重复堆叠构成。在卷积层块中,每个卷积层都使用5 × 5的窗口,并在输出上使用sigmoid激活函数。第一个卷积层输出通道数为6,第二个卷积层输出通道数则增加到16。这是因为第二个卷积层比第一个卷积层的输入的高和宽要小,所以增加输出通道使两个卷积层的参数尺寸类似(保留更多信息)。卷积层块的两个最大池化层的窗口形状均为2×2,且步幅(Stride)为2。由于池化窗口与步幅形状相同,池化窗口在输入上每次滑动所覆盖的区域互不重叠。
卷积层块的输出形状为(批量大小, 通道, 高, 宽)。当卷积层块的输出传入全连接层块时,全连接层块会将小批量中每个样本变平(flatten)。也就是说,全连接层的输入形状将变成二维,其中第一维是小批量中的样本,第二维是每个样本变平后的向量表示,且向量长度为通道、高和宽的乘积。全连接层块含3个全连接层。它们的输出个数分别是120、84和10,其中10为输出的类别个数。

下面我们通过Sequential类来实现LeNet模型。

Flatten之后,输出为全连接的输入,为通道数乘以核维度,通道 × 核高 × 和宽。
可以看到,在卷积层块中输入的高和宽在逐层减小。卷积层由于使用高和宽均为5的卷积核,从而将高和宽分别减小4,而池化层则将高和宽减半,但通道数则从1增加到16。全连接层则逐层减少输出个数,直到变成图像的类别数10。
2、获取数据和训练模型
仍然使用Fashion-MNIST作为训练集。
import time
import torch
from torch import nn, optim
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
nn.Sigmoid(),
nn.MaxPool2d(2, 2), # kernel_size, stride
nn.Conv2d(6, 16, 5),
nn.Sigmoid(),
nn.MaxPool2d(2, 2)
)
self.fc = nn.Sequential(
nn.Linear(16*4*4, 120),
nn.Sigmoid(),
nn.Linear(120, 84),
nn.Sigmoid(),
nn.Linear(84, 10)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
import sys
sys.path.append("./Dive-into-DL-PyTorch/code/")
import d2lzh_pytorch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
# 本函数已保存在d2lzh_pytorch包中方便以后使用。该函数将被逐步改进。
def evaluate_accuracy(data_iter, net, device=None):
if device is None and isinstance(net, torch.nn.Module):
# 如果没指定device就使用net的device
device = list(net.parameters())[0].device
acc_sum, n = 0.0, 0
with torch.no_grad():
for X, y in data_iter:
if isinstance(net, torch.nn.Module):
net.eval() # 评估模式, 这会关闭dropout
acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
net.train() # 改回训练模式
else: # 自定义的模型, 3.13节之后不会用到, 不考虑GPU
if('is_training' in net.__code__.co_varnames): # 如果有is_training这个参数
# 将is_training设置成False
acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
else:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
# 本函数已保存在d2lzh_pytorch包中方便以后使用
def train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):
net = net.to(device)
print("training on ", device)
loss = torch.nn.CrossEntropyLoss()
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n, batch_count, start = 0.0, 0.0, 0, 0, time.time()
for X, y in train_iter:
X = X.to(device)
y = y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
train_l_sum += l.cpu().item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
n += y.shape[0]
batch_count += 1
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
% (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
小结:
- 卷积神经网络就是含卷积层的网络。
- LeNet交替使用卷积层和最大池化层后接全连接层来进行图像分类。
补充:第三个卷积层C3层存在一种方式是:前6个特征图以S2(下采样层)中3个相邻的特征图子集为输入。接下来6个特征图以S2中4个相邻特征子图为输入。然后3个以不相邻的4个特征子集为输入。最后一个将S2中所有特征图为输入。6 + 6 + 3 +1 =16。所以16个通道是这么计算来的。
六、深度卷积神经网络(Alexnet)

AlexNet与LeNet的设计理念非常相似,但也有显著的区别。
第一:更深的结构:与相对较小的LeNet相比,AlexNet包含8层变换,其中有5层卷积核和2层全连接隐藏层,以及1个全连接输出层。
- AlexNet第一层中的卷积窗口形状是11×11。因为ImageNet中绝大多数图像的高和宽均比MNIST图像的高和宽大10倍以上,ImageNet图像的物体占用更多的像素,所以需要更大的卷积窗口来捕获物体

本文围绕卷积神经网络展开,介绍了二维卷积层、填充与步幅、多输入输出通道、池化层等基础概念。还阐述了LeNet、AlexNet、VGG、NiN、GoogLeNet等经典模型,以及批量归一化、残差网络和稠密连接网络等训练和设计深度模型的重要思路。
最低0.47元/天 解锁文章
475





