本文所有内容基于此:动手学深度学习-pytorch
且目的为自学总结,若有侵权,留言删除。
1、LeNet(1998)
论文地址:https://scholar.google.com.tw/scholar?q=Gradient-based+learning+applied+to+document+recognition&hl=zh-CN&as_sdt=0&as_vis=1&oi=scholart
1.1、网络结构图
第一层卷积输入通道为 3,输出通道为 6,size = 5,stride = 1,padding = 0,最大池化size = 2,stride = 2。
第二层卷积输入通道为 6,输出通道为 16,size = 5,stride = 1,padding = 0 ,最大池化size = 2,stride = 2。
Flatten(16 * 4 * 4);(这里4和图中5,是因为取整的方式不同导致)
第三层全连接输入通道为256(16 * 4 * 4),输出通道为120。
第四层全连接输入通道为120,输出通道为84。
输出层全连接输入通道为84,输出通道为10。
1.2、网络代码(pytorch)
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(3, 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
1.3、总结
a、卷积神经网络就是含卷积层的网络
b、LeNet交替使用卷积层和最大池化层后接全连接层来进行图像分类
c、LeNet的网络结构为 2 个卷积加 2 层池化层(激活函数为sigmoid),2 个全连接层,1 个分类层,总共 5 层
2、AlexNet(2012)
论文地址:http://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networ
2.1、网络结构图
论文原文中应用多GPU训练,但这里代码实现及解析中采用单GPU形式。
第一层卷积输入通道为 3,输出通道为 96,size = 11,stride = 4,padding = 1 ;最大池化size = 3,stride = 2
第二层卷积输入通道为 96,输出通道为 256,size = 5,stride = 1,padding = 2 ;最大池化size = 3,stride = 2
第三层卷积输入通道为256,输出通道为 384,size = 3,stride = 1,padding = 1 ;
第四层卷积输入通道为384,输出通道为 384,size = 3,stride = 1,padding = 1 ;
第五层卷积输入通道为384,输出通道为 256,size = 3,stride = 1,padding = 1 ;最大池化size = 3,stride = 2
flatten(256 * 5 * 5)之后接dropout(p = 0.5)
第六层全连接输入通道为6400,输出通道为 4096;dropout(p = 0.5)
第七层全连接输入通道为4096,输出通道为 4096;dropout(p = 0.5)
输出层全连接输入通道为4096,输出通道为 10。
2.2、网络代码(pytorch)
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(3, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256*5*5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIS T,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
2.3、总结
1、AlexNet跟LeNet类似,前者使用了更多的卷积和更大的参数空间来拟合ImageNet数据及,他是浅层网络和深层网络的分界线
2、AlexNet的网络结构为 5 个卷积层加 3 个池化层(激活函数为ReLU),2 个全连接层,1 个分类层
3、AlexNet网络采用了图像增广,以及dropout来防止网络过拟合。
3、VGGNet(2014)
论文地址:https://arxiv.org/abs/1409.1556
3.1、网络结构(VGG16)
这里举例VGG16网络结构
第一层卷积输入通道为 3, 输出通道为 64,size = 3,stride = 1,padding = 1 ;
第二层卷积输入通道为 64, 输出通道为 64,size = 3,stride = 1,padding = 1 ;最大池化size = 2,stride = 2
第一层卷积输入通道为 64, 输出通道为 128,size = 3,stride = 1,padding = 1 ;
第二层卷积输入通道为128,输出通道为128,size = 3,stride = 1,padding = 1 ;最大池化size = 2,stride = 2
第一层卷积输入通道为128,输出通道为 256,size = 3,stride = 1,padding = 1 ;
第一层卷积输入通道为256,输出通道为256,size = 3,stride = 1,padding = 1 ;
第二层卷积输入通道为256,输出通道为256,size = 3,stride = 1,padding = 1 ;最大池化size = 2,stride = 2
第一层卷积输入通道为256,输出通道为512,size = 3,stride = 1,padding = 1 ;
第一层卷积输入通道为512,输出通道为512,size = 3,stride = 1,padding = 1 ;
第二层卷积输入通道为512,输出通道为512,size = 3,stride = 1,padding = 1 ;最大池化size = 2,stride = 2
第一层卷积输入通道为512,输出通道为512,size = 3,stride = 1,padding = 1 ;
第一层卷积输入通道为512,输出通道为512,size = 3,stride = 1,padding = 1 ;
第二层卷积输入通道为512,输出通道为512,size = 3,stride = 1,padding = 1 ;最大池化size = 2,stride = 2
flatten(512* 7 * 7)之后接dropout(p = 0.5)
第六层全连接输入通道为25088,输出通道为 4096;dropout(p = 0.5)
第六层全连接输入通道为4096,输出通道为 4096;dropout(p = 0.5)
输出层全连接输入通道为4096,输出通道为 10。
3.2、网络代码(pytorch)
# VGG16总共有 5 个代码块,我们定义一个函数来实现一个,然后采取循环实现所有代码块
def vgg_block(num_convs, in_channels, out_channels):
blk = []
for i in range(num_convs):
if i == 0:
blk.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
else:
blk.append(nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1))
blk.append(nn.ReLU())
blk.append(nn.MaxPool2d(kernel_size=2, stride=2)) # 这里会使宽高减半
return nn.Sequential(*blk)
conv_arch = ((2, 3, 64), (2, 64, 128), (3, 128, 256), (3, 256, 512), (3, 512, 512))
# 经过5个vgg_block, 宽高会减半5次, 变成 224/32 = 7
fc_features = 512 * 7 * 7 # c * w * h
fc_hidden_units = 4096 # 任意
# 这里我们来实现VGG16
def vgg(conv_arch, fc_features, fc_hidden_units=4096):
net = nn.Sequential()
# 卷积层部分
for i, (num_convs, in_channels, out_channels) in enumerate(conv_arch):
# 每经过一个vgg_block都会使宽高减半
net.add_module("vgg_block_" + str(i+1), vgg_block(num_convs, in_channels, out_channels))
# 全连接层部分
net.add_module("fc", nn.Sequential(d2l.FlattenLayer(),
nn.Linear(fc_features, fc_hidden_units),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(fc_hidden_units, fc_hidden_units),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(fc_hidden_units, 10)
))
return net
3.3、总结
1、VGG16采用 5 个VGG模块来构造VGG网络,前两个采用 2 个卷积层,后三个采用 3 个 卷积层
2、VGG采用小卷积来替代大卷积(3 个 3 * 3卷积替代 1 个 7 * 7卷积,2 个3 * 3 卷积替代 1 个5 * 5卷积),小卷积核的作用是:在保证同样感受野的大小下,增加网络深度的同时能够减少参数量,提升网络的性能。
3、VGG16网络初始输入尺寸为224,每经过一个卷积块,输入尺寸减半,输出通道翻倍
4、VGG16网络由 13 个卷积层、3 个全连接层(其中最后一个为分类层)构成(VGG网络一般 3 个全连接层,若是VGG11则是 8 个卷积层,3个全连接层)
4、NiN网络(2013)
论文地址:https://arxiv.org/abs/1312.4400
4.1、网络结构
NiN模块解析如下:
1、NiN使用 1 X 1 来替代全连接层,从而保留了空间信息的传递
2、NiN 由 1 个3 X 3 卷积和 2 个 1 X 1 卷积串联组成
第一个模块
第一层卷积的输入通道为 3,输出通道为 96,size = 3,stride = 4,padding = 0;
第二层卷积的输入通道为 96,输出通道为 95,size = 1,stride = 1,padding = 0;
第二层卷积的输入通道为 96,输出通道为 95,size = 1,stride = 1,padding = 0;最大池化size = 3,stride = 2
第二个模块
第一层卷积的输入通道为 96,输出通道为 256,size = 3,stride = 1,padding = 2;
第二层卷积的输入通道为 256,输出通道为 256,size = 1,stride = 1,padding = 0;
第二层卷积的输入通道为256,输出通道为256,size = 1,stride = 1,padding = 0;最大池化size=3,stride = 2
第三个模块
第一层卷积的输入通道为 256,输出通道为 384,size = 3,stride = 1,padding = 1;
第二层卷积的输入通道为 384,输出通道为 384,size = 1,stride = 1,padding = 0;
第二层卷积的输入通道为384,输出通道为384,size = 1,stride = 1,padding = 0;最大池化size=3,stride = 2
第四个模块
第一层卷积的输入通道为 384,输出通道为 10,size = 3,stride = 1,padding = 1;
第二层卷积的输入通道为 10,输出通道为 10,size = 1,stride = 1,padding = 0;
第二层卷积的输入通道为10,输出通道为10,size = 1,stride = 1,padding = 0;
GlobalAvgPool2d((1,1))
Flatten(1024 * 1 * 1)
4.2、NiN网络代码
def nin_block(in_channels, out_channels, kernel_size, stride, padding):
blk = nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size, stride, 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())
return blk
class GlobalAvgPool2d(nn.Module):
# 全局平均池化层可通过将池化窗口形状设置成输入的高和宽实现
def __init__(self):
super(GlobalAvgPool2d, self).__init__()
def forward(self, x):
return F.avg_pool2d(x, kernel_size=x.size()[2:])
class Flatten(nn.Module):
def forward(self,input):
return input.view(input.size(0), -1)
net = nn.Sequential(
nin_block(3, 96, kernel_size=11, stride=4, padding=0),
nn.MaxPool2d(kernel_size=3, stride=2),
nin_block(96, 256, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=3, stride=2),
nin_block(256, 384, kernel_size=3, stride=1, padding=1),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Dropout(0.5),
# 标签类别数是10
nin_block(384, 10, kernel_size=3, stride=1, padding=1),
GlobalAvgPool2d(),
# 将四维的输出转成二维的输出,其形状为(批量大小, 10)
Flatten())
4.3、总结
1、NiN重复使用由卷积层和代替全连接层的1×11\times 11×1卷积层构成的NiN块来构建深层网络。
2、NiN去除了容易造成过拟合的全连接输出层,而是将其替换成输出通道数等于标签类别数的NiN块和全局平均池化层。
3、NiN的以上设计思想影响了后面一系列卷积神经网络的设计。
5、Inception(2015)
论文地址:https://www.cv-foundation.org/openaccess/content_cvpr_2015/html/Szegedy_Going_Deeper_With_2015_CVPR_paper.html
5.1、网络结构
每个inception模块都包含四条并行的线路,inception模块讲解如下:
1、前三条线路分别使用 1 X 1、3 X 3、5 X 5卷积分别获取不同空间尺寸下的信息,
2、中间两条线路会先对输入进行 1 X 1 卷积来减少通道的数量以降低模型的复杂度。
3、第四条线路使用 3 X 3 最大池化,后接 1 X 1卷积来改变通道数。
这里是inception-v1的结构模块,以inception-v1为例进行讲解。
第一个模块:
第一层卷积输入通道为 3,输出通道为 64,size = 7,stride = 2,padding = 3,最大池化size = 3,stride= 2,padding = 1;
第二个模块:
第一层卷积输入通道为 64,输出通道为 64,size = 1,stride = 1,padding =0;
第二层卷积输入通道为 64,输出通道为192,size = 3,stride = 1,padding = 1,最大池化size = 3,stride= 2,padding = 1;
第三个模块:
第一层inception模块输入通道为192,输出通道为256;
第二层inception模块输入通道为256,输出通道为480,最大池化size = 3,stride= 2,padding = 1;
第四个模块:
第一层inception模块输入通道为480,输出通道为512;
第二层inception模块输入通道为512,输出通道为512;
第四层inception模块输入通道为512,输出通道为528;
第四层inception模块输入通道为528,输出通道为832,最大池化size = 3,stride= 2,padding = 1;
第五个模块:
第一层inception模块输入通道为832,输出通道为832;
第二层inception模块输入通道为832,输出通道为1024,最大池化size = 3,stride= 2,padding = 1;
GlobalAvgPool2d((1,1))
Flatten(1024 * 1 * 1)
输出层全连接输入通道为 1024,输出通道为 10。
各线路都使用合适的填充来使得输出的高和宽一致,最后输出时在通道维度上进行连接,并输入到下一层。
记忆方法(前三条卷积尺寸为 1、 3、 5,第四条先用 3 X 3 最大池化再 1 X 1卷积)
5.2、网络代码(pytorch)
class Inception(nn.Module):
# c1 - c4为每条线路里的层的输出通道数
def __init__(self, in_c, c1, c2, c3, c4):
super(Inception, self).__init__()
# 线路1,单1 x 1卷积层
self.p1_1 = nn.Conv2d(in_c, c1, kernel_size=1)
# 线路2,1 x 1卷积层后接3 x 3卷积层
self.p2_1 = nn.Conv2d(in_c, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
# 线路3,1 x 1卷积层后接5 x 5卷积层
self.p3_1 = nn.Conv2d(in_c, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
# 线路4,3 x 3最大池化层后接1 x 1卷积层
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.p4_2 = nn.Conv2d(in_c, c4, kernel_size=1)
def forward(self, x):
p1 = F.relu(self.p1_1(x))
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
p4 = F.relu(self.p4_2(self.p4_1(x)))
return torch.cat((p1, p2, p3, p4), dim=1) # 在通道维上连结输出
class Flatten(nn.Module):
def forward(self, input):
return input.view(input.size(0), -1)
class GlobalAvgPool2d(nn.Module):
def __init__(self):
super(GlobalAvgPool2d,self).__init__()
def forward(self,x):
return F.avg_pool2d(x,kernel_size = x.size()[2:])
# 第一个模块
b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# 第二个模块
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# 第三个模块
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 = 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 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
GlobalAvgPool2d)
# 输出层
net = nn.Sequential(b1, b2, b3, b4, b5,
Flatten(),
nn.Linear(1024, 10))
5.3、总结
1、inception模块相当于一个有 4 条线路的子网络,通过不同尺寸的卷积来获取不同视野的空间信息,且通过 1 X 1卷积来改变通道数量,降低模型复杂度。
2、inception网络总共有 5 个模块,每个模块后接 size = 3 X 3,stride = 2,padding = 1 的最大池化,其中每个模块的通道数量的比例是通过大量实验得来。
6、ResNet(2016)
论文地址:https://openaccess.thecvf.com/content_cvpr_2016/html/He_Deep_Residual_Learning_CVPR_2016_paper.html
6.1、网络结构
residual 模块讲解如下:
1、每个 residual 模块都包含相同输出通道的 2 个 3 X 3 卷积层,
2、每个卷积层后面添加 1个 BN 层和 1 层 ReLU 激活层,
3、然后将输入跳过这 2 个卷积层加到最后的 ReLU 激活层前,
4、若在第 3 点中,输入跳过 2 个卷积层没有经过 1 X 1 卷积,则卷积层输出与输入的形状需一致,从而可以相加;若在第 3 点中,输入跳过 2 个卷积层有经过 1 X 1卷积,则 1 X 1 卷积的输出形状需要与第二个卷积的输出形状一致。
5、residual 模块通过将残差块的第一个 conv 的stride = 2 来将特征图的尺寸减半
这里以 resnet -18 为例进行讲解:
第一个模块:
第一层卷积输入通道为 3,输出通道为 64,size = 7,stride = 2,padding = 3,最大池化size = 3,stride= 2,padding = 1;
第二个模块:
第一层residual模块输入通道为 64,输出通道为 64,stride = 1
第二层residual模块输入通道为 64,输出通道为 64,
第三个模块:
第一层residual模块输入通道为 64,输出通道为 128,stride = 2
第二层residual模块输入通道为 128,输出通道为 128,
第四个模块:
第一层residual模块输入通道为 128,输出通道为 256,stride = 2
第二层residual模块输入通道为 256,输出通道为 256,
第五个模块:
第一层residual模块输入通道为 256,输出通道为 512,stride = 2
第二层residual模块输入通道为 512,输出通道为 512,
GlobalAvgPool2d((1,1))
Flatten(512 * 1 * 1)
输出层全连接输入通道为 512,输出通道为 10。
6.2、网络代码
# 残差模块
class Residual(nn.Module): # 本类已保存在d2lzh_pytorch包中方便以后使用
def __init__(self, in_channels, out_channels, use_1x1conv=False, stride=1):
super(Residual, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, stride=stride)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
if use_1x1conv:
self.conv3 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
def forward(self, X):
Y = F.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
return F.relu(Y + X)
class Flatten(nn.Module):
def forward(self, input):
return input.view(input.size(0), -1)
# 每两个残差块构成一个模块
def resnet_block(in_channels, out_channels, num_residuals, first_block=False):
if first_block:
assert in_channels == out_channels # 第一个模块的通道数同输入通道数一致
blk = []
for i in range(num_residuals):
if i == 0 and not first_block:
blk.append(Residual(in_channels, out_channels, use_1x1conv=True, stride=2))
else:
blk.append(Residual(out_channels, out_channels))
return nn.Sequential(*blk)
# 第一个模块
net = 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))
# 第二个模块,这里不使用 1 X 1 卷积跳过两个卷积与输出相加
net.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True))
# 第三个模块
net.add_module("resnet_block2", resnet_block(64, 128, 2))
# 第四个模块
net.add_module("resnet_block3", resnet_block(128, 256, 2))
# 第五个模块
net.add_module("resnet_block4", resnet_block(256, 512, 2))
net.add_module("global_avg_pool", nn.AdaptivAvgPool2d((1,1))) # AdaptivAvgPool2d的输出: (Batch, 512, 1, 1)
# 输出层
net.add_module("fc", nn.Sequential(Flatten(), nn.Linear(512, 10)))
6.3、总结
1、residual通过跨层的数据通道能够训练有效的神经网络
2、residual通过使 stride = 2来使 feature-map的尺寸减半
3、除了第一个残差模块没有使用 1 X 1卷积让通道数翻倍外,剩下的都有使用
7、DenseNet(2017)
论文地址:https://openaccess.thecvf.com/content_cvpr_2017/html/Huang_Densely_Connected_Convolutional_CVPR_2017_paper.html
7.1、网络结构
Dense 模块讲解如下:
1、dense 模块使用 ResNet 的改良版(BN-ReLU-Conv)结构
2、dense 模块里每一个卷积层的输入都会与输出在通道上连接成为下一层的输入,并且每一层的输入都是前面层有关。
3、通过过渡块来控制网络的复杂度,1 X 1卷积来控制通道数量,size = 2,stride = 2的pooling来控制特征尺寸
第一个模块:
第一层卷积输入通道为 3,输出通道为 64,size = 7,stride = 2,padding = 3,最大池化size = 3,stride= 2,padding = 1;
第二个模块:
第一层卷积输入通道为 64,输出通道为 96,size = 3,stride = 1,padding = 1
第二层卷积输入通道为 96,输出通道为 128,size = 3,stride = 1,padding = 1
第三层卷积输入通道为 128,输出通道为 160,size = 3,stride = 1,padding = 1
第四层卷积输入通道为 160,输出通道为 192,size = 3,stride = 1,padding = 1
第五层过渡层输入通道为 192,输出通道为 96,size = 1,stride = 1,padding = 0;最大池化size = 2,stride = 2;
第三个模块:
第一层卷积输入通道为 96,输出通道为 128,size = 3,stride = 1,padding = 1
第二层卷积输入通道为 128,输出通道为 160,size = 3,stride = 1,padding = 1
第三层卷积输入通道为 160,输出通道为 192,size = 3,stride = 1,padding = 1
第四层卷积输入通道为 192,输出通道为 224,size = 3,stride = 1,padding = 1
第五层过渡层输入通道为 224,输出通道为 112,size = 1,stride = 1,padding = 0;最大池化size = 2,stride = 2;
第四个模块:
第一层卷积输入通道为 112,输出通道为 144,size = 3,stride = 1,padding = 1
第二层卷积输入通道为 144,输出通道为 176,size = 3,stride = 1,padding = 1
第三层卷积输入通道为 176,输出通道为 208,size = 3,stride = 1,padding = 1
第四层卷积输入通道为 208,输出通道为 240,size = 3,stride = 1,padding = 1
第五层过渡层输入通道为 240,输出通道为 120,size = 1,stride = 1,padding = 0;最大池化size = 2,stride = 2;
第五个模块:
第一层卷积输入通道为 120,输出通道为 152,size = 3,stride = 1,padding = 1
第二层卷积输入通道为 152,输出通道为 184,size = 3,stride = 1,padding = 1
第三层卷积输入通道为 184,输出通道为 216,size = 3,stride = 1,padding = 1
第四层卷积输入通道为 216,输出通道为 248,size = 3,stride = 1,padding = 1
GlobalAvgPool2d((1,1))
Flatten(248 * 1 * 1)
输出层全连接输入通道为 248,输出通道为 10。
7.2、网络代码(pytorch)
# 稠密快
class DenseBlock(nn.Module):
def __init__(self, num_convs, in_channels, out_channels):
super(DenseBlock, self).__init__()
net = []
for i in range(num_convs):
in_c = in_channels + i * out_channels
net.append(conv_block(in_c, out_channels))
self.net = nn.ModuleList(net)
self.out_channels = in_channels + num_convs * out_channels # 计算输出通道数
def forward(self, X):
for blk in self.net:
Y = blk(X)
X = torch.cat((X, Y), dim=1) # 在通道维上将输入和输出连结
return X
class Flatten(nn.Module):
def forward(self,input):
return input.view(input.size(0), -1)
# 过渡块
def transition_block(in_channels, out_channels):
blk = nn.Sequential(
nn.BatchNorm2d(in_channels),
nn.ReLU(),
nn.Conv2d(in_channels, out_channels, kernel_size=1),
nn.AvgPool2d(kernel_size=2, stride=2))
return blk
# 第一个模块
net = 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))
# 第二~五个模块
num_channels, growth_rate = 64, 32 # num_channels为当前的通道数,growth_rate为每个模块的每层卷积所增加的通道数量
num_convs_in_dense_blocks = [4, 4, 4, 4] # 每个模块所包含的卷积层数量,总共四个dense模块
for i, num_convs in enumerate(num_convs_in_dense_blocks):
DB = DenseBlock(num_convs, num_channels, growth_rate)
net.add_module("DenseBlosk_%d" % i, DB)
# 上一个稠密块的输出通道数
num_channels = DB.out_channels
# 在稠密块之间加入通道数减半的过渡层
if i != len(num_convs_in_dense_blocks) - 1:
net.add_module("transition_block_%d" % i, transition_block(num_channels, num_channels // 2))
num_channels = num_channels // 2
# 输出层
net.add_module("BN", nn.BatchNorm2d(num_channels))
net.add_module("relu", nn.ReLU())
net.add_module("global_avg_pool", nn.AdaptivAvgPool2d((1,1))) # AdaptivAvgPool2d的输出: (Batch, num_channels, 1, 1)
net.add_module("fc", nn.Sequential(Flatten(), nn.Linear(num_channels, 10)))
7.3、总结
1、在跨层连接上,不同于ResNet中将输入与输出相加,DenseNet在通道维上连结输入与输出。
2、DenseNet的主要构建模块是稠密块和过渡层
3、dense模块每层3 X 3卷积size = 3,stride = 1,padding = 1,每层1 X 1卷积size = 1,stride = 1,padding=0,每层avg_pooling的size = 2 ,stride = 2