0%

现代卷积神经网络

深度卷积神经网络AlexNet

组成

  1. AlexNet比相对较小的LeNet5要深得多。AlexNet由八层组成:五个卷积层、两个全连接隐藏层和一个全连接输出层。
  2. AlexNet使用ReLU而不是sigmoid作为其激活函数
  3. 通过暂退法控制全连接层的模型复杂度

VGG网络

组成

经典卷积神经网络的基本组成部分是下面的这个序列:

  1. 带填充以保持分辨率的卷积层;
  2. 非线性激活函数,如ReLU;
  3. 汇聚层,如最大汇聚层。

VGG网络可以分为两部分:

第一部分主要由卷积层和汇聚层组成

第二部分由全连接层组成

VGG神经网络连接 的几个VGG块(在vgg_block函数中定义)。其中有超参数变量conv_arch。该变量指定了每个VGG块里卷积层个数和输出通道数。

如下是一种VGG11网络

运用:在前向传播的过程中从之前层的拼接变成块的拼接

就是常常用如下写法将卷积层,激活函数,池化层放到一个块中,全连接层,激活函数,Dropout函数放到另一个层,

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import torch.nn as nn
self.block1 = nn.Sequential(
nn.Conv2d(in_channel, out_channel, kernel_size),
nn.ReLU(),
nn.MaxPool2d(kernel_size)
)
self.block2 = nn.Sequential(
nn.Linear(in_features, out_features),
nn.ReLU(),
nn.Dropout2d(0.5)
)

网络中的网络NiN

设计思想

1.NiN的想法是在每个像素位置(针对每个高度和宽度)应用一个全连接层

2.将空间维度中的每个像素视为单个样本,将通道维度视为不同特征(feature)。

组成

卷积块NiN设计

  1. NiN块以一个普通卷积层开始,后面是两个1×1的卷积层。这两个1×1卷积层充当带有ReLU激活函数的逐像素全连接层。 第一层的卷积窗口形状通常由用户设置。 随后的卷积窗口形状固定为1×1。

  2. 上面这样的块设计三个,每个块的第一个卷积层窗口形状为11×11、5×5和3×3

  3. 每个NiN块后有一个最大汇聚层,汇聚窗口形状为3×3,步幅为2。

取消全连接层的替代设计

  1. NiN完全取消了全连接层,使用一个NiN块,其输出通道数等于标签类别的数量

  2. 通过一个全局平均汇聚层(global average pooling layer),生成一个对数几率 (logits)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def nin_block(in_channels, out_channels, kernel_size, strides, padding):
return nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU())
net = nn.Sequential(
nin_block(1, 96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2d(3, stride=2),
nin_block(96, 256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2d(3, stride=2),
nin_block(256, 384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2d(3, stride=2),
nn.Dropout(0.5),
# 标签类别数是10
nin_block(384, 10, kernel_size=3, strides=1, padding=1),
nn.AdaptiveAvgPool2d((1, 1)),
# 将四维的输出转成二维的输出,其形状为(批量大小,10)
nn.Flatten())

含并行连结的网络(GoogLeNet)

吸收了NiN中串联网络的思想,并在此基础上做了改进

解决了什么样大小的卷积核最合适的问题

组成

卷积块Inception块

  1. 由四条并行路径组成

  2. 前三条路径使用窗口大小为1×1、3×3和5×5的卷积层,从不同空间大小中提取信息,其中1×1卷积层直接接到通道合并层,3×3和5×5的卷积层是先在输入上执行1×1卷积,以减少通道数,从而降低模型的复杂性,然后再分别接上,最后接到合并层

  3. 第四条路径使用3×3最大汇聚层,然后使用1×1卷积层来改变通道数

  4. 这四条路径都使用合适的填充来使输入与输出的高和宽一致,最后我们将每条线路的输出在通道维度上连结,并构成Inception块的输出

注:通常调整的超参数是每层输出通道数。

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
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l


class Inception(nn.Module):
# c1--c4是每条路径的输出通道数
def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
super().__init__()
# 线路1,单1x1卷积层
self.p1 = nn.Sequential(
nn.Conv2d(in_channels, c1, kernel_size=1),
nn.ReLU()
)
# 线路2,1x1卷积层后接3x3卷积层
self.p2 = nn.Sequential(
nn.Conv2d(in_channels, c2[0], kernel_size=1), #c2[0]表示这一路1 x 1层的输出,也是3 x 3层的输入
nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1), #c2[0]表示这一路3 x 3层的输出
nn.ReLU()
)

# 线路3,1x1卷积层后接5x5卷积层
self.p3 = nn.Sequential(
nn.Conv2d(in_channels, c3[0], kernel_size=1),
nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2),
nn.ReLU()
)
# 线路4,3x3最大汇聚层后接1x1卷积层
self.p4 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
nn.Conv2d(in_channels, c4, kernel_size=1),
nn.ReLU()
)

def forward(self, x):
# 在通道维度上连结输出
return torch.cat((p1, p2, p3, p4), dim=1)

其他块

b1

