文章目录
1. ShuffleNet V1 理解
ShuffleNet可以看成是group convolution和depth wise separable convolution的结合。ShuffleNet的创新的点主要有:
- 利用分组卷积降低了普通卷积的计算量
- 利用channel shuffle增加了不同通道间的交互能力
2. group convolution的参数量与计算量
group convolution与普通卷积的参数量,计算量对比:
假设卷积的输入
(
H
,
W
,
c
1
)
(H,W,c_1)
(H,W,c1),卷积核大小
(
h
1
,
w
1
)
(h_1,w_1)
(h1,w1),输出
(
H
,
W
,
c
2
)
(H,W,c_2)
(H,W,c2),那么对于普通卷积:
参数量:
h
1
⋅
w
1
⋅
c
1
⋅
c
2
h_1 \cdot w_1 \cdot c_1 \cdot c_2
h1⋅w1⋅c1⋅c2
计算量:
H
⋅
W
⋅
c
2
⋅
h
1
⋅
w
1
⋅
c
1
H \cdot W \cdot c_2 \cdot h_1 \cdot w_1 \cdot c_1
H⋅W⋅c2⋅h1⋅w1⋅c1
同样的输入,对于分组卷积,假设分成g组,那么整个过程的:
参数量:
h
1
⋅
w
1
⋅
c
1
/
g
⋅
c
2
/
g
⋅
g
h_1 \cdot w_1 \cdot c_1/g \cdot c_2/g \cdot g
h1⋅w1⋅c1/g⋅c2/g⋅g =
h
1
⋅
w
1
⋅
c
1
⋅
c
2
/
g
h_1 \cdot w_1 \cdot c_1 \cdot c_2/g
h1⋅w1⋅c1⋅c2/g
计算量:
H
⋅
W
⋅
c
2
/
g
⋅
h
1
⋅
w
1
⋅
c
1
/
g
⋅
g
H \cdot W \cdot c_2/g \cdot h_1 \cdot w_1 \cdot c_1/g \cdot g
H⋅W⋅c2/g⋅h1⋅w1⋅c1/g⋅g =
H
⋅
W
⋅
c
2
⋅
h
1
⋅
w
1
⋅
c
1
/
g
H \cdot W \cdot c_2 \cdot h_1 \cdot w_1 \cdot c_1/g
H⋅W⋅c2⋅h1⋅w1⋅c1/g
由此可见分组卷积的参数量与计算量均是普通卷积的
1
g
\frac{1}{g}
g1,所以利用分组卷积代替普通卷积可以降低原始卷积的参数量和计算量。
3. 分组卷积的问题与channel shuffle
但是分组卷积存在的问题在于,输出的
c
2
/
g
c_2/g
c2/g个通道中只与对应的通道
c
1
/
g
c_1/g
c1/g有信息上的流动,与相邻的group之间缺少交互,所以作者针对上一组组卷积的结果采用了一种channel shuffle的操作,让相邻的group conv有一定的交互能力。
4. ShuffleNet V1代码理解
以下代码参考自https://github.com/megvii-model/ShuffleNet-Series/blob/master/ShuffleNetV1/blocks.py
import torch
import torch.nn as nn
import torch.nn.functional as F
class ShuffleV1Block(nn.Module):
def __init__(self, inp, oup, *, group, first_group, mid_channels, ksize, stride):
super(ShuffleV1Block, self).__init__()
self.stride = stride
assert stride in [1, 2]
self.mid_channels = mid_channels
self.ksize = ksize
pad = ksize // 2
self.pad = pad
self.inp = inp
self.group = group
if stride == 2:
outputs = oup - inp
else:
outputs = oup
branch_main_1 = [
# 将pw和dw与分组卷积相结合
# pw, point wise convolutiuon
nn.Conv2d(inp, mid_channels, 1, 1, 0, groups=1 if first_group else group, bias=False),
nn.BatchNorm2d(mid_channels),
nn.ReLU(inplace=True),
# dw,depth wise convolution
nn.Conv2d(mid_channels, mid_channels, ksize, stride, pad, groups=mid_channels, bias=False),
nn.BatchNorm2d(mid_channels),
]
branch_main_2 = [
# pw-linear
nn.Conv2d(mid_channels, outputs, 1, 1, 0, groups=group, bias=False),
nn.BatchNorm2d(outputs),
]
self.branch_main_1 = nn.Sequential(*branch_main_1)
self.branch_main_2 = nn.Sequential(*branch_main_2)
if stride == 2:
self.branch_proj = nn.AvgPool2d(kernel_size=3, stride=2, padding=1)
channel shuffle 过程:
def channel_shuffle(self, x):
batchsize, num_channels, height, width = x.data.size()
assert num_channels % self.group == 0 # 需要提前判断是否能够被整除
group_channels = num_channels # self.group
# 分成 self.group组,每一组group_channels个通道
x = x.reshape(batchsize, group_channels, self.group, height, width)
x = x.permute(0, 2, 1, 3, 4) # 交换不同组的信息
x = x.reshape(batchsize, num_channels, height, width)
return x
前向传递过程:
def forward(self, old_x):
x = old_x
x_proj = old_x
x = self.branch_main_1(x)
if self.group > 1:
x = self.channel_shuffle(x)
x = self.branch_main_2(x)
if self.stride == 1:
return F.relu(x + x_proj)
elif self.stride == 2:
return torch.cat((self.branch_proj(x_proj), F.relu(x)), 1)
5. shuffleNet V2 理解
shuffleNet V2 的一个Motivation是:在设计网络结构时,除了考了计算量Flops,还应该考虑内存的访问代价(MAC),并行化对应的时间,以及不同的部署环境ARM或者GPU
shuffleNetV2主要实验性的提出了一些网络设计方面的trick:
- 卷积的输入的通道数与输出的通道数应该尽量相同( M A C = h w ( c 1 + c 2 ) + c 1 c 2 MAC=hw(c_1+c_2)+c_1c_2 MAC=hw(c1+c2)+c1c2)
- 过多的组卷积只会增加内存访问时间
- 网络碎片化会降低并行度
- Element wise的运算增加内存访问时间
在上面的基础上,shuffleNet V1 需要组卷积(与2点违背)和 linear bottleneck (与1点违背);mobileNet V2需要残差结构(与4点违背)和expansion/projection layer(与1点违背)
shuffleNet V2 改进点:
- 在输入层采用channel split,代替group convolution的作用
- 在输出结果时采用concat 替代add操作
- 移除bottle neck部分的channel shuffle 操作
图©为shuffleNet V2 原始版本,图(d)为V2 的下采样版本
参考资料
- shufflenet系列 pytorch 代码: https://github.com/megvii-model/ShuffleNet-Series
- group convolution: https://blog.yani.ai/filter-group-tutorial/
- group convolution 计算量理解:https://zhuanlan.zhihu.com/p/65377955