MobileNet系列---mobileNetV2


       最近在利用SSD檢測物體時,由於實際項目要求,需要對模型進行輕量化,所以考慮利用輕量網絡替換原本的骨架VGG16,查找一些資料后最終采用了google開源的mobileNetV2。這里對學習mobileNet系列的過程做一些總結。mobileNetV1是由google在2017年發布的一個輕量級深度神經網絡,其主要特點是采用深度可分離卷積替換了普通卷積,2018年提出的mobileNetV2在V1的基礎上引入了線性瓶頸 (Linear Bottleneck)和倒殘差 (Inverted Residual)來提高網絡的表征能力。

2.mobileNetV2

        mobileNetV2是對mobileNetV1的改進,同樣是一種輕量級的神經網絡。為了防止非線性層(ReLU)損失一部分信息,引入了線性瓶頸層(Linear Bottleneck);另外借鑒ResNet及DenseNet等一系列網絡采用了shortcut的網絡得到了很好的效果,作者結合depthwise convolution的特點,提出了倒殘差 (Inverted Residual)。
         
        原文地址https://128.84.21.199/abs/1801.04381

2.1 線性瓶頸層

       對於mobileNetV1的深度可分離卷積而言, 寬度乘數壓縮后的M維空間后會通過一個非線性變換ReLU,根據ReLU的性質,輸入特征若為負數,該通道的特征會被清零,本來特征已經經過壓縮,這會進一步損失特征信息;若輸入特征是正數,經過激活層輸出特征是還原始的輸入值,則相當於線性變換。
 
       下圖是將低維流形的ReLU嵌入高維空間中的例子,原始特征通過隨機矩陣T變換,后面接ReLU層,變換到n維空間后再通過反變換T^-1轉變回原始空間。 當n=2,3時,會導致比較嚴重的信息丟失,部分特征重疊到一起了;當n=15到30時,信息丟失程度降低,但是變換矩陣已經是高度非凸的了。 由於非線性層會損失一部分信息,因而使用線性瓶頸層。

2.2 倒殘差

       殘差塊已經被證明有助於提高精度,所以mobileNetV2也引入了類似的塊。經典的殘差塊(residual block)的過程是:1x1(降維)-->3x3(卷積)-->1x1(升維), 但深度卷積層(Depthwise convolution layer)提取特征限制於輸入特征維度,若采用殘差塊,先經過1x1的逐點卷積(Pointwise convolution)操作先將輸入特征圖壓縮(一般壓縮率為0.25),再經過深度卷積后,提取的特征會更少。所以mobileNetV2是先經過1x1的逐點卷積操作將特征圖的通道進行擴張,豐富特征數量,進而提高精度。這一過程剛好和殘差塊的順序顛倒,這也就是倒殘差的由來:1x1(升維)-->3x3(dw conv+relu)-->1x1(降維+線性變換)。

2.3 網絡結構

        瓶頸層的具體結構如下表所示。輸入通過1*1的conv+ReLU層將維度從k維增加到tk維,之后通過3*3conv+ReLU可分離卷積對圖像進行降采樣(stride>1時),此時特征維度已經為tk維度,最后通過1*1conv(無ReLU)進行降維,維度從tk降低到k維。
        需要注意的是,整個模型中除了第一個瓶頸層的t=1之外,其他瓶頸層t=6(論文中Table 2),即第一個瓶頸層內部並不對特征進行升維。
另外對於瓶頸層,當stride=1時,才會使用elementwise 的sum將輸入和輸出特征連接(如下圖左側);stride=2時,無short cut連接輸入和輸出特征(下圖右側)。
 
MobileNetV2的模型如下圖所示,其中$t$為瓶頸層內部升維的倍數,$c$為特征的維數,$n$為該瓶頸層重復的次數,$s$為瓶頸層第一個conv的步幅。
 
 
需要注意的是
  1. 當$n>1$時(即該瓶頸層重復的次數>1),只在第一個瓶頸層stride為對應的s,其他重復的瓶頸層stride均為1;
  2. 只在$stride=1$時,輸出特征尺寸和輸入特征尺寸一致,才會使用elementwise sum將輸出與輸入相加;
  3. 當$n>1$時,只在第一個瓶頸層特征維度為$c$,其他時候channel不變。
例如,對於該圖中562*24的那層,共有3個該瓶頸層,只在第一個瓶頸層使用stride=2,后兩個瓶頸層stride=1;第一個瓶頸層由於輸入和輸出尺寸不一致,因而無short cut連接,后兩個由於stride=1,輸入輸出特征尺寸一致,會使用short cut將輸入和輸出特征進行elementwise的sum;只在第一個瓶頸層最后的1*1conv對特征進行升維,后兩個瓶頸層輸出維度不變(不要和瓶頸層內部的升維弄混了)。該層輸入特征為56*56*24,第一個瓶頸層輸出為28*28*32(特征尺寸降低,特征維度增加,無short cut),第二個、第三個瓶頸層輸入和輸出均為28*28*32(此時c=32,s=1,有short cut)。
       另外表中還有一個$k$。mobileNetV1中提出了寬度縮放因子,其作用是在整體上對網絡的每一層維度(特征數量)進行瘦身。mobileNetV2中,當該因子<1時,最后的那個1*1conv不進行寬度縮放;否則進行寬度縮放。

2.4 實現

 

  1 import torch
  2 import torch.nn as nn
  3 import numpy as np
  4 
  5 # 定義bottleneck
  6 class Bottlenect(nn.Module):
  7     def __init__(self, inplanes, outplanes, stride=1, expand_ratio=1):
  8         super(Bottlenect, self).__init__()
  9         
 10         self.stride = stride
 11         assert stride in [1, 2]
 12         self.use_res_connect = (self.stride == 1) and (inplanes == outplanes)  # 是否連接殘差
 13         
 14         hidden_dim = inplanes*expand_ratio # 中間層維度
 15         self.conv1 = nn.Conv2d(inplanes, hidden_dim, kernel_size=1, stride=1, padding=0, bias=False)
 16         self.bn1 = nn.BatchNorm2d(hidden_dim)
 17         
 18         self.conv2 = nn.Conv2d(hidden_dim, hidden_dim, 3, stride, padding=1, groups=hidden_dim, bias=False)
 19         self.bn2 = nn.BatchNorm2d(hidden_dim)
 20         
 21         self.conv3 = nn.Conv2d(hidden_dim, outplanes, 1, 1, 0, bias=False)
 22         self.bn3 = nn.BatchNorm2d(outplanes)
 23         
 24         self.relu = nn.ReLU(inplace=True)
 25         
 26     def forward(self, x):
 27         residual = x
 28         
 29         out = self.conv1(x)
 30         out = self.bn1(out)
 31         out = self.relu(out)
 32         
 33         out = self.conv2(out)
 34         out = self.bn2(out)
 35         out = self.relu(out)
 36         
 37         out = self.conv3(out)
 38         out = self.bn3(out)
 39         
 40         if self.use_res_connect:
 41             out += residual
 42         
 43         return out
 44 
 45 def make_divisible(x, divisible_by=8):
 46     return int(np.ceil(x * 1. / divisible_by) * divisible_by)
 47 
 48 class MobileNetV2(nn.Module):
 49     def __init__(self, n_class=1000, input_size=224, width_multi=1.0):
 50         super(MobileNetV2, self).__init__()
 51         
 52         input_channel = 32
 53         last_channel = 1280
 54         
 55         bottlenet_setting = [
 56             # t, c, n, s
 57             [1, 16, 1, 1],
 58             [6, 24, 2, 2],
 59             [6, 32, 3, 2],
 60             [6, 64, 4, 2],
 61             [6, 96, 3, 1],
 62             [6, 160, 3, 2],
 63             [6, 320, 1, 1]
 64         ]
 65         
 66         assert input_size % 32 == 0
 67         self.last_channel = make_divisible(last_channel*width_multi) if width_multi > 1.0 else last_channel
 68         
 69         # first conv layer 1
 70         self.conv1 = nn.Conv2d(3, input_channel, 3, 2, 1, bias=False)
 71         self.bn1 = nn.BatchNorm2d(input_channel)
 72         self.relu = nn.ReLU(inplace=True)
 73         
 74         # bottlenect layer 2-->8
 75         self.bottlenect_layer = []
 76         for t, c, n, s in bottlenet_setting:
 77             output_channel = make_divisible(c*width_multi) if t > 1 else c
 78             for i in range(n):
 79                 if i == 0:  # 第一層的stride = stride, 其他層stride = 1
 80                     self.bottlenect_layer.append(Bottlenect(input_channel, output_channel, s, expand_ratio=t))
 81                 else:
 82                     self.bottlenect_layer.append(Bottlenect(input_channel, output_channel, 1, expand_ratio=t))
 83                 input_channel = output_channel
 84         self.bottlenect_layer = nn.Sequential(*self.bottlenect_layer)
 85         
 86         # conv layer 9
 87         self.conv2 = nn.Conv2d(input_channel, self.last_channel, 1, 1, 0, bias=False)
 88         self.bn2 = nn.BatchNorm2d(self.last_channel)
 89         
 90         # avg pool layer
 91         self.avg_pool = nn.AvgPool2d(7, stride=1)
 92         
 93         # last conv layer
 94         self.conv9 = nn.Conv2d(self.last_channel, n_class, 1, 1, bias=False)
 95         self.bn3 = nn.BatchNorm2d(n_class)
 96         
 97     def forward(self, x):
 98         out = self.conv1(x)
 99         out = self.bn1(out)
100         out = self.relu(out)
101         
102         out = self.bottlenect_layer(out)
103         
104         out = self.conv2(out)
105         out = self.bn2(out)
106         out = self.relu(out)
107         
108         out = self.avg_pool(out)
109         
110         out = self.conv9(out)
111         out = self.bn3(out)
112         out = self.relu(out)
113         
114         out = out.view(x.size(0), -1)
115         
116         return out
117 
118 model = MobileNetV2(width_multi=1)

 

2.5 參考鏈接

 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM