Pytorch CIFAR10图像分类 EfficientNet v1篇
Posted 风信子的猫Redamancy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Pytorch CIFAR10图像分类 EfficientNet v1篇相关的知识,希望对你有一定的参考价值。
Pytorch CIFAR10图像分类 EfficientNet v1篇
文章目录
- Pytorch CIFAR10图像分类 EfficientNet v1篇
再次介绍一下我的专栏,很适合大家初入深度学习或者是Pytorch和Keras,希望这能够帮助初学深度学习的同学一个入门Pytorch或者Keras的项目和在这之中更加了解Pytorch&Keras和各个图像分类的模型。
他有比较清晰的可视化结构和架构,除此之外,我是用jupyter写的,所以说在文章整体架构可以说是非常清晰,可以帮助你快速学习到各个模块的知识,而不是通过python脚本一行一行的看,这样的方式是符合初学者的。
除此之外,如果你需要变成脚本形式,也是很简单的。
这里贴一下汇总篇:汇总篇
4. 定义网络(EfficientNet)
EfficientNet介绍
EfficientNet源自Google Brain的论文EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks. 从标题也可以看出,这篇论文最主要的创新点是Model Scaling. 论文提出了compound scaling,混合缩放,把网络缩放的三种方式:深度、宽度、分辨率,组合起来按照一定规则缩放,从而提高网络的效果。EfficientNet在网络变大时效果提升明显,把精度上限进一步提升,成为了当前最强网络。EfficientNet-B7在ImageNet上获得了最先进的 84.4%的top-1精度 和 97.1%的top-5精度,比之前最好的卷积网络(GPipe, Top-1: 84.3%, Top-5: 97.0%)大小缩小8.4倍、速度提升6.1倍。
在一般情况下,我们知道,增加网络参数可以获得更好的精度(有足够的数据,不过拟合的条件下),例如ResNet可以加深从ResNet-18到ResNet-200,GPipe将baseline模型放大四倍在ImageNet数据集上获得了84.3%的top-1精度。增加网络参数的方式有三种:深度、宽度和分辨率。
深度是指网络的层数,宽度指网络中卷积的channel数(例如wide resnet中通过增加channel数获得精度收益),分辨率是指通过网络输入大小(例如从112x112到224x224)。在EfficientNet之前,没有研究工作只是针对这三个维度中的某一个维度进行调整,因为没钱啊!!有限的计算能力,很少有研究对这三个维度进行综合调整的。
直观上来讲,这三种缩放方式并不不独立。对于分辨率高的图像,应该用更深的网络,因为需要更大的感受野,同时也应该增加网络宽度来获得更细粒度的特征。
EfficientNet性能比较
之前增加网络参数都是单独放大这三种方式中的一种,并没有同时调整,也没有调整方式的研究。EfficientNet使用了compound scaling方法,统一缩放网络深度、宽度和分辨率。类似于靠强大的搜索能力和计算能力,EfficientNet的主要创新点并不是结构,不像ResNet、SENet发明了shortcut或attention机制,EfficientNet的base结构是利用结构搜索搜出来的,然后使用compound scaling规则放缩,得到一系列表现优异的网络:B0~B7.下面两幅图分别是ImageNet的Top-1 Accuracy随参数量和flops变化关系图,可以看到EfficientNet饱和值高,并且到达速度快。可以这样说:对于ImageNet历史上的各种网络而言,可以说EfficientNet在效果上实现了碾压
EfficientNet的baseline
EfficientNet使用了MobileNet V2中的MBCConv作为模型的主干网络,同时也是用了SENet中的squeeze and excitation方法对网络结构进行了优化。MBConv在MobileNet V2中已经介绍过了,SENet会单独在之后的博文中进行详细讲解。
总之呢,综合了MBConv和squeeze and excitation方法的EfficientNet-B0的网络结构如下表所示:
这里找了一个更形象的图,方便我们理解和进行构建EfficientNet网络。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JmYLDSsa-1673061774717)(https://www.researchgate.net/publication/344410350/figure/fig4/AS:1022373302128641@1620764198841/Architecture-of-EfficientNet-B0-with-MBConv-as-Basic-building-blocks.png)]
EfficientNet模型混合缩放方法
EfficientNet的规范化混合调参方法使用了一个复合系数 ϕ \\phi ϕ ,来对三个参数进行符合调整:使用了compound scaling方法,统一缩放网络深度、宽度和分辨率。
其中的 α , β , γ \\alpha, \\beta, \\gamma α,β,γ都是常数,可以通过网格搜索获得。复合系数通过人工调节。考虑到如果网络深度翻番那么对应的计算量翻番,网络宽度和图像分辨率翻番对应的计算量会翻4番,卷积操作的计算量与d , w 2 , r 2 w^2 ,r^2 w2,r2成正比,。在这个约束下,网络的计算量大约是之前的 2 ϕ 2^\\phi 2ϕ。
后续就可以利用混合缩放的方法进行对baseline网络进行改进,如下图所示,(a)为baseline网络,(b)、©、(d)为单独通过增加width,depth以及resolution使得网络变大的方式,(e)为compound scaling的方式。
这样就衍生出了EfficientB1-B7,从上面的图我们可以看到使用了compound scaling后,效果非常显著,在不同参数量和计算量都取得了多倍的提升。EfficientNet在ImageNet上的效果碾压,而且模型规模比此前的GPipe小了8.4倍。
其他版本的EfficientNet(B1-B7)
Model | input_size | width_coefficient | depth_coefficient | drop_connect_rate | dropout_rate |
---|---|---|---|---|---|
EfficientNetB0 | 224x224 | 1.0 | 1.0 | 0.2 | 0.2 |
EfficientNetB1 | 240x240 | 1.0 | 1.1 | 0.2 | 0.2 |
EfficientNetB2 | 260x260 | 1.1 | 1.2 | 0.2 | 0.3 |
EfficientNetB3 | 300x300 | 1.2 | 1.4 | 0.2 | 0.3 |
EfficientNetB4 | 380x380 | 1.4 | 1.8 | 0.2 | 0.4 |
EfficientNetB5 | 456x456 | 1.6 | 2.2 | 0.2 | 0.4 |
EfficientNetB6 | 528x528 | 1.8 | 2.6 | 0.2 | 0.5 |
EfficientNetB7 | 600x600 | 2.0 | 3.1 | 0.2 | 0.5 |
-
input_size
代表训练网络时输入网络的图像大小 -
width_coefficient
代表channel
维度上的倍率因子,比如在 EfficientNetB0中Stage1
的3x3卷积层所使用的卷积核个数是32,那么在B6中就是 32 × 1.8 = 57.6 32 \\times 1.8=57.6 32×1.8=57.6接着取整到离它最近的8的整数倍即56,其它Stage
同理。 -
depth_coefficient
代表depth维度上的倍率因子(仅针对Stage2
到Stage8
),比如在EfficientNetB0中Stage7
的 L ^ i = 4 \\widehat L_i=4 L i=4,那么在B6中就是 4 × 2.6 = 10.4 4 \\times 2.6=10.4 4×2.6=10.4接着向上取整即11。 -
drop_connect_rate
是在MBConv
结构中dropout层使用的drop_rate
,在官方keras模块的实现中MBConv
结构的drop_rate
是从0递增到drop_connect_rate
的(注意,在源码实现中只有使用shortcut的时候才有Dropout层),还需要注意的是,这里的Dropout层是Stochastic Depth
,即会随机丢掉整个block的主分支(只剩捷径分支,相当于直接跳过了这个block)也可以理解为减少了网络的深度。 -
dropout_rate
是最后一个全连接层前的dropout
层(在stage9
的Pooling与FC之间)的dropout_rate
。最后给出原论文中关于EfficientNet与当时主流网络的性能参数对比:
判断是否使用GPU
首先我们还是得判断是否可以利用GPU,因为GPU的速度可能会比我们用CPU的速度快20-100倍左右,特别是对卷积神经网络来说,更是提升特别明显。
device = 'cuda' if torch.cuda.is_available() else 'cpu'
我们首先定义一下激活函数Swish和DropConnect方法,DropConnect是一种正则化方法,它在训练过程中随机将网络中的某些权重设置为0,在有些实现中,大家会使用DropPath进行,也是可以的。
# 激活函数
def swish(x):
return x * x.sigmoid()
# DropConnect是一种正则化方法,它在训练过程中随机将网络中的某些权重设置为0
# DropConnect是对网络中每一个权重进行随机设置的
def drop_connect(x, drop_ratio):
keep_ratio = 1.0 - drop_ratio
mask = torch.empty([x.shape[0], 1, 1, 1], dtype=x.dtype, device=x.device)
mask.bernoulli_(keep_ratio)
x.div_(keep_ratio)
x.mul_(mask)
return x
SE模块
有时候对于SE模块来说,我们会用1x1的卷积层实现,因为在这里,实际上1x1的卷积层是等价于全连接层,也就是Linear层的
SE模块如下所示,由一个全局平均池化,两个全连接层组成。第一个全连接层的节点个数是输入该MBConv
特征矩阵channels
的$ \\frac14 $,且使用Swish激活函数。第二个全连接层的节点个数等于Depthwise Conv
层输出的特征矩阵channels
,且使用Sigmoid激活函数。
# SE模块实现
class SE(nn.Module):
'''Squeeze-and-Excitation MBconv with Swish.'''
def __init__(self, in_channels, se_channels):
super(SE, self).__init__()
# 这里的1x1的卷积核与全连接层是等价的
self.se1 = nn.Conv2d(in_channels, se_channels,
kernel_size=1, bias=True)
self.se2 = nn.Conv2d(se_channels, in_channels,
kernel_size=1, bias=True)
def forward(self, x):
out = F.adaptive_avg_pool2d(x, (1, 1)) # 平均池化
out = swish(self.se1(out))
out = self.se2(out).sigmoid()
out = x * out # 进行连接相乘
return out
MBConv 结构
如图所示,MBConv 结构主要由一个 1x1 的普通卷积(升维作用),一个 kxk 的 Depthwise Conv 卷积。k 的具体值主要有 3x3 和 5x5 两种情况,一个 SE 模块,一个 1x1 的普通卷积(降维作用),一个 Droupout 层构成
-
第一个升维的 1x1 卷积层,它的卷积核个数是输入特征矩阵 channel 的 n 倍,n ∈ 1 , 6
-
当 n = 1 时,不要第一个升维的 1x1 卷积层,即 Stage2 中的 MBConv 结构都没有第一个升维的 1x1 卷积层(这和MobileNetV3网络类似)。
-
仅当输入 MBConv 结构的特征矩阵与输出的特征矩阵 shape 相同时才存在 shortcut 连接(代码中可通过
stride==1 and inputc_channels==output_channels
条件来判断)。 -
在源码实现中只有使用 shortcut 的时候才有 Dropout 层
# MBconv模块
class MBconv(nn.Module):
'''expansion + depthwise + pointwise + squeeze-excitation'''
def __init__(self,
in_channels,
out_channels,
kernel_size,
stride,
expand_ratio=1,
se_ratio=0.25, # 也就是1/4
drop_rate=0.):
super(MBconv, self).__init__()
self.stride = stride
self.drop_rate = drop_rate
self.expand_ratio = expand_ratio
# Expansion 第一个升维的 1x1 卷积层
channels = expand_ratio * in_channels
self.conv1 = nn.Conv2d(in_channels,
channels,
kernel_size=1,
stride=1,
padding=0,
bias=False)
self.bn1 = nn.BatchNorm2d(channels)
# Depthwise conv
self.conv2 = nn.Conv2d(channels,
channels,
kernel_size=kernel_size,
stride=stride,
padding=(1 if kernel_size == 3 else 2),
groups=channels,
bias=False)
self.bn2 = nn.BatchNorm2d(channels)
# SE layers SE模块
se_channels = int(in_channels * se_ratio)
self.se = SE(channels, se_channels)
# Output
self.conv3 = nn.Conv2d(channels,
out_channels,
kernel_size=1,
stride=1,
padding=0,
bias=False)
self.bn3 = nn.BatchNorm2d(out_channels)
# Skip connection if in and out shapes are the same (MV-V2 style)
# 仅当输入 MBConv 结构的特征矩阵与输出的特征矩阵 shape 相同时才存在 shortcut 连接
self.has_skip = (stride == 1) and (in_channels == out_channels)
def forward(self, x):
# 当n = 1的时候,是不要第一个升维的1x1的卷积层
out = x if self.expand_ratio == 1 else swish(self.bn1(self.conv1(x)))
out = swish(self.bn2(self.conv2(out)))
out = self.se(out)
out = self.bn3(self.conv3(out))
if self.has_skip:
if self.training and self.drop_rate > 0: # 有shortcut的时候用Dropout
out = drop_connect(out, self.drop_rate)
out = out + x
return out
定义EfficientNet的网络
这样我们就可以根据EFficientNet的论文的架构定义我们的网络,用到上述的SE模块和MBconv模块进行
# 定义EfficientNet方法
class EfficientNet(nn.Module):
# 默认识别的类别为10
def __init__(self,
width_coefficient=1.0,
depth_coefficient=1.0,
dropout_rate = 0.2,
num_classes=10):
super(EfficientNet, self).__init__()
# kernel_size, in_channel, out_channel, exp_ratio, strides, use_SE, drop_connect_rate, repeats
cfg =
'num_blocks': [1, 2, 2, 3, 3, 4, 1], # repeats
'expansion': [1, 6, 6, 6, 6, 6, 6], # expansion
'out_channels': [16, 24, 40, 80, 112, 192, 320], # out_channels
'kernel_size': [3, 3, 5, 3, 5, 5, 3], # kernel_size
'stride': [1, 2, 2, 2, 1, 2, 1], # stride
'dropout_rate': dropout_rate,
'drop_connect_rate': 0.2,
# 配置文件
self.cfg = cfg
self.width_coefficient = width_coefficient
self.depth_coefficient = depth_coefficient
# 第一个3x3的卷积层
self.conv1 = nn.Conv2d(3,
32,
kernel_size=3,
stride=1,
padding=1,
bias=False)
self.bn1 = nn.BatchNorm2d(32)
self.layers = self._make_layers(in_channels=32)
self.linear = nn.Linear(cfg['out_channels'][-1]*int(self.width_coefficient), num_classes)
def _make_layers(self, in_channels):
layers = []
cfg = [self.cfg[k] for k in ['expansion', 'out_channels', 'num_blocks', 'kernel_size', 'stride']]
b = 0
blocks = sum(self.cfg['num_blocks'])
for expansion, out_channels, num_blocks, kernel_size, stride in zip(*cfg):
import math
num_blocks = math.floor(self.depth_coefficient) * num_blocks
strides = [stride] + [1] * (num_blocks - 1)
for stride in strides:
drop_rate = self.cfg['drop_connect_rate'] * b / blocks
layers.append(
MBconv(in_channels,
out_channels*int(self.width_coefficient),
kernel_size,
stride,
expansion,
se_ratio=0.25,
drop_rate=drop_rate))
in_channels = out_channels*int(self.width_coefficient)
return nn.Sequential(*layers)
def forward(self, x):
out = swish(self.bn1(self.conv1(x)))
out = self.layers(out)
out = F.adaptive_avg_pool2d(out, 1)
out = out.view(out.size(0), -1)
dropout_rate = self.cfg['dropout_rate']
if self.training and dropout_rate > 0:
out = F.dropout(out, p=dropout_rate)
out = self.linear(out)
return out
定义EfficientNetB0~B7
在上面我们已经定义了两个参数,我们可以根据参数的配置,进行设置,这样就可以定义出我们的EfficientNetB0~B7的模型
def EfficientNetB0(num_classes = 10):
# input image size 224x224
return EfficientNet(width_coefficient=1.0,
depth_coefficient=1.0,
num_classes=num_classes)
def EfficientNetB1(num_classes = 10):
# input image size 240x240
return EfficientNet(width_coefficient=1.0,
depth_coefficient=1.1,
num_classes=num_classes)
def EfficientNetB2(num_classes = 10):
# input image size 260x260
return EfficientNet(width_coefficient=1.1,
depth_coefficient=1.2,
dropout_rate=0.3,
num_classes=num_classes)
def EfficientNetB3(num_classes = 10):
# input image size 300x300
return EfficientNet(width_coefficient=1.2,
depth_coefficient=1.4,
dropout_rate=0.3,
num_classes=num_classes)
def EfficientNetB4(num_classes = 10):
# input image size 380x380
return EfficientNet(width_coefficient=1.4,
depth_coefficient=1.8,
dropout_rate=0.4,
num_classes=num_classes)
def EfficientNetB5(num_classes = 10):
# input image size 456x456
return EfficientNet(width_coefficient=1.6,
depth_coefficient=2.2,
dropout_rate=0.4,
num_classes=num_classes)
def EfficientNetB6(num_classes = 10):
# input image size 528x528
return EfficientNet(width_coefficient=1.8,
depth_coefficient=2.6,
dropout_rate=0.5,
num_classes=num_classes)
def EfficientNetB7(num_classes = 10):
# input image size 600x600
return EfficientNet(width_coefficient=2.0,
depth_coefficient=3.1,
dropout_rate=0.5,
num_classes=num_classes)
net = EfficientNetB0(num_classes = 10).to(device)
summary查看网络
我们可以通过summary来看到,模型的维度的变化,经过层后shape的变化,是否最后也是输出(batch,shape)
summary(net,(2,3,32,32))
============================================以上是关于Pytorch CIFAR10图像分类 EfficientNet v1篇的主要内容,如果未能解决你的问题,请参考以下文章
Pytorch CIFAR10图像分类 EfficientNet v1篇