卷积神经网络convolutional neural networkCNN)是⼀类强⼤的、为处理图像数据⽽设计的神经⽹络。基于卷积神经⽹络架构的模型在计算机视觉领域中已经占主导地位,当今⼏乎所有的图像识别、⽬标检测或语义分割相关的学术竞赛和商业应⽤都以这种⽅法为基础。

本文将从原理出发对CNN进行简单的梳理,然后提供 PyTorch 版本的简单实现。

为什么是卷积

对于图像识别任务,一个合理的假设是:⽆论采用哪种⽅法找到这个物体,这个方法本身都应该和物体的位置⽆关——“沃尔多的样⼦并不取决于他潜藏的地⽅”。

更加学术的描述是这样的,我们希望图像识别的方法具有以下两种性质:

  1. 平移不变性(translation invariance):不管检测对象出现在图像中的哪个位置,神经⽹络的前⾯⼏层应该对相同的图像区域具有相似的反应,即为“平移不变性”。
  2. 局部性(locality):神经⽹络的前⾯⼏层应该只探索输⼊图像中的局部区域,⽽不过度在意图像中相隔较远区域的关系,这就是“局部性”原则。可以在最后聚合这些局部特征,以在整个图像级别进⾏预测。

二维卷积,正是符合以上两种性质的不二之选!

从 FC 到 Conv2d

思考卷积的由来时,有这样一种思路:从全连接层的原始形式做改进,最后逐步修正为卷积操作。

对于传统的全连接层来说,拓展到二维上,输入数据XX 经过全连接层得到输出数据(隐藏表示)HH ,它们的尺寸可以不同。比如HH 位置在(i,j)(i,j) 的元素[H]i,j[H]_{i,j} ,它的值由XX 的所有元素[X]k,l[X]_{k,l}通过加权[W]i,j,k,l[W]_{i,j,k,l} 和偏置[U]i,j[U]_{i,j} 得到。 即:

[H]i,j=kl[W]i,j,k,l[X]k,l+[U]i,j[H]_{i,j}=\sum_k\sum_l [W]_{i,j,k,l}[X]_{k,l}+[U]_{i,j}

相当于用和XX 同样大小的矩阵与之对应元素相乘,最后全部累加起来再加上偏置,得到隐藏表示HH(i,j)(i,j) 处的元素。


上述公式还可以写成:

[H]i,j=ab[V]i,j,a,b[X]i+a,j+b+[U]i,j[H]_{i,j}=\sum_a\sum_b [V]_{i,j,a,b}[X]_{i+a,j+b}+[U]_{i,j}

其中,[V]i,j,a,b=[W]i,j,i+a,j+b,k=i+a,  l=j+b[V]_{i,j,a,b}=[W]_{i,j,i+a,j+b},\quad k=i+a,\;l=j+b.

相当于对WW 内的元素进行重排得到VVVV 的索引是以(i,j)(i,j) 为中心的。这使得权重矩阵、输入和输出都聚焦于(i,j)(i,j)

接下来考虑针对两种性质对全连接层做修改。

平移不变性

平移不变性意味着检测目标无论在XX 中进行怎样的平移,也应该只有隐藏表示HH 会发生平移现象(数值不变)。换言之,检测目标在不同的(i,j)(i,j) ,与之对应的权重和偏置应该不能有变化,这才能使针对检测目标的输出响应不变。即VVUU 不随(i,j)(i,j) 发生改变。
于是有:

[H]i,j=ab[V]a,b[X]i+a,j+b+u[H]_{i,j}=\sum_a\sum_b [V]_{a,b}[X]_{i+a,j+b}+u

局部性

为了收集⽤来训练参数[H]i,j[H]_{i,j} 的相关信息,我们不应偏离到距(i,j)(i, j) 很远的地⽅。这意味着在a>|a| > ∆b>|b| > ∆ 的范围之外,我们置[V]a,b=0[V]_{a,b} = 0。相当于累加时只是小范围相乘。

[H]i,j=a=ΔΔb=ΔΔ[V]a,b[X]i+a,j+b+u[H]_{i,j}=\sum_{a=-\Delta}^{\Delta}\sum_{b=-\Delta}^{\Delta} [V]_{a,b}[X]_{i+a,j+b}+u

而这个公式正好就是二维离散“卷积”操作(如图所示)。换言之,因为二维卷积具有平移不变性和局部性这种优良特性,使得参数规模大幅度下降的同时也能很好地处理图像任务,所以它才如此流行。

计算示意图

