搭建簡單神經網路來識別圖片中是否有貓
代碼借鑒地址:純用NumPy實作神經網路
搭建一個簡單易懂的神經網路來幫你理解深度神經網路
通過簡單的貓識別的例子來幫你進一步進行理解
本代碼用 numpy 來實作,不含有正則化,批量等演算法
這里我們先來理清楚神經網路的步驟
(1) 構建資料,我們要構建出這樣的一個資料,shape = (n, m),n 代表特征數,m 代表樣本數
(2) 初始化引數,使用隨機初始化引數 W 和 b
(3) 前向傳播,
(4) 計算損失,
(5) 反向傳播,
(6) 更新引數,
(7) 構建模型,
(8) 預測,預測其實就是重新進行一次前向傳播
清楚了這些步驟之后,我們在構建神經網路的時候就不至于無從下手了
接下來我們就根據上面理出來的步驟開始一步一步來構架深度神經網路吧
目錄
目錄
- 1 構建資料
- 2 隨機初始化資料
- 3 前向傳播
- 4 計算損失
- 5 反向傳播
- 6 更新引數
- 7 構建模型
- 8 預測
- 9 開始訓練
- 10 進行預測
- 11 以圖片的形式展示預測后的結果
1 構建資料
我們先來看一下資料集是怎樣的
我們從 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
2 隨機初始化資料
定義神經網路的結構
在初始化之前我們要明白我們要搭建的這樣一個網路的結構是如何的,這里我們采取下面的方式來定義網路的結構

# 定義神經網路的結構
'''
即有四層,第一層為 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
3 前向傳播
先來看看前向傳播做了些什么事情

激活函式
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
4 計算損失
計算損失的公式
$$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
5 反向傳播
還是一樣我們先來看看反向傳播的結構

主要步驟就是,我們先通過 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
6 更新引數
更新引數的公式
$$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
7 構建模型
前面我們把相應的每個步驟的函式都撰寫好了,那么我們現在只需要把他們組合起來即可
# 定義模型
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
8 預測
# 預測函式
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
9 開始訓練
# 開始訓練
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

10 進行預測
# 預測測驗集的精確度
Y_hat = predict(test_x, test_y, params, nn_architecture)
預測精確度為:0.80
11 以圖片的形式展示預測后的結果
# 顯示圖片
# 因為測驗集有 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
* 以上代碼為學習時所記錄,如果在文中有錯誤的地方,請聯系我及時修改
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/65604.html
標籤:其他
