MobileNet系列---mobileNetV1


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

1.mobileNetV1

        mobileNet V1是一種體積較小、計算量較少、適用於移動設備的卷積神經網絡。mobileNet V1的主要創新點是用深度可分離卷積(depthwise separable convolution)代替普通的卷積,並使用寬度乘數(width multiply)減少參數量,不過減少參數的數量和操作的同時也會使特征丟失導致精度下降。
       
        原文地址https://arxiv.org/abs/1704.04861

1.1 普通卷積和深度可分離卷積

        標准的卷積過程如圖1,卷積核做卷積運算時得同時考慮對應圖像區域中的所有通道(channel),而深度可分離卷積對不同的通道采用不同的卷積核進行卷積,如圖2所示它將普通卷積分解成了深度卷積(Depthwise Convolution)和逐點卷積(Pointwise Convolution)兩個過程,這樣可以將通道(channel)相關性和空間(spatial)相關性解耦。原文中給出的深度可分離卷積后面都接了一個BN和ReLU層。
                
                            圖1 普通卷積                                                    圖2 深度可分離卷積                                                                               

1.1.1 標准卷積核

        設輸入特征維度為DF*DF*M,M為通道數。標准卷積核的參數為DK*DK*M*N,DK為卷積核大小,M為輸入的通道數, N為輸出的通道數。卷積后輸出維度為:DF*DF*N。卷積過程中每個卷積核對圖像區域進行DF*DF次掃描,每次掃描的深度為M(channel),每個通道需要DK*DK次加權求和運算, 所以理論計算量(floating point operatios FLOPs)為:N*DF*DF*M*Dk*DK

1.1.2 深度可分離卷積

  • 深度卷積:設輸入特征維度為DF*DF*M,M為通道數。卷積核的參數為DK*DK*1*M。輸出深度卷積后的特征維度為:DF*DF*M。卷積時每個通道只對應一個卷積核(掃描深度為1),所以 FLOPs為:M*DF*DF*DK*DK
  • 逐點卷積:輸入為深度卷積后的特征,維度為DF*DF*M。卷積核參數為1*1*M*N。輸出維度為DF*DF*N。卷積過程中對每個特征做1*1的標准卷積, FLOPs為:N*DF*DF*M。

1.1.3 深度可分離卷積的優勢

  • 參數量:關系到模型大小,通常參數用float32表示,所以模型大小一般時參數量的4倍。標准卷積的參數量為Dk*Dk*M*N(M為輸入通道數, N為輸出通道數),深度卷積的參數量為DK*DK*N,逐點卷積的參數量為1*1*M*N,所以深度可分離卷積相對於標准卷積的參數量為(DK*DK*N + M*N)/ DK*DK*M*N = 1/M + 1/DK*DK
  • 計算量: 可以用來衡量算法/模型的復雜度, 通常只考慮乘加操作(Multi-Adds)的數量,而且只考慮 CONV 和 FC 等參數層的計算量,忽略 BN 和PReLU 等等。一般情況,CONV 和 FC 層也會忽略僅純加操作的計算量,如 bias 偏置加和 shotcut 殘差加等。標准卷積的計算量為:N*DF*DF*M*DK*DK,深度可分離卷積的計算量為M*DF*DF*DK*DK+N*DF*DF*M。所以深度可分離卷積的計算量相比於標准卷積為(M*DF*DF*DK*DK+N*DF*DF*M)/ N*DF*DF*M*DK*DK = 1/N + 1/DK*DK      
  • 區域和通道分離: 深度可分離卷積將以往普通卷積操作同時考慮通道和區域改變(卷積先只考慮區域,然后再考慮通道),實現了通道和區域的分離。
      舉個例子
      假設輸入特征的維度為224*224*3,卷積核大小為3*3,輸出通道數為2,設置pad=1,stride=1,則如下圖(圖a)所示,標准卷積的輸出維度為224*224*2。參數量為3*3*3*2=54,計算量為2*224*224*3*3*3=2709504。
     在深度卷積過程中(圖b),輸入為224*224*3,卷積核參數為3*3*1*3,每個通道做3*3的卷積,收集了每個通道的空間特征(Depthwise特征),輸出特征維度為224*224*3。
     接着進入逐點卷積(圖c),卷積核參數為1*1*3*2,對Depthwise特征做2個1*1的普通卷積,這樣相當於收集了每個點的特征,輸出維度為224*224*2。
     深度可分離卷積的參數量為3*3*2 + 3*2 = 24,相比於標准卷積縮減了2.25倍, 計算量為3*224*224*3*3 + 2*224*224*3 = 1655808,相比於標准卷積縮減了1.6倍。
                         圖a 標准卷積過程                                                                                     圖b 深度卷積                                                                                              圖c 逐點卷積

1.2 mobileNetV1網絡結構

        mobileNetV1的網絡結構如Table 1.前面的卷積層中除了第一層為標准卷積層外,其他都是深度可分離卷積(Conv dw + Conv/s1),卷積后接了一個7*7的平均池化層,之后通過全連接層,最后利用Softmax激活函數將全連接層輸出歸一化到0-1的一個概率值,根據概率值的高低可以得到圖像的分類情況。
 

1.3 超參數

  • 寬度因子α(Width Multiplier)

        對於深度可分離卷積層,輸入的通道數M乘上一個寬度因子α變為αM,輸出通道數變為αN,其中α區間為(0,1],此時深度可分離卷積的參數量為:DK*DK*αN + αM*αN = α*α(1/α *DK*DK*N + M*N),計算量變為αM*DF*DF*DK*DK+αN*DF*DF*αM = α*α  (1/α*M*DF*DF*DK*DK+N*DF*DF*M),所以參數量和計算量差不多都變為原來的α*α倍。
  • 分辨率因子ρ (Resolution Multiplier)

        ρ改變輸入層的分辨率,所以深度可分離卷積的參數量不變,但計算量為M*ρDF*ρDF*DK*DK+N*ρDF*ρDF*M = ρ*ρ(M*DF*DF*DK*DK+N*DF*DF*M),即計算量變為原來的ρ*ρ倍。

1.4 mobileNetV1 實現(基於框架keras / pytorch)

 

import torch
import torch.nn as nn

def conv3x3(in_planes, out_planes, stride=1, padding=1):
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=padding, bias=False)

# why no bias: 如果卷積層之后是BN層,那么可以不用偏置參數,可以節省內存
def conv1x1(in_planes, out_planes):
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=1, bias=False)  

class DPBlock(nn.Module):
    '''
        Depthwise convolution and Pointwise convolution.
    '''
    def __init__(self, in_planes, out_planes, stride=1):
        super(DPBlock, self).__init__()  # 調用基類__init__函數初始化
        self.conv1 = conv3x3(in_planes, out_planes, stride)
        self.bn1 = nn.BatchNorm2d(in_planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv1x1(in_planes, out_planes)
        self.bn2 = nn.BatchNorm2d(out_planes)
        
    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)
        
        return out
        
class mobileNetV1Net(nn.Module):
    def __init__(self, block, num_class=1000):
        super(mobileNetV1Net, self).__init__()
        
        self.model = nn.Sequential(
            conv3x3(3, 32, 2),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True)
            block(32, 64, 1),
            block(64, 128, 2),
            block(128, 128, 1),
            block(128, 256, 2),
            block(256, 256, 1),
            block(256, 512, 2),
            block(512, 512, 1),
            block(512, 512, 1),
            block(512, 512, 1),
            block(512, 512, 1),
            block(512, 512, 1),
            block(512, 1024, 2),
            block(1024, 1024, 2),
            nn.AvgPool2d(7)
        )
        self.fc = nn.Linear(1024, num_class)
        
    def forward(self, x):
        x = self.model(x)
        x = x.view(-1, 1024)  # reshape
        out = self.fc(x)
        
        return out
             
mobileNetV1 = mobileNetV1Net(DPBlock)

 

1.5 參考鏈接


免責聲明!

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



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