DenseNet详述
簡介
從2012年AlexNet大展身手以來,卷積神經網絡經歷了(LeNet、)AlexNet、ZFNet、VGGNet、GoogLeNet(借鑒Network in Network)、ResNet、DenseNet的大致發展路線。其實,自從ResNet提出之后,ResNet的變種網絡層出不窮,各有特點,性能都略有提高。
在這種情況下,DenseNet可以說是“繼往開來”也不為過,作為2017年CVPR最佳論文(認真研讀這篇論文,絕對會感覺心潮澎湃),DenseNet思想上部分借鑒了“前輩”ResNet,但是采用的確實完全不同的結構,結構上并不復雜卻十分有效,在CIFAR數據集上全面超越了ResNet。
本項目著重實現使用Keras搭建DenseNet的網絡結構,同時,利用其在數據集上進行效果評測。
-
論文標題
Densely Connected Convolutional Networks
-
論文地址
https://arxiv.org/abs/1608.06993
-
論文源碼
https://github.com/pytorch/vision/blob/master/torchvision/models/densenet.py
網絡說明
設計背景
以往的卷積神經網絡提高效果的方法,要么更深(如ResNet,解決了深層網絡出現的梯度消失問題),要么更寬(如GoogLeNet的Inception結構),而DenseNet的作者從feature著手,通過對feature的極致利用來達到更好的效果同時減少參數。
為了解決梯度消失(vanishing-gradient)問題,很多論文及結構被提出如ResNet、Highway Networks、Stochastic depth等,盡管網絡結構有所差異,但是都不難改變一個核心思路從靠前的層到靠后的層之間創建直通路線如之前提到的ResNet中的shortcut。DenseNet的作者延續這個思路,提出在保證網絡中層與層之間最大程度的信息傳輸的前提下,直接將所有層連接起來。
結構設計
dense block
為了實現上述的所有層的信息傳遞,提出下面這張dense block結構。在之前的卷積神經網絡中,如果有L層就會有L個連接,但是在DenseNet中,L層會有L×(L+1)÷2L \times (L+1) \div 2L×(L+1)÷2個連接,這種連接方式稱為全連接。在下圖中,x0x_0x0?是輸入的feature map,層H1H_1H1?的輸入就是x0x_0x0?,層H2H_2H2?的輸入則是x0x_0x0?和x1x_1x1?。
DenseNet的網絡很窄參數很少,這很大程度上得益于dense block的設計,在dense block中每個卷積層輸出的feature map數量很少(基本上小于100)。同時這種連接方式使得特征和梯度的傳遞更加有效,網絡更加容易訓練,主要是因為梯度消失的主要原因就是輸入信息以及梯度信息在很多層之間傳遞導致的(這和前饋運算以及反饋運算的計算機制有關),采用dense block連接則相當于每一層和input及loss直接相連,減輕梯度消失的發生。
同時,這種dense connection有正則化的效果,對于過擬合有一定的抑制作用。
網絡結構
流程示意
結構說明
有趣的是,這篇論文全文只出現了兩個公式卻讓人明明白白理解的論文的核心要義(比起很多大段都是公式羅列的灌水文章確實好太多了)。
x?=H?(x??1)+x??1\mathbf{x}_{\ell}=H_{\ell}\left(\mathbf{x}_{\ell-1}\right)+\mathbf{x}_{\ell-1}x??=H??(x??1?)+x??1?
上式借鑒ResNet思路,第l層的輸出是l-1層的輸出加上對l-1層輸出的非線性變換(激活)。
x?=H?([x0,x1,…,x??1])\mathbf{x}_{\ell}=H_{\ell}\left(\left[\mathbf{x}_{0}, \mathbf{x}_{1}, \ldots, \mathbf{x}_{\ell-1}\right]\right)x??=H??([x0?,x1?,…,x??1?])
上式由DenseNet提出,對0到l-1層的輸出做concatenation(通道合并,類似Inception),H表示BN、Relu和3*3卷積。
作者將整個DenseNet理解為多個dense block以及其他層的組合,這樣可以確保每個dense block的size統一方便concate。
可以看到,作者主要提出了四種網絡架構分別是DenseNet121、DenseNet169、DenseNet201以及DenseNet264,其中k表示growth rate,表示每個dense block每層輸出的feature map個數,為了避免過寬的網絡,設計均使用較小的k如32(事實證明,小的k具有更好的效果)。而且,由于后面的各層均可以得到前面各層的輸出,所以最后concat得到的feature map通道數不小。
同時,每個dense block的3*3卷積前都加了一個1*1卷積,即為bottleneck layer,以求減少feature map數量,降維的同時融合了各通道的特征。此外,為了進一步壓縮參數量,每兩個dense block之間增加了1*1卷積操作,它和池化層配合構成了Transition layer,經過該層默認channel減半。DenseNet-C表示增加了Transition layer,DenseNet-BC則表示既有bottleneck又有Transition layer。bottleneck主要是為了減少通道數進行降維,減少計算量;Transition layer主要目的也是對feature map進行降維,減少通道數。
網絡效果
DenseNet-BC的網絡參數和相同深度的DenseNet相比確實減少了很多,參數減少可以節省內存,減少過擬合現象的發生。總的來說,有力沖擊ResNet的地位。
網絡優點
- 減輕了梯度消失(vanishing-gradient)
- 大大加強了feature的傳遞
- 更加深入利用了feature
- 一定程度上減少了參數數量
代碼實現
由于DenseNet最基礎的DenseNet121都高達121層且代碼封裝度很高,對該模型進行了高度封裝。
實際使用各個深度學習框架已經封裝了DenseNet的幾種主要網絡結構(DenseNet121等),使用很方便,不建議自己搭建(尤其對于DenseNet這樣很深的網絡)。
網絡構建對照結構表即可,這是復現論文網絡結構的主要依據。默認參數實現的是帶bottleneck的DenseNet121,其他結構調整函數的參數即可,參考代碼。
from keras.models import Model from keras.layers import BatchNormalization, Conv2D, Activation, Dropout, AveragePooling2D, concatenate, GlobalAveragePooling2D, MaxPooling2D, Dense, Input from keras.regularizers import l2 import keras.backend as Kdef Conv_Block(input_tensor, filters, bottleneck=False, dropout_rate=None, weight_decay=1e-4):"""封裝卷積層:param input_tensor: 輸入張量:param filters: 卷積核數目:param bottleneck: 是否使用bottleneck:param dropout_rate: dropout比率:param weight_decay: 權重衰減率:return:"""concat_axis = 1 if K.image_data_format() == 'channel_first' else -1 # 確定格式x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(input_tensor)x = Activation('relu')(x)if bottleneck:# 使用bottleneck進行降維inter_channel = filters * 4x = Conv2D(inter_channel, (1, 1),kernel_initializer='he_normal',padding='same', use_bias=False,kernel_regularizer=l2(weight_decay))(x)x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(x)x = Activation('relu')(x)x = Conv2D(filters, (3, 3), kernel_initializer='he_normal', padding='same', use_bias=False)(x)if dropout_rate:x = Dropout(dropout_rate)(x)return xdef Transition_Block(input_tensor, filters, compression_rate, weight_decay=1e-4):"""封裝Translation layer:param input_tensor: 輸入張量:param filters: 卷積核數目:param compression_rate: 壓縮率:param weight_decay: 權重衰減率:return:"""concat_axis = 1 if K.image_data_format() == 'channel_first' else -1 # 確定格式x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(input_tensor)x = Activation('relu')(x)x = Conv2D(int(filters * compression_rate), (1, 1),kernel_initializer='he_normal',padding='same',use_bias=False,kernel_regularizer=l2(weight_decay))(x)x = AveragePooling2D((2, 2), strides=(2, 2))(x)return xdef Dense_Block(x, nb_layers, filters, growth_rate, bottleneck=False, dropout_rate=None, weight_decay=1e-4, grow_nb_filters=True, return_concat_list=False):"""實現核心的dense block:param x: 張量:param nb_layers: 模型添加的conv_block數目:param filters: 卷積核數目:param growth_rate: growth rate:param bottleneck: 是否加入bottleneck:param dropout_rate: dropout比率:param weight_decay: 權重衰減:param grow_nb_filters: 是否允許核數目增長:param return_concat_list: 是否返回feature map 的list:return:"""concat_axis = 1 if K.image_data_format() == 'channels_first' else -1x_list = [x]for i in range(nb_layers):cb = Conv_Block(x, growth_rate, bottleneck, dropout_rate, weight_decay)x_list.append(cb)x = concatenate([x, cb], axis=concat_axis)if grow_nb_filters:filters += growth_rateif return_concat_list:return x, filters, x_listelse:return x, filtersdef DenseNet(n_classes=1000, input_shape=(224, 224, 3), include_top=True, nb_dense_block=4, growth_rate=32, nb_filter=64,nb_layers_per_block=[6, 12, 24, 16], bottleneck=True, reduction=0.5, dropout_rate=0.0, weight_decay=1e-4,subsample_initial_block=True):concat_axis = 1 if K.image_data_format() == 'channel_first' else -1final_nb_layer = nb_layers_per_block[-1]nb_layers = nb_layers_per_block[:-1]compression = 1.0 - reductionif subsample_initial_block:initial_kernel = (7, 7)initial_strides = (2, 2)else:initial_kernel = (3, 3)initial_strides = (1, 1)input_tensor = Input(shape=input_shape)x = Conv2D(nb_filter, initial_kernel, kernel_initializer='he_normal', padding='same',strides=initial_strides, use_bias=False, kernel_regularizer=l2(weight_decay))(input_tensor)if subsample_initial_block:x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(x)x = Activation('relu')(x)x = MaxPooling2D((3, 3), strides=(2, 2), padding='same')(x)for block_index in range(nb_dense_block - 1):x, nb_filter = Dense_Block(x, nb_layers[block_index], nb_filter, growth_rate, bottleneck=bottleneck,dropout_rate=dropout_rate, weight_decay=weight_decay)x = Transition_Block(x, nb_filter, compression_rate=compression, weight_decay=weight_decay)nb_filter = int(nb_filter * compression)x, nb_filter = Dense_Block(x, final_nb_layer, nb_filter, growth_rate, bottleneck=bottleneck,dropout_rate=dropout_rate, weight_decay=weight_decay)x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(x)x = Activation('relu')(x)x = GlobalAveragePooling2D()(x)if include_top:x = Dense(n_classes, activation='softmax')(x)model = Model(input_tensor, x, name='densenet121')return model模型訓練
數據集使用Caltech101數據集,比較性能,不進行數據增廣(注意刪除干擾項)。Batch大小指定為32,使用BN訓練技巧,二次封裝Conv2D。損失函數使用經典分類的交叉熵損失函數,優化函數使用Adam,激活函數使用Relu。(這都是比較流行的選擇)
具體訓練結果見文末Github倉庫根目錄notebook文件。
損失圖像
準確率圖像
可以對比之前的ResNet,顯然,同一個數據集上同樣的超參數設置,DenseNet的收斂速度快了很多(較快達到飽和準確率且這是諸多結構網絡所能達到的最高驗證集準確率),但是這樣的快速收斂的代價就是內存的巨大消耗。
補充說明
DenseNet最偉大之處在于其核心思想為建立不同層之間的連接關系,充分利用feature,減輕梯度消失問題。同時配合以bottleneck和transition layer以降維減參。本項目源碼開源于我的Github,歡迎Star或者Fork。
總結
以上是生活随笔為你收集整理的DenseNet详述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度学习项目-人群密度估计
- 下一篇: Keras-数据增广