你的 ResNet 是時候更新了

深藍學院發表於2020-12-07

 

作者簡介

CW,廣東深圳人,畢業於中山大學(SYSU)資料科學與計算機學院,畢業後就業於騰訊計算機系統有限公司技術工程與事業群(TEG)從事Devops工作,期間在AI LAB實習過,實操過道路交通元素與醫療病例影像分割、視訊實時人臉檢測與表情識別、OCR等專案。

目前也有在一些自媒體平臺上參與外包專案的研發工作,專案專注於CV領域(傳統影像處理與深度學習方向均有)。

前言

趕在4月末,終於有時間寫文了,最近工作上需求比較急,抽不出時間來更文,但我心早已狂熱!在我敲上這行字的過程中,真的很開心,因為真心很享受這種靜靜地碼字向別人分享知識的時光(雖然不知道有沒有人看..)。如今就為大家奉上這個新鮮出爐的新品 —— ResNeSt

文末可獲取相關Paper&原始碼連結

你沒看錯,是 ResNeSt 而不是 ResNet 喲!這是張航、李沐等大佬創造的 ResNet 改進版,在引數量沒有顯著增加的情況下顯著提升了效能,並且可以很方便地如 ResNet 般整合到現有演算法框架中。通過本文,我們就一起來看看它有多香吧!

Outline

I. 主要思想

II. 分組的通道注意力機制:Split-Attention

III. 從程式碼出發,知行合一

 

1

基本思想

ResNeSt 很好懂,不復雜,簡單來說就是結合了 ResNeXt 的分組卷積和 SE-Net 的通道注意力機制,將通道進行分組,對每組運用注意力機制,同時保留了 ResNet 的殘差結構。

2

分組的通道注意力機制:Split-Attention

你的 ResNet 是時候更新了—ResNeSt 來也

 

SplAtConv2d

這部分我們來詳談分組的通道注意力是怎樣一種操作,作者論述到可能的實現方式有多種,這裡我先談談其中一種。

瞭解 ResNeXt 的朋友們都知道,其引入了 Cardinality 的概念,代表分組的組數,為方便敘述,這裡記為 K;ResNeSt 則在此基礎上進一步分組,稱為 split 操作,同時引入一個超參 Radix,代表將 K 個組中的每一個進一步劃分的組數,這裡記為 R。這裡的分組都是在通道這個維度上進行,由此看來,就是將輸入在通道這個維度劃分為 KxR 個組。

分組完畢後,對每個組實施不同的特徵變換(Conv+Bn+Relu 等),然後將它們分成 R 份,這樣每份就包含原來的 K 個組,對每一份應用投票機制形成注意力(Softmax or Sigmoid),接著將這 R 份注意力與特徵圖對應相乘(element-wise multiply),最後將這 R 份結果加起來(element-wise sum)形成輸出,輸出相當於對應了原來的 K 個組。

梳理下,可以知道注意力在是分了 K 個組後再分R個組上執行的,記 R 中的每一份為 r,K 中的每一份為k,那麼每個 r 上得到的注意力是不同的,即每個 k split 下的每個 r 上的注意力不同,而同一個 r 下對應的不同 k 的注意力是一致的。

很奇妙,對於分得的K個組,每個組內切分R份分配不同的注意力,但不同組依次對應的這R份注意力卻分別是一致的,是謂同又不盡全同!

3

從程式碼出發,知行合一

看過 paper 和原始碼的朋友們可能會一頭霧水,paper 中展示的結構圖和程式碼實現的有出入,一開始 CW 也是如此,看了幾篇文但總感覺自己理解得依舊不那麼清晰,於是乎親自把程式碼手擼一遍,並結合畫圖理解,最終眼前的迷霧也就散開了。

我國古代優秀大佬王陽明推崇知行合一,雖然凡事不一定硬要知行結合,但是吾以為有了認知才有“行”的方向,“行”了才能加深認知或者說真正認知,這是一個迴圈,最終達到合二為一的高手境界。

接下來進入正題~

作者在原始碼中對於 split attention 的實現有兩種方式,分別對應兩個類,其中一個類為 SplAtConv2d,對應於上一部分展示的圖中結構;另一個類為 RadixMajorNaiveImp,對應下圖中的結構。

