搭建簡單神經網絡來識別圖片中是否有貓
代碼借鑑地址:純用NumPy實現神經網絡git
搭建一個簡單易懂的神經網絡來幫你理解深度神經網絡
經過簡單的貓識別的例子來幫你進一步進行理解
本代碼用 numpy 來實現,不含有正則化,批量等算法github
這裏咱們先來理清楚神經網絡的步驟算法
(1) 構建數據。咱們要構建出這樣的一個數據,shape = (n, m),n 表明特徵數,m 表明樣本數數組
(2) 初始化參數。使用隨機初始化參數 W 和 b緩存
(3) 前向傳播。bash
(4) 計算損失。網絡
(5) 反向傳播。app
(6) 更新參數。dom
(7) 構建模型。
(8) 預測。預測其實就是從新進行一次前向傳播
清楚了這些步驟以後,咱們在構建神經網絡的時候就不至於無從下手了
接下來咱們就根據上面理出來的步驟開始一步一步來構架深度神經網絡吧
---
目錄
咱們先來看一下數據集是怎樣的
咱們從 h5 文件中來獲取咱們須要的數據
這裏,我準備了兩個文件,一個是 test_catvnoncat.h5,另一個是 test_catvnoncat.h5,第一個文件裏面放置的是訓練集,第二個文件放置的是測試集
# 從文件加載數據的函數 def load_data(): # 把文件讀取到內存中 train_dataset = h5py.File('datasets/train_catvnoncat.h5', "r") train_x_orig = np.array(train_dataset["train_set_x"][:]) # 獲取訓練集特徵 train_y_orig = np.array(train_dataset["train_set_y"][:]) # 獲取訓練標籤 test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r") test_x_orig = np.array(test_dataset["test_set_x"][:]) # 獲取測試集特徵 test_y_orig = np.array(test_dataset["test_set_y"][:]) # 獲取測試標籤 classes = np.array(test_dataset["list_classes"][:]) # 類別,即 1 和 0 # 如今的數據維度是 (m,),咱們要把它變成 (1, m),m 表明樣本數量 train_y_orig = train_y_orig.reshape((1, train_y_orig.shape[0])) test_y_orig = test_y_orig.reshape((1, test_y_orig.shape[0])) return train_x_orig, train_y_orig, test_x_orig, test_y_orig, classes
咱們能夠輸出這些圖片看看
from random import randint import matplotlib.pyplot as plt # 加載數據 train_x_orig, train_y, test_x_orig, test_y, classes = load_data() # 隨機從訓練集中選取一張圖片 index = randint(0, 209) img = train_x_orig[index] # 顯示這張圖片 plt.imshow(img) print ('它的標籤是:{}'.format(train_y[0][index]))
演示結果爲:
轉換數據
由於咱們的數據爲是標準的圖片數據,咱們要把它轉換成符合咱們輸入的格式
也就是 (n, m) 格式,n 表明特徵數,m 表明樣本數量
train_x_orig, train_y, test_x_orig, test_y, classes = load_data() m_train = train_x_orig.shape[0] # 訓練樣本的數量 m_test = test_x_orig.shape[0] # 測試樣本的數量 num_px = test_x_orig.shape[1] # 每張圖片的寬/高 # 爲了方便後面進行矩陣運算,咱們須要將樣本數據進行扁平化和轉置 # 處理後的數組各維度的含義是(圖片數據,樣本數) train_x_flatten = train_x_orig.reshape(train_x_orig.shape[0], -1).T test_x_flatten = test_x_orig.reshape(test_x_orig.shape[0], -1).T # 下面咱們對特徵數據進行了簡單的標準化處理(除以255,使全部值都在[0,1]範圍內) train_x = train_x_flatten/255. test_x = test_x_flatten/255.
最後輸出數據是 (12288, m) 維度的,12288 表明特徵,即 64*64*3=12288
定義神經網絡的結構
在初始化以前咱們要明白咱們要搭建的這樣一個網絡的結構是如何的,這裏咱們採起下面的方式來定義網絡的結構
# 定義神經網絡的結構 ''' 即有四層,第一層爲 12288 的特徵輸入,第二層有 20 個單元,以此類推 ''' nn_architecture = [ {'input_dim': 12288, 'output_dim': 20, 'activation': 'relu'}, {'input_dim': 20, 'output_dim': 7, 'activation': 'relu'}, {'input_dim': 7, 'output_dim': 5, 'activation': 'relu'}, {'input_dim': 5, 'output_dim': 1, 'activation': 'sigmoid'} ]
初始化
# 根據結構隨機初始化參數 W, b def init_params(nn_architecture): np.random.seed(1) # 用來存放產生的參數 params = {} for id, layer in enumerate(nn_architecture): # layer_id -> [1, 2, 3, 4] layer_id = id + 1 params['W' + str(layer_id)] = np.random.randn(layer['output_dim'], layer['input_dim']) / np.sqrt(layer['input_dim']) params['b' + str(layer_id)] = np.zeros((layer['output_dim'], 1)) return params
先來看看前向傳播作了些什麼事情
激活函數
def sigmoid(Z): ''' 參數 Z: shape = (output_dim, m) # output_dim 指當前層的單元數 返回值 1/(1+np.exp(-Z)): sigmoid 計算結果 shape = (output_dim, m) ''' return 1/(1+np.exp(-Z)) def relu(Z): ''' 參數 Z: shape = (output_dim, m) # output_dim 指當前層的單元數 返回值 np.maximum(0, Z): relu 計算結果 shape = (output_dim, m) ''' return np.maximum(0,Z)
構建單層前向傳播
也就是咱們在一層內作了些什麼,咱們用這個函數來實現它
$$Z_curr = W_curr·A_prev + b_curr$$
$$A_curr = g(Z_curr)$$
# 單層前向傳播 def layer_forward(W_curr, b_curr, A_prev, activation): ''' 計算 Z_curr = W_curr·A_prev + b_curr A_curr = g(Z_curr) 參數 W_curr: 當前層的 W 參數 b_curr: 當前層的 b 參數 A_prev: 上一層的 A 矩陣 activation: 當前層要用的激活函數 返回值 Z_curr: 當前層的 Z A_curr: 當前層的 A ''' Z_curr = np.dot(W_curr, A_prev) + b_curr # 判斷激活函數並求 A if activation == 'relu': A_curr = relu(Z_curr) elif activation == 'sigmoid': A_curr = sigmoid(Z_curr) else: raise Exception('不支持的激活函數類型!') return Z_curr, A_curr
構建完整的前向傳播
完整的前向傳播網絡中,我把 Z_curr 和 A_prev 封裝成一個字典放入當前層的緩存中,以便後面進行梯度降低的時候用到,而後再把全部層的緩存構成一個列表 caches,這個 caches 咱們在後面要用到,因此這裏咱們要返回兩個數據 A 和 caches
# 完整前向傳播 def full_forward(X, params, nn_architecture): ''' 參數 X: 輸入 params: W, b 參數存放的變量 nn_architecture: 結構 caches 存儲格式 由於反向傳播的時候也要用到上一層的 A, 因此這裏把上一層的 A,當前層的 Z 存入到 caches 中,方便調用 caches = [ {'A_prev': A_prev, 'Z_curr': Z_curr}, # 第一層存儲的數據 {'A_prev': A_prev, 'Z_curr': Z_curr}, ... ... ] 返回值 A_curr: 最後一層的 A ,也就是 AL(Y_hat) caches: 存放上一層的 A 和 當前層的 Z 的列表 ''' caches = [] # X 做爲第零層 A A_curr = X for id, layer in enumerate(nn_architecture): # layer_id -> [1, 2, 3, 4] layer_id = id + 1 # 獲取上一層的 A A_prev = A_curr # 從 params 中獲取當前層的 W 和 b W_curr = params['W' + str(layer_id)] b_curr = params['b' + str(layer_id)] # 從 layer 中獲取激活函數 activation = layer['activation'] # 求當前層的 Z 和 A Z_curr, A_curr = layer_forward(W_curr, b_curr, A_prev, activation) # 把 上一層的 A 和 當前層的 Z 放入內存 caches.append({ 'A_prev': A_prev, 'Z_curr': Z_curr }) return A_curr, caches
計算損失的公式
$$J(cost) = -\frac{1}{m} [Y·log(Y_hat).T + (1-Y)·log(1-Y_hat).T]$$
# 獲取損失值 def get_cost(Y_hat, Y): # 獲取樣本數量 m = Y_hat.shape[1] cost = -1 / m * (np.dot(Y, np.log(Y_hat).T) + np.dot(1 - Y, np.log(1 - Y_hat).T)) # cost 爲一個一行一列的數據[[0.256654]], np.squeeze 讓其變成一個數值 cost = np.squeeze(cost) return cost
這裏咱們還能夠定義一個函數來求準確度
# 把預測值進行分類,預測值求出來都是一些小數,對於二分類問題,咱們把對他分紅兩類 def convert_into_class(Y_hat): # 複製矩陣 prob = np.copy(Y_hat) # 把矩陣裏面全部的 >0.5 歸類爲 1 # <=0.5 歸類爲 0 prob[prob > 0.5] = 1 prob[prob <= 0.5] = 0 return prob # 獲取準確度 def get_accuracy(Y_hat, Y): # 先進行分類,再求精度 prob = convert_into_class(Y_hat) # accu = float(np.dot(Y, prob.T) + np.dot(1 - Y, 1 - prob.T)) / float(Y_hat.shape[1]) # 上面的註釋的方法也可求精確度 ''' 這裏咱們的原理是,把預測值和真實值進行比較,相同的, 就表明預測正確,就把他們的個數加起來,而後再除總的 樣本量,Y_hat.shape[1] 就表明總的樣本量 ''' accu = np.sum((prob == Y) / Y_hat.shape[1]) accu = np.squeeze(accu) return accu
仍是同樣咱們先來看看反向傳播的結構
主要步驟就是,咱們先經過 J(cost) 求出對 A4 的偏導 dA4,再進行單層的反向傳播, L 表明最後層,l 表明當前層
$$dA^{L} = -(\frac{Y}{Y_hat} - \frac{1-Y}{1-Y_hat})$$
對激活函數進行求導
''' 計算dZ dZ = dA * g(Z) * (1 - g(Z)) ''' def relu_backward(dA, cache): ''' dA: shape = (output_dim, m) # output_dim 爲當前層的單元數 cache: shape = (output_dim, m) ''' Z = cache dZ = np.array(dA, copy=True) # 複製矩陣 # When z <= 0, dZ = 0 dZ[Z <= 0] = 0 return dZ def sigmoid_backward(dA, cache): ''' dA: shape = (output_dim, m) # output_dim 指當前層的單元數 cache: shape = (output_dim, m) ''' Z = cache s = 1/(1+np.exp(-Z)) dZ = dA * s * (1-s) assert (dZ.shape == Z.shape) return dZ
構建單層反向傳播
在單層裏面咱們主要就是計算 dZ, dW, db
$$dZ^{[l]} = dA^{[l]}*g^{[l]'}(Z^{[l]})$$
$$dW^{[l]} = dZ^{[l]}·A^{[l-1]}.T$$
$$db^{[l]} = sum(dZ^{[l]})$$
# 單層反向傳播 def layer_backward(dA_curr, W_curr, Z_curr, A_prev, activation): ''' 計算 dZ = dA * g(Z) * (1 - g(Z)) dW = dZ·A.T / m db = np.sum(dZ, axis=1, keepdims=True) / m 參數 dA_curr: 當前層的 dA W_curr: 當前層的 W 參數 Z_curr: 當前層的 Z 參數 A_prev: 上一層的 A 參數 activation: 當前層的激活函數 返回值 dW_curr: 當前層的 dW db_curr: 當前層的 db dA_prev: 上一層的 dA ''' m = A_prev.shape[1] # 求出樣本個數 # 求出 dZ_curr if activation == 'relu': dZ_curr = relu_backward(dA_curr, Z_curr) elif activation == 'sigmoid': dZ_curr = sigmoid_backward(dA_curr, Z_curr) else: raise Exception ("不支持的激活函數類型!") # 分別求 dZ, dW, db dW_curr = np.dot(dZ_curr, A_prev.T) / m db_curr = np.sum(dZ_curr, axis=1, keepdims=True) / m dA_prev = np.dot(W_curr.T, dZ_curr) return dW_curr, db_curr, dA_prev
構建完整的反向傳播
在構建完整的反向傳播的時候,必定要認真核對每個矩陣的維度
最後返回的是存放梯度值的字典 grads
# 完整反向傳播 def full_backward(Y_hat, Y, params, caches, nn_architecture): ''' 參數 Y_hat: 預測值(最後一層的 A 值) Y: 真實 Y 矩陣 params: 存放每層 W, b 參數 caches: 存放有前向傳播中的 A , Z nn_architecture: 結構 返回 grads: 梯度值 ''' # 存放要進行梯度降低的 dW, db 參數,存放形式和 params 同樣 grads = {} # 計算最後一層的 dA dA_prev = - (np.divide(Y, Y_hat) - np.divide(1 - Y, 1 - Y_hat)) for id, layer in reversed(list(enumerate(nn_architecture))): # layer_id -> [4, 3, 2, 1] layer_id = id + 1 # 當前層的 dA 爲上一次計算出來的 dA_prev dA_curr = dA_prev # 從 params 中取出 當前層的 W 參數 W_curr = params['W' + str(layer_id)] # 從 caches 內存中取出咱們在前向傳播中存放的數據 A_prev = caches[id]['A_prev'] Z_curr = caches[id]['Z_curr'] # 從當前層的結構中取出激活函數 activation = layer['activation'] # 計算當前層的梯度值 dW, db,以及上一層的 dA dW_curr, db_curr, dA_prev = layer_backward(dA_curr, W_curr, Z_curr, A_prev, activation) # 把梯度值放入 grads 中 grads['dW' + str(layer_id)] = dW_curr grads['db' + str(layer_id)] = db_curr return grads
更新參數的公式
$$W = W - \alpha * dW $$
$$b = b - \alpha * db $$
# 更新參數 def update_params(params, grads, learning_rate): ''' 參數 params: W,b 參數 grads: 梯度值 learning_rate: 梯度降低時的學習率 返回 params: 更新後的參數 ''' for id in range(len(params) // 2): # layer_id -> [1, 2, 3, 4] layer_id = id + 1 params['W' + str(layer_id)] -= learning_rate * grads['dW' + str(layer_id)] params['b' + str(layer_id)] -= learning_rate * grads['db' + str(layer_id)] return params
前面咱們把相應的每一個步驟的函數都編寫好了,那麼咱們如今只須要把他們組合起來便可
# 定義模型 def dnn_model(X, Y, nn_architecture, epochs=3000, learning_rate=0.0075): ''' 參數 X: (n, m) Y: (1, m) nn_architecture: 網絡結構 epochs: 迭代次數 learning_rate:學習率 返回值 params: 訓練好的參數 ''' np.random.seed(1) params = init_params(nn_architecture) costs = [] for i in range(1, epochs + 1): # 前向傳播 Y_hat, caches = full_forward(X, params, nn_architecture) # 計算損失 cost = get_cost(Y_hat, Y) # 計算精度 accu = get_accuracy(Y_hat, Y) # 反向傳播 grads = full_backward(Y_hat, Y, params, caches, nn_architecture) # 更新參數 params = update_params(params, grads, learning_rate) if i % 100 == 0: print ('Iter: {:05}, cost: {:.5f}, accu: {:.5f}'.format(i, cost, accu)) costs.append(cost) # 畫出 cost 曲線圖 plt.plot(np.squeeze(costs)) plt.ylabel('cost') plt.xlabel('iterations (per tens)') plt.title("DNN") plt.show() return params
# 預測函數 def predict(X, Y, params, nn_architecture): Y_hat, _ = full_forward(X, params, nn_architecture) accu = get_accuracy(Y_hat, Y) print ('預測精確度爲:{:.2f}'.format(accu)) return Y_hat
# 開始訓練 params = dnn_model( train_x, train_y, nn_architecture, )
這是訓練後的結果
Iter: 00100, cost: 0.67239, accu: 0.67943 Iter: 00200, cost: 0.64575, accu: 0.74641 Iter: 00300, cost: 0.62782, accu: 0.72727 Iter: 00400, cost: 0.59732, accu: 0.75598 Iter: 00500, cost: 0.52155, accu: 0.85646 Iter: 00600, cost: 0.48313, accu: 0.87560 Iter: 00700, cost: 0.43010, accu: 0.91866 Iter: 00800, cost: 0.36453, accu: 0.95694 Iter: 00900, cost: 0.34318, accu: 0.93780 Iter: 01000, cost: 0.29341, accu: 0.95215 Iter: 01100, cost: 0.25503, accu: 0.96172 Iter: 01200, cost: 0.22804, accu: 0.97608 Iter: 01300, cost: 0.19706, accu: 0.97608 Iter: 01400, cost: 0.18372, accu: 0.98086 Iter: 01500, cost: 0.16100, accu: 0.98086 Iter: 01600, cost: 0.14842, accu: 0.98086 Iter: 01700, cost: 0.13803, accu: 0.98086 Iter: 01800, cost: 0.12873, accu: 0.98086 Iter: 01900, cost: 0.12087, accu: 0.98086 Iter: 02000, cost: 0.11427, accu: 0.98086 Iter: 02100, cost: 0.10850, accu: 0.98086 Iter: 02200, cost: 0.10243, accu: 0.98086 Iter: 02300, cost: 0.09774, accu: 0.98086 Iter: 02400, cost: 0.09251, accu: 0.98086 Iter: 02500, cost: 0.08844, accu: 0.98565 Iter: 02600, cost: 0.08474, accu: 0.98565 Iter: 02700, cost: 0.08193, accu: 0.98565 Iter: 02800, cost: 0.07815, accu: 0.98565 Iter: 02900, cost: 0.07563, accu: 0.98565 Iter: 03000, cost: 0.07298, accu: 0.99043
# 預測測試集的精確度 Y_hat = predict(test_x, test_y, params, nn_architecture)
預測精確度爲:0.80
# 顯示圖片 # 由於測試集有 50 張圖片,因此咱們隨機生成 1-50 的整形數字 index = randint(1, 49) # 由於在前面,咱們把數據展開成了 (12288, 50) 的矩陣,如今讓它迴歸成圖片的矩陣 img = test_x[:, index].reshape((64, 64, 3)) # 顯示圖片 plt.imshow(img) # 把預測分類 Y_hat_ = convert_into_class(Y_hat) # 把 1,0 轉化成漢字並輸出 pred_ = '是' if int(Y_hat_[0, index]) else '不是' true_ = '是' if int(test_y[0, index]) else '不是' print ('這張圖片' + true_ + '貓') print ('預測圖片' + pred_ + '貓') # 判斷是否預測正確 if int(Y_hat_[0, index]) == int(test_y[0, index]): print ('預測正確!') else: print ('預測錯誤!')
這張圖片不是貓
預測圖片不是貓
預測正確!
以上咱們就搭建完成了整個實例的代碼搭建,若是你要訓練其餘的數據集,那麼更改相應的網絡結構便可,針對不一樣的數據集,有不一樣的結構,好比,咱們的 X 有兩個特徵,那麼第一層的 input_dim 能夠改成 2
* 以上代碼爲學習時所記錄,若是在文中有錯誤的地方,請聯繫我及時修改