CNN网络架构演进:从LeNet到DenseNet
原文來源:https://www.cnblogs.com/skyfsm/p/8451834.html
卷積神經(jīng)網(wǎng)絡(luò)可謂是現(xiàn)在深度學(xué)習(xí)領(lǐng)域中大紅大紫的網(wǎng)絡(luò)框架,尤其在計(jì)算機(jī)視覺領(lǐng)域更是一枝獨(dú)秀。CNN從90年代的LeNet開始,21世紀(jì)初沉寂了10年,直到12年AlexNet開始又再煥發(fā)第二春,從ZF Net到VGG,GoogLeNet再到ResNet和最近的DenseNet,網(wǎng)絡(luò)越來越深,架構(gòu)越來越復(fù)雜,解決反向傳播時(shí)梯度消失的方法也越來越巧妙。新年有假期,就好好總結(jié)一波CNN的各種經(jīng)典架構(gòu)吧,領(lǐng)略一下CNN的發(fā)展歷程中各路大神之間的智慧碰撞之美。
上面那圖是ILSVRC歷年的Top-5錯(cuò)誤率,我們會按照以上經(jīng)典網(wǎng)絡(luò)出現(xiàn)的時(shí)間順序?qū)λ麄冞M(jìn)行介紹。
本文將會談到以下經(jīng)典的卷積神經(jīng)網(wǎng)絡(luò):
開山之作:LeNet
閃光點(diǎn):定義了CNN的基本組件,是CNN的鼻祖。
LeNet是卷積神經(jīng)網(wǎng)絡(luò)的祖師爺LeCun在1998年提出,用于解決手寫數(shù)字識別的視覺任務(wù)。自那時(shí)起,CNN的最基本的架構(gòu)就定下來了:卷積層、池化層、全連接層。如今各大深度學(xué)習(xí)框架中所使用的LeNet都是簡化改進(jìn)過的LeNet-5(-5表示具有5個(gè)層),和原始的LeNet有些許不同,比如把激活函數(shù)改為了現(xiàn)在很常用的ReLu。
LeNet-5跟現(xiàn)有的conv->pool->ReLU的套路不同,它使用的方式是conv1->pool->conv2->pool2再接全連接層,但是不變的是,卷積層后緊接池化層的模式依舊不變。
以上圖為例,對經(jīng)典的LeNet-5做深入分析:
LeNet的Keras實(shí)現(xiàn):
def LeNet():model = Sequential()model.add(Conv2D(32,(5,5),strides=(1,1),input_shape=(28,28,1),padding='valid',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Conv2D(64,(5,5),strides=(1,1),padding='valid',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Flatten())model.add(Dense(100,activation='relu'))model.add(Dense(10,activation='softmax'))return model王者歸來:AlexNet
AlexNet在2012年ImageNet競賽中以超過第二名10.9個(gè)百分點(diǎn)的絕對優(yōu)勢一舉奪冠,從此深度學(xué)習(xí)和卷積神經(jīng)網(wǎng)絡(luò)名聲鵲起,深度學(xué)習(xí)的研究如雨后春筍般出現(xiàn),AlexNet的出現(xiàn)可謂是卷積神經(jīng)網(wǎng)絡(luò)的王者歸來。
閃光點(diǎn):
- 更深的網(wǎng)絡(luò)
- 數(shù)據(jù)增廣
- ReLU
- dropout
- LRN
以上圖AlexNet架構(gòu)為例,這個(gè)網(wǎng)絡(luò)前面5層是卷積層,后面三層是全連接層,最終softmax輸出是1000類,取其前兩層進(jìn)行詳細(xì)說明。
AlexNet共包含5層卷積層和三層全連接層,層數(shù)比LeNet多了不少,但卷積神經(jīng)網(wǎng)絡(luò)總的流程并沒有變化,只是在深度上加了不少。
AlexNet針對的是1000類的分類問題,輸入圖片規(guī)定是256×256的三通道彩色圖片,為了增強(qiáng)模型的泛化能力,避免過擬合,作者使用了隨機(jī)裁剪的思路對原來256×256的圖像進(jìn)行隨機(jī)裁剪,得到尺寸為3×224×224的圖像,輸入到網(wǎng)絡(luò)訓(xùn)練。
因?yàn)槭褂枚郍PU訓(xùn)練,所以可以看到第一層卷積層后有兩個(gè)完全一樣的分支,以加速訓(xùn)練。
。然后經(jīng)過歸一化處理,歸一化運(yùn)算的尺度為5*5。第一卷積層運(yùn)算結(jié)束后形成的像素層的規(guī)模為48×27×27。
輸入矩陣是[48,55,55].接著是池化層,做max pooling操作,池化運(yùn)算的尺度為3*3,運(yùn)算的步長為2,則池化后圖像的尺寸為(55-3)/2+1=27。所以得到的輸出矩陣是[48,27,27]。后面層不再重復(fù)敘述。
AlexNet用到訓(xùn)練技巧:
- 數(shù)據(jù)增廣技巧來增加模型泛化能力。
- 用ReLU代替Sigmoid來加快SGD的收斂速度
- Dropout:Dropout原理類似于淺層學(xué)習(xí)算法的中集成算法,該方法通過讓全連接層的神經(jīng)元(該模型在前兩個(gè)全連接層引入Dropout)以一定的概率失去活性(比如0.5)失活的神經(jīng)元不再參與前向和反向傳播,相當(dāng)于約有一半的神經(jīng)元不再起作用。在測試的時(shí)候,讓所有神經(jīng)元的輸出乘0.5。Dropout的引用,有效緩解了模型的過擬合。
- Local Responce Normalization:局部響應(yīng)歸一層的基本思路是,假如這是網(wǎng)絡(luò)的一塊,比如是 13×13×256, LRN 要做的就是選取一個(gè)位置,比如說這樣一個(gè)位置,從這個(gè)位置穿過整個(gè)通道,能得到 256 個(gè)數(shù)字,并進(jìn)行歸一化。進(jìn)行局部響應(yīng)歸一化的動機(jī)是,對于這張 13×13 的圖像中的每個(gè)位置來說,我們可能并不需要太多的高激活神經(jīng)元。但是后來,很多研究者發(fā)現(xiàn) LRN 起不到太大作用,因?yàn)椴⒉恢匾?#xff0c;而且我們現(xiàn)在并不用 LRN 來訓(xùn)練網(wǎng)絡(luò)。
AlexNet的Keras實(shí)現(xiàn):
def AlexNet():model = Sequential()model.add(Conv2D(96,(11,11),strides=(4,4),input_shape=(227,227,3),padding='valid',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(3,3),strides=(2,2)))model.add(Conv2D(256,(5,5),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(3,3),strides=(2,2)))model.add(Conv2D(384,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(384,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(3,3),strides=(2,2)))model.add(Flatten())model.add(Dense(4096,activation='relu'))model.add(Dropout(0.5))model.add(Dense(4096,activation='relu'))model.add(Dropout(0.5))model.add(Dense(1000,activation='softmax'))return model穩(wěn)步前行:ZF-Net
ZFNet是2013ImageNet分類任務(wù)的冠軍,其網(wǎng)絡(luò)結(jié)構(gòu)沒什么改進(jìn),只是調(diào)了調(diào)參,性能較Alex提升了不少。ZF-Net只是將AlexNet第一層卷積核由11變成7,步長由4變?yōu)?,第3,4,5卷積層轉(zhuǎn)變?yōu)?84,384,256。這一年的ImageNet還是比較平靜的一屆,其冠軍ZF-Net的名堂也沒其他屆的經(jīng)典網(wǎng)絡(luò)架構(gòu)響亮。
ZF-Net的Keras實(shí)現(xiàn):
def ZF_Net():model = Sequential() model.add(Conv2D(96,(7,7),strides=(2,2),input_shape=(224,224,3),padding='valid',activation='relu',kernel_initializer='uniform')) model.add(MaxPooling2D(pool_size=(3,3),strides=(2,2))) model.add(Conv2D(256,(5,5),strides=(2,2),padding='same',activation='relu',kernel_initializer='uniform')) model.add(MaxPooling2D(pool_size=(3,3),strides=(2,2))) model.add(Conv2D(384,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform')) model.add(Conv2D(384,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform')) model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform')) model.add(MaxPooling2D(pool_size=(3,3),strides=(2,2))) model.add(Flatten()) model.add(Dense(4096,activation='relu')) model.add(Dropout(0.5)) model.add(Dense(4096,activation='relu')) model.add(Dropout(0.5)) model.add(Dense(1000,activation='softmax')) return model越走越深:VGG-Nets
VGG-Nets是由牛津大學(xué)VGG(Visual Geometry Group)提出,是2014年ImageNet競賽定位任務(wù)的第一名和分類任務(wù)的第二名的中的基礎(chǔ)網(wǎng)絡(luò)。VGG可以看成是加深版本的AlexNet. 都是conv layer + FC layer,在當(dāng)時(shí)看來這是一個(gè)非常深的網(wǎng)絡(luò)了,因?yàn)閷訑?shù)高達(dá)十多層,我們從其論文名字就知道了(《Very Deep Convolutional Networks for Large-Scale Visual Recognition》),當(dāng)然以現(xiàn)在的目光看來VGG真的稱不上是一個(gè)very deep的網(wǎng)絡(luò)。
上面一個(gè)表格是描述的是VGG-Net的網(wǎng)絡(luò)結(jié)構(gòu)以及誕生過程。為了解決初始化(權(quán)重初始化)等問題,VGG采用的是一種Pre-training的方式,這種方式在經(jīng)典的神經(jīng)網(wǎng)絡(luò)中經(jīng)常見得到,就是先訓(xùn)練一部分小網(wǎng)絡(luò),然后再確保這部分網(wǎng)絡(luò)穩(wěn)定之后,再在這基礎(chǔ)上逐漸加深。表1從左到右體現(xiàn)的就是這個(gè)過程,并且當(dāng)網(wǎng)絡(luò)處于D階段的時(shí)候,效果是最優(yōu)的,因此D階段的網(wǎng)絡(luò)也就是VGG-16了!E階段得到的網(wǎng)絡(luò)就是VGG-19了!VGG-16的16指的是conv+fc的總層數(shù)是16,是不包括max pool的層數(shù)!
下面這個(gè)圖就是VGG-16的網(wǎng)絡(luò)結(jié)構(gòu)。
由上圖看出,VGG-16的結(jié)構(gòu)非常整潔,深度較AlexNet深得多,里面包含多個(gè)conv->conv->max_pool這類的結(jié)構(gòu),VGG的卷積層都是same的卷積,即卷積過后的輸出圖像的尺寸與輸入是一致的,它的下采樣完全是由max pooling來實(shí)現(xiàn)。
VGG網(wǎng)絡(luò)后接3個(gè)全連接層,filter的個(gè)數(shù)(卷積后的輸出通道數(shù))從64開始,然后沒接一個(gè)pooling后其成倍的增加,128、512,VGG的注意貢獻(xiàn)是使用小尺寸的filter,及有規(guī)則的卷積-池化操作。
閃光點(diǎn):
- 卷積層使用更小的filter尺寸和間隔
與AlexNet相比,可以看出VGG-Nets的卷積核尺寸還是很小的,比如AlexNet第一層的卷積層用到的卷積核尺寸就是11*11,這是一個(gè)很大卷積核了。而反觀VGG-Nets,用到的卷積核的尺寸無非都是1×1和3×3的小卷積核,可以替代大的filter尺寸。
3×3卷積核的優(yōu)點(diǎn):
- 多個(gè)3×3的卷基層比一個(gè)大尺寸filter卷基層有更多的非線性,使得判決函數(shù)更加具有判決性
- 多個(gè)3×3的卷積層比一個(gè)大尺寸的filter有更少的參數(shù),假設(shè)卷基層的輸入和輸出的特征圖大小相同為C,那么三個(gè)3×3的卷積層參數(shù)個(gè)數(shù)3×(3×3×C×C)=27CC;一個(gè)7×7的卷積層參數(shù)為49CC;所以可以把三個(gè)3×3的filter看成是一個(gè)7×7filter的分解(中間層有非線性的分解)
1*1卷積核的優(yōu)點(diǎn):
- 作用是在不影響輸入輸出維數(shù)的情況下,對輸入進(jìn)行線性形變,然后通過Relu進(jìn)行非線性處理,增加網(wǎng)絡(luò)的非線性表達(dá)能力。
VGG-16的Keras實(shí)現(xiàn):
def VGG_16(): model = Sequential()model.add(Conv2D(64,(3,3),strides=(1,1),input_shape=(224,224,3),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(64,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Conv2D(128,(3,2),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(128,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Flatten())model.add(Dense(4096,activation='relu'))model.add(Dropout(0.5))model.add(Dense(4096,activation='relu'))model.add(Dropout(0.5))model.add(Dense(1000,activation='softmax'))return model大浪推手:GoogLeNet
GoogLeNet在2014的ImageNet分類任務(wù)上擊敗了VGG-Nets奪得冠軍,其實(shí)力肯定是非常深厚的,GoogLeNet跟AlexNet,VGG-Nets這種單純依靠加深網(wǎng)絡(luò)結(jié)構(gòu)進(jìn)而改進(jìn)網(wǎng)絡(luò)性能的思路不一樣,它另辟幽徑,在加深網(wǎng)絡(luò)的同時(shí)(22層),也在網(wǎng)絡(luò)結(jié)構(gòu)上做了創(chuàng)新,引入Inception結(jié)構(gòu)代替了單純的卷積+激活的傳統(tǒng)操作(這思路最早由Network in Network提出)。GoogLeNet進(jìn)一步把對卷積神經(jīng)網(wǎng)絡(luò)的研究推上新的高度。
閃光點(diǎn):
- 引入Inception結(jié)構(gòu)
- 中間層的輔助LOSS單元
- 后面的全連接層全部替換為簡單的全局平均pooling
上圖結(jié)構(gòu)就是Inception,結(jié)構(gòu)里的卷積stride都是1,另外為了保持特征響應(yīng)圖大小一致,都用了零填充。最后每個(gè)卷積層后面都立刻接了個(gè)ReLU層。在輸出前有個(gè)叫concatenate的層,直譯的意思是“并置”,即把4組不同類型但大小相同的特征響應(yīng)圖一張張并排疊起來,形成新的特征響應(yīng)圖。Inception結(jié)構(gòu)里主要做了兩件事:1. 通過3×3的池化、以及1×1、3×3和5×5這三種不同尺度的卷積核,一共4種方式對輸入的特征響應(yīng)圖做了特征提取。2. 為了降低計(jì)算量。同時(shí)讓信息通過更少的連接傳遞以達(dá)到更加稀疏的特性,采用1×1卷積核來實(shí)現(xiàn)降維。
這里想再詳細(xì)談?wù)?×1卷積核的作用,它究竟是怎么實(shí)現(xiàn)降維的。現(xiàn)在運(yùn)算如下:下面圖1是3×3卷積核的卷積,圖2是1×1卷積核的卷積過程。對于單通道輸入,1×1的卷積確實(shí)不能起到降維作用,但對于多通道輸入,就不不同了。假設(shè)你有256個(gè)特征輸入,256個(gè)特征輸出,同時(shí)假設(shè)Inception層只執(zhí)行3×3的卷積。這意味著總共要進(jìn)行 256×256×3×3的卷積(589000次乘積累加(MAC)運(yùn)算)。這可能超出了我們的計(jì)算預(yù)算,比方說,在Google服務(wù)器上花0.5毫秒運(yùn)行該層。作為替代,我們決定減少需要卷積的特征的數(shù)量,比如減少到64(256/4)個(gè)。在這種情況下,我們首先進(jìn)行256到64的1×1卷積,然后在所有Inception的分支上進(jìn)行64次卷積,接著再使用一個(gè)64到256的1×1卷積。
- 256×64×1×1 = 16000
- 64×64×3×3 = 36000
- 64×256×1×1 = 16000
現(xiàn)在的計(jì)算量大約是70000(即16000+36000+16000),相比之前的約600000,幾乎減少了10倍。這就通過小卷積核實(shí)現(xiàn)了降維。
現(xiàn)在再考慮一個(gè)問題:為什么一定要用1×1卷積核,3×3不也可以嗎?考慮[50,200,200]的矩陣輸入,我們可以使用20個(gè)1×1的卷積核進(jìn)行卷積,得到輸出[20,200,200]。有人問,我用20個(gè)3×3的卷積核不是也能得到[20,200,200]的矩陣輸出嗎,為什么就使用1×1的卷積核?我們計(jì)算一下卷積參數(shù)就知道了,對于1×1的參數(shù)總數(shù):20×200×200×(1×1),對于3×3的參數(shù)總數(shù):20×200×200×(3×3),可以看出,使用1×1的參數(shù)總數(shù)僅為3×3的總數(shù)的九分之一!所以我們使用的是1×1卷積核。
GoogLeNet網(wǎng)絡(luò)結(jié)構(gòu)中有3個(gè)LOSS單元,這樣的網(wǎng)絡(luò)設(shè)計(jì)是為了幫助網(wǎng)絡(luò)的收斂。在中間層加入輔助計(jì)算的LOSS單元,目的是計(jì)算損失時(shí)讓低層的特征也有很好的區(qū)分能力,從而讓網(wǎng)絡(luò)更好地被訓(xùn)練。在論文中,這兩個(gè)輔助LOSS單元的計(jì)算被乘以0.3,然后和最后的LOSS相加作為最終的損失函數(shù)來訓(xùn)練網(wǎng)絡(luò)。
GoogLeNet還有一個(gè)閃光點(diǎn)值得一提,那就是將后面的全連接層全部替換為簡單的全局平均pooling,在最后參數(shù)會變的更少。而在AlexNet中最后3層的全連接層參數(shù)差不多占總參數(shù)的90%,使用大網(wǎng)絡(luò)在寬度和深度允許GoogleNet移除全連接層,但并不會影響到結(jié)果的精度,在ImageNet中實(shí)現(xiàn)93.3%的精度,而且要比VGG還要快。
GoogLeNet的Keras實(shí)現(xiàn):
def Conv2d_BN(x, nb_filter,kernel_size, padding='same',strides=(1,1),name=None):if name is not None:bn_name = name + '_bn'conv_name = name + '_conv'else:bn_name = Noneconv_name = Nonex = Conv2D(nb_filter,kernel_size,padding=padding,strides=strides,activation='relu',name=conv_name)(x)x = BatchNormalization(axis=3,name=bn_name)(x)return xdef Inception(x,nb_filter):branch1x1 = Conv2d_BN(x,nb_filter,(1,1), padding='same',strides=(1,1),name=None)branch3x3 = Conv2d_BN(x,nb_filter,(1,1), padding='same',strides=(1,1),name=None)branch3x3 = Conv2d_BN(branch3x3,nb_filter,(3,3), padding='same',strides=(1,1),name=None)branch5x5 = Conv2d_BN(x,nb_filter,(1,1), padding='same',strides=(1,1),name=None)branch5x5 = Conv2d_BN(branch5x5,nb_filter,(1,1), padding='same',strides=(1,1),name=None)branchpool = MaxPooling2D(pool_size=(3,3),strides=(1,1),padding='same')(x)branchpool = Conv2d_BN(branchpool,nb_filter,(1,1),padding='same',strides=(1,1),name=None)x = concatenate([branch1x1,branch3x3,branch5x5,branchpool],axis=3)return xdef GoogLeNet():inpt = Input(shape=(224,224,3))#padding = 'same',填充為(步長-1)/2,還可以用ZeroPadding2D((3,3))x = Conv2d_BN(inpt,64,(7,7),strides=(2,2),padding='same')x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)x = Conv2d_BN(x,192,(3,3),strides=(1,1),padding='same')x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)x = Inception(x,64)#256x = Inception(x,120)#480x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)x = Inception(x,128)#512x = Inception(x,128)x = Inception(x,128)x = Inception(x,132)#528x = Inception(x,208)#832x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)x = Inception(x,208)x = Inception(x,256)#1024x = AveragePooling2D(pool_size=(7,7),strides=(7,7),padding='same')(x)x = Dropout(0.4)(x)x = Dense(1000,activation='relu')(x)x = Dense(1000,activation='softmax')(x)model = Model(inpt,x,name='inception')return model里程碑式創(chuàng)新:ResNet
2015年何愷明推出的ResNet在ISLVRC和COCO上橫掃所有選手,獲得冠軍。ResNet在網(wǎng)絡(luò)結(jié)構(gòu)上做了大創(chuàng)新,而不再是簡單的堆積層數(shù),ResNet在卷積神經(jīng)網(wǎng)絡(luò)的新思路,絕對是深度學(xué)習(xí)發(fā)展歷程上里程碑式的事件。
閃光點(diǎn):
- 層數(shù)非常深,已經(jīng)超過百層
- 引入殘差單元來解決退化問題
從前面可以看到,隨著網(wǎng)絡(luò)深度增加,網(wǎng)絡(luò)的準(zhǔn)確度應(yīng)該同步增加,當(dāng)然要注意過擬合問題。但是網(wǎng)絡(luò)深度增加的一個(gè)問題在于這些增加的層是參數(shù)更新的信號,因?yàn)樘荻仁菑暮笙蚯皞鞑サ?#xff0c;增加網(wǎng)絡(luò)深度后,比較靠前的層梯度會很小。這意味著這些層基本上學(xué)習(xí)停滯了,這就是梯度消失問題。深度網(wǎng)絡(luò)的第二個(gè)問題在于訓(xùn)練,當(dāng)網(wǎng)絡(luò)更深時(shí)意味著參數(shù)空間更大,優(yōu)化問題變得更難,因此簡單地去增加網(wǎng)絡(luò)深度反而出現(xiàn)更高的訓(xùn)練誤差,深層網(wǎng)絡(luò)雖然收斂了,但網(wǎng)絡(luò)卻開始退化了,即增加網(wǎng)絡(luò)層數(shù)卻導(dǎo)致更大的誤差,比如下圖,一個(gè)56層的網(wǎng)絡(luò)的性能卻不如20層的性能好,這不是因?yàn)檫^擬合(訓(xùn)練集訓(xùn)練誤差依然很高),這就是煩人的退化問題。殘差網(wǎng)絡(luò)ResNet設(shè)計(jì)一種殘差模塊讓我們可以訓(xùn)練更深的網(wǎng)絡(luò)。
這里詳細(xì)分析一下殘差單元來理解ResNet的精髓。
從下圖可以看出,數(shù)據(jù)經(jīng)過了兩條路線,一條是常規(guī)路線,另一條則是捷徑(shortcut),直接實(shí)現(xiàn)單位映射的直接連接的路線,這有點(diǎn)類似與電路中的“短路”。通過實(shí)驗(yàn),這種帶有shortcut的結(jié)構(gòu)確實(shí)可以很好地應(yīng)對退化問題。我們把網(wǎng)絡(luò)中的一個(gè)模塊的輸入和輸出關(guān)系看作是y=H(x),那么直接通過梯度方法求H(x)就會遇到上面提到的退化問題,如果使用了這種帶shortcut的結(jié)構(gòu),那么可變參數(shù)部分的優(yōu)化目標(biāo)就不再是H(x),若用F(x)來代表需要優(yōu)化的部分的話,則H(x)=F(x)+x,也就是F(x)=H(x)-x。因?yàn)樵趩挝挥成涞募僭O(shè)中y=x就相當(dāng)于觀測值,所以F(x)就對應(yīng)著殘差,因而叫殘差網(wǎng)絡(luò)。為啥要這樣做,因?yàn)樽髡哒J(rèn)為學(xué)習(xí)殘差F(X)比直接學(xué)習(xí)H(X)簡單!設(shè)想下,現(xiàn)在根據(jù)我們只需要去學(xué)習(xí)輸入和輸出的差值就可以了,絕對量變?yōu)橄鄬α?#xff08;H(x)-x 就是輸出相對于輸入變化了多少),優(yōu)化起來簡單很多。
考慮到x的維度與F(X)維度可能不匹配情況,需進(jìn)行維度匹配。這里論文中采用兩種方法解決這一問題(其實(shí)是三種,但通過實(shí)驗(yàn)發(fā)現(xiàn)第三種方法會使performance急劇下降,故不采用):
- zero_padding:對恒等層進(jìn)行0填充的方式將維度補(bǔ)充完整。這種方法不會增加額外的參數(shù)
- projection:在恒等層采用1x1的卷積核來增加維度。這種方法會增加額外的參數(shù)
下圖展示了兩種形態(tài)的殘差模塊,左圖是常規(guī)殘差模塊,有兩個(gè)3×3卷積核卷積核組成,但是隨著網(wǎng)絡(luò)進(jìn)一步加深,這種殘差結(jié)構(gòu)在實(shí)踐中并不是十分有效。針對這問題,右圖的“瓶頸殘差模塊”(bottleneck residual block)可以有更好的效果,它依次由1×1、3×3、1×1這三個(gè)卷積層堆積而成,這里的1×1的卷積能夠起降維或升維的作用,從而令3×3的卷積可以在相對較低維度的輸入上進(jìn)行,以達(dá)到提高計(jì)算效率的目的。
ResNet-50的Keras實(shí)現(xiàn):
def Conv2d_BN(x, nb_filter,kernel_size, strides=(1,1), padding='same',name=None):if name is not None:bn_name = name + '_bn'conv_name = name + '_conv'else:bn_name = Noneconv_name = Nonex = Conv2D(nb_filter,kernel_size,padding=padding,strides=strides,activation='relu',name=conv_name)(x)x = BatchNormalization(axis=3,name=bn_name)(x)return xdef Conv_Block(inpt,nb_filter,kernel_size,strides=(1,1), with_conv_shortcut=False):x = Conv2d_BN(inpt,nb_filter=nb_filter[0],kernel_size=(1,1),strides=strides,padding='same')x = Conv2d_BN(x, nb_filter=nb_filter[1], kernel_size=(3,3), padding='same')x = Conv2d_BN(x, nb_filter=nb_filter[2], kernel_size=(1,1), padding='same')if with_conv_shortcut:shortcut = Conv2d_BN(inpt,nb_filter=nb_filter[2],strides=strides,kernel_size=kernel_size)x = add([x,shortcut])return xelse:x = add([x,inpt])return xdef ResNet50():inpt = Input(shape=(224,224,3))x = ZeroPadding2D((3,3))(inpt)x = Conv2d_BN(x,nb_filter=64,kernel_size=(7,7),strides=(2,2),padding='valid')x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x)x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3),strides=(1,1),with_conv_shortcut=True)x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3))x = AveragePooling2D(pool_size=(7,7))(x)x = Flatten()(x)x = Dense(1000,activation='softmax')(x)model = Model(inputs=inpt,outputs=x)return model繼往開來:DenseNet
自Resnet提出以后,ResNet的變種網(wǎng)絡(luò)層出不窮,都各有其特點(diǎn),網(wǎng)絡(luò)性能也有一定的提升。本文介紹的最后一個(gè)網(wǎng)絡(luò)是CVPR 2017最佳論文DenseNet,論文中提出的DenseNet(Dense Convolutional Network)主要還是和ResNet及Inception網(wǎng)絡(luò)做對比,思想上有借鑒,但卻是全新的結(jié)構(gòu),網(wǎng)絡(luò)結(jié)構(gòu)并不復(fù)雜,卻非常有效,在CIFAR指標(biāo)上全面超越ResNet。可以說DenseNet吸收了ResNet最精華的部分,并在此上做了更加創(chuàng)新的工作,使得網(wǎng)絡(luò)性能進(jìn)一步提升。
閃光點(diǎn):
- 密集連接:緩解梯度消失問題,加強(qiáng)特征傳播,鼓勵(lì)特征復(fù)用,極大的減少了參數(shù)量
DenseNet 是一種具有密集連接的卷積神經(jīng)網(wǎng)絡(luò)。在該網(wǎng)絡(luò)中,任何兩層之間都有直接的連接,也就是說,網(wǎng)絡(luò)每一層的輸入都是前面所有層輸出的并集,而該層所學(xué)習(xí)的特征圖也會被直接傳給其后面所有層作為輸入。下圖是 DenseNet 的一個(gè)dense block示意圖,一個(gè)block里面的結(jié)構(gòu)如下,與ResNet中的BottleNeck基本一致:BN-ReLU-Conv(1×1)-BN-ReLU-Conv(3×3) ,而一個(gè)DenseNet則由多個(gè)這種block組成。每個(gè)DenseBlock的之間層稱為transition layers,由BN?>Conv(1×1)?>averagePooling(2×2)組成
密集連接不會帶來冗余嗎?不會!密集連接這個(gè)詞給人的第一感覺就是極大的增加了網(wǎng)絡(luò)的參數(shù)量和計(jì)算量。但實(shí)際上 DenseNet 比其他網(wǎng)絡(luò)效率更高,其關(guān)鍵就在于網(wǎng)絡(luò)每層計(jì)算量的減少以及特征的重復(fù)利用。DenseNet則是讓l層的輸入直接影響到之后的所有層,它的輸出為:xl=Hl([X0,X1,…,xl?1]),其中[x0,x1,...,xl?1]就是將之前的feature map以通道的維度進(jìn)行合并。并且由于每一層都包含之前所有層的輸出信息,因此其只需要很少的特征圖就夠了,這也是為什么DneseNet的參數(shù)量較其他模型大大減少的原因。這種dense connection相當(dāng)于每一層都直接連接input和loss,因此就可以減輕梯度消失現(xiàn)象,這樣更深網(wǎng)絡(luò)不是問題
需要明確一點(diǎn),dense connectivity 僅僅是在一個(gè)dense block里的,不同dense block 之間是沒有dense connectivity的,比如下圖所示。
天底下沒有免費(fèi)的午餐,網(wǎng)絡(luò)自然也不例外。在同層深度下獲得更好的收斂率,自然是有額外代價(jià)的。其代價(jià)之一,就是其恐怖如斯的內(nèi)存占用。
DenseNet-121的Keras實(shí)現(xiàn):
def DenseNet121(nb_dense_block=4, growth_rate=32, nb_filter=64, reduction=0.0, dropout_rate=0.0, weight_decay=1e-4, classes=1000, weights_path=None):'''Instantiate the DenseNet 121 architecture,# Argumentsnb_dense_block: number of dense blocks to add to endgrowth_rate: number of filters to add per dense blocknb_filter: initial number of filtersreduction: reduction factor of transition blocks.dropout_rate: dropout rateweight_decay: weight decay factorclasses: optional number of classes to classify imagesweights_path: path to pre-trained weights# ReturnsA Keras model instance.'''eps = 1.1e-5# compute compression factorcompression = 1.0 - reduction# Handle Dimension Ordering for different backendsglobal concat_axisif K.image_dim_ordering() == 'tf':concat_axis = 3img_input = Input(shape=(224, 224, 3), name='data')else:concat_axis = 1img_input = Input(shape=(3, 224, 224), name='data')# From architecture for ImageNet (Table 1 in the paper)nb_filter = 64nb_layers = [6,12,24,16] # For DenseNet-121# Initial convolutionx = ZeroPadding2D((3, 3), name='conv1_zeropadding')(img_input)x = Convolution2D(nb_filter, 7, 7, subsample=(2, 2), name='conv1', bias=False)(x)x = BatchNormalization(epsilon=eps, axis=concat_axis, name='conv1_bn')(x)x = Scale(axis=concat_axis, name='conv1_scale')(x)x = Activation('relu', name='relu1')(x)x = ZeroPadding2D((1, 1), name='pool1_zeropadding')(x)x = MaxPooling2D((3, 3), strides=(2, 2), name='pool1')(x)# Add dense blocksfor block_idx in range(nb_dense_block - 1):stage = block_idx+2x, nb_filter = dense_block(x, stage, nb_layers[block_idx], nb_filter, growth_rate, dropout_rate=dropout_rate, weight_decay=weight_decay)# Add transition_blockx = transition_block(x, stage, nb_filter, compression=compression, dropout_rate=dropout_rate, weight_decay=weight_decay)nb_filter = int(nb_filter * compression)final_stage = stage + 1x, nb_filter = dense_block(x, final_stage, nb_layers[-1], nb_filter, growth_rate, dropout_rate=dropout_rate, weight_decay=weight_decay)x = BatchNormalization(epsilon=eps, axis=concat_axis, name='conv'+str(final_stage)+'_blk_bn')(x)x = Scale(axis=concat_axis, name='conv'+str(final_stage)+'_blk_scale')(x)x = Activation('relu', name='relu'+str(final_stage)+'_blk')(x)x = GlobalAveragePooling2D(name='pool'+str(final_stage))(x)x = Dense(classes, name='fc6')(x)x = Activation('softmax', name='prob')(x)model = Model(img_input, x, name='densenet')if weights_path is not None:model.load_weights(weights_path)return modeldef conv_block(x, stage, branch, nb_filter, dropout_rate=None, weight_decay=1e-4):'''Apply BatchNorm, Relu, bottleneck 1x1 Conv2D, 3x3 Conv2D, and option dropout# Argumentsx: input tensor stage: index for dense blockbranch: layer index within each dense blocknb_filter: number of filtersdropout_rate: dropout rateweight_decay: weight decay factor'''eps = 1.1e-5conv_name_base = 'conv' + str(stage) + '_' + str(branch)relu_name_base = 'relu' + str(stage) + '_' + str(branch)# 1x1 Convolution (Bottleneck layer)inter_channel = nb_filter * 4 x = BatchNormalization(epsilon=eps, axis=concat_axis, name=conv_name_base+'_x1_bn')(x)x = Scale(axis=concat_axis, name=conv_name_base+'_x1_scale')(x)x = Activation('relu', name=relu_name_base+'_x1')(x)x = Convolution2D(inter_channel, 1, 1, name=conv_name_base+'_x1', bias=False)(x)if dropout_rate:x = Dropout(dropout_rate)(x)# 3x3 Convolutionx = BatchNormalization(epsilon=eps, axis=concat_axis, name=conv_name_base+'_x2_bn')(x)x = Scale(axis=concat_axis, name=conv_name_base+'_x2_scale')(x)x = Activation('relu', name=relu_name_base+'_x2')(x)x = ZeroPadding2D((1, 1), name=conv_name_base+'_x2_zeropadding')(x)x = Convolution2D(nb_filter, 3, 3, name=conv_name_base+'_x2', bias=False)(x)if dropout_rate:x = Dropout(dropout_rate)(x)return xdef transition_block(x, stage, nb_filter, compression=1.0, dropout_rate=None, weight_decay=1E-4):''' Apply BatchNorm, 1x1 Convolution, averagePooling, optional compression, dropout # Argumentsx: input tensorstage: index for dense blocknb_filter: number of filterscompression: calculated as 1 - reduction. Reduces the number of feature maps in the transition block.dropout_rate: dropout rateweight_decay: weight decay factor'''eps = 1.1e-5conv_name_base = 'conv' + str(stage) + '_blk'relu_name_base = 'relu' + str(stage) + '_blk'pool_name_base = 'pool' + str(stage) x = BatchNormalization(epsilon=eps, axis=concat_axis, name=conv_name_base+'_bn')(x)x = Scale(axis=concat_axis, name=conv_name_base+'_scale')(x)x = Activation('relu', name=relu_name_base)(x)x = Convolution2D(int(nb_filter * compression), 1, 1, name=conv_name_base, bias=False)(x)if dropout_rate:x = Dropout(dropout_rate)(x)x = AveragePooling2D((2, 2), strides=(2, 2), name=pool_name_base)(x)return xdef dense_block(x, stage, nb_layers, nb_filter, growth_rate, dropout_rate=None, weight_decay=1e-4, grow_nb_filters=True):''' Build a dense_block where the output of each conv_block is fed to subsequent ones# Argumentsx: input tensorstage: index for dense blocknb_layers: the number of layers of conv_block to append to the model.nb_filter: number of filtersgrowth_rate: growth ratedropout_rate: dropout rateweight_decay: weight decay factorgrow_nb_filters: flag to decide to allow number of filters to grow'''eps = 1.1e-5concat_feat = xfor i in range(nb_layers):branch = i+1x = conv_block(concat_feat, stage, branch, growth_rate, dropout_rate, weight_decay)concat_feat = merge([concat_feat, x], mode='concat', concat_axis=concat_axis, name='concat_'+str(stage)+'_'+str(branch))if grow_nb_filters:nb_filter += growth_ratereturn concat_feat, nb_filter總結(jié)
以上是生活随笔為你收集整理的CNN网络架构演进:从LeNet到DenseNet的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python3访问纯真IP数据库的代码
- 下一篇: Linux grep不包含某些字符串的命