Python機器學習算法實作
Author:louwill
上一節中筆者和大家了解了感覺機的基本原理及其Python實作。本節筆者将在感覺機的基礎上繼續介紹神經網絡模型。從上一講我們知道,感覺機是一種線性模型,對于非線性問題很難給出解決方案。
比如咱們熟知的這種異或問題(XOR),就是一種典型的線性不可分問題,普通的感覺機很難處理:
(來自周志華 機器學習)
是以,在普通的感覺機基礎上,我們對感覺機結構進行了延申,通過添加隐藏層的方式來使得感覺機能夠拟合非線性問題。這種包含隐藏層結構的感覺機模型就是神經網絡,也叫多層感覺機(Multilayer Perceptron)。
關于神經網絡的衆多概念和知識:包括輸入層、隐藏層、輸出層、激活函數、前向傳播、反向傳播、梯度下降、權值更新等概念筆者不再贅述。在筆者的另一個系列推文——深度學習60講中有詳細介紹:深度學習第60講:深度學習筆記系列總結與感悟。
生成資料
本節筆者以一個兩層網絡,即單隐層網絡為例,來看看如何利用numpy實作一個神經網絡模型。正式搭建神經網絡之前我們先來準備一下資料。定義一個資料生成函數:
def create_dataset():
np.random.seed(1)
m = 400 # 資料量
N = int(m/2) # 每個标簽的執行個體數
D = 2 # 資料次元
X = np.zeros((m,D)) # 資料矩陣
Y = np.zeros((m,1), dtype='uint8') # 标簽次元
a = 4
for j in range(2):
ix = range(N*j,N*(j+1))
t = np.linspace(j*3.12,(j+1)*3.12,N) + np.random.randn(N)*0.2 # theta
r = a*np.sin(4*t) + np.random.randn(N)*0.2 # radius
X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
Y[ix] = j
X = X.T
Y = Y.T
return X, Y
資料可視化展示如下:
繼續回顧一下搭建一個神經網絡的基本思路和步驟:
- 定義網絡結構(指定輸出層、隐藏層、輸出層的大小)
- 初始化模型參數
- 循環操作:執行前向傳播/計算損失/執行後向傳播/權值更新
定義網絡結構
假設X為神經網絡的輸入特征矩陣,y為标簽向量。則含單隐層的神經網絡的結構如下所示:
網絡結構的函數定義如下:
def layer_sizes(X, Y):
n_x = X.shape[0] # 輸入層大小
n_h = 4 # 隐藏層大小
n_y = Y.shape[0] # 輸出層大小
return (n_x, n_h, n_y)
其中輸入層和輸出層的大小分别與X和 y的shape有關。而隐層的大小可由我們手動指定。這裡我們指定隐層的大小為4。
初始化模型參數
假設W1為輸入層到隐層的權重數組、b1為輸入層到隐層的偏置數組;W2為隐層到輸出層的權重數組,b2為隐層到輸出層的偏置數組。于是我們定義參數初始化函數如下:
def initialize_parameters(n_x, n_h, n_y):
W1 = np.random.randn(n_h, n_x)*0.01
b1 = np.zeros((n_h, 1))
W2 = np.random.randn(n_y, n_h)*0.01
b2 = np.zeros((n_y, 1))
assert (W1.shape == (n_h, n_x))
assert (b1.shape == (n_h, 1))
assert (W2.shape == (n_y, n_h))
assert (b2.shape == (n_y, 1))
parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}
return parameters
其中對權值的初始化我們利用了numpy中的生成随機數的子產品np.random.randn,偏置的初始化則使用了 np.zeros子產品。通過設定一個字典進行封裝并傳回包含初始化參數之後的結果。
前向傳播
在定義好網絡結構并初始化參數完成之後,就要開始執行神經網絡的訓練過程了。而訓練的第一步則是執行前向傳播計算。假設隐層的激活函數為tanh函數, 輸出層的激活函數為sigmoid函數。則前向傳播計算表示為:
定義前向傳播計算函數為:
def forward_propagation(X, parameters):
# 擷取各參數初始值
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']
# 執行前向計算
Z1 = np.dot(W1, X) + b1
A1 = np.tanh(Z1)
Z2 = np.dot(W2, A1) + b2
A2 = sigmoid(Z2)
assert(A2.shape == (1, X.shape[1]))
cache = {"Z1": Z1,
"A1": A1,
"Z2": Z2,
"A2": A2}
return A2, cache
從參數初始化結果字典裡取到各自的參數,然後執行一次前向傳播計算,将前向傳播計算的結果儲存到cache這個字典中, 其中A2為經過sigmoid激活函數激活後的輸出層的結果。
計算目前訓練損失
前向傳播計算完成後我們需要确定以目前參數執行計算後的的輸出與标簽值之間的損失大小。與筆記1一樣,損失函數同樣選擇為交叉熵損失:
定義計算損失函數為:
def compute_cost(A2, Y, parameters):
# 訓練樣本量
m = Y.shape[1]
# 計算交叉熵損失
logprobs = np.multiply(np.log(A2),Y) + np.multiply(np.log(1-A2), 1-Y)
cost = -1/m * np.sum(logprobs)
# 次元壓縮
cost = np.squeeze(cost)
assert(isinstance(cost, float))
return cost
執行反向傳播
目前向傳播和目前損失确定之後,就需要繼續執行反向傳播過程來調整權值了。中間涉及到各個參數的梯度計算,具體如下圖所示:
根據上述梯度計算公式定義反向傳播函數:
def backward_propagation(parameters, cache, X, Y):
m = X.shape[1]
# 擷取W1和W2
W1 = parameters['W1']
W2 = parameters['W2']
# 擷取A1和A2
A1 = cache['A1']
A2 = cache['A2']
# 執行反向傳播
dZ2 = A2-Y
dW2 = 1/m * np.dot(dZ2, A1.T)
db2 = 1/m * np.sum(dZ2, axis=1, keepdims=True)
dZ1 = np.dot(W2.T, dZ2)*(1-np.power(A1, 2))
dW1 = 1/m * np.dot(dZ1, X.T)
db1 = 1/m * np.sum(dZ1, axis=1, keepdims=True)
grads = {"dW1": dW1,
"db1": db1,
"dW2": dW2,
"db2": db2}
return grads
将各參數的求導計算結果放入字典grad進行傳回。
這裡需要提一下的是涉及到的關于數值優化方面的知識。在機器學習中,當所學問題有了具體的形式之後,機器學習就會形式化為一個求優化的問題。不論是梯度下降法、随機梯度下降、牛頓法、拟牛頓法,抑或是 Adam 之類的進階的優化算法,這些都需要花時間掌握去掌握其數學原理。
權值更新
疊代計算的最後一步就是根據反向傳播的結果來更新權值了,更新公式如下:
由該公式可以定義權值更新函數為:
def update_parameters(parameters, grads, learning_rate=1.2):
# 擷取參數
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']
# 擷取梯度
dW1 = grads['dW1']
db1 = grads['db1']
dW2 = grads['dW2']
db2 = grads['db2']
# 參數更新
W1 -= dW1 * learning_rate
b1 -= db1 * learning_rate
W2 -= dW2 * learning_rate
b2 -= db2 * learning_rate
parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}
return parameters
這樣,前向傳播-計算損失-反向傳播-權值更新的神經網絡訓練過程就算部署完成了。目前了,跟之前幾講一樣,為了更加pythonic一點,我們也将各個子產品組合起來,定義一個神經網絡模型:
def nn_model(X, Y, n_h, num_iterations=10000, print_cost=False):
np.random.seed(3)
n_x = layer_sizes(X, Y)[0]
n_y = layer_sizes(X, Y)[2]
# 初始化模型參數
parameters = initialize_parameters(n_x, n_h, n_y)
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']
# 梯度下降和參數更新循環
for i in range(0, num_iterations):
# 前向傳播計算
A2, cache = forward_propagation(X, parameters)
# 計算目前損失
cost = compute_cost(A2, Y, parameters)
# 反向傳播
grads = backward_propagation(parameters, cache, X, Y)
# 參數更新
parameters = update_parameters(parameters, grads, learning_rate=1.2)
# 列印損失
if print_cost and i % 1000 == 0:
print ("Cost after iteration %i: %f" %(i, cost))
return parameters
模型主體完成之後也可以再定義一個基于訓練結果的預測函數:
def predict(parameters, X):
A2, cache = forward_propagation(X, parameters)
predictions = (A2>0.5)
return predictions
下面我們便基于之前生成的資料來測試一下模型:
parameters = nn_model(X, Y, n_h = 4,
num_iterations=10000,
print_cost=True)
經過9000次疊代後損失下降到了0.21。我們再來看一下測試預測準确率:
# 預測準确率
predictions = predict(parameters, X)
print ('Accuracy: %d' % float((np.dot(Y,predictions.T) +
np.dot(1-Y,1-predictions.T))/float(Y.size)*100) + '%')
測試準确率達到0.9。
繪制神經網絡的決策邊界效果如下:
以上便是本節的主要内容,利用numpy手動搭建一個單隐層的神經網路。本例來自于Andrew NG deeplearningai深度學習系列課程第一門課的assignment3,感興趣的朋友可查找相關資料進行學習。完整代碼檔案和資料可參考我的GitHub位址:
https://github.com/luwill/machine-learning-code-writing
參考資料: