1. 程式人生 > >Python BP神經網路實現

Python BP神經網路實現

Python_BP神經網路實現(向量化運算、鳶尾花分類測試)

簡介

人工神經網路模型種類很多,其中根據網路內資料流向進行分類可以分為前饋網路、反饋網路和自組織網路。

通過對Andrew Ng的深度學習課程的學習,本文總結其中淺層神經網路一章的知識點(本文重點不在於公式講解,而是演算法的簡單實現,具體理論可看——深度學習工程師)。在此介紹和利用Python實現BP神經網路,BP神經網路是一種典型的前饋神經網路。

結構

BP神經網路分為三層分別是輸入層、隱層和輸出層,其中隱層的層數可以擴充套件,且每一層的神經元個數也可以增減。每一層中神經元與前後層神經元相連線,但是同一層神經元之間無連線。可看下方示意圖。

原理

當我們使用BP神經網路來對資料進行分類或者預測的時候,每對有連線的神經元之間都有一個權重,記為w;同時還有偏移量,記為b。每個神經元中還有一個啟用函式,記為σ(x),要注意的是這不是一個函式,有多個函式可以作為啟用函式SigmoidtanhRelu等。

在每一次迭代計算中,正向運算(輸入層開始),我們會計算出一個值,然後計算出該值與標準值的誤差;反向運算(輸出層開始),按照減小誤差方向,修正各連線權。通過一次次的迭代計算,直到誤差減小到給定的極小值,就可以結束迭代,完成訓練。

Python實現思路

通過python實現BP神經網路,主要有以下幾個步驟:

  1. 神經網路結構確定
  2. 權重和偏移量引數初始化
  3. 正向傳播計算
  4. 成本函式計算
  5. 反向傳播計算
  6. 權重和偏移量引數更新

神經網路結構確定

該函式主要是為了獲取輸入量x的矩陣大小,以及標籤y的矩陣大小。

def layer_size(X, Y):
"""
:param X: input dataset of shape (input size, number of examples)  (輸入資料集大小(幾個屬性,樣本量))
:param Y: labels of shape (output size, number of exmaples) (標籤資料大小(標籤數,樣本量))
:return: 
n_x: the size of the input layer
n_y: the size of the output layer
"""
n_x = X.shape[0]
n_y = Y.shape[0]

return (n_x, n_y)

權重和偏移量引數初始化

該函式主要是為了初始化我們的連線權重w和偏移量b。要注意的是確保引數矩陣大小正確。

def initialize_parameters(n_x, n_h, n_y):
"""
initialize_parameters
(引數初始化)
:param n_x: size of the input layer 
:param n_h: size of the hidden layer
:param n_y: size of the output layer
:return: 
W1: weight matrix of shape (n_h, n_x) (第1層的權重矩陣(n_h, n_x))
b1: bias vector of shape (n_h, 1) (第1層的偏移量向量(n_h, 1))
W2: weight matrix of shape (n_y, n_h) (第2層的權重矩陣(n_y, n_h))
b2: bias vector of shape (n_y, 1) (第2層的偏移量向量(n_y, 1))
"""
# np.random.seed(2)  #Random initialization (隨機種子初始化引數)

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))

parameters = {
    'W1': W1,
    'b1': b1,
    'W2': W2,
    'b2': b2,
}

return parameters

正向傳播計算

該函式為正向傳播計算,需要注意的是,中間層的啟用函式為tanh,輸出層的啟用函式為sigmoid。

def forward_propagation(X, parameters):
"""
forward_propagation
(正向傳播)
:param X: input data of size (n_x, m)  (輸入資料集X)
:param parameters: python dictionary containing your parameters (output of initialization function) (字典型別, 權重以及偏移量引數)
:return: 
A2: The sigmoid output of the second activation (第2層啟用函式sigmoid函式輸出向量)
cache: a dictionary containing "Z1", "A1", "Z2" and "A2" (字典型別,包含"Z1", "A1", "Z2", "A2")
"""
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']

Z1 = np.dot(W1, X) + b1
A1 = np.tanh(Z1)            #第1層啟用函式選擇tanh
Z2 = np.dot(W2, A1) + b2
A2 = sigmoid(Z2)            #第2層啟用函式選擇sigmod


assert (A2.shape == (1, X.shape[1])) #若A2的大小和((1, X.shape[1]))不同,則直接報異常

cache = {
    'Z1': Z1,
    'A1': A1,
    'Z2': Z2,
    'A2': A2,
}

return A2, cache

成本函式計算

該函式主要是為了計算成本函式,注意一個樣本的期望輸出和實際輸出的誤差的平方用來定義損失函式,在向量化的計算過程中,這裡使用了成本函式。詳細定義可見深度學習工程師

