五個很厲害的 CNN 架構
本文為 AI 研習社編譯的技術博客,原標題 :
Five Powerful CNN Architectures
作者 |Faisal Shahbaz
翻譯 | 小哥哥、Jaruce、zackary、Disillusion
校對 | 醬番梨 整理 | 菠蘿妹
https://medium.com/@faisalshahbaz/five-powerful-cnn-architectures-b939c9ddd57b
讓我們來看看一些強大的卷積神經網路,這些網路實現的深度學習為今天的計算機視覺的成就奠定了基礎。
LeNet-5?—?LeCun et al
LeNet-5,一個7層的卷積神經網路,被很多銀行用於識別支票上的手寫數字。
基於梯度的學習應用於文檔識別
LeNet-5?—?Architecture
手寫數字被數字化成尺寸為32*32的圖片。在這種情況下,由於計算能力的限制,這種技術無法應用於大規模的圖片。
我們來理解一下這種模型的結構。除了輸入層,這個模型有七層。由於結構十分的迷你,我們逐層來研究這個模型:
第一層:卷積層,總共6個卷積核,核尺寸5*5,步長1*1。所以,當輸入圖像尺寸為32*32*1時,輸出尺寸為28*28*6。這層的參數個數為5*5*6+6(偏置項的個數)
第二層:池化層,總共6個池化核,核尺寸為2*2,步長為2*2。但是這裡的池化層與先前見到的有些許不同。這裡的池化層,將接收到的輸入值求和後,乘一個訓練得到的參數(每個核一個),得到的結果再加一個訓練得到的偏置項(同樣每個核一個)。最後,將得到的結果通過Sigmod激活函數的映射,得到輸出。因此,從前級繼承的輸入尺寸28*28*6經過了這層,會得到14*14*6的子採樣。這層的參數個數為[1(訓練得到的參數)+1(訓練得到的偏置項)]×6=12
第三層:類似的,本層是和第一層有相同組態的卷積層,唯一不同的是,本層有16個卷積核而不是6個,所以,從前級繼承的輸入尺寸14*14*6經過了這層,輸出層為10*10*16。參數個數為5*5*16+16=416
第四層:同樣地,與第二層類似,這次的池化層中有16個核。請牢記,輸出同樣經過sigmod激活函數。從前級繼承的輸入尺寸10*10*16經過了這層池化層,會得到5*5*16的子採樣.參數個數為(1+1)*16=32
第五層:這次的卷積層使用的是120個5*5的卷積核。由於輸入尺寸恰好是5*5*16,所以我們甚至都不用考慮步長就可以得到輸出尺寸為1*1*120.本層共有5*5*120=3000個參數
第六層:這是一個有84個參數的全連接層。所以,輸入的120個單元會轉化成84個單元。因此,共有84*120+84=10164個參數。這裡使用了不止一個激活函數。可以確定的是,只要能讓問題變得簡單,你可以使用你想用的任意的備選激活函數
輸出層:最終的一層是一個10單元的全連接層,共有84*10+10=924個參數
我建議,在最後一層使用交叉熵損失函數和softmax激活函數,在這裡不再贅述損失函數的細節以及使用其的原因。請採用不同的訓練計劃和學習率進行訓練。
LeNet-5?—?代碼
fromkerasimportlayers
fromkeras.modelsimportModel
deflenet_5(in_shape=(32,32,1), n_classes=10, opt="sgd"):
in_layer = layers.Input(in_shape)
conv1 = layers.Conv2D(filters=20, kernel_size=5,
padding="same", activation="relu")(in_layer)
pool1 = layers.MaxPool2D()(conv1)
conv2 = layers.Conv2D(filters=50, kernel_size=5,
padding="same", activation="relu")(pool1)
pool2 = layers.MaxPool2D()(conv2)
flatten = layers.Flatten()(pool2)
dense1 = layers.Dense(500, activation="relu")(flatten)
preds = layers.Dense(n_classes, activation="softmax")(dense1)
model = Model(in_layer, preds)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
returnmodel
if__name__ =="__main__":
model = lenet_5()
print(model.summary())
AlexNet?—?Krizhevsky et al
在2012年,Hinton的深度神經網路參加了世界上最重要的計算機視覺挑戰賽imagenet,並將top-5損失從26%減少到15.3%,這一結果讓世人驚艷。
這個神經網路跟LeNetg很像,但是比它更深,有大概六千萬的參數。
使用深度卷積神經網路參加ImageNet
AlexNet?—?Architecture
這個計算過程看起來確實有點嚇人。這是因為網路由兩半組成,每一部分都在兩塊不同的GPU上進行訓練。我們把這個過程說的容易點,用一個精簡版的圖來說明這個問題:
這個結構包括5個卷積層和3個全連接層。這八層也都採用了當時的兩個新概念——最大池化和Relu激活來為模型提供優勢。
你可以在上圖中找到不同層及其相應的配置。每一層的描述如下表:
注:Relu激活函數被用在除了最後的softmax層的所有卷積層和全連接層的輸出部分。
作者也使用了其他很多技術(本帖不予以一一討論)——比如dropout,augmentatio和動量隨機梯度下降。
AlexNet?—?代碼
fromkerasimportlayers
fromkeras.modelsimportModel
defalexnet(in_shape=(227,227,3), n_classes=1000, opt="sgd"):
in_layer = layers.Input(in_shape)
conv1 = layers.Conv2D(96,11, strides=4, activation="relu")(in_layer)
pool1 = layers.MaxPool2D(3,2)(conv1)
conv2 = layers.Conv2D(256,5, strides=1, padding="same", activation="relu")(pool1)
pool2 = layers.MaxPool2D(3,2)(conv2)
conv3 = layers.Conv2D(384,3, strides=1, padding="same", activation="relu")(pool2)
conv4 = layers.Conv2D(256,3, strides=1, padding="same", activation="relu")(conv3)
pool3 = layers.MaxPool2D(3,2)(conv4)
flattened = layers.Flatten()(pool3)
dense1 = layers.Dense(4096, activation="relu")(flattened)
drop1 = layers.Dropout(0.5)(dense1)
dense2 = layers.Dense(4096, activation="relu")(drop1)
drop2 = layers.Dropout(0.5)(dense2)
preds = layers.Dense(n_classes, activation="softmax")(drop2)
model = Model(in_layer, preds)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
returnmodel
if__name__ =="__main__":
model = alexnet()
print(model.summary())
VGGNet?—?Simonyan et al
2014年IMAGENET挑戰賽的亞軍。因為這種統一架構十分輕巧,不少新人將之作為深度卷積神經網路的簡單形式。
在下面的文章中,我們將會學習這種最常用的網路架構之一是如何從圖片中提取特徵的(提取圖像信息將之轉化為包含圖片重要信息的低維數組)
VGGNet?—?Architecture
VGGNet有兩條需要遵守的簡單經驗法則:
每個卷積層的配置為:kernel size = 3×3, stride = 1×1, padding = same.唯一不同的是核數量。
每個最大池化層的配置為:windows size = 2×2 and stride = 2×2.因此,我們在每個池化層將圖片尺寸降為一半。
輸入是224*224的RGB圖像,所以輸入尺寸為224x224x3
總參數為138,000,000.這些參數的大部分都來自於全連接層:
第一層全連接層包含了4096 * (7 * 7 * 512) + 4096 = 102,764,544個參數
第二層全連接層包含了4096 * 4096 + 4096 = 16,781,312個參數
第三層全連接層包含了4096 * 1000 + 4096 = 4,100,096個參數
全連接層共包含了123,645,952個參數。
VGGNet?—?代碼
fromkerasimportlayers
fromkeras.modelsimportModel, Sequential
fromfunctoolsimportpartial
conv3 = partial(layers.Conv2D,
kernel_size=3,
strides=1,
padding="same",
activation="relu")
defblock(in_tensor, filters, n_convs):
conv_block = in_tensor
for_inrange(n_convs):
conv_block = conv3(filters=filters)(conv_block)
returnconv_block
def_vgg(in_shape=(227,227,3),
n_classes=1000,
opt="sgd",
n_stages_per_blocks=[2,2,3,3,3]):
in_layer = layers.Input(in_shape)
block1 = block(in_layer,64, n_stages_per_blocks[])
pool1 = layers.MaxPool2D()(block1)
block2 = block(pool1,128, n_stages_per_blocks[1])
pool2 = layers.MaxPool2D()(block2)
block3 = block(pool2,256, n_stages_per_blocks[2])
pool3 = layers.MaxPool2D()(block3)
block4 = block(pool3,512, n_stages_per_blocks[3])
pool4 = layers.MaxPool2D()(block4)
block5 = block(pool4,512, n_stages_per_blocks[4])
pool5 = layers.MaxPool2D()(block5)
flattened = layers.GlobalAvgPool2D()(pool5)
dense1 = layers.Dense(4096, activation="relu")(flattened)
dense2 = layers.Dense(4096, activation="relu")(dense1)
preds = layers.Dense(1000, activation="softmax")(dense2)
model = Model(in_layer, preds)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
returnmodel
defvgg16(in_shape=(227,227,3), n_classes=1000, opt="sgd"):
return_vgg(in_shape, n_classes, opt)
defvgg19(in_shape=(227,227,3), n_classes=1000, opt="sgd"):
return_vgg(in_shape, n_classes, opt, [2,2,4,4,4])
if__name__ =="__main__":
model = vgg19()
print(model.summary())
GoogLeNet/Inception?—?Szegedy et al
它使用了一個inception模塊,一個新穎的概念,具有較小的卷積,可以將參數的數量減少到僅僅400萬個。
Inception模塊
使用這些Inception模塊的原因:
每一層類從輸入中提取不同的信息。一個3×3層收集的信息將不同於一個5×5層收集的。我們如何知道在某一給定層中哪個變換是最好的?所以我們使用它們全部!
使用1×1卷積進行降維!考慮一個128x128x256的輸入。如果我們把輸入通過20個1×1大小的過濾器,我們將得到一個128 x128x20的輸出。我們將它們應用在3×3或5×5的卷積前以減少在用於降維的Inception塊層上輸入過濾器的數量。
GoogLeNet/Inception?—?架構
完整的inception架構:
深入了解卷積
你可能會在這個結構中看到一些帶有softmax的「輔助分類器」。引用這篇論文——「通過添加連接到這些中間層的輔助分類器,我們期望在分類器的較低階段加強辨別,增加被傳播回來的梯度信號,並提供額外的正則化。」
但這意味著什麼呢?他們的意思是:
低階段識別:我們將訓練網路的低層,其梯度來自較早階段的輸出概率。這保證了網路在開始階段對不同的對象都具有一定的識別能力。
增加反向傳播的梯度信號:在深層神經網路中,通常反向傳播的梯度變得非常小,以至於網路的前幾層很難進行學習。因此,早期的分類層通過傳播強梯度信號來訓練網路是有幫助的。
提供額外的正則化:深度神經網路傾向於對數據過擬合(或導致高方差),而小型神經網路傾向於對數據欠擬合 (或導致高偏差)。早期的分類器規範了深層的過擬合效果!
輔助分類器結構:
注意:這裡 #1×1代表Inception模塊中1×1卷積里的過濾器。
#3×3簡化(reduce)代表Inception模塊中3×3卷積前的1×1卷積里的過濾器。
#5×5簡化(reduce)代表Inception模塊中5×5卷積前的1×1卷積里的過濾器。
#3×3代表Inception模塊中3×3卷積里的過濾器。
#5×5代表Inception模塊中5×5卷積里的過濾器。
池項目(pool proj)代表了inception模塊中最大池前的1×1卷積里的過濾器。
GoogLeNet是典型的Inception架構
它使用了批處理標準化、圖像失真和RMSprop,這些我們將在以後的文章中討論。
GoogLeNet/Inception —代碼
Resnet--?Kaiming He et al
2015年imagenet挑戰賽中,top-5錯誤率在3.57%左右,低於人類top-5錯誤率。這都要歸功於微軟在競賽中使用的ResNet( Residual Network 殘差網路)。這個網路提出了一種全新的方法:「跳躍連接」
殘差學習:一個模塊
殘差網路為這樣一個現象提供了解決方案——當我們不停地加深神經網路時,深度神經網路的表現會變差。但從直覺上看來,這種事情不應該發生。如果一個深度為K的網路的表現用y來衡量,那麼深度為K+1的網路至少也要有y的表現才對。
這個現象帶來了一個假說:直接映射是很難學習的。所以,不去學習網路輸出層與輸入層間的映射,而是學習它們之間的差異——殘差。
例如,設x為輸入,H(x)是學習到的輸出。我們得學習F(x) = H(x) -x。我們可以首先用一層來學習F(x)然後將x 與F(x)相加便得到了H(x)。作為結果,我們將H(x) 送至下一層,正如我們之前做的那樣。這便是我們之前看到的殘差塊。
結果非常驚艷,這是因為導致神經網路無法學習的梯度消失問題被消除了。跳躍連接,或者說「捷徑」,給出了一條捷徑,以取得之前數層網路的梯度,跳過了之間的數層。
ResNet?—?架構
讓我們用到這裡:
本文提出了利用瓶頸進行更深的 ResNets- 50/101/152。神經網路使用1×1的卷積來增加和減少通道數量的維數,而不是使用上面提到的殘塊。
ResNet?—?代碼
fromkerasimportlayers
fromkeras.modelsimportModel
def_after_conv(in_tensor):
norm = layers.BatchNormalization()(in_tensor)
returnlayers.Activation("relu")(norm)
defconv1(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=1, strides=1)(in_tensor)
return_after_conv(conv)
defconv1_downsample(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=1, strides=2)(in_tensor)
return_after_conv(conv)
defconv3(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=3, strides=1, padding="same")(in_tensor)
return_after_conv(conv)
defconv3_downsample(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=3, strides=2, padding="same")(in_tensor)
return_after_conv(conv)
defresnet_block_wo_bottlneck(in_tensor, filters, downsample=False):
ifdownsample:
conv1_rb = conv3_downsample(in_tensor, filters)
else:
conv1_rb = conv3(in_tensor, filters)
conv2_rb = conv3(conv1_rb, filters)
ifdownsample:
in_tensor = conv1_downsample(in_tensor, filters)
result = layers.Add()([conv2_rb, in_tensor])
returnlayers.Activation("relu")(result)
defresnet_block_w_bottlneck(in_tensor,
filters,
downsample=False,
change_channels=False):
ifdownsample:
conv1_rb = conv1_downsample(in_tensor, int(filters/4))
else:
conv1_rb = conv1(in_tensor, int(filters/4))
conv2_rb = conv3(conv1_rb, int(filters/4))
conv3_rb = conv1(conv2_rb, filters)
ifdownsample:
in_tensor = conv1_downsample(in_tensor, filters)
elifchange_channels:
in_tensor = conv1(in_tensor, filters)
result = layers.Add()([conv3_rb, in_tensor])
returnresult
def_pre_res_blocks(in_tensor):
conv = layers.Conv2D(64,7, strides=2, padding="same")(in_tensor)
conv = _after_conv(conv)
pool = layers.MaxPool2D(3,2, padding="same")(conv)
returnpool
def_post_res_blocks(in_tensor, n_classes):
pool = layers.GlobalAvgPool2D()(in_tensor)
preds = layers.Dense(n_classes, activation="softmax")(pool)
returnpreds
defconvx_wo_bottleneck(in_tensor, filters, n_times, downsample_1=False):
res = in_tensor
foriinrange(n_times):
ifi ==:
res = resnet_block_wo_bottlneck(res, filters, downsample_1)
else:
res = resnet_block_wo_bottlneck(res, filters)
returnres
defconvx_w_bottleneck(in_tensor, filters, n_times, downsample_1=False):
res = in_tensor
foriinrange(n_times):
ifi ==:
res = resnet_block_w_bottlneck(res, filters, downsample_1,notdownsample_1)
else:
res = resnet_block_w_bottlneck(res, filters)
returnres
def_resnet(in_shape=(224,224,3),
n_classes=1000,
opt="sgd",
convx=[64,128,256,512],
n_convx=[2,2,2,2],
convx_fn=convx_wo_bottleneck):
in_layer = layers.Input(in_shape)
downsampled = _pre_res_blocks(in_layer)
conv2x = convx_fn(downsampled, convx[], n_convx[])
conv3x = convx_fn(conv2x, convx[1], n_convx[1],True)
conv4x = convx_fn(conv3x, convx[2], n_convx[2],True)
conv5x = convx_fn(conv4x, convx[3], n_convx[3],True)
preds = _post_res_blocks(conv5x, n_classes)
model = Model(in_layer, preds)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
returnmodel
defresnet18(in_shape=(224,224,3), n_classes=1000, opt="sgd"):
return_resnet(in_shape, n_classes, opt)
defresnet34(in_shape=(224,224,3), n_classes=1000, opt="sgd"):
return_resnet(in_shape,
n_classes,
opt,
n_convx=[3,4,6,3])
defresnet50(in_shape=(224,224,3), n_classes=1000, opt="sgd"):
return_resnet(in_shape,
n_classes,
opt,
[256,512,1024,2048],
[3,4,6,3],
convx_w_bottleneck)
defresnet101(in_shape=(224,224,3), n_classes=1000, opt="sgd"):
return_resnet(in_shape,
n_classes,
opt,
[256,512,1024,2048],
[3,4,23,3],
convx_w_bottleneck)
defresnet152(in_shape=(224,224,3), n_classes=1000, opt="sgd"):
return_resnet(in_shape,
n_classes,
opt,
[256,512,1024,2048],
[3,8,36,3],
convx_w_bottleneck)
if__name__ =="__main__":
model = resnet50()
print(model.summary())
引用:
基於梯度學習應用於文檔識別
基於梯度學習的目標識別
使用深度卷積神經網路進行ImageNet分類
用於大規模圖像識別的超深度卷積網路
進一步深入卷積
用於圖像識別的深度殘差學習
![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
※深度網路揭秘之深度網路背後的數學
※一文帶你讀懂機器學習和數據科學的決策樹
TAG:AI研習社 |