從頭開始利用Python實現ResNet模型

從頭開始利用Python實現ResNet模型

RESNET

當ResNet第一次被引入時,它是革命性的,它證明了當時深度神經網絡的一個巨大問題的新解決方案:梯度問消失題。神經網絡雖然是通用的函數逼近器,但在一定閾值下增加層數會使訓練速度變慢,使準確度趨於飽和。

從頭開始利用Python實現ResNet模型

這是由於梯度從最後一層到最早的一層的反向傳播——將0到1之間的數字相乘多次,使其變得越來越小:因此,當到達較早的一層時,梯度開始“消失”。這意味著早期的層不僅訓練速度較慢,而且更容易出錯。這是一個巨大的問題,因為最早的層是整個網絡的構建塊——它們負責識別基本的、核心的特徵!

為了緩解這個問題,ResNet採用了identity shortcut connections,它本質上跳過一個或多個層的訓練 - 創建一個殘差塊。

從頭開始利用Python實現ResNet模型

單個殘差塊

然後,作者提出了一個“優化”的殘差塊,添加了一個稱為bottleneck的擴展。它會降低前兩個CONV層的維數(在最後一個CONV層中學習的filters 的1/4),然後在最後一個CONV層中再次增加。這裡有兩個堆疊在一起的殘差模塊。

從頭開始利用Python實現ResNet模型

最後,He等人發表了關於殘差模塊的第二篇論文,稱為Identity Mappings in Deep Residual Networks

,它提供了更好的殘差塊版本:pre-activation residual model。這允許梯度通過shortcut connections傳播到任何較早的層而不受阻礙。

我們不是從卷積(權重)開始,而是從一系列(BN => RELU => CONV)* N層(假設正在使用bottleneck )開始。然後,殘差模塊輸出被饋送到網絡中的下一個殘差模塊的加法運算 (因為殘差模塊堆疊在彼此之上)。

從頭開始利用Python實現ResNet模型

(a)原始bottleneck 殘差模塊。(e)完整的預激活殘差模塊。稱為預激活,因為BN和ReLU層出現在卷積之前。

整體網絡架構看起來像這樣,我們的模型將與之類似。

從頭開始利用Python實現ResNet模型

讓我們開始用Python編寫實際的網絡。這個具體的實現受到He等人Caffe發行版和Wei Wu的mxnet實現的啟發。

我們將把它寫成一個類(ResNet),以便我們稍後在訓練深度學習模型時調用它。

# import the necessary packages
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import AveragePooling2D

from keras.layers.convolutional import MaxPooling2D
from keras.layers.convolutional import ZeroPadding2D
from keras.layers.core import Activation
from keras.layers.core import Dense
from keras.layers import Flatten
from keras.layers import Input
from keras.models import Model
from keras.layers import add
from keras.regularizers import l2
from keras import backend as K
class ResNet:
@staticmethod
def residual_module(data, K, stride, chanDim, red=False,
reg=0.0001, bnEps=2e-5, bnMom=0.9):
從頭開始利用Python實現ResNet模型

我們從標準的CNN導入開始,然後開始構建我們的residual_module函數。看看參數:

  • data:輸入到殘差模塊
  • K:最後一個CONV層將學習的filters數量(前兩個CONV層將學習K / 4的filters)
  • stride:控制卷積的步幅(將幫助我們減少空間維度而不使用最大池)
  • chanDim:定義將執行批歸一化的軸
  • red(即減少)將控制我們是否減少空間維度(True)或不(False),因為不是所有殘差模塊都將減少空間體積的維度
  • reg:對殘差模塊中的所有CONV層應用正則化強度
  • bnEps:控制Ɛ負責在歸一化輸入時避免“除以零”錯誤
  • bnMom:控制移動平均線的momentum

現在讓我們看看函數的其餘部分。Python代碼如下:

	# the shortcut branch of the ResNet module should be
# initialize as the input (identity) data
shortcut = data

# the first block of the ResNet module are the 1x1 CONVs
bn1 = BatchNormalization(axis=chanDim, epsilon=bnEps, momentum=bnMom)(data)
act1 = Activation("relu")(bn1)
conv1 = Conv2D(int(K * 0.25), (1, 1), use_bias=False,
kernel_regularizer=l2(reg))(act1)
從頭開始利用Python實現ResNet模型

首先,我們初始化 (identity) shortcut (connection),它實際上只是對輸入數據的引用。在殘差模塊的末尾,我們只需將shortcut 添加到我們的預激活/bottleneck 分支(第3行)的輸出中。

在第6-9行,ResNet模塊的第一個塊遵循BN ==> RELU ==> CONV ==>pattern。CONV層通過K / 4 filters使用1x1卷積。請注意,CONV層的偏差項已關閉,因為偏差已經在下面的BN層中,因此不需要第二個偏差項。

	# the second block of the ResNet module are the 3x3 CONVs
bn2 = BatchNormalization(axis=chanDim, epsilon=bnEps, momentum=bnMom)(conv1)
act2 = Activation("relu")(bn2)
conv2 = Conv2D(int(K * 0.25), (3, 3), strides=stride, padding="same", use_bias=False,
kernel_regularizer=l2(reg)(act2)
從頭開始利用Python實現ResNet模型

第二個CONV層學習3 x 3的K / 4 filters。

	# the third block of the ResNet module is another set of 1x1 CONVs
bn3 = BatchNormalization(axis=chanDim, epsilon=bnEps,momentum=bnMom)(conv2)
act3 = Activation("relu")(bn3)
conv3 = Conv2D(K, (1, 1), use_bias=False, kernel_regularizer=l2(reg))(act3)
從頭開始利用Python實現ResNet模型

最後一個塊將再次增加維度,應用尺寸為1 x 1的K filters。

為避免應用最大池化,我們需要檢查是否需要減少空間維度。

	# if we are to reduce the spatial size, apply a CONV layer to the shortcut
if red:
shortcut = Conv2D(K, (1, 1), strides=stride, use_bias=False,
kernel_regularizer=l2(reg))(act1)
# add together the shortcut and the final CONV
x = add([conv3, shortcut])
# return the addition as the output of the ResNet module
return x
從頭開始利用Python實現ResNet模型

如果我們被命令減小空間尺寸,則stride > 1的卷積層將應用於shortcut(第2-4行)。

最後,我們將shortcut和最終CONV層相加,創建輸出到我們的ResNet模塊(第7行)。我們終於有了“構建模塊”來開始構建我們的深度殘差網絡。

讓我們開始構建構建方法。

	@staticmethod
def build(width, height, depth, classes, stages, filters,
reg=0.0001, bnEps=2e-5, bnMom=0.9):
從頭開始利用Python實現ResNet模型

看一下參數stagesfilters (兩個都是列表)。在我們的架構中(如上所示),我們將N個殘差模塊堆疊在一起(N =stage value)。同一stage 中的每個殘差模塊學習相同數量的filters。在每個stage 學習其各自的filters之後,接著是降維。我們重複這個過程,直到我們準備應用平均池化層和softmax分類器。

Stages 和Filters

例如,讓我們設置stages=(3,4,6)和filters =(64,128,256,512)。第一個filter (64)應用於唯一的CONV層,而不是殘差模塊的一部分 - 網絡中的第一個CONV層。然後,三個(stages= 3)殘差模塊堆疊在彼此之上 - 每個模塊將學習128個filters。空間維度將減少,然後我們將四個(stage = 4)殘差模塊堆疊在一起 - 每個模擬256個filters。最後,我們再次減少空間維度,繼續將六個(stage = 6)殘差模塊堆疊在一起,每個模塊學習512個過濾器。

從頭開始利用Python實現ResNet模型

ResNet架構。帶圓圈的數字是filter值,而括號顯示stage 。注意每個stage 後如何降低維數。

讓我們回到構建構建方法。

	# initialize the input shape to be "channels last" and the
# channels dimension itself
inputShape = (height, width, depth)
chanDim = -1
# if we are using "channels first", update the input shape
# and channels dimension
if K.image_data_format() == "channels_first":
inputShape = (depth, height, width)
chanDim = 1
從頭開始利用Python實現ResNet模型

根據我們是使用“channel last”還是“channel first”排序(第3-4行)初始化inputShape和chanDim。

		# set the input and apply BN
inputs = Input(shape=inputShape)
x = BatchNormalization(axis=chanDim, epsilon=bnEps,
momentum=bnMom)(inputs)
# apply CONV => BN => ACT => POOL to reduce spatial size
x = Conv2D(filters[0], (5, 5), use_bias=False,
padding="same", kernel_regularizer=l2(reg))(x)
x = BatchNormalization(axis=chanDim, epsilon=bnEps,
momentum=bnMom)(x)
x = Activation("relu")(x)
x = ZeroPadding2D((1, 1))(x)
x = MaxPooling2D((3, 3), strides=(2, 2))(x)
從頭開始利用Python實現ResNet模型

如上所述,ResNet使用BN作為第一層作為輸入歸一化的附加級別(第2-4行)。然後,我們應用CONV =>,BN => ACT => POOL來減小空間大小(第7-13行)。現在,讓我們開始將殘差層堆疊在一起。

	# loop over the number of stages
for i in range(0, len(stages)):
# initialize the stride, then apply a residual module
# used to reduce the spatial size of the input volume
stride = (1, 1) if i == 0 else (2, 2)
x = ResNet.residual_module(x, filters[i + 1], stride,
chanDim, red=True, bnEps=bnEps, bnMom=bnMom)
# loop over the number of layers in the stage
for j in range(0, stages[i] - 1):
# apply a ResNet module
x = ResNet.residual_module(x, filters[i + 1],
(1, 1), chanDim, bnEps=bnEps, bnMom=bnMom)
從頭開始利用Python實現ResNet模型

為了在不使用池化層的情況下減小體積大小,我們可以改變卷積的步幅。stage中的第一個條目將具有(1,1)的步幅 - 表示沒有下采樣。然後,在此之後的每個stage,我們將應用具有(2,2)步幅的殘差模塊,這將減小體積大小。這在第5行顯示。

然後,我們在第10-13行循環遍歷當前stage的層數(將堆疊在彼此之上的殘差模塊的數量)。我們使用[i + 1]作為filters的索引,因為已經使用了第一個filter。一旦我們將stage[i]殘差模塊堆疊在彼此之上,我們將返回到第6-7行,在那裡我們減小體積的空間維度並重復該過程。

為避免dense的全連接層,我們將應用平均池化而不是將卷大小減小到1 x 1 x classes:

	# apply BN => ACT => POOL
x = BatchNormalization(axis=chanDim, epsilon=bnEps,
momentum=bnMom)(x)
x = Activation("relu")(x)
x = AveragePooling2D((8, 8))(x)
從頭開始利用Python實現ResNet模型

最後,我們將為我們要學習的類的總數創建一個dense層,然後應用softmax激活來生成我們的最終輸出概率!

	# softmax classifier
x = Flatten()(x)
x = Dense(classes, kernel_regularizer=l2(reg))(x)
x = Activation("softmax")(x)

# create the model
model = Model(inputs, x, name="resnet")

# return the constructed network architecture
return model
從頭開始利用Python實現ResNet模型

現在我們已經完全構建了ResNet模型!您可以調用此類在深度學習項目中實現ResNet體系結構。


分享到:


相關文章: