本文參考文章https://blog.csdn.net/u013733326/article/details/80250818完成,原文寫的非常好,非常詳細,如果是第一次做這個作業的同學可以去看一下這個作者的文章,非常推薦。
本次作業的內容有兩項,一是框架Keras的入門練習,二是搭建一個殘差網絡。下面開始第一項任務。
1 Keras入門 - 笑臉識別
Keras框架是一個高級的神經網絡的框架,能夠運行在包括TensorFlow和CNTK的幾個較低級別的框架之上的框架。通過引入所需要的庫可以實現從Keras中導入很多功能, 只需直接調用它們即可輕松使用它們。 比如:X = Input(…) 或者X = ZeroPadding2D(…)。
1 import numpy as np 2 from keras import layers 3 from keras.layers import Input, Dense, Activation, ZeroPadding2D, BatchNormalization, Flatten, Conv2D 4 from keras.layers import AveragePooling2D, MaxPooling2D, Dropout, GlobalMaxPooling2D, GlobalAveragePooling2D 5 from keras.models import Model 6 from keras.preprocessing import image 7 from keras.utils import layer_utils 8 from keras.utils.data_utils import get_file 9 from keras.applications.imagenet_utils import preprocess_input 10 import pydot 11 from IPython.display import SVG 12 from keras.utils.vis_utils import model_to_dot 13 from keras.utils import plot_model 14 import kt_utils 15 16 import keras.backend as K 17 K.set_image_data_format('channels_last') 18 import matplotlib.pyplot as plt 19 from matplotlib.pyplot import imshow
1.1 任務描述
此次項目實現的功能就是識別圖片中的人是否是笑臉,來判斷人物是否開心,下面是我們的數據集:

我們先來加載一下我們的數據:
1 X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = kt_utils.load_dataset() 2 3 # Normalize image vectors 4 X_train = X_train_orig/255. 5 X_test = X_test_orig/255. 6 7 # Reshape 8 Y_train = Y_train_orig.T 9 Y_test = Y_test_orig.T 10 print ("number of training examples = " + str(X_train.shape[0])) 11 print ("number of test examples = " + str(X_test.shape[0])) 12 print ("X_train shape: " + str(X_train.shape)) 13 print ("Y_train shape: " + str(Y_train.shape)) 14 print ("X_test shape: " + str(X_test.shape)) 15 print ("Y_test shape: " + str(Y_test.shape))

可以看到圖片的維度是64*64*3,訓練集有600張,測試集有150張。
1.2 構建模型
接下來我們利用Keras框架構建出來我們要的模型:
1 def HappyModel(input_shape): 2 """ 3 實現一個檢測笑容的模型 4 5 參數: 6 input_shape - 輸入的數據的維度 7 返回: 8 model - 創建的Keras的模型 9 10 """ 11 12 # 你可以參考和上面的大綱 13 X_input = Input(input_shape) 14 15 # 使用0填充:X_input的周圍填充0 16 X = ZeroPadding2D((3, 3))(X_input) 17 18 # 對X使用 CONV -> BN -> RELU 塊 19 X = Conv2D(32, (7, 7), strides=(1, 1), name='conv0')(X) 20 X = BatchNormalization(axis=3, name='bn0')(X) 21 X = Activation('relu')(X) 22 23 # 最大值池化層 24 X = MaxPooling2D((2, 2), name='max_pool')(X) 25 26 # 降維,矩陣轉化為向量 + 全連接層 27 X = Flatten()(X) 28 X = Dense(1, activation='sigmoid', name='fc')(X) 29 30 # 創建模型,講話創建一個模型的實體,我們可以用它來訓練、測試。 31 model = Model(inputs=X_input, outputs=X, name='HappyModel') 32 33 return model
可以看到用Keras框架實現的模型非常簡單,因為框架為我們提供了比如填充、卷積操作等函數,我們只需要用一行代碼調用一下就行,並且只需要完成前向傳播,所以看起來很方便。
現在我們已經設計好了我們的模型了,要訓練並測試模型我們需要這么做:
- 創建一個模型實體。
- 編譯模型,可以使用這個語句:
model.compile(optimizer = "...", loss = "...", metrics = ["accuracy"])。 - 訓練模型:
model.fit(x = ..., y = ..., epochs = ..., batch_size = ...)。 - 評估模型:
model.evaluate(x = ..., y = ...)。
接下來我們運行這些步驟:
1 #創建一個模型實體 2 happy_model = HappyModel(X_train.shape[1:]) 3 #編譯模型 4 happy_model.compile("adam","binary_crossentropy", metrics=['accuracy']) 5 #訓練模型 6 #請注意,此操作會花費你大約6-10分鍾。 7 happy_model.fit(X_train, Y_train, epochs=40, batch_size=50) 8 #評估模型 9 preds = happy_model.evaluate(X_test, Y_test, batch_size=32, verbose=1, sample_weight=None) 10 print ("誤差值 = " + str(preds[0])) 11 print ("准確度 = " + str(preds[1]))
下面是我的運行結果:

以上就是用Keras搭建的卷積神經網絡框架,Keras框架還是很強的,應該仔細的學一下,另外,在搭建項目之前遇到了版本對應的錯誤,因為Keras框架運行於TensorFlow之上,所以會用到這兩種框架,我安裝的TensorFlow是1.5版本的,默認安裝的Keras框架是最新版本2.4多的,所以這兩框架不匹配,大家在實現之前可以先看一下自己的TensorFlow框架的版本,在網上查一下匹配版本對應,再安裝對應版本的Keras。
2 殘差網絡的搭建
第二項作業是搭建殘差網絡,殘差網絡一般深度都很大,利用殘差網絡可以解決過深的網絡難以訓練的問題。此次作業實現兩部分:一是實現殘差塊,二是把這些殘差塊放一起,實現並訓練用於圖像分類的網絡。首先是用到的庫函數。
1 import numpy as np 2 import tensorflow as tf 3 4 from keras import layers 5 from keras.layers import Input, Add, Dense, Activation, ZeroPadding2D, BatchNormalization, Flatten, Conv2D, AveragePooling2D, MaxPooling2D, GlobalMaxPooling2D 6 from keras.models import Model, load_model 7 from keras.preprocessing import image 8 from keras.utils import layer_utils 9 from keras.utils.data_utils import get_file 10 from keras.applications.imagenet_utils import preprocess_input 11 from keras.utils.vis_utils import model_to_dot 12 from keras.utils import plot_model 13 from keras.initializers import glorot_uniform 14 15 import pydot 16 from IPython.display import SVG 17 import scipy.misc 18 from matplotlib.pyplot import imshow 19 import keras.backend as K 20 K.set_image_data_format('channels_last') 21 K.set_learning_phase(1) 22 23 import resnets_utils
2.1 構建一個殘差網絡
在殘差網絡中,一個“捷徑(shortcut)”或者說“跳躍連接(skip connection)”允許梯度直接反向傳播到更淺的層,如下圖:

圖像左邊是神經網絡的主路,圖像右邊是添加了一條捷徑的主路,通過這些殘差塊堆疊在一起,可以形成一個非常深的網絡。
我們在視頻中可以看到使用捷徑的方式使得每一個殘差塊能夠很容易學習到恆等式功能,這意味着我們可以添加很多的殘差塊而不會損害訓練集的表現。
殘差塊有兩種類型,主要取決於輸入輸出的維度是否相同
2.1.1 恆等塊(Identity block)
恆等塊是殘差網絡使用的的標准塊,對應於輸入的激活值(比如
)與輸出激活值(比如
)具有相同的維度。為了具象化殘差塊的不同步驟,我們來看看下面的圖吧~