def compute_cost(A2, Y, parameters):
"""
compute cost
(計算成本函式)
:param A2: The sigmoid output of the second activation, of shape (1, number of examples) (第2層啟用函式sigmoid函式輸出向量)
:param Y: "true" labels vector of shape (1, number of examples) (正確標籤向量)
:param parameters: python dictionary containing your parameters W1, b1, W2 and b2 (字典型別,權重以及偏移量引數)
:return: 
cost: cross-entropy cost 
"""
m = Y.shape[1]  # number of example

W1 = parameters['W1']
W2 = parameters['W2']

logprobs = np.multiply(np.log(A2), Y)
cost = - np.sum(np.multiply(np.log(A2), Y) + np.multiply(np.log(1. - A2), 1. - Y)) / m
# cost = np.sum(Y * np.log(A2) + (1 - Y) * np.log(1 - A2))/(-m)

cost = np.squeeze(cost) #squeeze()函式的功能是:從矩陣shape中,去掉維度為1的。例如一個矩陣是的shape是(5, 1),使用過這個函式後,結果為(5,)。

assert (isinstance(cost, float)) #若cost不是float型 則直接報異常

return cost

反向傳播計算

該函式為方向傳播計算。

def backward_propagation(parameters, cache, X, Y):
"""
backward propagation
(反向傳播)
:param parameters: python dictionary containing our parameters
:param cache: a dictionary containing "Z1", "A1", "Z2" and "A2"
:param X: input data of shape (2,number of examples)
:param Y: "ture" labels vector of shape (1, number of examples)
:return: 
grads: python dictionary containing your gradients with respect to different parameters (字典型別,梯度微分引數)
"""
m = X.shape[1]

W1 = parameters['W1']
W2 = parameters['W2']

A1 = cache['A1']
A2 = cache['A2']

dZ2 = A2 - Y
dW2 = np.dot(dZ2, A1.T) / m
db2 = np.sum(dZ2, axis=1, keepdims=True) / m
dZ1 = np.dot(W2.T, dZ2) * (1 - A1 ** 2)
dW1 = np.dot(dZ1, X.T) / m
db1 = np.sum(dZ1, axis=1, keepdims=True) / m

grads = {
    'dW1': dW1,
    'db1': db1,
    'dW2': dW2,
    'db2': db2,
}

return grads

權重和偏移量引數更新

該函式為更新權重和偏移量引數。

def update_parameters(parameters, grads, learning_rate):
"""
update parameters
(更新權重和偏移量引數)
:param parameters: python dictionary containing your parameters
:param grads: python dictionary containing your gradients 
:param learning_rate (學習速率)
:return: 
:parameters:  python dictionary containing your updated parameters 
"""
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 = W1 - learning_rate * dW1
b1 = b1 - learning_rate * db1
W2 = W2 - learning_rate * dW2
b2 = b2 - learning_rate * db2

parameters = {
    "W1": W1,
    "b1": b1,
    "W2": W2,
    "b2": b2,
}

return parameters

BP神經網路

選擇我們將上面的幾個函式組合起來,就可以得到一個兩層的BP神經網路模型。

def nn_model(X, Y, n_h, num_iterations, learning_rate, print_cost=False):
"""
Forward Neural Network model
(前向神經網路模型)
:param X: input dataset of shape (input size, number of examples)  (輸入資料集大小(幾個屬性,樣本量))
:param Y: labels of shape (output size, number of exmaples) (標籤資料大小(標籤數,樣本量))
:param n_h: size of the hidden layer (隱層神經元數量)
:param num_iterations:  Number of iterations in gradient descent loop (迭代次數)
:param learning_rate (學習速率)
:param print_cost: if True, print the cost every 1000 iterations (是否列印顯示)
:return: 
parameters: parameters learnt by the model. They can then be used to predict (訓練完成後的引數)
"""

# np.random.seed(4)
n_x = layer_size(X, Y)[0]
n_y = layer_size(X, Y)[1]

parameters = initialize_parameters(n_x, n_h, n_y)
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']

cost_list = []
for i in range(0, num_iterations):

    A2, cache = forward_propagation(X, parameters)

    cost = compute_cost(A2, Y, parameters)

    cost_list.append(cost)

    grads = backward_propagation(parameters, cache, X, Y)

    parameters = update_parameters(parameters, grads, learning_rate)

    if print_cost and i % 1000 == 0:
        print("Cost after iteration %i: %f" % (i, cost))

return parameters, cost_list

鳶尾花分類測試

既然已經完成了這個BP神經網路模型,現在就可以來試試效果。在這裡我們用鳶尾花的分類來檢測這個模型的可用性。

簡介

