本文參考博文https://blog.csdn.net/u013733326/article/details/80086090完成。
1.神經網絡的底層搭建
本次作業要求我們要實現一個擁有卷積層(CONV)和池化層(POOL)的網絡,它包含了前向和反向傳播。首先我們確定一下此次項目要實現的功能模塊。
-
卷積模塊,包含了以下函數:
- 使用0擴充邊界
- 卷積窗口
- 前向卷積
- 反向卷積(可選)
-
池化模塊,包含了以下函數:
- 前向池化
- 創建掩碼
- 值分配
- 反向池化(可選)
因為我寫這篇博客主要是為了鞏固學習吳恩達老師深度學習課程的知識,所以對於一些名詞的解釋就不再贅述了。直接進入正題。
1.1、邊界填充函數
邊界填充函數作用是在圖像周圍添加一些像素值為0的像素點,如下圖所示:
使用0填充邊界有以下好處:
1、卷積了上一層之后的CONV層,沒有縮小高度和寬度。 這對於建立更深的網絡非常重要,否則在更深層時,高度/寬度會縮小。 一個重要的例子是“same”卷積,其中高度/寬度在卷積完一層之后會被完全保留。
2、它可以幫助我們在圖像邊界保留更多信息。在沒有填充的情況下,卷積過程中圖像邊緣的極少數值會受到過濾器的影響從而導致信息丟失。
接下來編寫好零擴充邊界函數:
1 def zero_pad(X, pad): 2 """ 3 把數據集X的圖像邊界全部使用0來擴充pad個寬度和高度。 4 5 參數: 6 X - 圖像數據集,維度為(樣本數,圖像高度,圖像寬度,圖像通道數) 7 pad - 整數,每個圖像在垂直和水平維度上的填充量 8 返回: 9 X_paded - 擴充后的圖像數據集,維度為(樣本數,圖像高度 + 2*pad,圖像寬度 + 2*pad,圖像通道數) 10 11 """ 12 13 X_paded = np.pad(X, ( 14 (0, 0), # 樣本數,不填充 15 (pad, pad), # 圖像高度,你可以視為上面填充x個,下面填充y個(x,y) 16 (pad, pad), # 圖像寬度,你可以視為左邊填充x個,右邊填充y個(x,y) 17 (0, 0)), # 通道數,不填充 18 'constant', constant_values=0) # 連續一樣的值填充 19 20 return X_paded
1.2、單步卷積
填充好邊界之后,我們可以准備單步的卷積操作,卷積操作的意思老師課程里有講過,我自己理解就是利用過濾器進行特征的提取,下面是代碼:
1 def conv_single_step(a_slice_prev,W,b): 2 """ 3 在前一層的激活輸出的一個片段上應用一個由參數W定義的過濾器。 4 這里切片大小和過濾器大小相同 5 6 參數: 7 a_slice_prev - 輸入數據的一個片段,維度為(過濾器大小,過濾器大小,上一通道數) 8 W - 權重參數,包含在了一個矩陣中,維度為(過濾器大小,過濾器大小,上一通道數) 9 b - 偏置參數,包含在了一個矩陣中,維度為(1,1,1) 10 11 返回: 12 Z - 在輸入數據的片X上卷積滑動窗口(w,b)的結果。 13 """ 14 15 s = np.multiply(a_slice_prev,W) + b 16 17 Z = np.sum(s) 18 19 return Z
1.3、卷積層前向傳播
接下來利用編寫好的卷積操作函數來進行卷積神經網絡中的前向傳播操作,前向傳播就是利用多種過濾器對輸入的數據進行卷積計算,每一個濾波器卷積計算之后會得到一個2維的矩陣,把這一層所有的2維矩陣組合到一起形成一個高維矩陣。我們需要實現一個函數以實現對激活值進行卷積。我們需要在激活值矩陣Aprev上使用過濾器W進行卷積。該函數的輸入是前一層的激活輸出Aprev,F個過濾器,其權重矩陣為W、偏置矩陣為b,每個過濾器只有一個偏置,最后,我們需要一個包含了步長s和填充p的字典類型的超參數。
1 def conv_forward(A_prev, W, b, hparameters): 2 """ 3 實現卷積函數的前向傳播 4 5 參數: 6 A_prev - 上一層的激活輸出矩陣,維度為(m, n_H_prev, n_W_prev, n_C_prev),(樣本數量,上一層圖像的高度,上一層圖像的寬度,上一層過濾器數量) 7 W - 權重矩陣,維度為(f, f, n_C_prev, n_C),(過濾器大小,過濾器大小,上一層的過濾器數量,這一層的過濾器數量) 8 b - 偏置矩陣,維度為(1, 1, 1, n_C),(1,1,1,這一層的過濾器數量) 9 hparameters - 包含了"stride"與 "pad"的超參數字典。 10 11 返回: 12 Z - 卷積輸出,維度為(m, n_H, n_W, n_C),(樣本數,圖像的高度,圖像的寬度,過濾器數量) 13 cache - 緩存了一些反向傳播函數conv_backward()需要的一些數據 14 """ 15 16 #獲取來自上一層數據的基本信息 17 (m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape 18 19 #獲取權重矩陣的基本信息 20 ( f , f ,n_C_prev , n_C ) = W.shape 21 22 #獲取超參數hparameters的值 23 stride = hparameters["stride"] 24 pad = hparameters["pad"] 25 26 #計算卷積后的圖像的寬度高度,參考上面的公式,使用int()來進行板除 27 n_H = int(( n_H_prev - f + 2 * pad )/ stride) + 1 28 n_W = int(( n_W_prev - f + 2 * pad )/ stride) + 1 29 30 #使用0來初始化卷積輸出Z 31 Z = np.zeros((m,n_H,n_W,n_C)) 32 33 #通過A_prev創建填充過了的A_prev_pad 34 A_prev_pad = zero_pad(A_prev,pad) 35 36 for i in range(m): #遍歷樣本 37 a_prev_pad = A_prev_pad[i] #選擇第i個樣本的擴充后的激活矩陣 38 for h in range(n_H): #在輸出的垂直軸上循環 39 for w in range(n_W): #在輸出的水平軸上循環 40 for c in range(n_C): #循環遍歷輸出的通道 41 #定位當前的切片位置 42 vert_start = h * stride #豎向,開始的位置 43 vert_end = vert_start + f #豎向,結束的位置 44 horiz_start = w * stride #橫向,開始的位置 45 horiz_end = horiz_start + f #橫向,結束的位置 46 #切片位置定位好了我們就把它取出來,需要注意的是我們是“穿透”取出來的, 47 #自行腦補一下吸管插入一層層的橡皮泥就明白了 48 a_slice_prev = a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:] 49 #執行單步卷積 50 Z[i,h,w,c] = conv_single_step(a_slice_prev,W[: ,: ,: ,c],b[0,0,0,c]) 51 52 #數據處理完畢,驗證數據格式是否正確 53 assert(Z.shape == (m , n_H , n_W , n_C )) 54 55 #存儲一些緩存值,以便於反向傳播使用 56 cache = (A_prev,W,b,hparameters) 57 58 return (Z , cache)
注意上面的代碼用的是循環操作,沒有用向量化和框架。可以看出過程還是比較繁瑣的。
1.4、池化層
池化層會減少輸入的寬度和高度,這樣它會較少計算量的同時也使特征檢測器對其在輸入中的位置更加穩定。下面介紹兩種類型的池化層:
-
最大值池化層:在輸入矩陣中滑動一個大小為fxf的窗口,選取窗口里的值中的最大值,然后作為輸出的一部分。
-
均值池化層:在輸入矩陣中滑動一個大小為fxf的窗口,計算窗口里的值中的平均值,然后這個均值作為輸出的一部分。
池化層沒有用於進行反向傳播的參數,但是它們有像窗口的大小為f的超參數,它指定fxf窗口的高度和寬度,我們可以計算出最大值或平均值。
池化層前向傳播:
現在我們要在同一個函數中實現最大值池化層
和均值池化層
。
1 def pool_forward(A_prev,hparameters,mode="max"): 2 """ 3 實現池化層的前向傳播 4 5 參數: 6 A_prev - 輸入數據,維度為(m, n_H_prev, n_W_prev, n_C_prev) 7 hparameters - 包含了 "f" 和 "stride"的超參數字典 8 mode - 模式選擇【"max" | "average"】 9 10 返回: 11 A - 池化層的輸出,維度為 (m, n_H, n_W, n_C) 12 cache - 存儲了一些反向傳播需要用到的值,包含了輸入和超參數的字典。 13 """ 14 15 #獲取輸入數據的基本信息 16 (m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape 17 18 #獲取超參數的信息 19 f = hparameters["f"] 20 stride = hparameters["stride"] 21 22 #計算輸出維度 23 n_H = int((n_H_prev - f) / stride ) + 1 24 n_W = int((n_W_prev - f) / stride ) + 1 25 n_C = n_C_prev 26 27 #初始化輸出矩陣 28 A = np.zeros((m , n_H , n_W , n_C)) 29 30 for i in range(m): #遍歷樣本 31 for h in range(n_H): #在輸出的垂直軸上循環 32 for w in range(n_W): #在輸出的水平軸上循環 33 for c in range(n_C): #循環遍歷輸出的通道 34 #定位當前的切片位置 35 vert_start = h * stride #豎向,開始的位置 36 vert_end = vert_start + f #豎向,結束的位置 37 horiz_start = w * stride #橫向,開始的位置 38 horiz_end = horiz_start + f #橫向,結束的位置 39 #定位完畢,開始切割 40 a_slice_prev = A_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] 41 42 #對切片進行池化操作 43 if mode == "max": 44 A[ i , h , w , c ] = np.max(a_slice_prev) 45 elif mode == "average": 46 A[ i , h , w , c ] = np.mean(a_slice_prev) 47 48 #池化完畢,校驗數據格式 49 assert(A.shape == (m , n_H , n_W , n_C)) 50 51 #校驗完畢,開始存儲用於反向傳播的值 52 cache = (A_prev,hparameters) 53 54 return A,cache
1.5、卷積神經網絡中的反向傳播
在現在的深度學習框架中我們不需要事先反向傳播,只需要實現前向傳播就行,吳恩達老師的課程中也沒有介紹,我就在原博文中了解了一下。
1 def conv_backward(dZ,cache): 2 """ 3 實現卷積層的反向傳播 4 5 參數: 6 dZ - 卷積層的輸出Z的 梯度,維度為(m, n_H, n_W, n_C) 7 cache - 反向傳播所需要的參數,conv_forward()的輸出之一 8 9 返回: 10 dA_prev - 卷積層的輸入(A_prev)的梯度值,維度為(m, n_H_prev, n_W_prev, n_C_prev) 11 dW - 卷積層的權值的梯度,維度為(f,f,n_C_prev,n_C) 12 db - 卷積層的偏置的梯度,維度為(1,1,1,n_C) 13 14 """ 15 #獲取cache的值 16 (A_prev, W, b, hparameters) = cache 17 18 #獲取A_prev的基本信息 19 (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape 20 21 #獲取dZ的基本信息 22 (m,n_H,n_W,n_C) = dZ.shape 23 24 #獲取權值的基本信息 25 (f, f, n_C_prev, n_C) = W.shape 26 27 #獲取hparaeters的值 28 pad = hparameters["pad"] 29 stride = hparameters["stride"] 30 31 #初始化各個梯度的結構 32 dA_prev = np.zeros((m,n_H_prev,n_W_prev,n_C_prev)) 33 dW = np.zeros((f,f,n_C_prev,n_C)) 34 db = np.zeros((1,1,1,n_C)) 35 36 #前向傳播中我們使用了pad,反向傳播也需要使用,這是為了保證數據結構一致 37 A_prev_pad = zero_pad(A_prev,pad) 38 dA_prev_pad = zero_pad(dA_prev,pad) 39 40 #現在處理數據 41 for i in range(m): 42 #選擇第i個擴充了的數據的樣本,降了一維。 43 a_prev_pad = A_prev_pad[i] 44 da_prev_pad = dA_prev_pad[i] 45 46 for h in range(n_H): 47 for w in range(n_W): 48 for c in range(n_C): 49 #定位切片位置 50 vert_start = h 51 vert_end = vert_start + f 52 horiz_start = w 53 horiz_end = horiz_start + f 54 55 #定位完畢,開始切片 56 a_slice = a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:] 57 58 #切片完畢,使用上面的公式計算梯度 59 da_prev_pad[vert_start:vert_end, horiz_start:horiz_end,:] += W[:,:,:,c] * dZ[i, h, w, c] 60 dW[:,:,:,c] += a_slice * dZ[i,h,w,c] 61 db[:,:,:,c] += dZ[i,h,w,c] 62 #設置第i個樣本最終的dA_prev,即把非填充的數據取出來。 63 dA_prev[i,:,:,:] = da_prev_pad[pad:-pad, pad:-pad, :] 64 65 #數據處理完畢,驗證數據格式是否正確 66 assert(dA_prev.shape == (m, n_H_prev, n_W_prev, n_C_prev)) 67 68 return (dA_prev,dW,db)
1 def create_mask_from_window(x): 2 """ 3 從輸入矩陣中創建掩碼,以保存最大值的矩陣的位置。 4 5 參數: 6 x - 一個維度為(f,f)的矩陣 7 8 返回: 9 mask - 包含x的最大值的位置的矩陣 10 """ 11 mask = x == np.max(x) 12 13 return mask
1 def distribute_value(dz,shape): 2 """ 3 給定一個值,為按矩陣大小平均分配到每一個矩陣位置中。 4 5 參數: 6 dz - 輸入的實數 7 shape - 元組,兩個值,分別為n_H , n_W 8 9 返回: 10 a - 已經分配好了值的矩陣,里面的值全部一樣。 11 12 """ 13 #獲取矩陣的大小 14 (n_H , n_W) = shape 15 16 #計算平均值 17 average = dz / (n_H * n_W) 18 19 #填充入矩陣 20 a = np.ones(shape) * average 21 22 return a
1 def pool_backward(dA,cache,mode = "max"): 2 """ 3 實現池化層的反向傳播 4 5 參數: 6 dA - 池化層的輸出的梯度,和池化層的輸出的維度一樣 7 cache - 池化層前向傳播時所存儲的參數。 8 mode - 模式選擇,【"max" | "average"】 9 10 返回: 11 dA_prev - 池化層的輸入的梯度,和A_prev的維度相同 12 13 """ 14 #獲取cache中的值 15 (A_prev , hparaeters) = cache 16 17 #獲取hparaeters的值 18 f = hparaeters["f"] 19 stride = hparaeters["stride"] 20 21 #獲取A_prev和dA的基本信息 22 (m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape 23 (m , n_H , n_W , n_C) = dA.shape 24 25 #初始化輸出的結構 26 dA_prev = np.zeros_like(A_prev) 27 28 #開始處理數據 29 for i in range(m): 30 a_prev = A_prev[i] 31 for h in range(n_H): 32 for w in range(n_W): 33 for c in range(n_C): 34 #定位切片位置 35 vert_start = h 36 vert_end = vert_start + f 37 horiz_start = w 38 horiz_end = horiz_start + f 39 40 #選擇反向傳播的計算方式 41 if mode == "max": 42 #開始切片 43 a_prev_slice = a_prev[vert_start:vert_end,horiz_start:horiz_end,c] 44 #創建掩碼 45 mask = create_mask_from_window(a_prev_slice) 46 #計算dA_prev 47 dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] += np.multiply(mask,dA[i,h,w,c]) 48 49 elif mode == "average": 50 #獲取dA的值 51 da = dA[i,h,w,c] 52 #定義過濾器大小 53 shape = (f,f) 54 #平均分配 55 dA_prev[i,vert_start:vert_end, horiz_start:horiz_end ,c] += distribute_value(da,shape) 56 #數據處理完畢,開始驗證格式 57 assert(dA_prev.shape == A_prev.shape) 58 59 return dA_prev
以上就是使用原生代碼實現的神經網絡,接下來作者使用了TensorFlow框架來搭建了一個卷積神經網絡,並將它應用於手勢識別中。
2、神經網絡的應用
2.1、TensorFlow模型
首先是導入所需要的庫:
1 import math 2 import numpy as np 3 import h5py 4 import matplotlib.pyplot as plt 5 import matplotlib.image as mpimg 6 import tensorflow as tf 7 from tensorflow.python.framework import ops 8 9 import cnn_utils 10 11 np.random.seed(1)
數據集使用的是課程二中TensorFlow入門那節課的課后作業的數據集:
2.2、 創建placeholders
TensorFlow要求您為運行會話時將輸入到模型中的輸入數據創建占位符。現在我們要實現創建占位符的函數,因為我們使用的是小批量數據塊,輸入的樣本數量可能不固定,所以我們在數量那里我們要使用None作為可變數量。輸入X的維度為**[None,n_H0,n_W0,n_C0],對應的Y是[None,n_y]**。
1 def create_placeholders(n_H0, n_W0, n_C0, n_y): 2 """ 3 為session創建占位符 4 5 參數: 6 n_H0 - 實數,輸入圖像的高度 7 n_W0 - 實數,輸入圖像的寬度 8 n_C0 - 實數,輸入的通道數 9 n_y - 實數,分類數 10 11 輸出: 12 X - 輸入數據的占位符,維度為[None, n_H0, n_W0, n_C0],類型為"float" 13 Y - 輸入數據的標簽的占位符,維度為[None, n_y],維度為"float" 14 """ 15 X = tf.placeholder(tf.float32,[None, n_H0, n_W0, n_C0]) 16 Y = tf.placeholder(tf.float32,[None, n_y]) 17 18 return X,Y
2.3、初始化參數
現在我們將使用tf.contrib.layers.xavier_initializer(seed = 0) 來初始化權值/過濾器W1、W2。在這里,我們不需要考慮偏置,因為TensorFlow會考慮到的。需要注意的是我們只需要初始化為2D卷積函數,全連接層TensorFlow會自動初始化的。
1 def initialize_parameters(): 2 """ 3 初始化權值矩陣,這里我們把權值矩陣硬編碼: 4 W1 : [4, 4, 3, 8] 5 W2 : [2, 2, 8, 16] 6 7 返回: 8 包含了tensor類型的W1、W2的字典 9 """ 10 tf.set_random_seed(1) 11 12 W1 = tf.get_variable("W1",[4,4,3,8],initializer=tf.contrib.layers.xavier_initializer(seed=0)) 13 W2 = tf.get_variable("W2",[2,2,8,16],initializer=tf.contrib.layers.xavier_initializer(seed=0)) 14 15 parameters = {"W1": W1, 16 "W2": W2} 17 18 return parameters
2.4、前向傳播
在TensorFlow里面有一些可以直接拿來用的函數:
-
tf.nn.conv2d(X,W1,strides=[1,s,s,1],padding='SAME')
:給定輸入XXX和一組過濾器W1W1W1,這個函數將會自動使用W1W1W1來對XXX進行卷積,第三個輸入參數是**[1,s,s,1]**是指對於輸入 (m, n_H_prev, n_W_prev, n_C_prev)而言,每次滑動的步伐。 -
tf.nn.max_pool(A, ksize = [1,f,f,1], strides = [1,s,s,1], padding = 'SAME')
:給定輸入XXX,該函數將會使用大小為(f,f)以及步伐為(s,s)的窗口對其進行滑動取最大值 -
tf.contrib.layers.flatten(P)
:給定一個輸入P,此函數將會把每個樣本轉化成一維的向量,然后返回一個tensor變量,其維度為(batch_size,k) -
tf.contrib.layers.fully_connected(F, num_outputs)
:給定一個已經一維化了的輸入F,此函數將會返回一個由全連接層計算過后的輸出。
使用tf.contrib.layers.fully_connected(F, num_outputs)
的時候,全連接層會自動初始化權值且在你訓練模型的時候它也會一直參與,所以當我們初始化參數的時候我們不需要專門去初始化它的權值。
我們實現前向傳播的時候,我們需要定義一下我們模型的大概樣子:
我們具體實現的時候,我們需要使用以下的步驟和參數:
- Conv2d : 步伐:1,填充方式:“SAME”
- ReLU
- Max pool : 過濾器大小:8x8,步伐:8x8,填充方式:“SAME”
- Conv2d : 步伐:1,填充方式:“SAME”
- ReLU
- Max pool : 過濾器大小:4x4,步伐:4x4,填充方式:“SAME”
- 一維化上一層的輸出
- 全連接層(FC):使用沒有非線性激活函數的全連接層。這里不要調用SoftMax, 這將導致輸出層中有6個神經元,然后再傳遞到softmax。 在TensorFlow中,softmax和cost函數被集中到一個函數中,在計算成本時您將調用不同的函數。
1 def forward_propagation(X,parameters): 2 """ 3 實現前向傳播 4 CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED 5 6 參數: 7 X - 輸入數據的placeholder,維度為(輸入節點數量,樣本數量) 8 parameters - 包含了“W1”和“W2”的python字典。 9 10 返回: 11 Z3 - 最后一個LINEAR節點的輸出 12 13 """ 14 W1 = parameters['W1'] 15 W2 = parameters['W2'] 16 17 #Conv2d : 步伐:1,填充方式:“SAME” 18 Z1 = tf.nn.conv2d(X,W1,strides=[1,1,1,1],padding="SAME") 19 #ReLU : 20 A1 = tf.nn.relu(Z1) 21 #Max pool : 窗口大小:8x8,步伐:8x8,填充方式:“SAME” 22 P1 = tf.nn.max_pool(A1,ksize=[1,8,8,1],strides=[1,8,8,1],padding="SAME") 23 24 #Conv2d : 步伐:1,填充方式:“SAME” 25 Z2 = tf.nn.conv2d(P1,W2,strides=[1,1,1,1],padding="SAME") 26 #ReLU : 27 A2 = tf.nn.relu(Z2) 28 #Max pool : 過濾器大小:4x4,步伐:4x4,填充方式:“SAME” 29 P2 = tf.nn.max_pool(A2,ksize=[1,4,4,1],strides=[1,4,4,1],padding="SAME") 30 31 #一維化上一層的輸出 32 P = tf.contrib.layers.flatten(P2) 33 34 #全連接層(FC):使用沒有非線性激活函數的全連接層 35 Z3 = tf.contrib.layers.fully_connected(P,6,activation_fn=None) 36 37 return Z3 38
2.5、計算成本
我們要在這里實現計算成本的函數,下面的兩個函數是我們要用到的:
-
tf.nn.softmax_cross_entropy_with_logits(logits = Z3 , lables = Y)
:計算softmax的損失函數。這個函數既計算softmax的激活,也計算其損失 -
tf.reduce_mean
:計算的是平均值,使用它來計算所有樣本的損失來得到總成本。
1 def compute_cost(Z3,Y): 2 """ 3 計算成本 4 參數: 5 Z3 - 正向傳播最后一個LINEAR節點的輸出,維度為(6,樣本數)。 6 Y - 標簽向量的placeholder,和Z3的維度相同 7 8 返回: 9 cost - 計算后的成本 10 11 """ 12 13 cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y)) 14 15 return cost
2.6、構建模型
最后,我們已經實現了我們所有的函數,我們現在就可以實現我們的模型了。
我們之前在課程2就實現過random_mini_batches()
這個函數,它返回的是一個mini-batches的列表。
在實現這個模型的時候我們要經歷以下步驟:
- 創建占位符
- 初始化參數
- 前向傳播
- 計算成本
- 反向傳播
- 創建優化器
最后,我們將創建一個session來運行模型。
1 def model(X_train, Y_train, X_test, Y_test, learning_rate=0.009, 2 num_epochs=100,minibatch_size=64,print_cost=True,isPlot=True): 3 """ 4 使用TensorFlow實現三層的卷積神經網絡 5 CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED 6 7 參數: 8 X_train - 訓練數據,維度為(None, 64, 64, 3) 9 Y_train - 訓練數據對應的標簽,維度為(None, n_y = 6) 10 X_test - 測試數據,維度為(None, 64, 64, 3) 11 Y_test - 訓練數據對應的標簽,維度為(None, n_y = 6) 12 learning_rate - 學習率 13 num_epochs - 遍歷整個數據集的次數 14 minibatch_size - 每個小批量數據塊的大小 15 print_cost - 是否打印成本值,每遍歷100次整個數據集打印一次 16 isPlot - 是否繪制圖譜 17 18 返回: 19 train_accuracy - 實數,訓練集的准確度 20 test_accuracy - 實數,測試集的准確度 21 parameters - 學習后的參數 22 """ 23 ops.reset_default_graph() #能夠重新運行模型而不覆蓋tf變量 24 tf.set_random_seed(1) #確保你的數據和我一樣 25 seed = 3 #指定numpy的隨機種子 26 (m , n_H0, n_W0, n_C0) = X_train.shape 27 n_y = Y_train.shape[1] 28 costs = [] 29 30 #為當前維度創建占位符 31 X , Y = create_placeholders(n_H0, n_W0, n_C0, n_y) 32 33 #初始化參數 34 parameters = initialize_parameters() 35 36 #前向傳播 37 Z3 = forward_propagation(X,parameters) 38 39 #計算成本 40 cost = compute_cost(Z3,Y) 41 42 #反向傳播,由於框架已經實現了反向傳播,我們只需要選擇一個優化器就行了 43 optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) 44 45 #全局初始化所有變量 46 init = tf.global_variables_initializer() 47 48 #開始運行 49 with tf.Session() as sess: 50 #初始化參數 51 sess.run(init) 52 #開始遍歷數據集 53 for epoch in range(num_epochs): 54 minibatch_cost = 0 55 num_minibatches = int(m / minibatch_size) #獲取數據塊的數量 56 seed = seed + 1 57 minibatches = cnn_utils.random_mini_batches(X_train,Y_train,minibatch_size,seed) 58 59 #對每個數據塊進行處理 60 for minibatch in minibatches: 61 #選擇一個數據塊 62 (minibatch_X,minibatch_Y) = minibatch 63 #最小化這個數據塊的成本 64 _ , temp_cost = sess.run([optimizer,cost],feed_dict={X:minibatch_X, Y:minibatch_Y}) 65 66 #累加數據塊的成本值 67 minibatch_cost += temp_cost / num_minibatches 68 69 #是否打印成本 70 if print_cost: 71 #每5代打印一次 72 if epoch % 5 == 0: 73 print("當前是第 " + str(epoch) + " 代,成本值為:" + str(minibatch_cost)) 74 75 #記錄成本 76 if epoch % 1 == 0: 77 costs.append(minibatch_cost) 78 79 #數據處理完畢,繪制成本曲線 80 if isPlot: 81 plt.plot(np.squeeze(costs)) 82 plt.ylabel('cost') 83 plt.xlabel('iterations (per tens)') 84 plt.title("Learning rate =" + str(learning_rate)) 85 plt.show() 86 87 #開始預測數據 88 ## 計算當前的預測情況 89 predict_op = tf.arg_max(Z3,1) 90 corrent_prediction = tf.equal(predict_op , tf.arg_max(Y,1)) 91 92 ##計算准確度 93 accuracy = tf.reduce_mean(tf.cast(corrent_prediction,"float")) 94 print("corrent_prediction accuracy= " + str(accuracy)) 95 96 train_accuracy = accuracy.eval({X: X_train, Y: Y_train}) 97 test_accuary = accuracy.eval({X: X_test, Y: Y_test}) 98 99 print("訓練集准確度:" + str(train_accuracy)) 100 print("測試集准確度:" + str(test_accuary)) 101 102 return (train_accuracy,test_accuary,parameters)
然后我們啟動模型:
1 _, _, parameters = model(X_train, Y_train, X_test, Y_test,num_epochs=150)
2.7、預測
之后我們利用自己的照片測試一下效果如何:
1 my_image1 = "test.png" #定義圖片名稱 2 fileName1 = "datasets/" + my_image1 #圖片地址 3 image1 = mpimg.imread(fileName1) #讀取圖片 4 plt.imshow(image1) #顯示圖片 5 my_image1 = image1.reshape(1,64 * 64 * 3).T #重構圖片 6 my_image_prediction = cnn_utils.predict(my_image1, parameters) #開始預測 7 print("預測結果: y = " + str(np.squeeze(my_image_prediction)))
以上就是本次作業的全部內容,本次是對卷積神經網絡的初次探索,利用原生代碼實現還是比較復雜的,tensflow框架用起來很簡單,但是還是不太熟練,很多函數需要多練習。