1
2
3
4
5
b1 = nn.Sequential(
nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

b2

1
2
3
4
5
6
7
b2 = nn.Sequential(
nn.Conv2d(64, 64, kernel_size=1),
nn.ReLU(),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

b3

1
2
3
4
5
b3 = nn.Sequential(
Inception(192, 64, (96, 128), (16, 32), 32),
Inception(256, 128, (128, 192), (32, 96), 64),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

b4

1
2
3
4
5
6
7
8
b4 = nn.Sequential(
Inception(480, 192, (96, 208), (16, 48), 64),
Inception(512, 160, (112, 224), (24, 64), 64),
Inception(512, 128, (128, 256), (24, 64), 64),
Inception(512, 112, (144, 288), (32, 64), 64),
Inception(528, 256, (160, 320), (32, 128), 128),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

b5

1
2
3
4
5
6
7
8

b5 = nn.Sequential(
Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten(),
nn.Linear(1024, 10)
)

残差网络(ResNet)

## 组成

残差块

基本定义:

  1. 假设我们的原始输入为x,而希望学出的理想映射为f(x),上图左图虚线框中的部分需要直接拟合出该映射f(x),而右图虚线框中的部分则需要拟合出残差映射f(x) - x

  2. 在残差块中,输入可通过跨层数据线路更快地向前传播

组成

基本结构
  1. 残差块里首先有2个有相同输出通道数的3×3卷积层。 每个卷积层后接一个批量规范化层和ReLU激活函数,然后通过跨层数据通路,跳过这2个卷积运算,将输入直接加在最后的ReLU激活函数前

  2. 要求2个卷积层的输出与输入形状一样,从而使它们可以相加

改变通道数

通过引入一个额外的1×1卷积层来将输入变换成需要的形状(另一条正常计算的路已经改变通道数了)后再做相加运算

代码实现

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
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l


class Residual(nn.Module):
def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
super().__init__()
self.block1 = nn.Sequential(
nn.Conv2d(input_channels, num_channels,kernel_size=3, padding=1, stride=strides),
nn.BatchNorm2d(num_channels),
nn.ReLU()
)
self.block2 = nn.Sequential(
nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1),
nn.BatchNorm2d(num_channels),
nn.ReLU(),
)
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides)
else:
self.conv3 = None

def forward(self, X):
Y = self.block1(X)
Y = self.block2(Y)
if self.conv3:
X = self.conv3(X)
Y += X
return F.relu(Y)

生成两种类型的网络: 一种是当use_1x1conv=False时,应用ReLU非线性函数之前,将输入添加到输出。 另一种是当use_1x1conv=True时,添加通过1×1卷积调整通道和分辨率。

其他块(以ResNet-18为例)

b1

前两层在输出通道数为64、步幅为2的7×7卷积层后接批量规范化层,再接步幅为2的3×3的最大汇聚层

1
2
3
4
5
6
self.b1 = nn.Sequential(
nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

b2, b3, b4, b5

使用4个由残差块组成的模块,

每个模块使用若干个残差块构成(下面举的例子是2个)。

第一个模块的通道数同输入通道数一致。 因为之前已经使用了步幅为2的最大汇聚层,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def resnet_block(input_channels, num_channels, num_residuals, first_block=False):
blk = []
for i in range(num_residuals):
# 第一个模块(b2)的resnet块我们不进行高宽减半、通道翻倍,不然减太多了
# 如果不是第一个模块,就在该模块第一个resnet块进行一次高宽减半通道加倍操作,因此要用1x1conv处理变换残差项X
if i == 0 and not first_block: # i为bx模块中的第i个残差块,first_block表示为b2模块
blk.append(Residual(input_channels, num_channels, use_1x1conv=True, strides=2))
else:
blk.append(Residual(num_channels, num_channels))
return blk
self.b2 = nn.Sequential(resnet_block(64, 64, 2, first_block=True))
self.b3 = nn.Sequential(resnet_block(64, 128, 2))
self.b4 = nn.Sequential(resnet_block(128, 256, 2))
self.b5 = nn.Sequential(resnet_block(256, 512, 2))

在ResNet中加入全局平均汇聚层,以及全连接层输出。

1
2
3
net = nn.Sequential(b1, b2, b3, b4, b5,
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten(), nn.Linear(512, 10))

完整残差网络实现

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import torch
from torch import nn
from torch.nn import functional as F

class Residual(nn.Module):
def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
super().__init__()
self.block1 = nn.Sequential(
nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1, stride=strides),
nn.BatchNorm2d(num_channels),
nn.ReLU()
)
self.block2 = nn.Sequential(
nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1),
nn.BatchNorm2d(num_channels),
nn.ReLU(),
)
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides)
else:
self.conv3 = None

def forward(self, X):
Y = self.block1(X)
Y = self.block2(Y)
if self.conv3:
X = self.conv3(X)
Y += X
return F.relu(Y)

class ResNet18(nn.Module):
@staticmethod
def _resnet_block(input_channels, num_channels, num_residuals, first_block=False):
blk = []
for i in range(num_residuals):
if i == 0 and not first_block:
blk.append(Residual(input_channels, num_channels, use_1x1conv=True, strides=2))
else:
blk.append(Residual(num_channels, num_channels))
return blk

def __init__(self):
super(ResNet18, self).__init__()
self.b1 = nn.Sequential(
nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
self.b2 = nn.Sequential(*self._resnet_block(64, 64, 2, first_block=True))
self.b3 = nn.Sequential(*self._resnet_block(64, 128, 2))
self.b4 = nn.Sequential(*self._resnet_block(128, 256, 2))
self.b5 = nn.Sequential(*self._resnet_block(256, 512, 2))
self.last = nn.Sequential(
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Linear(512, 10)
)

def forward(self, x):
output = self.b1(x)
output = self.b2(output)
output = self.b3(output)
output = self.b4(output)
output = self.b5(output)
output = self.last(output)
return output