論文DenseNet(Densely Connected Convolutional Networks)解讀

論文地址:arxiv-paper

實現代碼:github

Introduction

DenseNet在ResNet的基礎上(ResNet介紹),進一步擴展網絡連接,**對於網絡的任意一層,該層前面所有層的feature map都是這層的輸入,該層的feature map是後面所有層的輸入。**示意圖如下:

mark

原本LL層的網絡有LL個連接,現在LL層的網絡共有CL+12=L(L+1)2C_{L+1}^2=\frac{L(L+1)}{2}個連接。

DenseNet有幾個明顯的優點:

  • 減輕了梯度消失問題(vanishing-gradient problem)
  • 增強了feature map的傳播,利用率也上升了(前面層的feature map直接傳給後面,利用更充分了)
  • 大大減少了參數量


Related Work

在CNN模型裏,傳統的feed-forward架構可以視爲狀態模型,狀態在層與層之間傳播,每一層讀取它上一層狀態,改變狀態並保留一些需要保留的信息並將裝備傳給下一層。ResNet通過增加額外的identity transformations讓狀態內需要保留的信息顯性化。作者的另一篇paper指出ResNet中有一個非常有意義的現象:網絡的許多層貢獻較小並且在訓練過程中可以被隨機丟棄。

這裏引用Lyken的回答,分析ResNet,其gradient的主要來源是residual分支;在測試過程中,即便移除掉正常鏈接(僅留下 shortcut),模型照樣能保持較好的正確率。

本論文指出了residual connection實質是 highway network 的一種特殊例子,將 ResNet 展開以後,論文1指出帶 Residual Connection 的深網絡可以“近似地”看作寬模型(印證了爲什麼移除主幹連接不會大幅影響正確率)。

ResNet再分析

但是ResNet和真正的寬模型還是不同的:Forward 時兩者可以看做相同,但 backward 時有部分 gradient 路徑無法聯通。也就是說, ResNet 在回傳 gradient 時,尚有提升的空間,這就是爲什麼 ResNeXt,Wide-ResNet 等文章能有提升的原因:因爲 ResNet 並不是真正的寬模型

以ResNet中一個Residual unit的gradient回傳爲例,示意圖如下:

在這裏插入圖片描述

KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ y_2 &= y_1+f_2…

注意上式的不等於,爲什麼backward的部分gradient路徑無法聯通?這是因爲f2()f_2(·)是非線性的,即變現爲f2(y0+f1(y0,w1),w2)f2(y0,w2)+f2(f1(y0,w1),w2)f_2(y_0+f_1(y_0,w_1),w_2)≠ f_2(y_0,w_2)+f_2(f_1(y_0,w_1),w_2)

DenseNet的Insight

既然 Residual Connection 能夠讓模型趨向於寬網絡,那麼爲什麼不直接來個狠得,這就是 Densenet論文核心思想:對每一層的前面所有層都加一個單獨的 shortcut到該層,使得任意兩層網絡都可以直接“溝通”。即下圖:

mark

這一舉看似粗暴,實則帶來不少好處:

  • 從feature來考慮,每一層feature 被用到時,都可以被看作做了新的 normalization,論文3可以看到即便去掉BN, 深層 DenseNet也可以保證較好的收斂率。
  • 從perceptual field來看,淺層和深層的field 可以更自由的組合,會使得模型的結果更加robust。
  • 從 wide-network 來看, DenseNet 看以被看作一個真正的寬網絡,在訓練時會有比 ResNet 更穩定的梯度,收斂速度自然更好(paper的實驗可以佐證)


DenseNet

記模型的輸入圖片爲xox_o,模型由LL層組成,每層的非線性轉換函數爲Hl()H_l(·)ll是層的序號。將lthl^{th}層的輸出記爲xlx_l

Dense connectivity

DenseNet中每層的輸入是前面的所有層,故任何兩層之間都有連接。但在實際情況下,因爲多層之間feature maps大小不同,不便於任何兩層之間的組合,受到GoogleNet的啓發,**論文提出了Dense Block,即在每個Block內,所有layer都保持dense connectivity,而在Block之間是沒有dense connectivity,而是通過transition layer連接的。**如下圖:

mark

Composite function

即單個Block內,層與層之間的非線性轉換函數Hl()H_l(·)就是Composite function,,每個Composite function的結構如下:
BNReLUConv(3×3)BN \rightarrow ReLU \rightarrow Conv_{(3×3)}

Transition layer

不同層的feature map大小不同,考慮到池化層在CNN模型內的重要性,提出一個Transition layer用於連接Block與Block,每個Transition layer的結構如下:
BNConv(1×1)Avg  Pool(2×2) BN \rightarrow Conv_{(1×1)} \rightarrow Avg \ \ Pool_{(2×2)}

Growth rate

如果一個HlH_l輸出kk個feature maps,那麼lthl^{th}層有k0+k×(l1)k_0+k×(l-1)個feature maps輸入。k0k_0是輸入層的通道數。如果kk太多,即feature map太多,從而導致模型參數太多。這裏我們定義Growth rate就是超參數kk,用於控制feature maps的數量。

DenseNet-BC

Bottleneck layers

儘管每層只產生kk個feature maps,但還是很多。**這裏就要用到1×1×n1×1×n的小卷積來降維了。**作者發現在DenseNet上使用1×1×n1×1×n小卷積很有效,並定義了Bottleneck layers,結構如下:
BNReLUConv(1×1)BNReLUConv(3×3)BN \rightarrow ReLU \rightarrow Conv_{(1×1)} \rightarrow BN \rightarrow ReLU \rightarrow Conv_{(3×3)}

