論文地址:arxiv-paper
實現代碼:github
Introduction
DenseNet在ResNet的基礎上(ResNet介紹),進一步擴展網絡連接,**對於網絡的任意一層,該層前面所有層的feature map都是這層的輸入,該層的feature map是後面所有層的輸入。**示意圖如下:
原本層的網絡有個連接,現在層的網絡共有個連接。
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路徑無法聯通?這是因爲是非線性的,即變現爲。
DenseNet的Insight
既然 Residual Connection 能夠讓模型趨向於寬網絡,那麼爲什麼不直接來個狠得,這就是 Densenet論文核心思想:對每一層的前面所有層都加一個單獨的 shortcut到該層,使得任意兩層網絡都可以直接“溝通”。即下圖:
這一舉看似粗暴,實則帶來不少好處:
- 從feature來考慮,每一層feature 被用到時,都可以被看作做了新的 normalization,論文3可以看到即便去掉BN, 深層 DenseNet也可以保證較好的收斂率。
- 從perceptual field來看,淺層和深層的field 可以更自由的組合,會使得模型的結果更加robust。
- 從 wide-network 來看, DenseNet 看以被看作一個真正的寬網絡,在訓練時會有比 ResNet 更穩定的梯度,收斂速度自然更好(paper的實驗可以佐證)
DenseNet
記模型的輸入圖片爲,模型由層組成,每層的非線性轉換函數爲,是層的序號。將層的輸出記爲。
Dense connectivity
DenseNet中每層的輸入是前面的所有層,故任何兩層之間都有連接。但在實際情況下,因爲多層之間feature maps大小不同,不便於任何兩層之間的組合,受到GoogleNet的啓發,**論文提出了Dense Block,即在每個Block內,所有layer都保持dense connectivity,而在Block之間是沒有dense connectivity,而是通過transition layer連接的。**如下圖:
Composite function
即單個Block內,層與層之間的非線性轉換函數就是Composite function,,每個Composite function的結構如下:
Transition layer
不同層的feature map大小不同,考慮到池化層在CNN模型內的重要性,提出一個Transition layer
用於連接Block與Block,每個Transition layer的結構如下:
Growth rate
如果一個輸出個feature maps,那麼層有個feature maps輸入。是輸入層的通道數。如果太多,即feature map太多,從而導致模型參數太多。這裏我們定義Growth rate就是超參數,用於控制feature maps的數量。
DenseNet-BC
Bottleneck layers
儘管每層只產生個feature maps,但還是很多。**這裏就要用到的小卷積來降維了。**作者發現在DenseNet上使用小卷積很有效,並定義了Bottleneck layers,結構如下:
並將使用Bottleneck layers的DenseNet表示爲DenseNet-B。(在paper實驗裏,將小卷積裏的設置爲)
Compression
考慮到feature maps的數量多,爲了進一步的提高模型的緊湊性,我們可以在transition layers上下手,如果Dense Block內包含個feature maps,那麼可以通過transition layers減少feature maps。
這裏讓transition layers輸出個feature maps,這樣就能通過控制參數來控制feature maps的數量了。我們把參數定義爲compression factor。
一般,在paper的實驗中,,使用compression factor的DenseNet記爲DenseNet-C。同時使用compression factor 和 Bottleneck layers的DenseNet記爲DenseNet-BC。
Implementation Details
非ImageNet數據集
- 使用3個Dense Block
- 每個Block都有相同的層數
- 模型爲DenseNet,配置爲
- 模型爲DenseNet-BC,配置爲
- 在送入第一個Dense Block前,會先送到一個16通道的卷積層
- 使用的小卷積,採用zero-padding保持feature map尺寸
- 最後一個Dense Block後接一個global average pooling,再跟softmax分類。
ImageNet數據集
-
使用的是DenseNet-BC
-
使用4個Dense Block
-
在送入第一個Dense Block前,會先送到一個的的卷積層
-
所有的layers的feature map都設置爲
在ImageNet上,具體的DenseNet-BC如下圖:
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 | |
Nesterov momentum | 0.9 |
dropout | 在C10,C100,SVHN上,沒有使用data augmentation,則在每個卷積層後添加dropout layer(除了第一個),並設置爲0.2 |
實驗結果
DenseNet在CIFAR和SVHV上的表現如下:
表示網絡深度,爲growth rate。藍色字體表示最優結果,表示對原數據庫進行data augmentation。DenseNet相比ResNet取得更低的錯誤率,且參數更少。
DenseNet在ImageNet上的表現如下:
可以看到DenseNet相比於ResNet有着更少的參數,更好的測試結果。
Conclusion
DenseNet的優點在前面講過了,總結的來說就是Feature Reuse,模型Robustness。這裏主要關注DenseNet的缺點。
DenseNet 的缺點
在圖中可以看到,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),那麼內存依賴大概是這個樣子的:
對於 Backward 來說,深紅色的算完一塊就可以扔掉(它的出度爲1),這也是幾乎所有16 年以後新framework都會做的 shareGradient 優化。
但是淺紅色的內存塊因爲要在backward的時候還會被用到,所以不能扔,那腫麼辦?[1] 說可以用時間換空間,即在需要用粉紅塊的時候,重新計算即可。而對於 DenseNet 來說,每個 DenseLayer (Concat-BN-ReLU-Conv),Concat 和 BN 兩層的 output 全扔掉就可以省下很多內存,卻只多花了 15% 的計算量。
現在已經整理出來的乾淨代碼有 Torch 版本,見PyTorch版,有不同 level 的優化,最多能省 70% 的顯存。