1. 程式人生 > >BP神經網路python程式碼詳細解答(來自原文翻譯)

BP神經網路python程式碼詳細解答(來自原文翻譯)

翻譯如下
**

        在 SCRATCH採用python 上實現一種神經網路

**
        注: Scratch是一款由麻省理工學院(MIT) 設計開發的一款面向少年的簡易程式設計工具。這裡寫連結內容         本文翻譯自“IMPLEMENTING A NEURAL NETWORK FROM SCRATCH IN PYTHON – AN INTRODUCTION”,原文連結為這裡寫連結內容。並且,我在這裡給出原文數學公式的推導和對原文一些概念的修正;
        在這裡,我將展示一種簡單的三層神經網路,我不會詳細推匯出與本文有關的所有數學公式,我將我的想法以一種直觀的形式展示出來,我也會提供一些相關連結,來讓讀者能更好的理解本文的細節。
我認為,你們對於基本的微積分知識和機器學習的概念有一定的認識。同樣你們應該能理解分類和迴歸的概念,如果你們能夠理解最優化的概念,如梯度下降法的原理,那就更好了。如果你對本文的概念都不瞭解,你同樣可以發現這篇文章還是很有意思的。
但是為什麼用scratch來實現神經網路 ,即使你在以後採用其他軟體平臺如PyBrain,僅僅用scratch 做一次神經網路都是非常有價值的,他幫助你理解神經網路的運作原理,這種原理對於建立模型是至關重要的。
我要指出的是這裡的程式碼例子不是非常高效,但是他容易理解,在以後的文章中,我將用Theano.展示一種更加高效的神經網路。

產生一組資料
        讓我們產生一組資料來開始,幸運的是scikit-learn 有非常有用的資料發生器,我們沒有必要自己寫程式碼來產生資料了。我們用make_moons function.來 出資料

#輸出:資料集, 對應的類別標籤
#描述:生成一個數據集和對應的類別標籤
np.random.seed(0)
X, y = sklearn.datasets.make_moons(dim, noise=cnoise)
plt.scatter(X[:, 0], X[:, 1], s=40, c=y, cmap=plt.cm.Spectral
)

這裡寫圖片描述

        我們得到兩類資料,分別以紅點和藍點畫出,你可以認為藍色的點代表男性病人,紅點代表女性病人,X,Y軸分別代表治療措施。我們的目標是訓練機器分類器,讓機器來分類,然後給出想X,Y的座標。需要指出的是這些資料不是線性可分的。我們不能用一條直線來將這兩組資料分成兩類。這就意味著,像Logistic Regression這樣的演算法不能用於這樣資料的分類,除非你採用對於分類比較好的,手動非線性迴歸(如多項式迴歸)演算法,
事實上,神經網路的一個優勢就是你沒有必要擔心特徵的收集,隱藏層就會自動學習特徵。

LOGISTIC REGRESSION(邏輯斯提克迴歸)

為了證明這一點,我們來訓練Logistic Regression 分類器,輸入X,Y值和輸出分類(0 or 1)

import numpy as np
from sklearn import datasets, linear_model
import matplotlib.pyplot as plt


def generate_data():
    np.random.seed(0)
    X, y = datasets.make_moons(200, noise=0.20)
    return X, y


def visualize(X, y, clf):
    # plt.scatter(X[:, 0], X[:, 1], s=40, c=y, cmap=plt.cm.Spectral)
    # plt.show()
    plot_decision_boundary(lambda x: clf.predict(x), X, y)
    plt.title("Logistic Regression")


def plot_decision_boundary(pred_func, X, y):
    # Set min and max values and give it some padding
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    h = 0.01
    # Generate a grid of points with distance h between them
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # Predict the function value for the whole gid
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # Plot the contour and training examples
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
    plt.show()


def classify(X, y):
    clf = linear_model.LogisticRegressionCV()
    clf.fit(X, y)
    return clf

邏輯斯提克迴歸

        圖片顯示,分類邊界已經被Logistic Regression classifier畫出,他將資料盡其所能按直線分成兩類,但是他不能捕捉月亮形狀的資料。

TRAINING A NEURAL NETWORK(訓練一種神經網路)

        我們將建立一個三層神經網路,這個網路包括一個輸入層,一個輸出層和一個隱藏層。輸入層的節點數(2)由資料的維度確定(資料時二維的,包括X,Y的座標 ),輸出層的節點數(2)由所分的種類確定(我們只有兩類資料,我們實際上可以只設置一個輸出節點來預測0或者1,但是當設定兩個節點時,該神經網路可以擴充套件,使該網路可以計算更多的分類),輸入層只要輸入X,Y的座標,輸出層要輸出兩種分類的概率。