RadixMajorNaiveImp

你的 ResNet 是時候更新了—ResNeSt 來也

 

RadixMajorNaiveImp

結合上圖和程式碼,先來看看 RadixMajorNaiveImp 具體如何實現。

首先將輸入分為 KxR 個組,然後依次對K中的每個 k 執行注意力機制,具體做法是取出同一個 k 下的所有 r,然後把它們加起來,輸入全域性平均池化層和兩層全連線層。

你的 ResNet 是時候更新了—ResNeSt 來也

 

RadixMajorNaiveImp (i)

接著令通道這個維度等於 R,在這個維度上生成注意力權重,同時,將同一 k 下的所有 r 在通道這個維度上拼接起來,與注意力權重相乘,相乘後的結果分為 R 份,將這 R 份結果加起來形成這一個 k 的輸出,最終將K組中所有 k 的結果在通道數這個維度上拼接起來。

你的 ResNet 是時候更新了—ResNeSt 來也

 

RadixMajorNaiveImp (ii)

總的來說,這種方式就是依次對 K 組中的每份 k 進行處理,每份 k 進一步 split 成 R 份,其中每份 r 生成不同的注意力,K 組中的每份 k 都結合完注意力後,再將它們的結果在通道上拼接起來。

SplAtConv2d

接下來看看 SplAtConv2d 的實現方式。

你的 ResNet 是時候更新了—ResNeSt 來也

 

SplAtConv2d

仔細觀察上圖,我們可以發現,這種實現方式是將輸入分為 R 份,其中的每份 r 包含了 K 個組,每份 r 生成的注意力不同(對應上圖中的虛線框),上一節便說到了,同一 k 下 不同的 split r 上形成的注意力不一致,但不同的 k 對應相同的 r 上形成的注意力卻是一致的

再回顧下 RadixMajorNaiveImp 的實現方式,同一 k 下 不同的 split r 上形成的注意力也是不一致,但不同 k 的注意力是獨立生成的,它們之間並沒有聯絡,這就是兩種實現方式的最大差別了。

一起來瞄瞄程式碼~

你的 ResNet 是時候更新了—ResNeSt 來也

 

SplAtConv2d (i)

這裡提醒大家注意下,訓練過程中在測試這個模組時,記住把 batch size 設定大於1,由於使用了 global average pooling,輸出特徵的大小變為1x1,因此其後接 bn 的話(上圖中 self.bn1)就要求每個通道上多於一個元素,而如果 batch size 為1的話就會報錯了:

ValueError: Expected more than 1 value per channel when training

bn 是在每個通道上(channel-wise)做歸一化的,如果通道上只有1個元素,那麼歸一化就無意義了,所以在訓練過程中, bn 要求每個通道上必須多於1個元素。

你的 ResNet 是時候更新了—ResNeSt 來也

 

SplAtConv2d (ii)

另外,SplAtConv2d 這種實現方式不需要依次對 K 組中的每份進行處理,而是直接對 K 個組同時進行處理,相比於 RadixMajorNaiveImp 的方式更加簡潔些。

作者在 paper 和 github 原始碼中也給出了兩者等價性的證明,原始碼可以檢視《SplAtConv2d 和 RadixMajorNaiveImp 的等價性證明》,連結:https://github.com/zhanghang1989/ResNeSt/blob/master/tests/test_radix_major.py#L100

4

最後

對於 ResNeSt, 初次接觸時往往會感覺其程式碼實現和paper描述得有出入,因此要把它講述明白,自己本身一定要理解得透徹。如果沒有親自敲過一遍程式碼,就很難做到。對於其它演算法模型也一樣,能真正掌握的辦法就是親自上陣實踐一番,所謂知而不行,乃是未知。

深藍學院 發起了一個讀者討論對於這個新的ResNeSt有什麼想法呢?趕快參與【讀者討論】,與筆者溝通交流吧!

 

【參考】

原始碼連結:

https://github.com/zhanghang1989/ResNeSt

https://github.com/zhanghang1989/ResNeSt/blob/master/tests/test_radix_major.py#L100

相關文章