編程作業 4 - 神經網絡
對於這個練習,我們將再次處理手寫數字數據集,這次使用反向傳播的前饋神經網絡。 我們將通過反向傳播算法實現神經網絡成本函數和梯度計算的非正則化和正則化版本。 我們還將實現隨機權重初始化和使用網絡進行預測的方法。
由於我們在練習3中使用的數據集是相同的,所以我們將重新使用代碼來加載數據。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
data = loadmat('ex4data1.mat')
data
{'__header__': b'MATLAB 5.0 MAT-file, Platform: GLNXA64, Created on: Sun Oct 16 13:09:09 2011',
'__version__': '1.0',
'__globals__': [],
'X': array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]]),
'y': array([[10],
[10],
[10],
...,
[ 9],
[ 9],
[ 9]], dtype=uint8)}
由於我們以後需要這些(並將經常使用它們),我們先來創建一些有用的變量。
X = data['X']
y = data['y']
X.shape, y.shape#看下維度
((5000, 400), (5000, 1))
我們也需要對我們的y標籤進行一次one-hot 編碼。 one-hot 編碼將類標籤n(k類)轉換爲長度爲k的向量,其中索引n爲“hot”(1),而其餘爲0。 Scikitlearn有一個內置的實用程序,我們可以使用這個。
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse=False)
y_onehot = encoder.fit_transform(y)
y_onehot.shape
(5000, 10)
y[0], y_onehot[0,:]
(array([10], dtype=uint8), array([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]))
我們要爲此練習構建的神經網絡具有與我們的實例數據(400 +偏置單元)大小匹配的輸入層,25個單位的隱藏層(帶有偏置單元的26個),以及一個輸出層, 10個單位對應我們的一個one-hot編碼類標籤。 有關網絡架構的更多詳細信息和圖像,請參閱“練習”文件夾中的PDF。
我們需要實現的第一件是評估一組給定的網絡參數的損失的代價函數。 源函數在練習文本中(看起來很嚇人)。 以下是代價函數的代碼。
sigmoid 函數
g 代表一個常用的邏輯函數(logistic function)爲S形函數(Sigmoid function),公式爲: \[g\left( z \right)=\frac{1}{1+{{e}^{-z}}}\]
合起來,我們得到邏輯迴歸模型的假設函數:
\[{{h}_{\theta }}\left( x \right)=\frac{1}{1+{{e}^{-{{\theta }^{T}}X}}}\]
def sigmoid(z):
return 1 / (1 + np.exp(-z))
前向傳播函數
(400 + 1) -> (25 + 1) -> (10)
def forward_propagate(X, theta1, theta2):
# INPUT:參數值theta,數據X
# OUTPUT:當前參數值下前項傳播結果
# TODO:根據參數和輸入的數據計算前項傳播結果
# STEP1:獲取樣本個數
# your code here (appro ~ 1 lines)
m = X.shape[0]
# STEP2:實現神經網絡正向傳播
# your code here (appro ~ 5 lines)
a1 =np.insert(X, 0, values=np.ones(m), axis=1) #給X矩陣插入一行1元素
z2 =a1 * theta1.T
a2 =np.insert(sigmoid(z2), 0, values=np.ones(m), axis=1) #注意插入1元素
z3 = a2 * theta2.T
h = sigmoid(z3)
return a1, z2, a2, z3, h
代價函數
Tip:重新定義矩陣維度可以使用reshape函數
def cost(params, input_size, hidden_size, num_labels, X, y, lamda):
# INPUT:神經網絡參數,輸入層維度,隱藏層維度,訓練數據及標籤,正則化參數
# OUTPUT:當前參數值下的代價函數
# TODO:根據上面的公式計算代價函數
# STEP1:獲取樣本個數
# your code here (appro ~ 1 lines)
m = X.shape[0]
# STEP2:將矩陣X,y轉換爲numpy型矩陣
# your code here (appro ~ 2 lines)
X =np.matrix(X)
y =np.matrix(y)
# STEP3:從params中獲取神經網絡參數,並按照輸入層維度和隱藏層維度重新定義參數的維度
# your code here (appro ~ 2 lines)
theta1 = np.matrix(np.reshape(params[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
theta2 = np.matrix(np.reshape(params[hidden_size * (input_size + 1):], (num_labels, (hidden_size + 1))))
# STEP4:調用前面寫好的前項傳播函數
# your code here (appro ~ 1 lines)
a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
# STEP5:初始化代價函數
# your code here (appro ~ 1 lines)
J = 0
# STEP6:根據公式計算代價函數
for i in range(m): #遍歷每個樣本
# your code here (appro ~ 2 lines)
first_term =np.multiply(-y[i,:], np.log(h[i,:]))
second_term = np.multiply((1 - y[i,:]), np.log(1 - h[i,:]))
J += np.sum(first_term - second_term)
J = J / m
# STEP7:計算代價函數的正則化部分
# your code here (appro ~ 1 lines)
J += (float(lamda) / (2 * m)) * (np.sum(np.power(theta1[:,1:], 2)) + np.sum(np.power(theta2[:,1:], 2)))
return J
這個Sigmoid函數我們以前使用過。 前向傳播函數計算給定當前參數的每個訓練實例的假設。 它的輸出形狀應該與y的一個one-hot編碼相同。
# 初始化設置
input_size = 400
hidden_size = 25
num_labels = 10
lamda = 1
# 隨機初始化完整網絡參數大小的參數數組
params = (np.random.random(size=hidden_size * (input_size + 1) + num_labels * (hidden_size + 1)) - 0.5) * 0.25
m = X.shape[0]
X = np.matrix(X)
y = np.matrix(y)
# 將參數數組解開爲每個層的參數矩陣
theta1 = np.matrix(np.reshape(params[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
theta2 = np.matrix(np.reshape(params[hidden_size * (input_size + 1):], (num_labels, (hidden_size + 1))))
theta1.shape, theta2.shape
((25, 401), (10, 26))
如果你的程序正確,這裏的輸出應該是:((25, 401), (10, 26))
a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
a1.shape, z2.shape, a2.shape, z3.shape, h.shape
((5000, 401), (5000, 25), (5000, 26), (5000, 10), (5000, 10))
如果你的程序正確,這裏的輸出應該是:((5000, 401), (5000, 25), (5000, 26), (5000, 10), (5000, 10))
代價函數在計算假設矩陣h之後,應用代價函數來計算y和h之間的總誤差。
cost(params, input_size, hidden_size, num_labels, X, y_onehot, lamda)
7.189709282447219
##error case 6.7329429719363878 & 6.8604869039403171
#當用python進行數組運算,出現RuntimeWarning: invalid value encountered的報錯,往往是因爲數組中存在0元素
#RuntimeWarning: invalid value encountered in multiply 溢出
如果你的程序正確,這裏的輸出應該是:7.1170579556373621
接下來是反向傳播算法。 反向傳播參數更新計算將減少訓練數據上的網絡誤差。 我們需要的第一件事是計算我們之前創建的Sigmoid函數的梯度的函數。
def sigmoid_gradient(z):
return np.multiply(sigmoid(z), (1 - sigmoid(z)))
現在我們準備好實施反向傳播來計算梯度。 由於反向傳播所需的計算是代價函數中所需的計算過程,我們實際上將擴展代價函數以執行反向傳播並返回代價和梯度。
def backprop(params, input_size, hidden_size, num_labels, X, y, lamda):
# INPUT:神經網絡參數,輸入層維度,隱藏層維度,訓練數據及標籤,正則化參數
# OUTPUT:當前參數值下的代價函數
# TODO:根據上面的公式計算代價函數
# STEP1:獲取樣本個數
# your code here (appro ~ 1 lines)
m = X.shape[0]
# STEP2:將矩陣X,y轉換爲numpy型矩陣
# your code here (appro ~ 2 lines)
X = np.matrix(X)
y = np.matrix(y)
# STEP3:從params中獲取神經網絡參數,並按照輸入層維度和隱藏層維度重新定義參數的維度
# your code here (appro ~ 2 lines)
theta1 = np.matrix(np.reshape(params[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
theta2 = np.matrix(np.reshape(params[hidden_size * (input_size + 1):], (num_labels, (hidden_size + 1))))
# STEP4:調用前面寫好的前項傳播函數
# your code here (appro ~ 1 lines)
a1, z2, a2, z3, h =forward_propagate(X, theta1, theta2)
# STEP5:初始化
# your code here (appro ~ 3 lines)
J = 0
delta1 = np.zeros(theta1.shape) # (25, 401)
delta2 = np.zeros(theta2.shape) # (10, 26)
# STEP6:計算代價函數(調用函數)
# your code here (appro ~ 1 lines)
for i in range(m):
first_term = np.multiply(-y[i,:], np.log(h[i,:]))
second_term = np.multiply((1 - y[i,:]), np.log(1 - h[i,:]))
J += np.sum(first_term - second_term)
J = J / m
# STEP7:實現反向傳播(這裏用到的公式請參考原版作業PDF的第5頁)
for t in range(m): #遍歷每個樣本
a1t = a1[t,:] # (1, 401)
z2t = z2[t,:] # (1, 25)
a2t = a2[t,:] # (1, 26)
ht = h[t,:] # (1, 10)
yt = y[t,:] # (1, 10)
# your code here (appro ~ 5 lines)
d3t = ht - yt
z2t = np.insert(z2t, 0, values=np.ones(1)) # (1, 26)
d2t = np.multiply((theta2.T * d3t.T).T, sigmoid_gradient(z2t)) # (1, 26)
delta1 = delta1 + (d2t[:,1:]).T * a1t
delta2 = delta2 + d3t.T * a2t
# STEP8:加入正則化
# your code here (appro ~ 1 lines)
delta1[:,1:] = delta1[:,1:] + (theta1[:,1:] * lamda) / m
delta2[:,1:] = delta2[:,1:] + (theta2[:,1:] * lamda) / m
# STEP9:將梯度矩陣轉換爲單個數組
grad = np.concatenate((np.ravel(delta1), np.ravel(delta2)))
return J, grad
反向傳播計算的最難的部分(除了理解爲什麼我們正在做所有這些計算)是獲得正確矩陣維度。 順便說一下,你容易混淆了A * B與np.multiply(A,B)使用。 基本上前者是矩陣乘法,後者是元素乘法(除非A或B是標量值,在這種情況下沒關係)。
無論如何,讓我們測試一下,以確保函數返回我們期望的。
J, grad = backprop(params, input_size, hidden_size, num_labels, X, y_onehot, lamda)
J, grad.shape
(7.184458705259231, (10285,))
如果你的程序正確,這裏的輸出應該是:(7.3264766401720607, (10285,))
我們終於準備好訓練我們的網絡,並用它進行預測。 這與以往的具有多類邏輯迴歸的練習大致相似。
from scipy.optimize import minimize
# minimize the objective function
fmin = minimize(fun=backprop, x0=params, args=(input_size, hidden_size, num_labels, X, y_onehot, lamda),
method='TNC', jac=True, options={'maxiter': 250})
fmin
D:\sofewore\anaconda\lib\site-packages\ipykernel_launcher.py:34: RuntimeWarning: divide by zero encountered in log
D:\sofewore\anaconda\lib\site-packages\ipykernel_launcher.py:34: RuntimeWarning: invalid value encountered in multiply
fun: 0.36223489551718385
jac: array([-2.37751129e+01, -6.52893193e-06, -4.08905647e-06, ...,
1.22569704e+00, -7.10083281e+00, -6.25293206e-01])
message: 'Linear search failed'
nfev: 218
nit: 13
status: 4
success: False
x: array([-0.29594079, -0.03264466, -0.02044528, ..., -1.35811354,
2.59361744, -0.90221032])
如果你的程序正確,這裏的輸出應該是:
fun: 0.34648873971573946
jac: array([ 3.45137403e-04, 2.31929337e-06, -2.41850602e-06, …,
-2.66641069e-04, 2.12514869e-04, -4.37669150e-04])
message: ‘Max. number of function evaluations reached’
nfev: 250
nit: 19
status: 3
success: False
x: array([-0.92927497, 0.01159647, -0.01209253, …, -3.70653505,
-0.59870645, 0.43449445])
由於目標函數不太可能完全收斂,我們對迭代次數進行了限制。 我們的總代價已經下降到0.5以下,這是算法正常工作的一個很好的指標。 讓我們使用它發現的參數,並通過網絡轉發,以獲得一些預測。
讓我們使用它找到的參數,並通過網絡前向傳播以獲得預測。
X = np.matrix(X)
theta1 = np.matrix(np.reshape(fmin.x[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
theta2 = np.matrix(np.reshape(fmin.x[hidden_size * (input_size + 1):], (num_labels, (hidden_size + 1))))
a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
y_pred = np.array(np.argmax(h, axis=1) + 1)
y_pred
array([[10],
[10],
[10],
...,
[ 9],
[ 9],
[ 9]], dtype=int64)
最後,我們可以計算準確度,看看我們訓練完畢的神經網絡效果怎麼樣。
correct = [1 if a == b else 0 for (a, b) in zip(y_pred, y)]
accuracy = (sum(map(int, correct)) / float(len(correct)))
print ('accuracy = {0}%'.format(accuracy * 100))
accuracy = 96.14%
我們已經成功地實施了一個基本的反向傳播神經網絡,並用它來分類手寫數字圖像。 在下一個練習中,我們將介紹另一個強大的監督學習算法,支持向量機。