這裡寫圖片描述
        我們可以設定隱藏層的節點數,隱藏層的節點數越多,分類的效果越好。但是這樣做的代價也比較大,首先,該網路的在預測是需要學習的引數就比較多,導致計算量的增加。其次,大量的引數也容易導致過擬合。怎樣去確定隱藏層的規模呢? 儘管有一些大致的規則和建議,更多時候取決於實際問題的情況。隱藏層的節點數更多時候是一個藝術的問題而不是一個科學的問題,我們將改變隱藏層的 節點數來看看輸出 的效果
我們需要為隱藏層選取一個啟用函式,這個啟用函式將層的輸入值轉化為輸出值,非線性的啟用函式允許我們完成非線性的預測,啟用函式一般選取,tanh, the sigmoid function, or ReLUs. /font>

這裡寫圖片描述

        我們將使用tanh,tanh在很多情況下表現都很好。這些函式都有一個很好的優點,即他們的導數可以用他們的原函式來表示。例如 這裡寫圖片描述 這樣的函式非常有用,因為我們只需要計算一次函式值,然後再次利用函式值來得到他的導數值
        因為我們希望該網路最後輸出概率,所以我們將輸出層的啟用函式選為 softmax,softmax可以將原始資料轉化為概率,如果你對logistic function 比較熟悉的話你可以把它看做logistic function 的推廣。

        譯者說明:本神經網路在演算法本質上是BP 神經網路,但是他與一般的BP網又存在不同點:

        1. 一般教科書上的BP網都採用同一個啟用函式,下文的程式碼中,隱藏層的啟用函式為tanh ,輸出層的啟用函式為softmax.為什麼採用softmax 作為輸出層的啟用函式,我會在下文中進行說明。

HOW OUR NETWORK MAKES PREDICTIONS (我們的網路如何預測)
       我們的網路採用前饋神經網路,即一串矩陣相乘,然後再利用我們之前定義的啟用函式。如果x是一個二維陣列,輸入到神經網路,輸出值為 這裡寫圖片描述同樣是二維資料。

z1=xW1+b1a1=tanh(z1)z2=a1W2+b2a2=y^=softmax(z2)

zi 是第i層的輸入值,ai是第i層通過啟用函式的輸出值,w1,b1,w2,b2,為該神經網路的引數,這些引數同對資料的學習來不斷的修正(w1為輸入層和隱藏層之間的權矩陣,b1為隱藏層的偏置,w2為隱藏層和輸出層之間的權矩陣,b1為輸出層的偏置)。你也可以認為他是各層之間的轉換矩陣。通過對上面矩陣相乘我們可以看出各個矩陣的維度,如果我們在隱藏層設定500個節點,那麼
              這裡寫圖片描述
        現在你可以看出,如果我們增加隱藏層的節點,我們將需要更多的引數

LEARNING THE PARAMETERS

       通過最小化誤差來不斷的修正各層的引數(w1,b1,w2,b2,)。但是如何定義誤差呢,我們把這個函式叫作誤差損失函式;關於softmax 函式的誤差函式叫做叫做交叉熵損失(也叫做log似然代價函式),如果我們有N個訓練例子和C個人分類,那麼log似然代價函式如下, 這裡寫圖片描述是我們的預測值(計算值),而y是標籤值,(目標值)

               這裡寫圖片描述

       這個公式看起來非常複雜,但是他只是將所有例子的誤差加起來,如果我們預測分類不正確,y 是(目標值),這裡寫圖片描述是預測值(計算值),如果這兩個值相差越大,則誤差函式就越大。通過訓練資料最大化似然函式,來最小化誤差,同時不斷修正網路引數。
        我們可以用梯度下降法來,尋求最小值,我將常用的梯度下降法,也叫採用固定學習速度的批量梯度下降法。他的變種是隨機梯度下降法和 minibatch gradient descent,表現都很好,如果你對這種演算法感興趣,你可以嘗試一下各種演算法。

       輸入時,梯度下降法需要輸入梯度,(這裡寫圖片描述),損失函式的向量導數。為了計算梯度我們運用著名的BP演算法。該演算法是一種計算梯度的有效演算法。我不想展示BP演算法的的求導細節。

        譯者註釋:

