精選 | Hi,我們是-MobileNet-家族!
作者CW,廣東深圳人,畢業(yè)于中山大學(xué)(SYSU)數(shù)據(jù)科學(xué)與計(jì)算機(jī)學(xué)院,畢業(yè)后就業(yè)于騰訊計(jì)算機(jī)系統(tǒng)有限公司技術(shù)工程與事業(yè)群(TEG)從事Devops工作,期間在AI LAB實(shí)習(xí)過,實(shí)操過道路交通元素與醫(yī)療病例圖像分割、視頻實(shí)時(shí)人臉檢測與表情識(shí)別、OCR等項(xiàng)目。目前也有在一些自媒體平臺(tái)上參與外包項(xiàng)目的研發(fā)工作,項(xiàng)目專注于CV領(lǐng)域(傳統(tǒng)圖像處理與深度學(xué)習(xí)方向均有)。
前言
輕量化網(wǎng)絡(luò)是深度學(xué)習(xí)領(lǐng)域的研究熱點(diǎn)之一,其目標(biāo)是在網(wǎng)絡(luò)的參數(shù)量少、訓(xùn)練和預(yù)測速度快的情況下又能夠保持一定的準(zhǔn)確率,以下是一些常用的減少網(wǎng)絡(luò)計(jì)算量的方法:
1. 網(wǎng)絡(luò)模型設(shè)計(jì):使用組卷積(Group Conv)、1x1 卷積(Ponit-Wise Conv)等在達(dá)到相同(或近似)效果的同時(shí)減少計(jì)算量,類似的案例如 Inception、Xception、ShuffleNet 以及本文的主角MobileNet等;
2. 剪枝:通過剪去網(wǎng)絡(luò)中的冗余部分來減少網(wǎng)絡(luò)計(jì)算量;
3. 知識(shí)蒸餾:利用大模型(Teacher-Model)來輔助小模型(Student-Model)學(xué)習(xí),從而在小模型上達(dá)到目標(biāo)效果。
MobileNet是Google的作品,能夠?qū)⒛P筒渴饝?yīng)用于移動(dòng)端,同時(shí)又保證良好的效果,目前Google Pixel 系列的手機(jī)就是用的MobileNet。MobileNet目前更新到了v3版本,CW參考和收集了網(wǎng)上的資料及博文,在本文中會(huì)從v1到v3都進(jìn)行解讀,供大家參考學(xué)習(xí)。
如有不當(dāng)之處,盡管反饋,大家一起學(xué)習(xí)與進(jìn)步,歡迎交流,謝謝!
(全文7000字,建議收藏后閱讀!支持原創(chuàng),給個(gè)點(diǎn)贊加關(guān)注我們吧~)

MobileNetv1
v1的主要工作是將標(biāo)準(zhǔn)的卷積過程拆為兩個(gè)步驟 —— Depth-Wise Conv 和 Point-Wise Conv,并且引入2個(gè)超參,減少了計(jì)算量并且模型也更小,從而可以輕松地匹配移動(dòng)和嵌入式視覺應(yīng)用的設(shè)計(jì)要求。
1. 對(duì)標(biāo)準(zhǔn)卷積層的改進(jìn) —— Depth-Wise Conv 和 Point-Wise Conv
Depth-Wise Conv(簡稱dw),也稱深度可分離卷積,它將卷積核的每個(gè)通道分別應(yīng)用于輸入特征圖的每個(gè)通道(這樣,輸入和輸出的通道數(shù)維持一致),而非如標(biāo)準(zhǔn)卷積般將輸入特征圖的所有通道加權(quán)組合在一起。
Point-Wise Conv(簡稱pw), 也稱逐點(diǎn)卷積,它使用1x1大小的卷積核,用于組合深度可分離卷積的輸出結(jié)果(將深度可分離卷積的輸出通道數(shù)映射到目標(biāo)通道數(shù))。
標(biāo)準(zhǔn)卷積將提取空間特征和通道之間的相關(guān)性組合在一起,而MobileNet將其分解為Depth-Wise + Point-Wise,這樣的效果是先基于各個(gè)通道提取空間特征(空間相關(guān)性),之后再組合通道之間的相關(guān)性,這種因式分解操作具有大幅度減少計(jì)算和模型大小的效果。

為何說這樣能夠減少計(jì)算量?根據(jù)上圖,舉個(gè)例子動(dòng)手計(jì)算下才有說服力(下文均使用 channel first 慣例,所謂的計(jì)算量指乘法次數(shù)):
(a) 表示標(biāo)準(zhǔn)卷積的過程:有N個(gè)卷積核,每個(gè)卷積核維度是M x

x

,輸入特征圖的通道數(shù)是M,輸出特征圖維度為N x

x

,計(jì)算量為

(b) 表示dw的過程:用M個(gè)維度為1 x

x

的卷積核去卷積對(duì)應(yīng)輸入特征圖的M個(gè)通道,得到M個(gè)輸出結(jié)果,注意這M個(gè)結(jié)果不會(huì)進(jìn)行相加(相比標(biāo)準(zhǔn)卷積是卷積輸入特征圖的所有通道,并累加這M個(gè)結(jié)果),輸出結(jié)果的維度是

,這時(shí)計(jì)算量為

;
(c) 表示pw的過程:用N個(gè)維度為M*1*1的卷積核卷積上述(b)的輸出結(jié)果,最終得到維度為

的 feature map。這個(gè)過程和普通卷積無異,只是卷積核大小為1 x 1,此時(shí)計(jì)算量是

。
(b) + (c) 總共的計(jì)算量是

,我們拿它除以標(biāo)準(zhǔn)卷積 (a)的計(jì)算量,來看看縮減了多少:

也就是說,如果使用將我們最常使用的3x3標(biāo)準(zhǔn)卷積分解為dw+pw,那么計(jì)算量將縮減為原來的1/9有多!
根據(jù)paper原文的意思,有些點(diǎn)還需要提下:
MobileNet在每一層均使用BN和ReLU兩種非線性變換;
與標(biāo)準(zhǔn)卷積相比,dw非常有效。然而,它只會(huì)濾除輸入通道,不會(huì)將它們組合起來以創(chuàng)建新功能。因此,需要通過pw來計(jì)算深度卷積輸出的線性組合的附加層來生成這些新特征。
與標(biāo)準(zhǔn)卷積相比,dw非常有效。然而,它只會(huì)濾除輸入通道,不會(huì)將它們組合起來以創(chuàng)建新功能。因此,需要通過pw來計(jì)算深度卷積輸出的線性組合的附加層來生成這些新特征。

2. 使用2個(gè)超參數(shù)構(gòu)建更小的模型
(1) 寬度乘數(shù)(mulitiplier)
記寬度乘數(shù)為α,其作用是在每層均勻地減薄網(wǎng)絡(luò)。對(duì)于給定的層和寬度乘數(shù)α,輸入通道M的數(shù)量變?yōu)棣罬,輸出通道數(shù)量N變?yōu)棣罭。
再拿上述例子來說,使用了寬度乘數(shù)后,dw+pw的計(jì)算量為:

,通常

設(shè)置為0.75。
(2) 分辨率乘數(shù)
這個(gè)超參通常隱式設(shè)置,應(yīng)用于輸入圖像,以降低分辨率,記其為

。同樣地,拿上述例子來說,在使用了寬度乘數(shù)的基礎(chǔ)上,進(jìn)一步使用分辨率乘數(shù)后,dw+pw的計(jì)算量為:

最后來看下MobileNetv1的整體架構(gòu),總共28個(gè)卷積層(conv+bn+relu),第1個(gè)和最后1個(gè)都是標(biāo)準(zhǔn)卷積層,中間13個(gè)是dw+pw卷積層(dw+pw算作1個(gè)卷積層)


