参考链接:https://blog.csdn.net/csdnldp/article/details/78648543
https://blog.csdn.net/u011995719/article/details/78908755
原论文:https://arxiv.org/pdf/1602.07360.pdf
SqueezeNet 于2016年11月发布,是一个人工设计的轻量化网络,它在ImageNet上实现了和AlexNet相同水平的正确率,但是只使用了1/50的参数。更进一步,使用模型压缩技术,可以将SqueezeNet压缩到0.5MB,这是AlexNet的1/510。
引入了两个术语CNN微结构(microarchitecture)和CNN宏结构(macroarchitecture)。
- CNN微结构: 由层或几个卷积层组成的小模块,如inception模块。
- CNN微结构: 由层或模块组成的完整的网络结构,此时深度是一个重要的参数。
SqueezeNet结构设计
网络结构的设计策略
(1)代替3x3的滤波器为1x1,这样会减少9倍的参数。
(2)减少输入到3x3滤波器的输入通道,这样可以进一步减少参数,本文使用squeeze层来实现。
(3)降采样操作延后,可以给卷积层更大的激活特征图,意味着保留的信息更多,可以提升准确率。
策略(1)(2)是减少参数的方案,(3)是在限制参数预算的情况下最大化准确率。
Fire模块
作者引入了Fire模块来构造CNN,此模块成功地应用了上述的3个策略。
模块由squeeze层和expand层组成,squeeze层由1x1的卷积层组成,,可以减少输入expand层的特征图的输入通道。expand层由1x1和3x3的卷积混合而成,且
s
1
x
1
<
e
1
x
1
+
e
3
x
3
s_{1x1}<e_{1x1}+e_{3x3}
s1x1<e1x1+e3x3,称为扩展了特征图。
full结构
以普通的卷积层(conv1)开始,接着连接8个Fire(2-9)模块,最后以卷积层(conv10)结束。每个Fire模块的filter数量逐渐增加,并且在conv1,Fire4,Fire8和conv10后使用步长为2的max-pooling,这种相对延迟的pooling符合了策略(3)。如下作者对比了添加跳跃层的squeezenet:
其他的实验细节:
- 在3x3的filter之前特征图用0填充1像素边缘,使3x3与1x1卷积的输出具有相同的高度和宽度。
- 在squeeze层和expand层之间使用ReLU函数激励
- Fire9模块之后使用Dropout层,比例为50%。
- 最后不使用全连接层的思想来源于Network in network。
网络结构维度信息和压缩情况
论文使用Han Song提出的Deep Compression对网络进行进一步压缩。
CNN microarchitecture(Fire模块hyper-parameter分析)
对于一个Fire模块,要确定各超参数:squeeze层的filter数量
s
1
x
1
s_{1x1}
s1x1和expand层的filter数量
s
1
x
1
,
s
3
x
3
s_{1x1},s_{3x3}
s1x1,s3x3。在搭建CNN时,会进行Fire模块的堆叠,此时从上到下的Fire模块被标记为
i
=
1
,
2
,
.
.
.
i=1,2,...
i=1,2,...。每增长
f
r
e
q
freq
freq个模块,expand层的filter增加
i
n
c
r
e
incr_{e}
incre。因此,对于第
i
i
i个Fire模块,其filter数量
e
i
=
s
1
x
1
+
s
3
x
3
=
b
a
s
e
e
+
(
i
n
c
r
e
∗
⌊
i
f
r
e
q
⌋
)
e_{i}=s_{1x1}+s_{3x3}=base_{e}+ (incr_{e} * \left \lfloor\frac{i}{freq}\right \rfloor)
ei=s1x1+s3x3=basee+(incre∗⌊freqi⌋)
定义3x3卷积在expand层的比例为
p
c
t
3
x
3
pct_{3x3}
pct3x3,定义压缩比
S
R
SR
SR,有
s
i
,
1
x
1
=
S
R
∗
e
i
s_{i,1x1}=SR*e_{i}
si,1x1=SR∗ei在squeezenet中,作者定义
b
a
s
e
e
=
128
,
i
n
c
r
e
=
128
,
p
c
t
3
x
3
=
0.5
,
f
r
e
q
=
2
,
S
R
=
0.125
base_{e}=128,incr_{e}=128,pct_{3x3}=0.5,freq=2,SR=0.125
basee=128,incre=128,pct3x3=0.5,freq=2,SR=0.125.
作者分析了不同的
p
c
t
3
x
3
pct_{3x3}
pct3x3和
S
R
SR
SR对性能的影响。
对于
S
R
=
0.75
SR=0.75
SR=0.75,此时为与准确率的plateau,再继续增加为
S
R
=
1.0
SR=1.0
SR=1.0不能提升准确率但是却增加了模型的复杂度。对于
p
c
t
3
x
3
pct3x3
pct3x3,继续增加3x3卷积的比例无法提升准确率但是却增加了模型的复杂度。
CNN macroarchitecture(添加bypass连接)
作者对原生的,和添加simple bypass(图2(2),没有增加参数的跳连),以及complex bypass(添加1x1卷积的跳连)的squeezenet进行对比。
发现加入simple bypass 的结果要比其他两者都高。
用pytorch实现squeezenet
来自Github torch/vision
首先实现Fire模块:
class Fire(nn.Module):
def __init__(self, inplanes, squeeze_planes,
expand1x1_planes, expand3x3_planes):
super(Fire, self).__init__()
self.inplanes = inplanes
self.squeeze = nn.Conv2d(inplanes, squeeze_planes, kernel_size=1)
self.squeeze_activation = nn.ReLU(inplace=True)
self.expand1x1 = nn.Conv2d(squeeze_planes, expand1x1_planes,
kernel_size=1)
self.expand1x1_activation = nn.ReLU(inplace=True)
self.expand3x3 = nn.Conv2d(squeeze_planes, expand3x3_planes,
kernel_size=3, padding=1)
self.expand3x3_activation = nn.ReLU(inplace=True)
def forward(self, x):
x = self.squeeze_activation(self.squeeze(x))
return torch.cat([
self.expand1x1_activation(self.expand1x1(x)),
self.expand3x3_activation(self.expand3x3(x))
], 1)
实现squeezenet网络1.0和1.1版本,论文中所述的是1.0版本,1.1版本对1.0做了改进,首先是将第一个普通的conv由7x7改为了3x3。Fire模块的参数维度除in_planes以外保持不变,将max_pool层提前了,减少了计算量。1.1版相比
1.0版具有相同水平的准确率,且计算量减少了2.4x倍,参数量也有轻微减少。
class SqueezeNet(nn.Module):
def __init__(self, version=1.0, num_classes=1000):
super(SqueezeNet, self).__init__()
if version not in [1.0, 1.1]:
raise ValueError("Unsupported SqueezeNet version {version}:"
"1.0 or 1.1 expected".format(version=version))
self.num_classes = num_classes
if version == 1.0:
self.features = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=7, stride=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
Fire(96, 16, 64, 64),
Fire(128, 16, 64, 64),
Fire(128, 32, 128, 128),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
Fire(256, 32, 128, 128),
Fire(256, 48, 192, 192),
Fire(384, 48, 192, 192),
Fire(384, 64, 256, 256),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
Fire(512, 64, 256, 256),
)
else:
self.features = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
Fire(64, 16, 64, 64),
Fire(128, 16, 64, 64),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
Fire(128, 32, 128, 128),
Fire(256, 32, 128, 128),
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
Fire(256, 48, 192, 192),
Fire(384, 48, 192, 192),
Fire(384, 64, 256, 256),
Fire(512, 64, 256, 256),
)
# Final convolution is initialized differently form the rest
final_conv = nn.Conv2d(512, self.num_classes, kernel_size=1)
self.classifier = nn.Sequential(
nn.Dropout(p=0.5),
final_conv,
nn.ReLU(inplace=True),
nn.AdaptiveAvgPool2d((1, 1))
)
for m in self.modules():
if isinstance(m, nn.Conv2d):
if m is final_conv:
init.normal_(m.weight, mean=0.0, std=0.01)
else:
init.kaiming_uniform_(m.weight)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x.view(x.size(0), self.num_classes)