參考目錄:
- 1 背景
- 2 深度可分離卷積
- 2.2 一般卷積計算量
- 2.2 深度可分離卷積計算量
- 2.3 網絡結構
- 3 PyTorch實作
本來計劃是想在今天講EfficientNet PyTorch的,但是發現EfficientNet是依賴于SENet和MobileNet兩個網絡結構,是以本着本系列是給“小白”初學者學習的,是以這一課先講解MobileNet,然後下一課講解SENet,然後再下一課講解EfficientNet,當然,每一節課都是由PyTorch實作的。
這個系列《小白學PyTorch》的所有代碼和資料集放在了公衆号【機器學習煉丹術】背景,回複【pytorch】擷取(還在更新的呢):
當然有什麼問題、有事沒事都可以找煉丹兄交流哈哈,煉丹兄就是一個非Top學校的跨專業應屆研究所學生。
1 背景
Mobile是移動、手機的概念,MobileNet是Google在2017年提出的輕量級深度神經網絡,專門用于移動端、嵌入式這種計算力不高、要求速度、實時性的裝置。
2 深度可分離卷積
主要應用了深度可分離卷積來代替傳統的卷積操作,并且放棄pooling層。把标準卷積分解成:
- 深度卷積(depthwise convolution)
- 逐點卷積(pointwise convolution)。這麼做的好處是可以大幅度降低參數量和計算量。
2.2 一般卷積計算量
我們先來回顧一下什麼是一般的卷積:
先說一下題目:特征圖尺寸是H(高)和W(寬),尺寸(邊長)為K,M是輸入特征圖的通道數,N是輸出特征圖的通道數。
現在簡化問題,如上圖所示,輸入單通道特征圖,輸出特征圖也是單通道的, 我們知道每一個卷積結果為一個标量,從輸出特征圖來看,總共進行了9次卷積。每一次卷積計算了9次,因為每一次卷積都需要讓卷積核上的每一個數字與原來特征圖上對應的數字相乘(這裡隻算乘法不用考慮加法)。是以圖6.18所示,總共計算了:
9*9=3*3*3*3=81
如果輸入特征圖是一個2通道的 ,那麼意味着卷積核也是要2通道的卷積核才行,此時輸出特征圖還是單通道的。這樣計算量就變成:
9*9*2=3*3*3*3*2=162
原本單通道特征圖每一次卷積隻用計算9次乘法,現在因為輸入通道數變成2,要計算18次乘法才能得到輸出中的1個數字。現在假設輸出特征圖要輸出3通道的特征圖。 那麼就要準備3個不同的卷積核,重複上述全部操作3次才能拿的到3個特征圖。是以計算量就是:
9*9*2*3=3*3*3*3*2*3=486
現在解決原來的問題:特征圖尺寸是H(高)和W(寬),卷積核是正方形的,尺寸(邊長)為K,M是輸入特征圖的通道數,N是輸出特征圖的通道數。 那麼這樣卷積的計算量為:
H*W*K*K*M*N
這個就是卷積的計算量的公式。
2.2 深度可分離卷積計算量
- 深度可分離卷積(Depthwise Separable Convolution,DSC)
假設在一次一般的卷積中,需要将一個輸入特征圖64×7×7,經過3×3的卷積核,變成128×7×7的輸出特征圖。計算一下這個過程需要多少的計算量:
7*7*3*3*64*128=3612672
如果用了深度可分離卷積,就是把這個卷積變成兩個步驟:
- Depthwise:先用64×7×7經過3×3的卷積核得到一個64×7×7的特征圖。注意注意!這裡是64×7×7的特征圖經過3×3的卷積核,不是64×3×3的卷積核!這裡将64×7×7的特征圖看成64張7×7的圖檔,然後依次與3×3的卷積核進行卷積;
- Pointwise:在Depthwise的操作中,不難發現,這樣的計算根本無法整合不同通道的資訊,因為上一步把所有通道都拆開了,是以在這一步要用64×1×1的卷積核去整合不同通道上的資訊,用128個64×1×1的卷積核,産生128×7×7的特征圖。
最後的計算量就是:
7*7*3*3*64+7*7*1*1*64*128=429632
計算量減少了百分之80以上。
分解過程示意圖如下:
在圖中可以看到:
- (a)表示一般卷積過程, 卷積核都是M個通道,然後總共有N和卷積核,意味着輸入特征圖有M個通道,然後輸出特征圖有N個通道。
- (b)表示depthwise過程, 總共有M個卷積核,這裡是對輸入特征圖的M個通道分别做一個卷積,輸出的特征圖也是M個通道的;
- (c)表示pointwise過程,總共有N個
1 \times 1
的卷積核,這樣來整合不同通道的資訊,輸出特征圖有N個通道數。
2.3 網絡結構
左圖表示的是一般卷積過程,卷積之後跟上BN和ReLU激活層,因為DBC将分成了兩個卷積過程,是以就變成了圖右這種結構,Depthwise之後加上BN和ReLU,然後Pointwise之後再加上Bn和ReLU。
從整個網絡結構可以看出來:
- 除了第一層為标準的卷積層之外,其他的層都為深度可分離卷積。
- 整個網絡沒有使用Pooling層。
3 PyTorch實作
import torch
import torch.nn as nn
import torch.nn.functional as F
class Block(nn.Module):
'''Depthwise conv + Pointwise conv'''
def __init__(self, in_planes, out_planes, stride=1):
super(Block, self).__init__()
self.conv1 = nn.Conv2d\
(in_planes, in_planes, kernel_size=3, stride=stride,
padding=1, groups=in_planes, bias=False)
self.bn1 = nn.BatchNorm2d(in_planes)
self.conv2 = nn.Conv2d\
(in_planes, out_planes, kernel_size=1,
stride=1, padding=0, bias=False)
self.bn2 = nn.BatchNorm2d(out_planes)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
return out
class MobileNet(nn.Module):
# (128,2) means conv planes=128, conv stride=2,
# by default conv stride=1
cfg = [64, (128,2), 128, (256,2), 256, (512,2),
512, 512, 512, 512, 512, (1024,2), 1024]
def __init__(self, num_classes=10):
super(MobileNet, self).__init__()
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_planes=32)
self.linear = nn.Linear(1024, num_classes)
def _make_layers(self, in_planes):
layers = []
for x in self.cfg:
out_planes = x if isinstance(x, int) else x[0]
stride = 1 if isinstance(x, int) else x[1]
layers.append(Block(in_planes, out_planes, stride))
in_planes = out_planes
return nn.Sequential(*layers)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layers(out)
out = F.avg_pool2d(out, 2)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
net = MobileNet()
x = torch.randn(1,3,32,32)
y = net(x)
print(y.size())
> torch.Size([1, 10])
複制
正常情況下這個預訓練模型都會輸出1024個線性節點,然後這裡我自己加上了一個1024->10的一個全連接配接層。
我們來看一下這個網絡結構:
print(net)
複制
輸出結果:
然後代碼中:
關于模型通道數的設定部分:
MobileNet就差不多完事了,下一節課為SENet的PyTorch實作和詳解。
- END -