附:MobileNetv1 paper
MobileNetv2

回顧下MobileNetv1的騷操作——首先利用3×3的深度可分離卷積提取空間特征,然后利用1×1的逐點(diǎn)卷積來組合通道特征,同時(shí)映射到目標(biāo)通道數(shù)。這樣既減少了參數(shù)量、計(jì)算量,同時(shí)又提高了網(wǎng)絡(luò)運(yùn)算速度,還能得到一個(gè)接近于標(biāo)準(zhǔn)卷積的不錯(cuò)的結(jié)果,看起來十分美好。
然而,現(xiàn)實(shí)是殘酷的!
不少煉丹者在實(shí)際使用過程中, 發(fā)現(xiàn)深度卷積部分的卷積核被廢掉了——訓(xùn)練完之后發(fā)現(xiàn)深度卷積核有不少是空的!

于是,作者趕緊再發(fā)一篇paper(心想:本來想坑你們一把,沒想到這么快就被識(shí)破了,不好玩 ? ?。?,把鍋甩給了ReLU(ReLU內(nèi)心獨(dú)白:我太難了?。?,同時(shí),MobileNetv2也誕生了(此刻腦補(bǔ)眾煉丹者們內(nèi)心:也不知道是不是新挖的坑...)。
在MobileNetv2的paper中,作者舉出一個(gè)充分必要條件將鍋“合乎所以然”地甩給了ReLU——將低維流形映射到高維空間后使用ReLU變換再復(fù)原的例子:

如上圖,在2維空間有一組由m個(gè)點(diǎn)組成的螺旋線Xm數(shù)據(jù)(上圖input),利用隨機(jī)矩陣T映射到n維空間上并進(jìn)行ReLU運(yùn)算:

這時(shí),再利用隨機(jī)矩陣T的逆矩陣

,將y映射回2維空間中:

最后,根據(jù)映射到不同維度空間的情況,對(duì)比下逆映射后的結(jié)果與原始輸入:

可以看到,當(dāng)映射到2、3維空間(n = 2, 3)時(shí),與原始輸入input相比有很大一部分的信息已經(jīng)丟失了;而映射到15、30維空間(n = 15, 30)時(shí),還有許多信息能被保留下來。
這就是說,在低維度空間下與ReLU擁抱(做ReLU運(yùn)算),很容易造成信息丟失;而在高維度的情況下,信息丟失則相對(duì)沒那么嚴(yán)重。
這就解釋了為什么dw的卷積核有不少是空,因?yàn)閐w本身不會(huì)改變通道數(shù),如果輸入通道本來就少的話,經(jīng)過ReLU后信息丟失就會(huì)比較嚴(yán)重。
咦...說了那么久,怎么好像都沒有提到MobileNetv2呀?是不是跑題了?矣?對(duì)耶!
哦,不不不,以上鋪墊的這個(gè)問題很重要,因?yàn)獒槍?duì)這個(gè)問題的解決手段正是MobileNetv2最重要的騷操作,好了,接下來進(jìn)入正題。
1. Expansion Layer
對(duì)于深度可分離卷積,其本身沒有改變通道的能力,輸入通道多少輸出就是多少。有上述可知,這樣的話,若輸入通道很少,經(jīng)過深度可分離卷積后再接ReLU,效果便不是很好,那怎么辦呢?
我們可以在深度可分離卷積前先擴(kuò)展通道數(shù),也稱作Expansion Layer(擴(kuò)展層)。OK,那具體如何擴(kuò)展呢?
既然在深度可分離卷積后我們使用了逐點(diǎn)卷積來將feature map的通道數(shù)映射到目標(biāo)通道數(shù),那么同樣地,我們也可以在深度可分離卷積之前先使用逐點(diǎn)卷積進(jìn)行升維(paper原文是升6倍)。

