回顧五個強大的CNN架構介紹及Python示例
讓我們來回顧一些強大的卷積神經網路,它們為今天的計算機視覺成就奠定了基礎,這些成就是通過深度學習獲得的。
LeNet-5
LeNet-5是一個7層卷積神經網路,部署在許多銀行系統中,用於識別支票上的手寫數字。
LeNet-5 - 架構
手寫數字被數字化為畫素大小的灰度影象--32×32。那時,計算能力有限,因此該技術無法擴充套件到大規模影象。學習資料也可以加下Python扣扣裙:304零五零799自己下載學習下
該模型包含7層(不包括輸入層)。由於它是一個相對較小的架構,讓我們逐層解釋:
第1層:卷積層,核大小為5×5,步長為1×1,總共為6個核。因此,大小為32x32x1的輸入影象的輸出為28x28x6。層中的總引數= 5 * 5 * 6 + 6(偏差項)
第2層:具有2×2核大小的池化層,總共2×2和6個核。這個池化層的行為與之前的文章略有不同。將接收器中的輸入值相加,然後乘以可訓練引數(每個filter 1個),最後將結果加到可訓練的偏差(每個filter 1個)。最後,將sigmoid啟用應用於輸出。因此,來自前一層大小為28x28x6的輸入被子取樣為14x14x6。層中的總引數= [1(可訓練引數)+ 1(可訓練偏差)] * 6 = 12
第3層:與第1層類似,此層是具有相同配置的卷積層,除了它有16個filters而不是6個。因此,前一個大小為14x14x6的輸入提供10x10x16的輸出。層中的總引數= 5 * 5 * 16 + 16 = 416。
第4層:與第2層類似,此層是一個池化層,這次有16個filters。請記住,輸出通過sigmoid啟用函式傳遞。來自前一層的大小為10x10x16的輸入被子取樣為5x5x16。層中的總引數=(1 + 1)* 16 = 32
第5層:卷積層,核大小為5×5,filters為120。由於輸入大小為5x5x16,因此我們無需考慮步幅,因此輸出為1x1x120。層中的總引數= 5 * 5 * 120 = 3000
第6層:這是一個包含84個引數的dense層。因此,120個units的輸入轉換為84個units。總引數= 84 * 120 + 84 = 10164.此處使用的啟用函式相當獨特。我要說的是,你可以在這裡嘗試你的任何選擇,因為按照今天的標準,這個任務非常簡單。
輸出層:最後,使用具有10個units的dense層。總引數= 84 * 10 + 10 = 924。
跳過所使用的損失函式的細節及其使用原因,我建議在最後一層使用softmax啟用的交叉熵損失。嘗試不同的訓練計劃和學習率。
LeNet-5 - Python程式碼
from keras import layers
from keras.models import Model
def lenet_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"])
return model
if __name__ == '__main__':
model = lenet_5()
print(model.summary())
AlexNet
2012年,Hinton的深度神經網路將世界上最重要的計算機視覺挑戰影象網路中的損失從26%減少到15.3%。
該網路與LeNet非常相似,但更深,擁有大約6000萬個引數。
深度卷積神經網路的ImageNet分類
AlexNet - 架構
當然這個數字看起來嚇人。這是因為網路被分成兩個半部分,每個部分在兩個不同的gpu上同時被訓練。讓我們做一下簡單的為我們帶來一個更簡單的版本的圖片:
該架構由5個卷積層和3個全連線層組成。這8個層與當時的兩個新概念相結合--MaxPooling和ReLU啟用為他們的模型提供了優勢。
您可以在上圖中看到各種層及其配置。這些層如下表所示:
注意:ReLU啟用應用於除最後一個softmax圖層之外的每個卷積和全連線層的輸出。
作者使用了各種其他技術 - dropout,augmentation 和Stochastic Gradient Descent with momentum。
AlexNet - Python程式碼
from keras import layers
from keras.models import Model
def alexnet(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"])
return model
if __name__ == '__main__':
model = alexnet()
print(model.summary())
VGGNet
2014年imagenet挑戰的亞軍被命名為VGGNet。由於其簡單的統一結構,它以更簡單的形式提出了一種更簡單的深度卷積神經網路的形式。
VGGNet - 架構
VGGNet有兩條簡單的經驗法則:
每個卷積層都有配置 - 核大小= 3×3,stride = 1×1,padding = same。唯一不同的是filters的數量。
每個Max Pooling層都有配置 - windows size= 2×2和stride = 2×2。因此,我們在每個Pooling層的影象大小減半。
輸入影象是224×224畫素的RGB影象。所以輸入大小= 224x224x3
總引數= 1.38億。大多數這些引數由全連線層貢獻。
第一個FC層= 4096 *(7 * 7 * 512)+ 4096 = 102,764,544
第二個FC層= 4096 * 4096 + 4096 = 16,781,312
第三個FC層= 4096 * 1000 + 4096 = 4,100,096
FC層貢獻的總引數= 123,645,952。
VGGNet - 程式碼
from keras import layers
from keras.models import Model, Sequential
from functools import partial
conv3 = partial(layers.Conv2D,
kernel_size=3,
strides=1,
padding='same',
activation='relu')
def block(in_tensor, filters, n_convs):
conv_block = in_tensor
for _ in range(n_convs):
conv_block = conv3(filters=filters)(conv_block)
return conv_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[0])
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"])
return model
def vgg16(in_shape=(227,227,3), n_classes=1000, opt='sgd'):
return _vgg(in_shape, n_classes, opt)
def vgg19(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
2014年imagenet競賽的獲勝者 - GoogLeNet(又名Inception v1)。它使用了一個inception 模組,一個新穎的概念,具有較小的卷積,允許將引數數量減少到僅400萬。
Inception module
使用這些Inception module的原因:
每個層型別從輸入中提取不同的資訊。從3×3層收集的資訊將與從5×5層收集的資訊不同。我們怎麼知道哪一種transformation 是最好的呢?所以我們全部使用它們!
使用1×1卷積減少尺寸!考慮一個128x128x256輸入。如果我們通過20個大小為1×1的過濾器,我們將獲得128x128x20的輸出。因此,我們在3×3或5×5卷積之前應用它們,以減少用於降維的inception block中這些層的輸入filters的數量。
GoogLeNet / Inception - 架構
完整的初始架構:
您可能會在此結構中看到一些帶有softmax的“輔助分類器”。在這裡引用論文 - “通過增加連線到這些中間層的輔助分類器,我們期望鼓勵分類器的較低階段的區分,增加傳播後的梯度訊號,並提供附加的正則化。”
但是這是什麼意思?基本上他們的意思是:
在較低階段的discrimination :我們將在網路中訓練較低層,其中梯度來自較早階段的層以用於輸出概率。這可以確保網路在較早的時候對不同的物件有一些區別。
增加傳播回來的梯度訊號:在深度神經網路中,通常,迴流的梯度(使用反向傳播)變得非常小,以至於網路的早期層很難學習。因此,較早的分類層通過傳播強梯度訊號來訓練網路而變得有用。
提供額外的正規化:深度神經網路往往overfit(或導致高方差)資料,同時小神經網路往往underfit(或導致高偏差)。較早的分類器規範了更深層的過度擬合效果!
輔助分類器的結構:
注意:
#1×1表示inception module中1×1卷積的filters 。
#3×3 reduce表示在inception module中3×3卷積之前的1×1卷積中的filters 。
#5×5 reduce表示在inception module中5×5卷積之前的1×1卷積中的filters 。
#3×3表示inception module中3×3卷積的filters 。
#5×5表示inception module中5×5卷積的filters 。
Pool Proj表示在inception module中Max Pool之前的1×1卷積中的filters 。
GoogLeNet構成了Inception體系結構
它使用了批量歸一化,影象失真和RMSprop。
GoogLeNet / Inception - 程式碼
from keras import layers
from keras.models import Model
from functools import partial
conv1x1 = partial(layers.Conv2D, kernel_size=1, activation='relu')
conv3x3 = partial(layers.Conv2D, kernel_size=3, padding='same', activation='relu')
conv5x5 = partial(layers.Conv2D, kernel_size=5, padding='same', activation='relu')
def inception_module(in_tensor, c1, c3_1, c3, c5_1, c5, pp):
conv1 = conv1x1(c1)(in_tensor)
conv3_1 = conv1x1(c3_1)(in_tensor)
conv3 = conv3x3(c3)(conv3_1)
conv5_1 = conv1x1(c5_1)(in_tensor)
conv5 = conv5x5(c5)(conv5_1)
pool_conv = conv1x1(pp)(in_tensor)
pool = layers.MaxPool2D(3, strides=1, padding='same')(pool_conv)
merged = layers.Concatenate(axis=-1)([conv1, conv3, conv5, pool])
return merged
def aux_clf(in_tensor):
avg_pool = layers.AvgPool2D(5, 3)(in_tensor)
conv = conv1x1(128)(avg_pool)
flattened = layers.Flatten()(conv)
dense = layers.Dense(1024, activation='relu')(flattened)
dropout = layers.Dropout(0.7)(dense)
out = layers.Dense(1000, activation='softmax')(dropout)
return out
def inception_net(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
in_layer = layers.Input(in_shape)
conv1 = layers.Conv2D(64, 7, strides=2, activation='relu', padding='same')(in_layer)
pad1 = layers.ZeroPadding2D()(conv1)
pool1 = layers.MaxPool2D(3, 2)(pad1)
conv2_1 = conv1x1(64)(pool1)
conv2_2 = conv3x3(192)(conv2_1)
pad2 = layers.ZeroPadding2D()(conv2_2)
pool2 = layers.MaxPool2D(3, 2)(pad2)
inception3a = inception_module(pool2, 64, 96, 128, 16, 32, 32)
inception3b = inception_module(inception3a, 128, 128, 192, 32, 96, 64)
pad3 = layers.ZeroPadding2D()(inception3b)
pool3 = layers.MaxPool2D(3, 2)(pad3)
inception4a = inception_module(pool3, 192, 96, 208, 16, 48, 64)
inception4b = inception_module(inception4a, 160, 112, 224, 24, 64, 64)
inception4c = inception_module(inception4b, 128, 128, 256, 24, 64, 64)
inception4d = inception_module(inception4c, 112, 144, 288, 32, 48, 64)
inception4e = inception_module(inception4d, 256, 160, 320, 32, 128, 128)
pad4 = layers.ZeroPadding2D()(inception4e)
pool4 = layers.MaxPool2D(3, 2)(pad4)
aux_clf1 = aux_clf(inception4a)
aux_clf2 = aux_clf(inception4d)
inception5a = inception_module(pool4, 256, 160, 320, 32, 128, 128)
inception5b = inception_module(inception5a, 384, 192, 384, 48, 128, 128)
pad5 = layers.ZeroPadding2D()(inception5b)
pool5 = layers.MaxPool2D(3, 2)(pad5)
avg_pool = layers.GlobalAvgPool2D()(pool5)
dropout = layers.Dropout(0.4)(avg_pool)
preds = layers.Dense(1000, activation='softmax')(dropout)
model = Model(in_layer, [preds, aux_clf1, aux_clf2])
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
return model
if __name__ == '__main__':
model = inception_net()
print(model.summary())
ResNet
2015年的imagenet競賽帶來了Top-5的錯誤率3.57%。這是由於使用了ResNet(殘差網路)模型。該網路引入了一種稱為“skip connections”的新方法。
Residual learning: a building block
該想法作為一種解決辦法來解決一個深度神經網路的問題,因為我們一直在新增層。但直覺來說,不應該這樣。如果具有k層的網路執行為y,則具有k + 1層的網路應該至少執行y。
觀察一個假設:直接對映很難學習。因此,不是學習層的輸出和它的輸入之間的對映,而是學習它們之間的差異 - 學習殘差。
比方說,x是輸入,H(x)是學習輸出。所以,我們需要學習F(x)= H(x) - x。我們可以通過首先建立一個層來學習F(x)然後將x新增到F(x)來實現這一點,從而實現H(x)。結果,我們在下一層傳送與之前一樣的H(x)!這導致我們在上面看到的殘差塊。
結果令人驚訝,因為通常使深度神經網路的消失梯度問題被消除了。我們可以這樣說,skip connections或shortcuts為前面的層提供了梯度的捷徑,跳過了一堆層。
ResNet - 架構
論文提到了更深層ResNets的瓶頸使用 - 50/101/152。不使用上述殘差塊,網路使用1×1卷積來增加和減少通道數量的維數。
ResNet - 程式碼
from keras import layers
from keras.models import Model
def _after_conv(in_tensor):
norm = layers.BatchNormalization()(in_tensor)
return layers.Activation('relu')(norm)
def conv1(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=1, strides=1)(in_tensor)
return _after_conv(conv)
def conv1_downsample(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=1, strides=2)(in_tensor)
return _after_conv(conv)
def conv3(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=3, strides=1, padding='same')(in_tensor)
return _after_conv(conv)
def conv3_downsample(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=3, strides=2, padding='same')(in_tensor)
return _after_conv(conv)
def resnet_block_wo_bottlneck(in_tensor, filters, downsample=False):
if downsample:
conv1_rb = conv3_downsample(in_tensor, filters)
else:
conv1_rb = conv3(in_tensor, filters)
conv2_rb = conv3(conv1_rb, filters)
if downsample:
in_tensor = conv1_downsample(in_tensor, filters)
result = layers.Add()([conv2_rb, in_tensor])
return layers.Activation('relu')(result)
def resnet_block_w_bottlneck(in_tensor,
filters,
downsample=False,
change_channels=False):
if downsample:
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)
if downsample:
in_tensor = conv1_downsample(in_tensor, filters)
elif change_channels:
in_tensor = conv1(in_tensor, filters)
result = layers.Add()([conv3_rb, in_tensor])
return result
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)
return pool
def _post_res_blocks(in_tensor, n_classes):
pool = layers.GlobalAvgPool2D()(in_tensor)
preds = layers.Dense(n_classes, activation='softmax')(pool)
return preds
def convx_wo_bottleneck(in_tensor, filters, n_times, downsample_1=False):
res = in_tensor
for i in range(n_times):
if i == 0:
res = resnet_block_wo_bottlneck(res, filters, downsample_1)
else:
res = resnet_block_wo_bottlneck(res, filters)
return res
def convx_w_bottleneck(in_tensor, filters, n_times, downsample_1=False):
res = in_tensor
for i in range(n_times):
if i == 0:
res = resnet_block_w_bottlneck(res, filters, downsample_1, not downsample_1)
else:
res = resnet_block_w_bottlneck(res, filters)
return res
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[0], n_convx[0])
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"])
return model
def resnet18(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
return _resnet(in_shape, n_classes, opt)
def resnet34(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
return _resnet(in_shape,
n_classes,
opt,
n_convx=[3, 4, 6, 3])
def resnet50(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)
def resnet101(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)
def resnet152(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())
更多程式設計方面的分享請關注薇信工宗號:程式設計師大牛,每天分享乾貨!