这两种特性也叫卷积神经网络的 局部连接权重共享

互相关运算

事实上,上述得到的公式其实二维数据XX模板VV 之间的互相关运算。(模板也就是前面提到的权重矩阵;数字图像处理中也叫模板算子、卷积核、滤波器,对应于空间滤波的概念)

互相关运算描述的就是将卷积核与图像进行滑动窗口的对应相乘并求和得到输出元素。
这与二维卷积有着一些区别,根据二维卷积的公式,真正的卷积操作在数值上应该等同于对XX 进行转置(旋转180度)然后再和卷积核进行互相关运算。

至于为什么我们严格执行卷积操作,那是因为在深度学习训练过程中,我们学习的参数就是模板元素的取值,因此⽆论这些层执行严格的卷积运算还是互相关运算,卷积层的输出都不会受到影响。

为了与深度学习⽂献中的标准术语保持⼀致,我们将继续把“互相关运算”称为卷积运算,尽管严格地说,它们略有不同。

本站图像处理相关跳转文章:

其他术语

通道 |Channel

在前面的描述中,我们忽略了图像⼀般包含三个通道/三种原⾊(红⾊、绿⾊和蓝⾊)。实际上,图像不是⼆维张量,⽽是⼀个由⾼度、宽度和颜⾊组成的三维张量,⽐如包含1024×1024×31024 × 1024 × 3 个像素。前两个轴与像素的空间位置有关,⽽第三个轴可以看作每个像素的多维表示。

对于每⼀个空间位置,我们想要采用⼀组而不是⼀个隐藏表示。这样⼀组隐藏表示可以想象成⼀些互相堆叠的⼆维⽹格。因此,我们可以把隐藏表示想象为⼀系列具有⼆维张量的通道(channel)。这些通道有时也被称为特征映射(feature maps)(也有译作:特征图),因为每个通道都向后续层提供⼀组空间化的学习特征。

因此,此前的公式中的卷积核应该针对RGB三色通道扩充维度,使得卷积核来到了三维

多输入通道的卷积示例

此外,特征图的张数(输出通道数)也可以自定义扩张,针对每一个特征图(输出)学习不同的三维卷积核(针对多输入),最终总体来说卷积核也就扩张到了四维(长、宽、输入通道、输出通道)。

多输入多输出通道的卷积示例

感受野 |Receptive field

在卷积神经⽹络中,对于某⼀层的任意元素xx,其 感受野(receptive field) 是指在前向传播期间可能影响xx 计算的所有元素(来⾃所有先前层)。

填充 |Padding

假设输⼊形状为nh×nwn_h × n_w ,卷积核形状为kh×kwk_h × k_w ,那么输出形状将是(nhkh+1)×(nwkw+1)(n_h − k_h + 1) × (n_w − k_w + 1)
因此,卷积的输出形状取决于输⼊形状和卷积核的形状。在应⽤多层卷积时,我们因此常常丢失边缘像素

尽管我们通常使⽤较小的卷积核,因此对于任何单个卷积,我们可能只会丢失⼏个像素。但随着我们应⽤许多连续卷积层,累积丢失的像素数就多了。

解决这个问题的简单⽅法即为填充(padding):在输⼊图像的边界填充元素(有零填充、镜像填充、复制填充等)

无填充示意图有填充示意图
无填充示意图有填充示意图

步幅 |Stride

在计算互相关时,卷积窗⼝从输⼊张量的左上⻆开始,向下、向右滑动。在前⾯的例⼦中,我们默认每次滑动⼀个元素。

但是,有时候为了⾼效计算或是缩减采样次数,卷积窗⼝可以跳过中间位置,每次滑动多个元素。我们将每次滑动元素的数量称为步幅(stride)

步幅示例

经过简单的数学推导,我们还可以得出图像经过卷积后的大小:

新宽度 = (原宽度 - 卷积核宽度 + 2*填充) / 步幅 + 1
新高度 = (原高度 - 卷积核高度 + 2*填充) / 步幅 + 1

池化层/汇聚层

池化pooling)操作一般在卷积层之后,对特征图进行处理。它具有双重⽬的:降低卷积层对位置的敏感性(保持并提取高阶特征),同时降低对空间降采样表⽰的敏感性(防止过拟合)。

实际上,池化层就是卷积核确定的一种卷积(互相关)操作,也是一种降采样操作。

默认情况下,深度学习框架中的步幅与池化窗⼝的大小相同
此外,在处理多通道输⼊数据时,池化层在每个输入通道上单独运算,⽽不是像卷积层⼀样在通道上对输⼊进⾏汇总。这意味着汇聚层的输出通道数与输⼊通道数相同。(实际上也是因为它只是做一种特征选择和降采样的工作)

常见的池化有 最大池化(Max Pooling)和平均池化(Avg Pooling)。

两种池化的示例

卷积神经网络 LeNet

LeNet 是最早发布的卷积神经⽹络之⼀,因其在计算机视觉任务中的⾼效性能⽽受到⼴泛关注。
这个模型是由 AT&T ⻉尔实验室的研究员Yann LeCun在1989年提出的(并以其命名),⽬的是识别图像中的⼿写数字。

当时,LeNet取得了与⽀持向量机性能相媲美的成果,成为监督学习的主流⽅法。

网络结构详细

LeNet

输入层|Input layer

输入层接收大小为 32×32 的手写数字图像,其中包括灰度值(0-255)。

在实际应用中,我们通常会对输入图像进行预处理,例如对像素值进行归一化,以加快训练速度和提高模型的准确性。

卷积层C1|Convolutional layer C1

卷积层C1包括6个卷积核,每个卷积核的大小为 5×5 ,步幅为1,填充为0。

因此,每个卷积核会产生一个大小为 28×28 的特征图(输出通道数为6)。

采样层S2|Subsampling layer S2

采样层S2采用最大池化(max-pooling)操作,每个窗口的大小为 2×2 ,步幅为2。

因此,每个池化操作会从4个相邻的特征图中选择最大值,产生一个大小为 14×14 的特征图(输出通道数为6)。这样可以减少特征图的大小,提高计算效率,并且对于轻微的位置变化可以保持一定的不变性。

卷积层C3|Convolutional layer C3

卷积层C3包括16个卷积核,每个卷积核的大小为 5×5 ,步幅为1,填充为0。

因此,每个卷积核会产生一个大小为 10×10 的特征图(输出通道数为16)。

采样层S4|Subsampling layer S4

采样层S4采用最大池化操作,每个窗口的大小为 2×2 ,步幅为2。

因此,每个池化操作会从4个相邻的特征图中选择最大值,产生一个大小为 5×5 的特征图(输出通道数为16)。

全连接层C5|Fully connected layer C5

C5将每个大小为 5×5 的特征图拉成一个长度为400的向量,并通过一个带有120个神经元的全连接层进行连接。

120是由LeNet-5的设计者根据实验得到的最佳值。

全连接层F6|Fully connected layer F6

全连接层F6将120个神经元连接到84个神经元。

输出层|Output layer

输出层由10个神经元组成,每个神经元对应0-9中的一个数字,并输出最终的分类结果。

PyTorch实现

引入必要依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#加载库
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.datasets as datasets
import torchvision.transforms as transforms

def try_gpu(i=0):

"""如果存在,则返回gpu(i),否则返回cpu()"""

if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')

定义 LeNet-5 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 定义LeNet-5模型
class LeNet5(nn.Module):
def __init__(self):
super(LeNet5, self).__init__()

self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, stride=1)
self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1)
self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
self.fc1 = nn.Linear(in_features=16 * 4 * 4, out_features=120)
self.fc2 = nn.Linear(in_features=120, out_features=84)
self.fc3 = nn.Linear(in_features=84, out_features=10)

def forward(self, x):
x = self.pool1(torch.relu(self.conv1(x)))
x = self.pool2(torch.relu(self.conv2(x)))
x = x.view(-1, 16 * 4 * 4)
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x)
return x

训练模型

在训练过程中,使用交叉熵损失函数计算输出层的误差,并通过反向传播算法更新卷积核和全连接层的权重参数。

当而,在实际应用中,通常会对LeNet-5进行一些改进,例如增加网络深度、增加卷积核数量、添加正则化等方法,以进一步提高模型的准确性和泛化能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 加载MNIST数据集
def load_mnist_data(batch_size = 64):
train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor())

# 定义数据加载器
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)

return train_loader,test_loader


def train_model(model, train_data, num_epochs, criterion, learn_rate, device = None):

# 参数初始化
def init_param(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
model.apply(init_param)

# 利用选定 device 加速
if not device:
device = next(iter(model.parameters())).device #指定使用和参数相同的设备
print('training on', device)
model.to(device)

# 设置优化器
optimizer = optim.SGD(model.parameters(),lr=learn_rate)

for epoch in range(num_epochs):
model.train()
for i, (X, y) in enumerate(train_data):
X, y = X.to(device), y.to(device)
optimizer.zero_grad() #梯度清零
loss = criterion(model(X), y)
loss.backward()
optimizer.step() #更新所有的参数

num_batches = len(train_data)
if (i+1) % (num_batches//5) == 0 or i == num_batches - 1:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, i+1, num_batches, loss.item()))


模型测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def test_model(model, test_data, device):

    model.eval()
    with torch.no_grad():
        correct = 0
        total = 0
        for X, y in test_data:
            X, y = X.to(device), y.to(device)
            outputs = model(X)
            _, predicted = torch.max(outputs.data, 1)
            total += y.size(0)
            correct += (predicted == y).sum().item()
           
        print('Test Accuracy: {:.2f}%'.format(100 * correct / total))


# 开始训练和测试

# 加载数据
train_data, test_data = load_mnist_data()

# 实例化模型
model = LeNet5()

# 损失函数
criterion = nn.CrossEntropyLoss()

device = try_gpu()

train_model(model, train_data, 5, criterion, 0.01, device)
test_model(model, test_data, device)

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
training on cuda:0

Epoch [1/5], Step [187/938], Loss: 2.1481
Epoch [1/5], Step [374/938], Loss: 0.6471
Epoch [1/5], Step [561/938], Loss: 0.5093
Epoch [1/5], Step [748/938], Loss: 0.5021
Epoch [1/5], Step [935/938], Loss: 0.3279
Epoch [1/5], Step [938/938], Loss: 0.1223
Epoch [2/5], Step [187/938], Loss: 0.3075
Epoch [2/5], Step [374/938], Loss: 0.3728
Epoch [2/5], Step [561/938], Loss: 0.3427
Epoch [2/5], Step [748/938], Loss: 0.2168
Epoch [2/5], Step [935/938], Loss: 0.2212
Epoch [2/5], Step [938/938], Loss: 0.1953
Epoch [3/5], Step [187/938], Loss: 0.2128
Epoch [3/5], Step [374/938], Loss: 0.1818
Epoch [3/5], Step [561/938], Loss: 0.2097
Epoch [3/5], Step [748/938], Loss: 0.2217
Epoch [3/5], Step [935/938], Loss: 0.1669
Epoch [3/5], Step [938/938], Loss: 0.1489
Epoch [4/5], Step [187/938], Loss: 0.3106
Epoch [4/5], Step [374/938], Loss: 0.3208
Epoch [4/5], Step [561/938], Loss: 0.1300
Epoch [4/5], Step [748/938], Loss: 0.2032
Epoch [4/5], Step [935/938], Loss: 0.1013
Epoch [4/5], Step [938/938], Loss: 0.1105
Epoch [5/5], Step [187/938], Loss: 0.0439
Epoch [5/5], Step [374/938], Loss: 0.2064
Epoch [5/5], Step [561/938], Loss: 0.0960
Epoch [5/5], Step [748/938], Loss: 0.1430
Epoch [5/5], Step [935/938], Loss: 0.1692
Epoch [5/5], Step [938/938], Loss: 0.2758

Test Accuracy: 94.98%

深度卷积神经网络 AlexNet

在计算机视觉中,直接将神经⽹络与其他机器学习⽅法进⾏⽐较也许不公平。

卷积神经⽹络的输⼊是由原始像素值或是经过简单预处理(例如居中、缩放)的像素值组成的。
但传统机器学习⽅法不会将原始像素作为输⼊,而是根据光学、⼏何学、其他知识以及偶然的发现进行预处理,通过标准的特征提取算法,如SIFT(尺度不变特征变换)和SURF(加速鲁棒特征)或其他⼿动调整的流⽔线来输⼊数据,最后将提取的特征送⼊传统分类器。(例如线性模型或其它核⽅法)

而部分研究⼈员想法则与众不同:他们认为特征本身应该被学习

于是,2012年,AlexNet横空出世。它⾸次证明了学习到的特征可以超越⼿⼯设计的特征。它⼀举打破了计算机视觉研究的现状。AlexNet使⽤了8层卷积神经⽹络,并以很⼤的优势赢得了2012年ImageNet图像识别挑战赛。

网络结构详细

AlexNet的网络结构

AlexNet 输入为RGB三通道的224 × 224 × 3大小的图像。
AlexNet 共包含5 个卷积层(包含3个池化)和 3 个全连接层。其中,每个卷积层都包含卷积核、偏置项、ReLU激活函数和局部响应归一化(LRN) 模块。
第1、2、5个卷积层后面都跟着一个最大池化层,后三个层为全连接层。最终输出层为softmax,将网络输出转化为概率值,用于预测图像的类别。

AlexNet的创新点

不难发现,AlexNet的网络组成结构和 LeNet-5 十分相似,其在深度和一些处理上做到了创新,对后续 CNNs 的研究起到了关键作用。

更深的神经网络结构

AlexNet 是首个真正意义上的深度卷积神经网络,它的深度达到了当时先前神经网络的数倍。通过增加网络深度,AlexNet 能够更好地学习数据集的特征,从而提高了图像分类的精度。

使用ReLU

AlexNet 在CNN首次使用了ReLU这一非线性激活函数(LeNet原始版本用的是Sigmoid,而我们前面的PyTorch实现是改进版本)。它在保持计算速度的同时,有效地解决了梯度消失问题,从而使得训练更加高效。

提出局部响应归一化(LRN)

局部响应归一化(Local Response Normalization,LRN)是AlexNet首次提出的,在卷积层和池化层之间添加的一种归一化操作。在卷积层中,每个卷积核都对应一个特征图,LRN就是对这些特征图进行归一化。

具体来说,对于每个特征图上的每个位置,计算该位置周围的像素的平方和,然后将当前位置的像素值除以这个和。公式如图所示:

局部响应归一化

LRN本质是抑制邻近神经元的响应,从而增强了神经元的较大响应。这种技术在一定程度上能够避免过拟合,并提高网络的泛化能力。

在2015年的论文 Very Deep Convolutional Networks for Large-Scale Image Recognition. 中提到LRN基本没什么用。
于是在后面的 Googlenet,以及之后的一些CNN架构模型,LRN已经不再使用,因为出现了更加有说服能力的块归一化,也称之为批量归一化,即BN

数据增强和Dropout

为了防止过拟合,AlexNet 引入了数据增强和 Dropout 技术。

数据增强可以通过对图像进行旋转、翻转、裁剪等变换,增加训练数据的多样性,提高模型的泛化能力。

Dropout 也就是暂退法,在训练过程中随机删除一定比例的神经元,强制网络学习多个互不相同的子网络,从而提高网络的泛化能力。

详见本站文章:

大规模分布式训练

AlexNet 也在2012年对运算问题上实现了重⼤突破。

Alex Krizhevsky和Ilya Sutskever意识到卷积神经⽹络中的计算瓶颈:卷积和矩阵乘法,都是可以在GPU上并⾏的。
由于早期GPU显存有限,他们使⽤两个显存为3GB的NVIDIA GTX580 GPU,采⽤了双数据流设计,使得每个GPU只负责存储和计算模型的⼀半参数,实现了快速卷积运算。

这种大规模 GPU 集群进行分布式训练的方法在后来的深度学习中也得到了广泛的应用。

VGG网络

VGGNet 是牛津大学计算机视觉组(Visual Geometry Group)和谷歌 DeepMind 一起研究出来的深度卷积神经网络,因而冠名为 VGG。VGG是一种被广泛使用的卷积神经网络结构,在2014年的 ImageNet 大规模视觉识别挑战(ILSVRC -2014)中获得了亚军(冠军是 GoogLeNet)。

网络结构

VGGNet

实现VGG块

经过LeNet和AlexNet两种网络的学习我们得出,经典卷积神经⽹络的基本组成部分是下⾯的这几道工序:

  1. 带填充以保持分辨率的卷积层;
  2. ⾮线性激活函数,如ReLU;
  3. 池化层,如Max Pooling。

而从VGGNet的网络图中可以看出,VGG的卷积层是通过一个个块相连的。
其中,块内的卷积层结构相同;块与块之间通过最大池化连接。

⼀个VGG块就包含了上述几道工序:由⼀系列卷积层组成,后⾯再加上⽤于空间下采样的最⼤池化层。

例如 VGG-16(这也是人们常说的VGG版本) 中的某一块,由三个conv3-256\text{conv3-256} 组成。
其中,conv3-256\text{conv3-256} 表示:这是一个卷积层,卷积核尺寸为3,通道数为256。

于是,我们可以编写函数实现指定块的生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def vgg_block(num_convs, in_channels, out_channels):
layers = []

# 创建 num_convs 个 3*3 卷积层
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
# ReLU激活
layers.append(nn.ReLU())
in_channels = out_channels

# 最大池化
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))

