携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
前言
在上一篇文章中,我们介绍了如何去定义一个ResNet网络,如何去定义一个ResNet的基本单元以及和另一种非常经典的神经网络结构——Mobilenet。
今天,我们来介绍另一种非常重要的网络结构——Inception.
-
1.1 Inception介绍
Inception网络结构是由google提出并且应用在了Inception v1、Inception v2 和 Inception v3、Inception v4等一系列需要去加宽网络宽度,进而提高模型性能的网络结构中。
-
1.2 搭建Inception的模块
Inception这样的模块,从本质上看,相比于ResNet而言,多了更多的分支,在这些更多的分支上,分别进行了不同的卷积核的操作。
在实际设计模型的时候,基本上有两种方式,一种是套用现成的网络结构,另外一种就是需要去针对现成的模型结构去做一些剪枝,这些模型剪枝的时候就会对网络结构进行修改。
对于InceptionNet的网络结构,最重要的是模块就是Inception,这个模块可以理解成一个网中网的结构,或者可以理解成多个分支并联的结构
input: AB1 = f1(A)
B2 = f2(A)
B3 = f3(A)
-
resnet的结构:
- B = g(A) + f(A)
- 为了保证A和f(A)分特征图大小是一致的,可能需要对A进行卷积运算。
-
Inception的结构:
- concat([B1, B2, B3]),
- 在Inception中,为了节省计算量,
- 会引入对卷积核的拆分,比如说,我们在使用5x5,7x7的卷积核的时候,
- 在inceptionv2中一个5x5的卷积核会拆成两个3x3的卷积核的串联
- 一个7x7的卷积核会拆成三个3x3的卷积核的串联
- 在inceptionv3中会将卷积核进一步拆成1x3和3x1的卷积核,这个时候就能够压缩计算量
定义Inception这种结构单元,还是比较复杂的,因为要写很多并行的代码,就比较麻烦,而且并行化在芯片实现的时候比较麻烦,因为branch的分支很多,所以,在芯片里,我们使用比较多的还是ResNet这种比较干净的网络结构和MobileNet这种比较轻量型的网络结构;当然在服务端跑这种比较大型的网络结构是可以考虑Inception这种网络结构 的。它的效果还是不错的。
每一个网络我们是可以把它切成几个模块的,我们在读网络,尤其是在一些大型的网络结构的时候,我们一般是把这个大型的网络结构分成几个**模块**,每个模块又有几个小的**基本单元**来组成,通常将一个模块称之为**block**。
import torch
import torch.nn as nn
def ConvBNRelu(in_channel, out_channel, kernel_size):
return nn.Sequential(
nn.Conv2d(in_channel, out_channel,
kernel_size=kernel_size,
stride=1,
padding=kernel_size // 2),
nn.BatchNorm2d(out_channel),
nn.ReLU()
)
class BaseInception(nn.Module):
def __init__(self,
in_channel,
out_channel_list,
reduce_channel_list):
super(BaseInception, self).__init__()
self.branch1_conv = ConvBNRelu(in_channel,
out_channel_list[0],
1)
self.branch2_conv1 = ConvBNRelu(in_channel,
reduce_channel_list[0],
1)
self.branch2_conv2 = ConvBNRelu(reduce_channel_list[0],
out_channel_list[1],
3)
self.branch3_conv1 = ConvBNRelu(in_channel,
reduce_channel_list[1],
1)
self.branch3_conv2 = ConvBNRelu(reduce_channel_list[1],
out_channel_list[2],
5)
self.branch4_pool = nn.MaxPool2d(kernel_size=3,
stride=1,
padding=1)
self.branch4_conv = ConvBNRelu(in_channel,
out_channel_list[3],
3)
def forward(self, x):
out1 = self.branch1_conv(x)
out2 = self.branch2_conv1(x)
out2 = self.branch2_conv2(out2)
out3 = self.branch3_conv1(x)
out3 = self.branch3_conv2(out3)
out4 = self.branch4_pool(x)
out4 = self.branch4_conv(out4)
out = torch.cat([out1, out2, out3, out4], dim=1)
return out
class InceptionNet(nn.Module):
def __init__(self):
super(InceptionNet, self).__init__()
self.block1 = nn.Sequential(
nn.Conv2d(3, 64,
kernel_size=7,
stride=2,
padding=1),
nn.BatchNorm2d(64),
nn.ReLU()
)
self.block2 = nn.Sequential(
nn.Conv2d(64, 128,
kernel_size=3,
stride=2,
padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
)
self.block3 = nn.Sequential(
BaseInception(in_channel=128,
out_channel_list=[64, 64,
64, 64],
reduce_channel_list=[16, 16]),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
self.block4 = nn.Sequential(
BaseInception(in_channel=256,
out_channel_list=[96, 96,
96, 96],
reduce_channel_list=[32, 32]),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
self.fc = nn.Linear(384, 10)
def forward(self, x):
out = self.block1(x)
out = self.block2(out)
out = self.block3(out)
out = self.block4(out)
out = torch.nn.functional.avg_pool2d(out, 2)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
def InceptionNetSmall():
return InceptionNet()
注:cifar10数据训练的代码参考我之前的文章Pytorch——Cifar10图像分类中的训练模型的代码,只需要修改一下net即可。
-
这个网络是可以收敛到80%多的,因为我们的网络结构定义的还是比较简单的,相比于GoogLeNet而言,我们裁剪掉了很多东西,所以,我们在学习新的网络结构的时候,还是去抓住它的核心理念,它的核心的地方在哪。
-
VGGNet主要是一个串联的结构,定义的时候主要采用3x3的小卷积核来进行卷积运算;
-
ResNet主要是一个跳连的结构;
-
InceptionNet主要是增加网络的宽度,采用不同的分支去增加网络的宽度,另外就是采用1x1的卷积核去压缩计算量,
-
这种采用1x1的卷积核去压缩计算量的方式在设计ResNetv1,ResNetv2的时候也被用到了,先对标准的结构进行channel的压缩,压缩之后在用3x3的卷积核进行运算,这样压缩了计算量,同时加大了网络的深度,由一层网络变成了两层,网络的非线性表达能力也会有所提升,相比于直接采用卷积核来做,在计算量上有了节省,而且在性能上基本上没什么损失的。