Iris資料集是常用的分類實驗資料集,由Fisher, 1936收集整理。Iris也稱鳶尾花卉資料集,是一類多重變數分析的資料集。資料集包含150個數據集,分為3類,每類50個數據,每個資料包含4個屬性。可通過花萼長度,花萼寬度,花瓣長度,花瓣寬度4個屬性預測鳶尾花卉屬於(Setosa,Versicolour,Virginica)三個種類中的哪一類。

屬性:

  • Sepal.Length(花萼長度),單位是cm;

  • Sepal.Width(花萼寬度),單位是cm;

  • Petal.Length(花瓣長度),單位是cm;

  • Petal.Width(花瓣寬度),單位是cm;

種類:

  • Iris Setosa(山鳶尾)(本例中使用數字‘0’表示)
  • Iris Versicolour(雜色鳶尾)(本例中使用數字‘1’表示)
  • Iris Virginica(維吉尼亞鳶尾)(本例中使用數字‘2’表示)

測試程式

#!/usr/bin/env python  
# _*_ coding:utf-8 _*_  
#  
# @Version : 1.0  
# @Time    : 2018/6/6  
# @Author  : 圈圈烴
# @File    : User_BPNN
import numpy as np
import matplotlib.pyplot as plt
from Forward_NeuralNetwork import *


def data_process():
    """Iris.txt資料預處理"""
    with open("iris.txt", 'r') as f:
        data = f.read()
        data = data.replace('Iris-setosa', '0,')
        data = data.replace('Iris-versicolor', '1,')
        data = data.replace('Iris-virginica', '2,')
    with open("iris1.txt", 'w') as fw:
        fw.write(data)
        fw.close()


def load_csv():
    """載入處理好存入csv格式的資料"""
    tmp = np.loadtxt("iris.csv",dtype=np.str, delimiter=",")
    data = tmp[0:, 0:4].astype(np.float)
    label = tmp[0:, 4].astype(np.float)
    label = label.reshape(150, 1)
    return data.T, label.T


def normalized(X):
    """
    :param X: 待歸一化的資料 
    :return: 
    X:歸一化後的資料
    """
    Xmin, Xmax = X.min(), X.max()
    XN = (X - Xmin) / (Xmax - Xmin)
    return XN


def main():

    X, Y = load_csv()
    X = normalized(X)
    Y = normalized(Y)
    """訓練集90個數據"""
    train_x = np.hstack((X[:, 0:30], X[:, 50:80], X[:, 100:130]))
    train_y = np.hstack((Y[:, 0:30], Y[:, 50:80], Y[:, 100:130]))
    """測試集60個數據"""
    test_x = np.hstack((X[:, 30:50], X[:, 80:100], X[:, 130:150]))
    test_y = np.hstack((Y[:, 30:50], Y[:, 80:100], Y[:, 130:150]))
    """訓練,中間層10個神經元,迭代10000次,學習率0.25"""
    n_h = 10
    parameter, cost_list = nn_model(train_x, train_y, n_h, num_iterations=10000, learning_rate=0.25, print_cost=True)
    """測試,代入測試集資料"""
    A2, cache = forward_propagation(test_x, parameters=parameter)
    TY = A2
    TY[TY > 0.8] = 1
    TY[TY < 0.2] = 0
    TY[(TY >= 0.2) & (TY <= 0.8)] = 0.5
    # print(A2,TY)
    count = 0
    for i in range(0, 60):
        if TY[0, i] == test_y[0, i]:
            count += 1
    print("準確率為:%f %%" %(100*count/60))
    """繪製梯度下降曲線"""
    plt.plot(cost_list)
    plt.show()


if __name__ == '__main__':
    main()

測試結果

測試中,將150個數劃分成了90個訓練資料,60個測試資料。神經網路的中間層為10個神經元,迭代次數為10000次,學習率為0.25。在訓練和測試中,需要對資料進行歸一化,其中包括對標籤資料Y的歸一化,原來,我設定的三類鳶尾花的標籤分別是0,1,2。通過歸一化之後,獲得的標籤資料為0,0.5,1。對測試集獲得的結果,進行歸檔,小於0.2的為0,大於0.8的為1,其餘的均為0.5。最終獲得的分類結果的準確率為98.3%。

Cost after iteration 0: 0.693152
Cost after iteration 1000: 0.280715
Cost after iteration 2000: 0.275627
Cost after iteration 3000: 0.274676
Cost after iteration 4000: 0.274162
Cost after iteration 5000: 0.273742
Cost after iteration 6000: 0.273368
Cost after iteration 7000: 0.273018
Cost after iteration 8000: 0.272678
Cost after iteration 9000: 0.272336
準確率為:98.333333 %

寫在最後

還有不足,歡迎交流。