return nn.Sequential(*layers)

使用小卷积核

小卷积核是VGG的一个重要特点。
VGG没有采用AlexNet中比较大的卷积核尺寸(如7×7),而是通过降低卷积核的大小(3×3),并增加卷积子层数(构成一个VGG块)来达到同样的性能(感受野相同)。

使用小卷积核还有一些好处:

  1. 大幅度减少模型参数数量;
  2. 小卷积核选取小的 stride 可以防止细节信息的丢失;
  3. 多层卷积层(每个卷积层后都有非线性激活函数),增加非线性,提升模型性能。

此外,我们注意到在VGG网络结构D中,还使用了1×1卷积核,1×1卷积核可以在不改变感受野的情况下,增加模型的非线性。同时,还可以用它来整合各通道的信息,并输出指定通道数。通道数减小即降维,通道数增加即升维。

其他特点

  • 池化层都将上一层的卷积层特征缩减一半
  • 通道数逐块翻倍
  • 网络深度较深,参数量较大

关于第一点,实际上是因为 VGG 统一使用了 2×2 的最大池化;
关于第二点,通道数的增加,使得更多的信息可以被提取出来;
关于第三点,较深的网络层数使得训练得到的模型分类效果变得更好,但是因此产生了较大的参数学习成本。

含并行连结的网络 GoogLeNet