上圖就是一個恆等塊,大家可以看一下原文作者對於恆等塊的介紹,要實現這個恆等塊不僅要實現主路徑的內容,還要把這條捷徑表示出來。具體步驟如下:
-
主路徑的第一部分:
-
第一個CONV2D有F1個過濾器,其大小為(1,1),步長為(1,1),使用填充方式為“valid”,命名規則為
conv_name_base + '2a',使用0作為隨機種子為其初始化。 -
第一個BatchNorm是通道的軸歸一化,其命名規則為
bn_name_base + '2a'。 -
接着使用ReLU激活函數,它沒有命名也沒有超參數。
-
-
主路徑的第二部分:
-
第二個CONV2D有F2個過濾器,其大小為(f,f),步長為(1,1),使用填充方式為“same”,命名規則為
conv_name_base + '2b',使用0作為隨機種子為其初始化。 -
第二個BatchNorm是通道的軸歸一化,其命名規則為
bn_name_base + '2b'。 -
接着使用ReLU激活函數,它沒有命名也沒有超參數。
-
-
主路徑的第三部分:
-
第三個CONV2D有F3個過濾器,其大小為(1,1),步長為(1,1),使用填充方式為“valid”,命名規則為
conv_name_base + '2c',使用0作為隨機種子為其初始化。 -
第三個BatchNorm是通道的軸歸一化,其命名規則為
bn_name_base + '2c'。 -
注意這里沒有ReLU函數
-
-
最后一步:
-
將捷徑與輸入加在一起
-
使用ReLU激活函數,它沒有命名也沒有超參數。
-
接下來我們就要代碼實現殘差網絡的恆等塊了,在實現之前,我們可以先看看Keras框架的中文使用手冊:https://keras-cn.readthedocs.io/en/latest/layers/convolutional_layer/#conv2d
1 def identity_block(X, f, filters, stage, block): 2 """ 3 實現圖3的恆等塊 4 5 參數: 6 X - 輸入的tensor類型的數據,維度為( m, n_H_prev, n_W_prev, n_H_prev ) 7 f - 整數,指定主路徑中間的CONV窗口的維度 8 filters - 整數列表,定義了主路徑每層的卷積層的過濾器數量 9 stage - 整數,根據每層的位置來命名每一層,與block參數一起使用。 10 block - 字符串,據每層的位置來命名每一層,與stage參數一起使用。 11 12 返回: 13 X - 恆等塊的輸出,tensor類型,維度為(n_H, n_W, n_C) 14 15 """ 16 17 #定義命名規則 18 conv_name_base = "res" + str(stage) + block + "_branch" 19 bn_name_base = "bn" + str(stage) + block + "_branch" 20 21 #獲取過濾器 22 F1, F2, F3 = filters 23 24 #保存輸入數據,將會用於為主路徑添加捷徑 25 X_shortcut = X 26 27 #主路徑的第一部分 28 ##卷積層 29 X = Conv2D(filters=F1, kernel_size=(1,1), strides=(1,1) ,padding="valid", 30 name=conv_name_base+"2a", kernel_initializer=glorot_uniform(seed=0))(X) 31 ##歸一化 32 X = BatchNormalization(axis=3,name=bn_name_base+"2a")(X) 33 ##使用ReLU激活函數 34 X = Activation("relu")(X) 35 36 #主路徑的第二部分 37 ##卷積層 38 X = Conv2D(filters=F2, kernel_size=(f,f),strides=(1,1), padding="same", 39 name=conv_name_base+"2b", kernel_initializer=glorot_uniform(seed=0))(X) 40 ##歸一化 41 X = BatchNormalization(axis=3,name=bn_name_base+"2b")(X) 42 ##使用ReLU激活函數 43 X = Activation("relu")(X) 44 45 46 #主路徑的第三部分 47 ##卷積層 48 X = Conv2D(filters=F3, kernel_size=(1,1), strides=(1,1), padding="valid", 49 name=conv_name_base+"2c", kernel_initializer=glorot_uniform(seed=0))(X) 50 ##歸一化 51 X = BatchNormalization(axis=3,name=bn_name_base+"2c")(X) 52 ##沒有ReLU激活函數 53 54 #最后一步: 55 ##將捷徑與輸入加在一起 56 X = Add()([X,X_shortcut]) 57 ##使用ReLU激活函數 58 X = Activation("relu")(X) 59 60 return X
2.1.2 卷積塊
我們已經實現了殘差網絡的恆等塊,現在,殘差網絡的卷積塊是另一種類型的殘差塊,它適用於輸入輸出的維度不一致的情況,它不同於上面的恆等塊,與之區別在於,捷徑中有一個CONV2D層,如下圖:

捷徑中的卷積層將把輸入x卷積為不同的維度,因此在主路徑最后那里需要適配捷徑中的維度。比如:把激活值中的寬高減少2倍,我們可以使用1x1的卷積,步伐為2。捷徑上的卷積層不使用任何非線性激活函數,它的主要作用是僅僅應用(學習后的)線性函數來減少輸入的維度,以便在后面的加法步驟中的維度相匹配。
具體步驟如下:
-
主路徑第一部分:
-
第一個卷積層有F1個過濾器,其維度為(1,1),步伐為(s,s),使用“valid”的填充方式,命名規則為
conv_name_base + '2a' -
第一個規范層是通道的軸歸一化,其命名規則為
bn_name_base + '2a' -
使用ReLU激活函數,它沒有命名規則也沒有超參數。
-
-
主路徑第二部分:
-
第二個卷積層有F2個過濾器,其維度為(f,f),步伐為(1,1),使用“same”的填充方式,命名規則為
conv_name_base + '2b' -
第二個規范層是通道的軸歸一化,其命名規則為
bn_name_base + '2b' -
使用ReLU激活函數,它沒有命名規則也沒有超參數。
-
-
主路徑第三部分:
-
第三個卷積層有F3個過濾器,其維度為(1,1),步伐為(s,s),使用“valid”的填充方式,命名規則為
conv_name_base + '2c' -
第三個規范層是通道的軸歸一化,其命名規則為
bn_name_base + '2c' -
沒有激活函數
-
-
捷徑:
-
此卷積層有F3個過濾器,其維度為(1,1),步伐為(s,s),使用“valid”的填充方式,命名規則為
conv_name_base + '1' -
此規范層是通道的軸歸一化,其命名規則為
bn_name_base + '1'
-
-
最后一步:
-
將捷徑與輸入加在一起
-
使用ReLU激活函數
-
下面是實現代碼:
1 def convolutional_block(X, f, filters, stage, block, s=2): 2 """ 3 實現圖5的卷積塊 4 5 參數: 6 X - 輸入的tensor類型的變量,維度為( m, n_H_prev, n_W_prev, n_C_prev) 7 f - 整數,指定主路徑中間的CONV窗口的維度 8 filters - 整數列表,定義了主路徑每層的卷積層的過濾器數量 9 stage - 整數,根據每層的位置來命名每一層,與block參數一起使用。 10 block - 字符串,據每層的位置來命名每一層,與stage參數一起使用。 11 s - 整數,指定要使用的步幅 12 13 返回: 14 X - 卷積塊的輸出,tensor類型,維度為(n_H, n_W, n_C) 15 """ 16 17 #定義命名規則 18 conv_name_base = "res" + str(stage) + block + "_branch" 19 bn_name_base = "bn" + str(stage) + block + "_branch" 20 21 #獲取過濾器數量 22 F1, F2, F3 = filters 23 24 #保存輸入數據 25 X_shortcut = X 26 27 #主路徑 28 ##主路徑第一部分 29 X = Conv2D(filters=F1, kernel_size=(1,1), strides=(s,s), padding="valid", 30 name=conv_name_base+"2a", kernel_initializer=glorot_uniform(seed=0))(X) 31 X = BatchNormalization(axis=3,name=bn_name_base+"2a")(X) 32 X = Activation("relu")(X) 33 34 ##主路徑第二部分 35 X = Conv2D(filters=F2, kernel_size=(f,f), strides=(1,1), padding="same", 36 name=conv_name_base+"2b", kernel_initializer=glorot_uniform(seed=0))(X) 37 X = BatchNormalization(axis=3,name=bn_name_base+"2b")(X) 38 X = Activation("relu")(X) 39 40 ##主路徑第三部分 41 X = Conv2D(filters=F3, kernel_size=(1,1), strides=(1,1), padding="valid", 42 name=conv_name_base+"2c", kernel_initializer=glorot_uniform(seed=0))(X) 43 X = BatchNormalization(axis=3,name=bn_name_base+"2c")(X) 44 45 #捷徑 46 X_shortcut = Conv2D(filters=F3, kernel_size=(1,1), strides=(s,s), padding="valid", 47 name=conv_name_base+"1", kernel_initializer=glorot_uniform(seed=0))(X_shortcut) 48 X_shortcut = BatchNormalization(axis=3,name=bn_name_base+"1")(X_shortcut) 49 50 #最后一步 51 X = Add()([X,X_shortcut]) 52 X = Activation("relu")(X) 53 54 return X
2.2 構建一個殘差網絡(50層)
我們已經做完所需要的所有殘差塊了,下面這個圖就描述了神經網絡的算法細節,圖中的"ID BLOCK"是指標准的恆等塊,"ID BLOCK X3"是指把三個恆等塊放在一起。

這個50層的網絡的細節如下:
-
對輸入數據進行0填充,padding =(3,3)
-
stage1:
-
卷積層有64個過濾器,其維度為(7,7),步伐為(2,2),命名為“conv1”
-
規范層(BatchNorm)對輸入數據進行通道軸歸一化。
-
最大值池化層使用一個(3,3)的窗口和(2,2)的步伐。
-
-
stage2:
-
卷積塊使用f=3個大小為[64,64,256]的過濾器,f=3,s=1,block=“a”
-
2個恆等塊使用三個大小為[64,64,256]的過濾器,f=3,block=“b”、“c”
-
-
stage3:
-
卷積塊使用f=3個大小為[128,128,512]的過濾器,f=3,s=2,block=“a”
-
3個恆等塊使用三個大小為[128,128,512]的過濾器,f=3,block=“b”、“c”、“d”
-
-
stage4:
-
卷積塊使用f=3個大小為[256,256,1024]的過濾器,f=3,s=2,block=“a”
-
5個恆等塊使用三個大小為[256,256,1024]的過濾器,f=3,block=“b”、“c”、“d”、“e”、“f”
-
-
stage5:
-
卷積塊使用f=3個大小為[512,512,2048]的過濾器,f=3,s=2,block=“a”
-
2個恆等塊使用三個大小為[256,256,2048]的過濾器,f=3,block=“b”、“c”
-
-
均值池化層使用維度為(2,2)的窗口,命名為“avg_pool”
-
展開操作沒有任何超參數以及命名
-
全連接層(密集連接)使用softmax激活函數,命名為
"fc" + str(classes)
1 def ResNet50(input_shape=(64,64,3),classes=6): 2 """ 3 實現ResNet50 4 CONV2D -> BATCHNORM -> RELU -> MAXPOOL -> CONVBLOCK -> IDBLOCK*2 -> CONVBLOCK -> IDBLOCK*3 5 -> CONVBLOCK -> IDBLOCK*5 -> CONVBLOCK -> IDBLOCK*2 -> AVGPOOL -> TOPLAYER 6 7 參數: 8 input_shape - 圖像數據集的維度 9 classes - 整數,分類數 10 11 返回: 12 model - Keras框架的模型 13 14 """ 15 16 #定義tensor類型的輸入數據 17 X_input = Input(input_shape) 18 19 #0填充 20 X = ZeroPadding2D((3,3))(X_input) 21 22 #stage1 23 X = Conv2D(filters=64, kernel_size=(7,7), strides=(2,2), name="conv1", 24 kernel_initializer=glorot_uniform(seed=0))(X) 25 X = BatchNormalization(axis=3, name="bn_conv1")(X) 26 X = Activation("relu")(X) 27 X = MaxPooling2D(pool_size=(3,3), strides=(2,2))(X) 28 29 #stage2 30 X = convolutional_block(X, f=3, filters=[64,64,256], stage=2, block="a", s=1) 31 X = identity_block(X, f=3, filters=[64,64,256], stage=2, block="b") 32 X = identity_block(X, f=3, filters=[64,64,256], stage=2, block="c") 33 34 #stage3 35 X = convolutional_block(X, f=3, filters=[128,128,512], stage=3, block="a", s=2) 36 X = identity_block(X, f=3, filters=[128,128,512], stage=3, block="b") 37 X = identity_block(X, f=3, filters=[128,128,512], stage=3, block="c") 38 X = identity_block(X, f=3, filters=[128,128,512], stage=3, block="d") 39 40 #stage4 41 X = convolutional_block(X, f=3, filters=[256,256,1024], stage=4, block="a", s=2) 42 X = identity_block(X, f=3, filters=[256,256,1024], stage=4, block="b") 43 X = identity_block(X, f=3, filters=[256,256,1024], stage=4, block="c") 44 X = identity_block(X, f=3, filters=[256,256,1024], stage=4, block="d") 45 X = identity_block(X, f=3, filters=[256,256,1024], stage=4, block="e") 46 X = identity_block(X, f=3, filters=[256,256,1024], stage=4, block="f") 47 48 #stage5 49 X = convolutional_block(X, f=3, filters=[512,512,2048], stage=5, block="a", s=2) 50 X = identity_block(X, f=3, filters=[512,512,2048], stage=5, block="b") 51 X = identity_block(X, f=3, filters=[512,512,2048], stage=5, block="c") 52 53 #均值池化層 54 X = AveragePooling2D(pool_size=(2,2),padding="same")(X) 55 56 #輸出層 57 X = Flatten()(X) 58 X = Dense(classes, activation="softmax", name="fc"+str(classes), 59 kernel_initializer=glorot_uniform(seed=0))(X) 60 61 62 #創建模型 63 model = Model(inputs=X_input, outputs=X, name="ResNet50") 64 65 return model
然后我們對模型做實體化和編譯工作:
1 model = ResNet50(input_shape=(64,64,3),classes=6) 2 model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"])
模型准備好后,我們就可以加載數據進行訓練了:
1 X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = resnets_utils.load_dataset() 2 3 # Normalize image vectors 4 X_train = X_train_orig / 255. 5 X_test = X_test_orig / 255. 6 7 # Convert training and test labels to one hot matrices 8 Y_train = resnets_utils.convert_to_one_hot(Y_train_orig, 6).T 9 Y_test = resnets_utils.convert_to_one_hot(Y_test_orig, 6).T 10 11 model.fit(X_train,Y_train,epochs=2,batch_size=32)


這樣,殘差網絡就訓練完畢了,原作者還利用模型測試了自己拍的圖片,我就不做測試了,另外作者繪制了網絡結構圖(可以去原作者那里看這張圖,圖片比較長),利用這張圖可以很清晰的看懂網絡結構,其實殘差網絡並不復雜,無非就是網絡層數比較多,但是沒有太復雜的結構。本周的作業是基於Keras框架的,由於對該框架掌握不深,所以此次作業的代碼都是參考的原作者的代碼,再次感謝一下作者。
參考文章:https://blog.csdn.net/u013733326/article/details/80250818
