(原)MobileNetV2


轉載請注明出處:

https://www.cnblogs.com/darkknightzh/p/9410574.html

論文:

MobileNetV2: Inverted Residuals and Linear Bottlenecks

網址:

https://arxiv.org/abs/1801.04381

代碼:

官方的tensorflow代碼:

https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet

非官方的pytorch代碼:

https://github.com/tonylins/pytorch-mobilenet-v2

 

參考網址:

https://blog.csdn.net/u011995719/article/details/79135818

https://github.com/Randl/MobileNetV2-pytorch/blob/master/model.py#L54

1. 深度可分離卷積

MobileNetV1

2. 線性瓶頸層(linear bottlenecks

mobileNetV2使用了線性瓶頸層。原因是,當使用ReLU等激活函數時,會導致信息丟失。如下圖所示,低維(2維)的信息嵌入到n維的空間中,並通過隨機矩陣T對特征進行變換,之后再加上ReLU激活函數,之后在通過T-1進行反變換。當n=2,3時,會導致比較嚴重的信息丟失,部分特征重疊到一起了;當n=15到30時,信息丟失程度降低,但是變換矩陣已經是高度非凸的了。

由於非線性層會毀掉一部分信息,因而非常有必要使用線性瓶頸層。且線性瓶頸層包含所有的必要信息,擴張層則是供非線性層豐富信息使用。

下圖對比了不同的卷積方式,其中顏色最淺的代表下一個塊(本塊輸出,下塊輸入)。帶斜杠的為不包含ReLU等非線性激活函數的層。傳統的卷積如(a)所示,輸入和輸出維度不一樣,且卷積核直接對輸入的紅色立方體進行濾波。(b)為可分離卷積,左側3*3卷積的每個卷積核只對輸入的對應層進行濾波,此時特征維度不變;右邊的1*1的卷積對特征進行升維或者降維(圖中為升維)。(c)中為帶線性瓶頸層的可分離卷積,輸入通過3*3 depthwise卷積+ReLU6,得到中間相同維度的特征。之后在通過1*1conv+ReLU6,得到降維后的特征(帶斜線立方體)。之后在通過1*1卷積(無ReLU)進行升維。(d)中則是維度比較低的特征,先通過1*1conv(無ReLU)升維,而后通過3*3 depthwise卷積+ReLU6保持特征數量不變,再通過1*1conv+ReLU6得到降維后的下一層特征(下一層特征在升維時,無ReLU,因而圖中最右邊立方體帶斜線)。

說明:(b)不太確定,因為如果左側3*3的卷積為depthwise convolution的話,左側紅色矩形映射到中間應該是一個點,和(c)一樣,但是(b)中出現了一個方框,不太懂。。。

文中提出了反轉殘差塊(inverted residual block)的概念。下圖顯示了傳統的殘差塊和反轉殘差塊的區別。傳統的殘差塊如(a)將高維特征先使用1*1conv降維,然后在使用3*3conv進行濾波,並使用1*1conv進行升維(這些卷積中均包含ReLU),得到輸出特征(下一層的輸入),並進行element wise的相加。反轉殘差塊則是將低維特征使用1*1conv升維(不含ReLU),而后使用3*3conv+ReLU對特征進行濾波,並使用1*1conv+ReLU對特征再降維,得到本層特征的輸出(即下一層特征的輸入,由於下一層的輸入在使用1*1conv升維時,無ReLU,因而最右邊的立方體帶斜線),並進行element wise的相加。

反轉的原因,上面已經提到,瓶頸層的輸入包含了所有的必要信息,因而右側最左邊的層后面不加ReLU,防止信息丟失。升維后,信息更加豐富,此時加上ReLU,之后在降維,理論上可以保持所有的必要信息不丟失。

為何使用ReLU?使用ReLU可以增加模型的稀疏性。過於稀疏了,信息就丟失了。。。

那瓶頸層內部為何需要升維呢?原因是為了增加模型的表達能力(不確定這樣理解是否正確):當使用ReLU對某通道的信息進行處理后,該通道會不可避免的丟失信息;然而如果有足夠多的通道的話,某通道丟失的信息,可能仍舊保留在其他通道中,因而才會在瓶頸層內部對特征進行升維。文中附錄證明了,瓶頸層內部升維足夠大時,能夠抵消ReLU造成的信息丟失(如文中將特征維度擴大了6倍)。

瓶頸層的具體結構如下表所示。輸入通過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連接輸入和輸出特征(下圖右側)。

3. 網絡模型

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不進行寬度縮放;否則進行寬度縮放。

4. pytorch代碼

  1 import torch.nn as nn
  2 import math
  3 
  4 
  5 def conv_bn(inp, oup, stride):
  6     return nn.Sequential(
  7         nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
  8         nn.BatchNorm2d(oup),
  9         nn.ReLU6(inplace=True)
 10     )
 11 
 12 
 13 def conv_1x1_bn(inp, oup):
 14     return nn.Sequential(
 15         nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
 16         nn.BatchNorm2d(oup),
 17         nn.ReLU6(inplace=True)
 18     )
 19 
 20 
 21 class InvertedResidual(nn.Module):
 22     def __init__(self, inp, oup, stride, expand_ratio):
 23         super(InvertedResidual, self).__init__()
 24         self.stride = stride
 25         assert stride in [1, 2]
 26 
 27         self.use_res_connect = self.stride == 1 and inp == oup
 28 
 29         self.conv = nn.Sequential(
 30             # pw
 31             nn.Conv2d(inp, inp * expand_ratio, 1, 1, 0, bias=False),
 32             nn.BatchNorm2d(inp * expand_ratio),
 33             nn.ReLU6(inplace=True),
 34             # dw
 35             nn.Conv2d(inp * expand_ratio, inp * expand_ratio, 3, stride, 1, groups=inp * expand_ratio, bias=False),
 36             nn.BatchNorm2d(inp * expand_ratio),
 37             nn.ReLU6(inplace=True),
 38             # pw-linear
 39             nn.Conv2d(inp * expand_ratio, oup, 1, 1, 0, bias=False),
 40             nn.BatchNorm2d(oup),
 41         )
 42 
 43     def forward(self, x):
 44         if self.use_res_connect:
 45             return x + self.conv(x)
 46         else:
 47             return self.conv(x)
 48 
 49 
 50 class MobileNetV2(nn.Module):
 51     def __init__(self, n_class=1000, input_size=224, width_mult=1.):
 52         super(MobileNetV2, self).__init__()
 53         # setting of inverted residual blocks
 54         self.interverted_residual_setting = [
 55             # t, c, n, s
 56             [1, 16, 1, 1],
 57             [6, 24, 2, 2],
 58             [6, 32, 3, 2],
 59             [6, 64, 4, 2],
 60             [6, 96, 3, 1],
 61             [6, 160, 3, 2],
 62             [6, 320, 1, 1],
 63         ]
 64 
 65         # building first layer
 66         assert input_size % 32 == 0
 67         input_channel = int(32 * width_mult)
 68         self.last_channel = int(1280 * width_mult) if width_mult > 1.0 else 1280
 69         self.features = [conv_bn(3, input_channel, 2)]
 70         # building inverted residual blocks
 71         for t, c, n, s in self.interverted_residual_setting:
 72             output_channel = int(c * width_mult)
 73             for i in range(n):
 74                 if i == 0:
 75                     self.features.append(InvertedResidual(input_channel, output_channel, s, t))
 76                 else:
 77                     self.features.append(InvertedResidual(input_channel, output_channel, 1, t))
 78                 input_channel = output_channel
 79         # building last several layers
 80         self.features.append(conv_1x1_bn(input_channel, self.last_channel))
 81         self.features.append(nn.AvgPool2d(input_size/32))
 82         # make it nn.Sequential
 83         self.features = nn.Sequential(*self.features)
 84 
 85         # building classifier
 86         self.classifier = nn.Sequential(
 87             nn.Dropout(),
 88             nn.Linear(self.last_channel, n_class),
 89         )
 90 
 91         self._initialize_weights()
 92 
 93     def forward(self, x):
 94         x = self.features(x)
 95         x = x.view(-1, self.last_channel)
 96         x = self.classifier(x)
 97         return x
 98 
 99     def _initialize_weights(self):
100         for m in self.modules():
101             if isinstance(m, nn.Conv2d):
102                 n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
103                 m.weight.data.normal_(0, math.sqrt(2. / n))
104                 if m.bias is not None:
105                     m.bias.data.zero_()
106             elif isinstance(m, nn.BatchNorm2d):
107                 m.weight.data.fill_(1)
108                 m.bias.data.zero_()
109             elif isinstance(m, nn.Linear):
110                 n = m.weight.size(1)
111                 m.weight.data.normal_(0, 0.01)
112                 m.bias.data.zero_()

 


免責聲明!

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



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