【小白學PyTorch】11 MobileNet詳解及PyTorch實現

文章來自微信公衆號【機器學習煉丹術】。我是煉丹兄,歡迎加我微信好友交流學習:cyx645016617。

@


本來計劃是想在今天講EfficientNet PyTorch的,但是發現EfficientNet是依賴於SENet和MobileNet兩個網絡結構,所以本着本系列是給“小白”初學者學習的,所以這一課先講解MobileNet,然後下一課講解SENet,然後再下一課講解EfficientNet,當然,每一節課都是由PyTorch實現的。

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\)

如果用了深度可分離卷積,就是把這個卷積變成兩個步驟:

  1. 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的卷積核進行卷積;
  2. 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實現和詳解。

文章來自微信公衆號【機器學習煉丹術】。我是煉丹兄,歡迎加我微信好友交流學習:cyx645016617。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章