並將使用Bottleneck layers的DenseNet表示爲DenseNet-B。(在paper實驗裏,將1×1×n1×1×n小卷積裏的nn設置爲4k4k)

Compression

考慮到feature maps的數量多,爲了進一步的提高模型的緊湊性,我們可以在transition layers上下手,如果Dense Block內包含mm個feature maps,那麼可以通過transition layers減少feature maps。

這裏讓transition layers輸出θm\left \lfloor \theta m \right \rfloor個feature maps,這樣就能通過控制參數θ\theta來控制feature maps的數量了。我們把參數θ\theta定義爲compression factor。

一般0<θ<10<\theta<1,在paper的實驗中,θ=0.5\theta=0.5使用compression factor的DenseNet記爲DenseNet-C。同時使用compression factor 和 Bottleneck layers的DenseNet記爲DenseNet-BC

Implementation Details

非ImageNet數據集

  • 使用3個Dense Block
  • 每個Block都有相同的層數
  • 模型爲DenseNet,配置爲{L=40,k=12}\{L=40,k=12\},{L=100,k=12}\{L=100,k=12\},{L=100,k=24}\{L=100,k=24\}
  • 模型爲DenseNet-BC,配置爲{L=100,k=12}\{L=100,k=12\},{L=250,k=24}\{L=250,k=24\},{L=190,k=40}\{L=190,k=40\}
  • 在送入第一個Dense Block前,會先送到一個16通道的卷積層
  • 使用3×33×3的小卷積,採用zero-padding保持feature map尺寸
  • 最後一個Dense Block後接一個global average pooling,再跟softmax分類。

ImageNet數據集

  • 使用的是DenseNet-BC

  • 使用4個Dense Block

  • 在送入第一個Dense Block前,會先送到一個7×7×2k7×7×2kstride=2stride=2的卷積層

  • 所有的layers的feature map都設置爲kk

在ImageNet上,具體的DenseNet-BC如下圖:
mark



Experiments

Training Details

所有的網絡都是使用SGD訓練的,具體的batch和learning rate設置如下:

數據集 description
CIFAR and SCHN batch size 64 for 300 and 40 epochs
learning rate初始設置爲0.1,在epoch執行到50%和75%的時候降低10倍
ImageNet batch size 256 for 90 epochs
learning rate初始設置爲0.1,在epoch執行到30和60的時候降低10倍
DenseNet-161
考慮到GPU顯存問題
mini-batch size 128 for 100 epochs
learning rate初始設置爲0.1,在epoch執行到90的時候降低10倍
其他設置 description
weight decay 10410^{-4}
Nesterov momentum 0.9
dropout 在C10,C100,SVHN上,沒有使用data augmentation,則在每個卷積層後添加dropout layer(除了第一個),並設置爲0.2

實驗結果

DenseNet在CIFAR和SVHV上的表現如下:

mark

LL表示網絡深度,kk爲growth rate。藍色字體表示最優結果,++表示對原數據庫進行data augmentation。DenseNet相比ResNet取得更低的錯誤率,且參數更少。

DenseNet在ImageNet上的表現如下:

mark

可以看到DenseNet相比於ResNet有着更少的參數,更好的測試結果。



Conclusion

DenseNet的優點在前面講過了,總結的來說就是Feature Reuse,模型Robustness。這裏主要關注DenseNet的缺點。

DenseNet 的缺點

mark

在圖中可以看到,DenseNet-100層增長率爲24時(無BottleNeck的最早版),parameter快要是ResNet-1001的三倍了。一般顯卡根本塞不下更深的DenseNet。在不斷的優化後,DenseNet 的顯存問題已大有改善。

但Flops消耗問題仍令人頭疼。本來 DenseNet 的實時性尚還可以(拓撲序跟普通網絡一樣),但由於其過多的Dense 的num_filters,計算量就超過了很多卡的上限。爲了優化這兩個問題,論文中採用了bottleneck和compression來大幅壓縮filters數目。(將DenseNet實用–>bengio組的DenseNet for segmentation

爲什麼會很耗費顯存

引用taineleau的回答。首先,無論是什麼 framework的NN,都由forward和backward兩部分構成。假設只考慮一個 feed-forward network,並且移除所有 in-place 操作(如 ReLU),那麼內存依賴大概是這個樣子的:

mark

inputi=fwi(inputi1)gradInputi=bwi(inputi1,gradInputi+1) input_i = fw_i(input_{i-1}) \\ gradInput_i = bw_i(input_{i-1}, gradInput_{i+1})

對於 Backward 來說,深紅色的gradInputgradInput算完一塊就可以扔掉(它的出度爲1),這也是幾乎所有16 年以後新framework都會做的 shareGradient 優化。

但是淺紅色的內存塊因爲要在backward的時候還會被用到,所以不能扔,那腫麼辦?[1] 說可以用時間換空間,即在需要用粉紅塊的時候,重新計算即可。而對於 DenseNet 來說,每個 DenseLayer (Concat-BN-ReLU-Conv),Concat 和 BN 兩層的 output 全扔掉就可以省下很多內存,卻只多花了 15% 的計算量。

mark

現在已經整理出來的乾淨代碼有 Torch 版本,見PyTorch版,有不同 level 的優化,最多能省 70% 的顯存。



參考資料

Lyken回答

發佈了77 篇原創文章 · 獲贊 572 · 訪問量 67萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章