VGGNet详述
簡介
縱觀如今DeepLearning的研究領域,主要的研究領域還是CV和NLP及其衍生領域。CV的常見神經網絡中,VGGNet在卷積神經網絡的發展過程有著舉足輕重的作用,它作為著LeNet與Resnet的承接者,直到今天類VGG結構仍在圖像領域發揮著巨大的作用。由于最近的課題設計到VGGNet和Resnet的研究,在本項目著重實現每一個版本的VGGNet兼以近年的優化方式優化VGGNet。
-
論文標題
Very deep convolutional networks for large-scale image recognition
-
論文地址
https://arxiv.org/abs/1409.1556
-
論文源碼
https://github.com/pytorch/vision/blob/master/torchvision/models/vgg.py(PyTorch實現)
網絡說明
設計背景
VGGNet的基本模型主要由牛津大學的幾何視覺組(Visual Geometry Group)于2014年提出,這也是網絡名稱的由來,其獲得了2014年ILSVRC競賽的分類任務的第二名和定位任務的第一名,其最偉大之處在于證明使用3*3的小卷積核通過多層堆疊可以有效提高模型性能及泛化能力,這也是后面較長時間CNN傾向于小卷積核的原因之一。
結構說明
如圖所示,共有6種網絡配置,其實按照層數來說只有11層、13層、16層、19層四種,主要區別在于11層網絡嘗試了使用Local Response Normalisation(LRN),結果并沒有提高網絡性能,另一個區別就是16層(這就是著名的VGG16)結構在最后三個block使用不同的卷積核大小。網絡上可以找到很多概念結構圖,上面列舉了一個很經典的。
主要貢獻
使用3*3的小卷積核堆疊。
為什么使用3*3的小卷積核?這主要基于兩個考慮,不過解釋之前必須明確一個概念,兩個3*3卷積核可以獲得一個5*5的卷積核視野,三個3*3的卷積核堆疊可以獲得7*7的感受野。(注意,之間不能有池化層)
- 第一,三次卷積會進行三次非線性變換。這種非線性變換會有效提高不同信息的判別能力(即對差異的識別能力)。
- 第二,利用三個3*3代替一個7*7可以減少參數數量。(過多的參數是神經網絡的通有的問題)假設對于3通道,三個3*3卷積核的參數量為3×(32C2)=27C23\times (3^2C^2)=27C^23×(32C2)=27C2,而一個7*7卷積核參數量為72C2=49C27^2C^2=49C^272C2=49C2。
真正提高了網絡的深度。
- 更深的模型的好處就是可以進行更多的非線性映射次數,從而提高網絡的信息判別能力。這樣做的前提是參數量不會增加太多,這是基于小卷積核做到的。
- 使用1*1小卷積核也是為了增加非線性變換次數。
代碼實現
由于代碼過多,只列舉VGG16和VGG19的實現代碼,其余見文末Github。
def VGG16D(input_shape=(224, 224, 3), n_classes=1000):"""實現VGG16D的網絡結構(著名的VGG16)沒有使用Dropout和BN:param input_shape::param n_classes::return:"""# input layerinput_layer = Input(shape=input_shape)# block1x = Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='relu')(input_layer)x = Conv2D(64, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)# block2x = Conv2D(128, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(128, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)# block3x = Conv2D(256, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(256, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(256, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)# block4x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)x = BatchNormalization()(x)# block5x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)x = BatchNormalization()(x)# fcx = Flatten()(x)x = Dense(4096, activation='relu')(x)x = Dropout(rate=0.5)(x)x = Dense(4096, activation='relu')(x)x = Dropout(rate=0.5)(x)output_layer = Dense(n_classes, activation='softmax')(x)model = Model(inputs=input_layer, outputs=output_layer)return modeldef VGG19(input_shape=(224, 224, 3), n_classes=1000):"""實現VGG16C的網絡結構(著名的VGG16)沒有使用Dropout和BN:param input_shape::param n_classes::return:"""# input layerinput_layer = Input(shape=input_shape)# block1x = Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='relu')(input_layer)x = Conv2D(64, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)# block2x = Conv2D(128, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(128, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)# block3x = Conv2D(256, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(256, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(256, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(256, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)# block4x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)x = BatchNormalization()(x)# block5x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = Conv2D(512, (3, 3), strides=1, padding='same', activation='relu')(x)x = MaxPooling2D(2, 2, padding='same')(x)x = BatchNormalization()(x)# fcx = Flatten()(x)x = Dense(4096, activation='relu')(x)x = Dropout(rate=0.5)(x)x = Dense(4096, activation='relu')(x)x = Dropout(rate=0.5)(x)output_layer = Dense(n_classes, activation='softmax')(x)model = Model(inputs=input_layer, outputs=output_layer)return model為了編寫效率,均使用Function API。(事實上Keras的構建精髓正是Function API)
模型的訓練及測試均在Caltech101數據集上進行(該數據集由李飛飛整理,含一個干擾項)。為了比較模型性能,不進行數據增廣,采用同樣的優化函數Adam。模型訓練選取適中batch_size,為128,使用了BN和Dropout等訓練技巧(這不影響核心網絡結構)。后面的深層模型如VGG16和VGG19不使用Dropout或者BN難以訓練,每個block輸出時使用BN層。
訓練結果
損失圖像
從上圖可以看出,隨著模型深度加深,訓練集上損失收斂速度變慢,驗證集上損失收斂波動大。
準確率圖像
從上圖可以看出,隨著模型深度加深,訓練集上準確率上升變慢,在同樣的epoch下,到達的最終驗證集準確率變低。
整體看來,隨著模型加深,需要更多的訓練控制如Dropout和BN這樣的中間層來提高訓練效果。
關鍵提示
當使用VGGNet時一般使用的并非上述任何一種網絡模型,每個block的卷積層數目、是否使用Dropout、是否使用BN等完全依據當前任務修改即可。VGG最偉大之處絕對不是這個VGG模型而是小卷積核多層疊加的思想,這也是后來的卷積網絡的大部分采用的思路。
補充說明
本項目實現基于Keras2(TensorFlow后端)以及Python3。具體代碼已經開源于我的Github,歡迎star或者fork。訓練過程在ipynb文件內可見。如有疏漏,歡迎評論指出。
總結
- 上一篇: 卷积神经网络结构可视化工具PlotNeu
- 下一篇: ResNet详述