1.BP 神經網路求導細節請看這裡這裡寫連結內容這是一篇非常好的文章還有這篇這裡寫連結內容
2.在下面程式碼中還涉及到softmax的求導,這裡寫連結內容請好好看看上面三篇文章,你就對BP 演算法有比較深刻的認識。

       通過誤差反向傳播演算法,我們可以得到如下公式(相信我,下面的公式是正確的)
                      這裡寫圖片描述

        譯者註釋:那我就將推導上面所有公式!!!

BP演算法
1.0

       這裡寫圖片描述  表示第 L-1層的第i個神經元連線到第層的第j個神經元的連線權重;

       這裡寫圖片描述    表示第L層的第i個神經元的輸入

       這裡寫圖片描述

       這裡寫圖片描述    表示第L層的第i個神經元的輸出

       這裡寫圖片描述

       這裡寫圖片描述    表示輸出層的啟用函式,在本文中,啟用函式為softmax函式。
2.0
代價函式為最大似然函式,如下: 這裡寫圖片描述是我們的預測值(計算值),而y是標籤值,(目標值)
               這裡寫圖片描述

3.0
首先,將第L層第i個神經元中產生的錯誤對第i層的倒數定義為

               這裡寫圖片描述

方程一(計算損失函式對輸出層某個節點輸入值的導數)

這裡寫圖片描述

方程2(由後往前,計算損失函式對前一層某個節點輸入值的導數):
這裡寫圖片描述

方程3(計算損失函式對最靠近當前層與當前層某個權值的偏導數):

方程4(計算損失函式對當前層某個偏置的偏導數):

這裡寫圖片描述

4.0

BP演算法虛擬碼
1,輸入資料
2,初始化權值和偏置我w,b
3,前向傳播
這裡寫圖片描述

3,計算損失函式對輸出層某個節點輸入值的偏導數
這裡寫圖片描述

4,計算損失函式對隱藏層某個節點輸入值的偏導數

這裡寫圖片描述

5,計算損失函式對各層各個節點的權值,和偏置的梯度,採用梯度下降法最小化似然函式
這裡寫圖片描述

在這裡我們已經清楚BP演算法的的原理,我們要感謝這裡寫連結內容這位博主,他用精細化的數學語言向我們展示了BP演算法。他的推到有點問題,我已經在我的文章中糾正了。

但是要理解下面的程式碼,有一個很重要的問題就是softmax函式求導的問題,這裡我們將探討這個問題。

1. softmax函式及其求導;我們要感謝這裡寫連結內容內容這位博主,他用精細化的數學語言向我們展示了softmax求導演算法。

softmax的函式這裡寫圖片描述

其中,這裡寫圖片描述表示第L層(通常是最後一層)第j個神經元的輸入,這裡寫圖片描述表示第L層第j個神經元的輸出,e表示自然常數。注意看,這裡寫圖片描述表示了第L層所有神經元的輸入之和。

softmax函式最明顯的特點在於:它把每個神經元的輸入佔當前層所有神經元輸入之和的比值,當作該神經元的輸出。這使得輸出更容易被解釋:神經元的輸出值越大,則該神經元對應的類別是真實類別的可能性更高。
另外,softmax不僅把神經元輸出構造成概率分佈,而且還起到了歸一化的作用,適用於很多需要進行歸一化處理的分類問題。
由於softmax在ANN演算法中的求導結果比較特別,分為兩種情況。希望能幫助到正在學習此類演算法的朋友們。求導過程如下所示:
這裡寫圖片描述

二次代價函式在訓練ANN時可能會導致訓練速度變慢的問題。那就是,初始的輸出值離真實值越遠,訓練速度就越慢。這個問題可以通過採用交叉熵代價函式來解決。其實,這個問題也可以採用另外一種方法解決,那就是採用softmax啟用函式,並採用log似然代價函式(log-likelihood cost function)來解決。

log似然代價函式的公式為:
在上一篇博文“交叉熵代價函式”中講到,二次代價函式在訓練ANN時可能會導致訓練速度變慢的問題。那就是,初始的輸出值離真實值越遠,訓練速度就越慢。這個問題可以通過採用交叉熵代價函式來解決。其實,這個問題也可以採用另外一種方法解決,那就是採用softmax啟用函式,並採用log似然代價函式(log-likelihood cost function)來解決。
log似然代價函式的公式為:

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

下面為羅幹註釋的程式碼

# -*- coding: utf-8 -*-
"""
Created on Sun Feb 12 14:57:00 2017

@author: Denny Britz
@Translater:luogan  羅幹
@luogan has make some coment on the code 
@ 我愛婷婷和臭臭
"""

import numpy as np
from sklearn import datasets, linear_model
import matplotlib.pyplot as plt


class Config:
    nn_input_dim = 2 #陣列輸入的維度是2(x,y兩個座標當然是二維啊)
    nn_output_dim = 2#陣列輸出的維度是2(分為兩類當然是二維啊) 
    epsilon = 0.01  # 梯度下降學習步長
    reg_lambda = 0.01  # 修正的指數?


def generate_data():
    np.random.seed(0)#偽隨機數的種子0,當然也可以是1,2啊
    X, y = datasets.make_moons(200, noise=0.20)#產生200個數據,噪聲誤差為0.2
    return X, y


def visualize(X, y, model):
    plot_decision_boundary(lambda x:predict(model,x), X, y)#好好看這個程式碼,函式名字做引數哦
    plt.title("Logistic Regression")


def plot_decision_boundary(pred_func, X, y):
    #把X的第一列的最小值減掉0.5賦值給x_min,把X的第一列的最大值加0.5賦值給x_max
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    h = 0.01
    # 根據最小最大值和一個網格距離生成整個網格,就是在圖上細分好多個點,畫分類邊界的時候要用這些點
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
    plt.show()

def predict(model, x):
    #這是字典啊
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    z1 = x.dot(W1) + b1# 輸入層向隱藏層正向傳播
    a1 = np.tanh(z1) # 隱藏層啟用函式使用tanh = (exp(x) - exp(-x)) / (exp(x) + exp(-x))
    z2 = a1.dot(W2) + b2# 隱藏層向輸出層正向傳播
    exp_scores = np.exp(z2)#這兩步表示輸出層的啟用函式為softmax函式哦
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    return np.argmax(probs, axis=1)



def build_model(X, y, nn_hdim, num_passes=20000, print_loss=False):
    num_examples = len(X)
    np.random.seed(0)#初始化權值和偏置
    W1 = np.random.randn(Config.nn_input_dim, nn_hdim) / np.sqrt(Config.nn_input_dim)
    b1 = np.zeros((1, nn_hdim))
    W2 = np.random.randn(nn_hdim, Config.nn_output_dim) / np.sqrt(nn_hdim)
    b2 = np.zeros((1, Config.nn_output_dim))
    model = {}

    for i in range(0, num_passes):

        z1 = X.dot(W1) + b1# 輸入層向隱藏層正向傳播
        a1 = np.tanh(z1)# 隱藏層啟用函式使用tanh = (exp(x) - exp(-x)) / (exp(x) + exp(-x))
        z2 = a1.dot(W2) + b2# 隱藏層向輸出層正向傳播
        exp_scores = np.exp(z2)#這兩步表示輸出層的啟用函式為softmax函式哦
        probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

        delta3 = probs
        #下面這才是delta3,為損失函式對z2求偏導數,y-y^
        delta3[range(num_examples), y] -= 1
        dW2 = (a1.T).dot(delta3)#損失函式對w2的偏導數
        db2 = np.sum(delta3, axis=0, keepdims=True)#損失函式對b2的偏導數
        delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))#損失函式對z1的偏導數
        dW1 = np.dot(X.T, delta2)#損失函式對w1的偏導數
        db1 = np.sum(delta2, axis=0)#損失函式對b1的偏導數
        #個人認為下面兩行程式碼完全沒有必要存在
        dW2 += Config.reg_lambda * W2#w2梯度增量的修正  屁話
        dW1 += Config.reg_lambda * W1#w1梯度增量的修正  屁話
        #更新權值和偏置
        W1 += -Config.epsilon * dW1
        b1 += -Config.epsilon * db1
        W2 += -Config.epsilon * dW2
        b2 += -Config.epsilon * db2

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

    return model


def main():
    X, y = generate_data()
    model = build_model(X, y, 8)
    visualize(X, y, model)


if __name__ == "__main__":
     main()
    #2017.3.20 羅幹註釋於同濟大學圖書館 

這裡寫圖片描述

在這裡首先要感謝dennybritz這裡是dennybritz的github
(High-school dropout. Google Brain, Stanford, Berkeley. Into Startups, Deep Learning. Writing at wildml.com and dennybritz.com. Lived in 日本 and 한국)
操,高中輟學都這麼屌,還上了斯坦福,和伯克利。
dennybritz在GitHub上分享了他的程式碼和文章。我的這篇部落格就是對dennybritz原文的翻譯,但是他的文章中沒有BP網路和softmax函式的求導。

我在這篇文章中插入數學推導,主要參考這裡天才的部落格這位天才的文章,為了讓讀者更好的理解BP,我就直接引用了。

特碼的,搞神經網路,畢業都延期了,不過也是值得的

未完待續,我還會修改的