文章目錄
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