2. Linear Bottleneck
相對(duì)于上述方式,這是一個(gè)比較“暴力”的手段,干脆把ReLU換掉,使用線性激活層替代。不過,我們可不能把所有的激活函數(shù)都換成線性的,這樣整張網(wǎng)絡(luò)就相當(dāng)于一個(gè)線性操作了(別忘記使用激活函數(shù)的一個(gè)原因之一就是引入非線性變換),那么應(yīng)該替換掉哪里的ReLU呢?
仔細(xì)看上圖,第1個(gè)pw已將通道數(shù)擴(kuò)展,中間的pw不會(huì)改變通道數(shù),那么這兩層后面使用ReLU通常較為“安全”,而最后1個(gè)pw通常是壓縮通道,如果后面接ReLU,效果可能就相對(duì)沒那么好了,因此可以將最后1個(gè)pw后的ReLU替換為線性激活函數(shù),這樣的結(jié)構(gòu)也稱作Linear Bottleneck。
3. Inverted Residual
還記得ResNet那貨不?哦,這肯定是廢話了,對(duì)于眾煉丹者來說,它也算得上是個(gè)全明星了。
殘差結(jié)構(gòu)(通常也稱作shortcut)能夠其起到特征復(fù)用和避免梯度消失的作用,于是在MobileNetv2里也引入這一騷操作。

根據(jù)上圖,對(duì)比下可以發(fā)現(xiàn),兩者都采用了 1×1 -> 3 ×3 -> 1 × 1 的卷積模式以及shortcut,不同點(diǎn)在于ResNet先使用pw降維,后使用pw升維,而MobileNetV2“反其道而行之”——先使用pw升維,后使用pw降維。
這樣一來,MobileNetv2的block形狀看起來就剛好與ResNet的block相反,因此作者給了它一個(gè)網(wǎng)紅名字——Inverted-Residual。
綜合以上,我們來看下MobileNetv2的block:

再來對(duì)比下v1和v2的block

上圖左邊是v1的block,沒有shortcut并且最后的pw后面接了ReLU6;
右邊的是v2的block,加入了Expansion Layer(使用1×1 pw 升維),引入shortcut,同時(shí)去掉了最后pw后的ReLU,改為線性激活函數(shù)。注意,v2的block有2種,當(dāng)dw的步長(stride)為1時(shí),使用了shortcut,形成殘差結(jié)構(gòu);而dw的步長(stride)為2時(shí),由于input與output的尺寸不同,因此沒有使用shortcut結(jié)構(gòu)。
最后,附上MobileNetv2的網(wǎng)絡(luò)結(jié)構(gòu):

附:MobileNetv2 paper
MobileNetv3
本以為v2能成為網(wǎng)紅,沒想到一下子被v3干掉了...好吧,我們先來回顧下v1和v2都做了哪些騷操作。
v1將標(biāo)準(zhǔn)卷積分解為dw+pw,同時(shí)使用2個(gè)超參進(jìn)一步構(gòu)建小模型和降低計(jì)算量;
v2在v1基礎(chǔ)上,在dw前加入 Expansion Layer 進(jìn)行通道數(shù)的擴(kuò)展,將dw后的pw接的ReLU6替換為線性激活層,并且引入了shortcut,最終形成 inverted-residual(“倒殘差”) 這樣的結(jié)構(gòu)。
簡單精煉,OK!那么v3在以上的基礎(chǔ)上又有哪些騷操作呢?
1. 新的非線性激活函數(shù) —— h-swish
在介紹h-swish先來認(rèn)識(shí)一個(gè)家伙——swish,它一次是在谷歌大腦2017的論文 Searching for Activation functions 種亮相,其具備無上界有下界、平滑以及非單調(diào)的特性,并且在深層模型上的效果優(yōu)于ReLU,并且作者通過實(shí)驗(yàn)進(jìn)行了驗(yàn)證。

