回顧:
一、序言
前兩天寫了關於單神經元的解析,這里再接再厲繼續淺層神經網絡的解析。淺層神經網絡即是“層次較少”的神經網絡,雖然層次少但其性能相對單神經元強大了不只一點。
注:本文內容主要是對“床長”的系列教程進行總結,強烈推薦“床長”的人工智能系列教程(https://www.captainbed.net/)
二、淺層神經網絡的構成
回顧前面單神經元的構成,我們知道神經元包含4個關鍵函數:
1)傳播函數,由輸入x、偏置w、閾值b計算出a
2)激活函數,將a映射到0~1之間的結果y,可理解為(是、否)的概率
3)反向傳播函數,通過y、答案label計算出dw、db(用以更新w和b)
4)損失函數,計算y與label間的誤差
直觀上我們知道淺層神經網絡自然是由數個神經元構成的,對於一個簡單的兩層神經網絡其結構如下圖所示:
它包含了輸入層(即X)、隱藏層、輸出層,其中輸入層不是神經元,所以說這是一個兩層的神經網絡。
在實際實現時我們並不是挨個計算每一個經元的結果,最后再計算輸出的結果。我們是一次計算出一層所有的神經元結果,再將每一層的結果作為輸入計算下一層。而且第一層的傳播函數並不與第二層傳播函數分離,神經網絡的傳播函數包含了所有層的傳播計算,反向傳播函數也是包含了所有層的反向計算,所以從單神經元到神經網絡代碼的結構其實變化不大。
下面我們直接上代碼來解析,如果你看明白了前面單神經元的解析,那這里是非常好理解的。
(文末附完整代碼下載方式)
三、准備工作
1)要處理的問題
之前我們用單神經元要處理的問題是“從圖片中識別出數字9”,即使使用單神經元也有93%的正確率,所以這里我們要增加問題的難度。將問題改為“從圖片中識別出奇數”,代碼上只需要很小的修改,將下面代碼
#將label不是9的數據全部轉為0,將9轉為1
train_label = np.where(train_label==9,1,0)
test_label = np.where(test_label==9,1,0)
修改為:
#找出圖片中的奇數將label中13579置為1、02468置為0
train_label = np.where((train_label%2)!=0,1,0)
test_label = np.where((test_label%2)!=0,1,0)
使用之前的單神經元代碼執行后的結果為下圖所示,可以看到預測效果大幅下降。
2)要使用的網絡結構
首先要明確神經網絡的層數,這里我們是一個簡單的網絡,所以只需要兩層。其次是每一層神經元的個數,這里我們網絡第一層設置4個神經元,第二層是輸出層所以只要一個神經元。輸入層跟以前一樣,是784個輸入(一張28x28圖片的所有像素),網絡結構如下圖:
四、隨機初始化參數
#初始化參數w和b
def initialize_parameters(input_num, hide_num, out_num):
#input_num 輸入層神經元個數
#hide_num 隱藏層神經元個數
#out_num 輸出層神經元個數
np.random.seed(2)
#隨機初始化第一層相關參數w、b
W1 = np.random.rand(hide_num, input_num) * 0.01
b1 = np.zeros(shape=(hide_num, 1))
#隨機初始化第二層相關參數w、b
W2 = np.random.randn(out_num, hide_num) * 0.01
b2 = np.zeros(shape=(out_num, 1))
return W1,b1,W2,b2
淺層神經網絡與單神經元在初始化參數w、b的區別有以下幾點:
1)每一層神經元使用不同的w和b,所以有w1、w2、b1、b2,如果是三層網絡則相應會有w3、b3.
2)不同層神經元的w、b的數據形狀是不一樣的。比如我們輸入的圖片有784個像素,且第二層有4個神經元,所以第一層w的形狀是(4,784)、b的形狀是(4,1)。第二層神經元只有一個,來自第一層的輸入只有4個參數,所以第二層w的形狀是(1,4)、b的形狀是(1,1)。
3)w1和w2是隨機生成的,之前單神經元結構中,w初始值為0,如果這里還使用全0的話,最終結果可能與單神經元一樣,而且還乘以0.01確保初始值足夠小。
五、傳播函數
#向前傳播函數
def forward(img, W1,b1,W2,b2):
#第一層
A1 = np.dot(W1, img) + b1
Y1 = np.tanh(A1)#第一層和第二層使用不同的激活函數
#第二層
A2 = np.dot(W2, Y1) + b2
Y2 = sigmoid(A2)
return Y1,Y2
傳播函數與單神經元結構類似,不過需要注意的是,這里第一層使用的激活函數為tanh、第二層依舊使用的是sigmoid。他們的區別在於,sigmoid是將輸出映射到0~1而tanh是將輸出映射到-1~1。具體原因和區別以后再說(因為我也沒搞清楚呢),這里只要知道有區別就行了。
六、反向傳播函數
#反向傳播函數
def backward(img, label, W1,b1,W2,b2, Y1,Y2):
m = img.shape[1]
#第二層
dZ2 = Y2 - label
dW2 = np.dot(dZ2, Y1.T)/m
db2 = np.sum(dZ2, axis=1, keepdims=True)/m
#第一層
dZ1 = np.multiply(np.dot(W2.T, dZ2), 1-np.power(Y1, 2))
dW1 = np.dot(dZ1, img.T)/m
db1 = np.sum(dZ1, axis=1, keepdims=True)/m
return dW1,db1,dW2,db2
與傳播函數方向相反,這里是先計算第二層的反向傳播再計算第一層的反向傳播。需要注意的是,由於第一層使用的激活函數是tanh,所以其反向計算公式與第一層的公式不一樣,因為使用不同激活函數其反向求導也就不一樣了。
另外np.sum中的兩個參數axis=1、keepdims=True是為了確保db1的數據形式為(1,4),其中axis=1的意思是按行求和、keepdims=True的意思是保留矩陣的形狀,不同參數的np.sum計算示意如下:
np.sum(dZ1)/m 0.0016664927232987162
np.sum(dZ1, axis=1)/m [0.00026393,0.00077922,0.0003274 ,0.00029595]
np.sum(dZ1, axis=1, keepdims=True)/m
[[0.00026393]
[0.00077922]
[0.0003274 ]
[0.00029595]]
七、梯度下降
#梯度下降 更新w、b參數
def update(W1,b1,W2,b2, dW1,db1,dW2,db2, learning_rate=1.2):
W1 = W1 - learning_rate*dW1
b1 = b1 - learning_rate*db1
W2 = W2 - learning_rate*dW2
b2 = b2 - learning_rate*db2
return W1,b1,W2,b2
梯度下降與單神經元的情況差不多。
八、損失函數
#損失函數
def costCal(Y2, label):
m = label.shape[1]
logprobs = np.multiply(np.log(Y2), label) + np.multiply((1-label), np.log(1-Y2))
cost = -np.sum(logprobs)/m
return cost
損失函數與單神經元的情況也差不多,需要注意的是np.multiply就是將兩個矩陣做對應元素的乘。
九、預測函數
#預測函數
def predict(W1,b1,W2,b2, img):
Y1,Y2 = forward(img, W1,b1,W2,b2)
predictions = np.round(Y2)#對結果四舍五入
return predictions
與單神經元的情況類似,預測函數其實就是做一次“向前傳播”。
十、訓練模型並預測
#訓練模型
def model(img, label, hide_num, num_iterations = 1000, learning_rate=0.1, print_cost = False):
np.random.seed(3)
input_num = img.shape[0]
out_num = label.shape[0]
#初始化參數
W1,b1,W2,b2 = initialize_parameters(input_num,hide_num,out_num)
#循環若干次完成訓練
for i in range(0, num_iterations):
#向前傳播
Y1,Y2 = forward(img, W1,b1,W2,b2)
#計算本次成本
cost = costCal(Y2, label)
#反向傳播,得到梯度
dW1,db1,dW2,db2 = backward(img, label, W1,b1,W2,b2, Y1,Y2)
#參數優化
W1,b1,W2,b2 = update(W1,b1,W2,b2, dW1,db1,dW2,db2, learning_rate)
# 將本次訓練的成本打印出來
if print_cost and i % 100 == 0:
print ("在訓練%i次后,成本是: %f" % (i, cost))
return W1,b1,W2,b2
#調用訓練模型
W1,b1,W2,b2 = model(train_img, train_label, 4, num_iterations=2000, learning_rate=1, print_cost=True)
#調用預測函數
predictions = predict(W1,b1,W2,b2, test_img)
print ('預測准確率是: %d' % float((np.dot(test_label, predictions.T) + np.dot(1 - test_label, 1 - predictions.T)) / float(test_label.size) * 100) + '%')
需要注意的是這里的learning_rate=1,而單神經元時的learning_rate為0.005。
十一、總結回顧
通過實現一個簡單的二層神經網絡我們發現,其實代碼並沒有修改很多,整體的結構也變化不大,其中最主要的變化在於第一層使用的激活函數變為tanh,由此導致反向傳播的計算也有了較大的變化。
運行后我們可以發現,預測的准確度較單神經元有了較大幅度的提升:
在訓練0次后,成本是: 0.693817
在訓練100次后,成本是: 0.251725
在訓練200次后,成本是: 0.176756
在訓練300次后,成本是: 0.110538
在訓練400次后,成本是: 0.372297
在訓練500次后,成本是: 0.128188
在訓練600次后,成本是: 0.091792
在訓練700次后,成本是: 0.075769
在訓練800次后,成本是: 0.064764
在訓練900次后,成本是: 0.055826
在訓練1000次后,成本是: 0.132452
在訓練1100次后,成本是: 0.102556
在訓練1200次后,成本是: 0.131425
在訓練1300次后,成本是: 0.086445
在訓練1400次后,成本是: 0.178343
在訓練1500次后,成本是: 0.077496
在訓練1600次后,成本是: 0.093846
在訓練1700次后,成本是: 0.071567
在訓練1800次后,成本是: 0.070109
在訓練1900次后,成本是: 0.060202
預測准確率是: 94%
關注公眾號“零基礎愛學習”回復"AI5"可獲得完整代碼。后面我們還會繼續更新“如何構建深度神經網絡”,以及對目前還未明晰的問題解析。