在介绍 VGG 时我们提到,在2014年的ImageNet图像识别挑战赛中,拿到冠军的是GoogLeNet。

VGG继承了 LeNet 以及 AlexNet 的一些框架结构,而GoogLeNet则做了更加大胆的网络结构尝试,虽然深度只有22层,但大小却比 AlexNet 和 VGG 小很多。

GoogleNet参数为500万个
AlexNet参数个数是GoogleNet的12倍,VGGNet参数又是AlexNet的3倍

而 GoogLeNet 在结构上所做的大胆尝试就是做了网络并联。结构图如下:
GoogLeNet

Inception块

在GoogLeNet中,基本的卷积块被称为Inception块。这很可能得名于电影《盗梦空间》(Inception),因为电影中的⼀句话“我们需要⾛得更深”(“We need to go deeper”)。

Inception块的结构

如图所示,Inception块由四条并行路径组成。

前三条路径使⽤窗⼝大小为1×1、3×3和5×5的卷积层,从不同空间大小中提取信息。

其中,中间的两条路径在输⼊上执⾏1×1卷积,以减少通道数,从⽽降低模型的复杂度。第四条路径使⽤ 3×3最⼤汇聚层,然后使⽤1×1卷积来改变通道数

四条路径都使⽤合适的填充来使输出大小⼀致,便于最后连结在一起构成Inception块的输出。所以,在Inception块中,通常调整的超参数是每层输出通道数。

全局平均池化

网络最后采用了全局平均池化层(global average pooling layer)来代替一系列全连接层,显著减少了模型所需参数的数量。该想法来自NIN(Network in Network)。

全局平均池化

如上图所示,传统方法将输出特征图展平并进行后续全连接层处理,而全局平均池化直接对每一个特征图求平均,其结果直接就作为该特征图的特征。

具体来说,如果要预测KK 个类别,在卷积特征提取部分的最后一层卷积层,设计通道数为KK ,也就是会使得卷积后生成KK 个特征图,然后通过全局平均池化就可以得到KK1×11×1 的特征图,这些1×11×1 的特征图完全就可以看成是大小为KK 的一维张量,直接将其输入到 Softmax Layer之后,就能直接得到分类结果。

在 PyTorch 中 可以使用 torch.nn.AdaptiveAvgPool2d(output_size) 并设置参数 outpout_size=1outpout_size=(1,1) 实现。

其他网络……

卷积神经网络现今已经得到了蓬勃发展,除了以上提到的经典网络结构以外,还有更多更新的网络在不断更新着。比如残差网络、稠密连接网络,空间金字塔池化方法等等。期待读者自己去探索与发现,本文章暂时就到此告一段落了。

参考资料

  1. 动手学习深度学习|D2L Discussion - Dive into Deep Learning
  2. 谈谈离散卷积和卷积神经网络 | 始终 (liam.page)
  3. 卷积神经网络经典回顾之LeNet-5 - 知乎
  4. 深度学习饱受争议的局部响应归一化(LRN)详解-CSDN博客
  5. 卷积神经网络经典回顾之AlexNet - 知乎
  6. 手撕 CNN 经典网络之 VGGNet - 知乎
  7. 全局平均池化Global Average Pooling-CSDN博客