這好東西v3當(dāng)然也嘗試使用過,但是作者又認(rèn)為這家伙雖然提高了精度,但在嵌入式環(huán)境中,還是有不少計(jì)算成本,原因是在移動(dòng)設(shè)備上計(jì)算sigmoid 函數(shù)成本不小,因此把sigmoid這部分替換掉,改為ReLU6,并且將這整個(gè)激活函數(shù)命名為h-swish:
為啥叫h-siwsh?h代表什么呢?h其實(shí)是單詞hard的第一個(gè)字母,h-swish的意思就是這個(gè)近似的函數(shù)可以逼近swish,讓swish硬(hard)起來(別想歪呀...)。那為啥使用ReLU6呢?作者認(rèn)為幾乎所有的軟硬件框架上都可以方便計(jì)算ReLU6,同時(shí)它能在特定模式下消除由于近似sigmoid的不同實(shí)現(xiàn)而帶來的潛在數(shù)值精度損失。

下圖展示了使用h-swish對(duì)于時(shí)間以及精度的影響,可以發(fā)現(xiàn),使用h-swish@16可以提高大約0.2%的精度,但是持劍延長了大約20%。因此,總的來說,在更深的層中使用h-swish好處更大,于是建議通常在模型的后半部分使用。

2. 基于Squeeze & Excitation(SE)的輕量級(jí)注意力結(jié)構(gòu)
在bottleneck中加入了SE結(jié)構(gòu),并且放在了dw之后。另外,由于SE部分的計(jì)算會(huì)消耗一定的時(shí)間,因此在SE結(jié)構(gòu)的第1個(gè)FC層中,壓縮了通道數(shù)(paper原文是變?yōu)樵瓉淼?/4),在最后的FC層再復(fù)原,通過實(shí)驗(yàn)證明,這樣在減少時(shí)耗的同時(shí)也提高了精度,這是屬于MobileNet的輕量級(jí)SE結(jié)構(gòu)。

關(guān)于這里的SE,有幾點(diǎn)提醒下:
(1). 這里的Pool指的是GAP(Global Average Pooling),即全局平均池化,經(jīng)過它的“沐浴”后,feature map的大小變?yōu)? x 1,通道數(shù)維持不變;
(2). 第1個(gè)FC后的激活函數(shù)是ReLU,最后1個(gè)FC后的激活函數(shù)是h-sigmoid(注意不是 h-swish,相比 h-swish 少乘了1個(gè) x,即

3. 修改了部分v2的結(jié)構(gòu) —— 頭部卷積核的通道數(shù) 和 尾部結(jié)構(gòu)
(1). 修改頭部卷積核的通道數(shù)
v2中的頭部卷積核是 32 x 3 x 3,v3中改成了 16 x 3 x 3,作者發(fā)現(xiàn)這樣在保證了精度的前提下還降低了3ms的時(shí)耗。
(2). 修改尾部結(jié)構(gòu)
v2中,在最后的 Avg-Pooling 之前,使用了1個(gè)pw來升維,有利于結(jié)構(gòu)的預(yù)測,但這同時(shí)引入了一定的計(jì)算量,于是作者在v3中作了修改:
先去掉這個(gè) pw 前的 bottleneck 中的 dw 和后面的 pw,然后將原本位于 Avg-Pooling 前的 pw 放置于 Avg-Pooling 后,這樣就變?yōu)橄扔?Avg-Pooling 將feature map大小由 7 x 7 降到了 1 x 1(Avg-Pooling 的 kernel size 是 7 x 7),然后再使用 pw 升維度,減少了計(jì)算量和時(shí)耗,同時(shí)發(fā)現(xiàn)精度并沒有得到損失,最終變?yōu)槿缦滤镜慕Y(jié)構(gòu):

相比于v2:

4. 網(wǎng)絡(luò)結(jié)構(gòu)搜索 —— 資源受限的NAS(Platform-Aware NAS)與 NetAdapt
通過上述,可以發(fā)現(xiàn)v3相比于v2并沒有做太多創(chuàng)新,其實(shí)重頭之戲在于這部分。但是CW對(duì)這部分沒有深入了解,參考了一些博文,在這里簡單介紹一下。
總體過程就是先用資源受限的NAS,在計(jì)算和參數(shù)量受限的前提下搜索網(wǎng)絡(luò)來優(yōu)化各個(gè)塊(block),稱之為模塊級(jí)搜索(Block-wise Search)。然后再使用 NetAdapt 算法對(duì)各個(gè)模塊確定每一層的卷積核數(shù)量,稱之為層級(jí)搜索(Layer-wise Search)。
MobileNet為何這么快?
看到這里,你有沒有認(rèn)真想過其實(shí)為何MobileNet會(huì)這么快呢?它是閃電俠嗎,sorry跑題...
或許你會(huì)說因?yàn)樗鼘?biāo)準(zhǔn)卷積分解為dw+pw、使用了寬度乘數(shù)和分辨率乘數(shù)縮減計(jì)算量、使用了h-swish...blablabla等等,嗯,也沒錯(cuò),但是這里可以嘗試從另一個(gè)視角去理解。
以v1為例,來看看計(jì)算資源分布情況:

可以看到,大部分計(jì)算都分布在了pw也就是1 x 1卷積上,這么看來,快就快在1 × 1卷積了。咦,難道1 x 1卷積才是閃電俠?(⊙o⊙)…又跑題,sorry...
我們知道,卷積實(shí)質(zhì)是矩陣之間的加乘運(yùn)算,而在計(jì)算機(jī)進(jìn)行操作時(shí),需要將其先存入內(nèi)存再操作,按照 “行先序”:

再來一張圖,你會(huì)發(fā)現(xiàn)這是有點(diǎn)“辛苦”的:

于是,通常都會(huì)使用到一個(gè)叫im2col的騷操作,其實(shí)質(zhì)是通過犧牲空間的手段(約擴(kuò)增K×倍),將特征圖轉(zhuǎn)換成更大的矩陣來進(jìn)行卷積計(jì)算,具體為:
把在特征圖中每一次循環(huán)所需的數(shù)據(jù)排列成列向量,然后逐一堆疊起來形成矩陣(按通道順序在列方向上拼接),比如輸入特征圖維度為 Ci × Wi × Hi ,卷積核尺寸為 K × K ,輸出維度為Co×Wo×Ho,那么卷積核轉(zhuǎn)換為 Co x ( K x K) 的矩陣,同時(shí)輸入特征圖轉(zhuǎn)換為(K x K) × (Ci x Wo x Ho)的矩陣,如下所示。

然后調(diào)用諸如 GEMM(矩陣乘矩陣)這樣的庫加速兩矩陣的相乘計(jì)算。由于這過程按照計(jì)算需求排布了數(shù)據(jù)順序,使得計(jì)算機(jī)每次計(jì)算過程中能夠依次訪問到特征圖中所需的數(shù)據(jù),因此極大地提高了運(yùn)算速度。

OK,現(xiàn)在回來看看MobileNet的寶貝1×1卷積,根據(jù)上述,1×1卷積的“行先序”儲(chǔ)存結(jié)構(gòu)以及使用im2col后的結(jié)構(gòu)分別如下圖所示:

咦,這是...一樣噠!也就是說,1x1卷積根本不需要im2col的過程,“即插即用”!大大節(jié)省了數(shù)據(jù)重排列的時(shí)間和空間,因此在底層計(jì)算中相對(duì)更快。
附:MobileNetv3 paper
若你認(rèn)真看完了本文,那么CW真心感謝你!與此同時(shí),如果你覺得本文給予了你幫助或者讓你有了新的認(rèn)知,那我就更開心了,CW期待與大家共同成長!
參考:
https://cloud.tencent.com/developer/article/1467101
https://blog.csdn.net/u010712012/article/details/95922901
https://blog.csdn.net/qq_38807688/article/details/84590717
https://blog.csdn.net/Chunfengyanyulove/article/details/91358187