神經網絡 ——一個很好的解釋以及簡單實現 Implementing a Neural Network from Scratch in Python – An Introduction


代碼均在這裏:
Get the code: To follow along, all the code is also available as an iPython notebook on Github.


在這篇文章中,我們將從頭開始實現一個簡單的3層神經網絡。
我們不會推導出所有需要的數學內容,但是我會嘗試直觀地解釋我們正在做什麼,我也會給出資源給你詳細閱讀用。

在這裏,我假設你熟悉基本的微積分和機器學習概念,例如,你知道什麼是分類和正則化。理想情況下,你還知道一些關於如何使用梯度下降優化的技術。但是即使你不熟悉上述任何列出來的,仍然可以是很有趣的 : )

然而,爲什麼要從頭開始實現一個神經網絡呢?即使您計劃在未來使用像PyBrain這樣的神經網絡庫,至少一次從頭開始實現一個網絡是非常有價值的練習。它可以幫助您瞭解神經網絡的工作原理,這對設計有效的模型至關重要。

有一點需要注意的是,這裏的代碼示例並不是非常有效。它們被設計成比較容易理解。在即將發佈的帖子中,我將探討如何使用Theano編寫一個高效的神經網絡實現。 (更新:now available


Generating a dataset 生成數據集

讓我們從生成一個數據集給我們使用開始。幸運的是,scikit-learn有一些有用的數據集生成器,所以我們不需要自己編寫代碼。 我們將使用make_moons函數。

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

# Generate a dataset and plot it
np.random.seed(0)
X, y = sklearn.datasets.make_moons(200, noise=0.20)
plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral)

dataset1

我們生成的數據集有兩個類,繪製爲紅色和藍色點。你可以將藍點作爲男性患者,將紅點視爲女性患者,X軸和Y軸是醫學測量。

我們的目標是訓練一個機器學習分類器,根據給出的xy座標來預測正確的分類(男性或女性)。請注意數據不是線性分離的,我們不能繪製分開兩個類的直線。這意味着線性分類器像邏輯迴歸,將無法適應數據,除非你手工設計適用於給定數據集的非線性特徵(如多項式)。

事實上,這是神經網絡的主要優點之一。 您不需要擔心特徵工程。 神經網絡的隱藏層將爲你學習feature。


Logistic Regression 邏輯迴歸

爲了說明我們訓練一個邏輯迴歸分類器,它的輸入是xy值,輸出預測類(0或1)。爲了方便簡單,我們從scikit-learn中使用 Logistic Regression class.

!!! 代碼是GitHub上的那個文件 simple_classification.py

# Train the logistic rgeression classifier
clf = sklearn.linear_model.LogisticRegressionCV()
clf.fit(X, y)

# Plot the decision boundary
plot_decision_boundary(lambda x: clf.predict(x))
plt.title("Logistic Regression")

這裏寫圖片描述
該圖顯示了由Logistic迴歸分類器學到的判定界限。 它將數據分離成可以使用直線,但它無法捕獲我們的數據的“月亮形狀”。


Training a Neural Network

現在我們來構建一個三層神經網絡,一個輸入層,一個隱藏層和一個輸出層。
輸入層中的節點數由我們的數據的維度決定,就是2;
類似地,輸出層中的節點數由我們所擁有的類的數量所決定,也是2。(因爲我們只有2個類, 實際上可以只有一個輸出節點預測0或1,但是有2個可以更容易地將網絡擴展到更多的類)。
網絡的輸入將是x和y座標,其輸出將是兩個概率,一個用於class 0(“female”),一個用於class 1(“male”)。
看起來像這樣:

神經網絡圖示

我們可以選擇隱藏層的維數(節點數)。我們放入隱藏層中的節點越多,我們將能夠適應的更復雜的功能。但更高的維度會帶來成本。
首先,需要更多的計算來進行預測並學習網絡的參數。
更多的參數也意味着我們更容易過度擬合我們的數據。

那麼如何選擇隱藏層的大小呢?雖然有一些一般的指導方針和建議,但它總是取決於你的具體問題,更像是一門藝術而不是一門科學。稍後我們會着手於隱藏的節點數,看看它是如何影響我們的輸出的。

我們還需要給我們的隱藏層選擇激活函數。激活函數(The activation function)將層的輸入轉換爲其輸出。非線性激活函數是允許我們擬合非線性假設的。常見選擇的激活函數是tanh, the sigmoid function, 或者ReLUs.
我們將使用tanh,在許多情況下表現相當好。這些函數的一個很好的屬性是它們的倒數可以使用原始函數值來計算。
例如,tanhx 的導數就是1tanh2x . 這是有用的,因爲它允許我們計算tanhx 一次,然後重新使用這個tanhx 值來獲得導數。

因爲我們希望我們的network輸出概率,輸出層的激活函數將是softmax,一種方法簡單地將原始score轉換爲概率。
如果你熟悉logistic function,you can think of softmax as its generalization to multiple classes.(這句還是放原文比較好 T_T).


How our network makes predictions

我們的網絡使用正向傳播去預測,正向傳播只是一堆矩陣乘法和上面定義的激活函數的應用。
x是我們的網絡的二維輸入,那麼我們如下計算我們的預測y (也是二維的):

z1=xW1+b1
a1=tanh(z1)
z2=a1W2+b2
a2=y=softmax(z2)

zi 是第i層的輸入,ai 是 第i層 應用激活函數 之後的輸出。
W1,b1,W2,b2 是我們網絡的參數,這些參數我們需要從training data中學習(learn from our training data). 你可以把它們當做是矩陣,在網絡層之間轉換數據的矩陣。
看着上面的矩陣乘法,我們可以計算出這些矩陣的維數:
如果我們使用500個節點在隱含層,那麼 W1R(2500),b1R(500),W2R(5002),b2R(2)
現在你可以看到了爲什麼我們需要更多的參數,如果我們提高了隱藏層的size。

Learning the Parameters

學習我們網絡參數意味着我們需要找到能最小化錯誤,在我們的訓練數據上。(the error on our training data)。不過,我們該如何定義我們的error?
We call the function that measures our error the loss funtion. 我們叫它 loss function。
softmax輸出的一個常見的選擇是分類交叉熵損失(也稱負對數似然值)。
the categorical cross-entropy loss

如果我們有N個訓練樣本 和 C個類,那麼 預測值 y’ 相對於 真實的 labels y 的損失(loss):

![loss](https://img-blog.csdn.net/20171129153846589?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3VvaGFvX3poYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 這個公式看起來很複雜,但是它真正的作用是總結我們的訓練樣例,並且如果我們預測了不正確的類,那就增加了損失。 兩個概率分佈越遠 y(正確的標籤)和 y’(我們的預測),我們的損失就越大。 通過尋找 能最小化損失的參數,we maximize the likelihood of our training data. 我們可以使用**梯度下降 (gradient descent)**來找到最小值 我將實現梯度下降的最普通的版本,也稱爲批量梯度下降,具有固定的學習率(batch gradient descent with a fixed learning rate) Variations such as **SGD (stochastic gradient descent) or minibatch gradient descent** typically perform better in practice. 【變體 例如 SGD(隨機梯度下降)或者小批次梯度下降 等 通常在實踐中表現更好】 作爲輸入,梯度下降需要相對於我們的參數的損失函數的梯度(導數的向量): As an input, gradient descent needs the gradients (vector of derivatives) of the loss function with respect to our parameters:LW1,Lb1,LW2,Lb2 爲了計算這些梯度,我們使用著名的**反向傳播算法**,這是一種有效地計算從輸出開始的梯度的方法。 我不會詳細討論反向傳播是如何工作的,但是在網絡上有很多很好的解釋。

Implementation:

We start by defining some useful variables and parameters for gradient descent:

num_examples = len(X) # training set size
nn_input_dim = 2 # input layer dimensionality
nn_output_dim = 2 # output layer dimensionality

# Gradient descent parameters (I picked these by hand)
epsilon = 0.01 # learning rate for gradient descent
reg_lambda = 0.01 # regularization strength

首先讓我們來實現我們上面定義的損失函數。 我們用這個來評估我們的模型在做什麼:

# Helper function to evaluate the total loss on the dataset
def calculate_loss(model):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # Forward propagation to calculate our predictions
    z1 = X.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    # Calculating the loss
    corect_logprobs = -np.log(probs[range(num_examples), y])
    data_loss = np.sum(corect_logprobs)
    # Add regulatization term to loss (optional)
    data_loss += reg_lambda/2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
    return 1./num_examples * data_loss

我們還實現了一個輔助函數來計算網絡的輸出。 它按前面定義的那樣進行前向傳播,並以最高概率返回類。

# Helper function to predict an output (0 or 1)
def predict(model, x):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # Forward propagation
    z1 = x.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    return np.argmax(probs, axis=1)

最後,這裏是訓練我們的神經網絡的功能。 它使用我們在上面提到的反向傳播實現批量梯度下降。

# This function learns parameters for the neural network and returns the model.
# - nn_hdim: Number of nodes in the hidden layer
# - num_passes: Number of passes through the training data for gradient descent
# - print_loss: If True, print the loss every 1000 iterations
def build_model(nn_hdim, num_passes=20000, print_loss=False):

    # Initialize the parameters to random values. We need to learn these.
    np.random.seed(0)
    W1 = np.random.randn(nn_input_dim, nn_hdim) / np.sqrt(nn_input_dim)
    b1 = np.zeros((1, nn_hdim))
    W2 = np.random.randn(nn_hdim, nn_output_dim) / np.sqrt(nn_hdim)
    b2 = np.zeros((1, nn_output_dim))

    # This is what we return at the end
    model = {}

    # Gradient descent. For each batch...
    for i in xrange(0, num_passes):

        # Forward propagation
        z1 = X.dot(W1) + b1
        a1 = np.tanh(z1)
        z2 = a1.dot(W2) + b2
        exp_scores = np.exp(z2)
        probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

        # Backpropagation
        delta3 = probs
        delta3[range(num_examples), y] -= 1
        dW2 = (a1.T).dot(delta3)
        db2 = np.sum(delta3, axis=0, keepdims=True)
        delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
        dW1 = np.dot(X.T, delta2)
        db1 = np.sum(delta2, axis=0)

        # Add regularization terms (b1 and b2 don't have regularization terms)
        dW2 += reg_lambda * W2
        dW1 += reg_lambda * W1

        # Gradient descent parameter update
        W1 += -epsilon * dW1
        b1 += -epsilon * db1
        W2 += -epsilon * dW2
        b2 += -epsilon * db2

        # Assign new parameters to the model
        model = { 'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}

        # Optionally print the loss.
        # This is expensive because it uses the whole dataset, so we don't want to do it too often.
        if print_loss and i % 1000 == 0:
          print "Loss after iteration %i: %f" %(i, calculate_loss(model))

    return model

A network with a hidden layer of size 3

讓我們看看如果我們訓練隱藏層大小爲3的網絡會發生什麼:

# Build a model with a 3-dimensional hidden layer
model = build_model(3, print_loss=True)

# Plot the decision boundary
plot_decision_boundary(lambda x: predict(model, x))
plt.title("Decision Boundary for hidden layer size 3")

這裏寫圖片描述

這看起來很不錯。 我們的神經網絡能夠找到一個成功分離類的決策邊界。


Varying the hidden layer size 改變隱藏層的大小

在上面的例子中,我們選擇了隱藏層大小3.現在讓我們瞭解隱藏層大小如何改變結果。

plt.figure(figsize=(16, 32))
hidden_layer_dimensions = [1, 2, 3, 4, 5, 20, 50]
for i, nn_hdim in enumerate(hidden_layer_dimensions):
    plt.subplot(5, 2, i+1)
    plt.title('Hidden Layer size %d' % nn_hdim)
    model = build_model(nn_hdim)
    plot_decision_boundary(lambda x: predict(model, x))
plt.show()

這裏寫圖片描述

我們可以看到隱藏的低維度層很好地捕捉了我們數據的總體趨勢。 更高的維度容易過度擬合。 他們是“記憶”數據而不是擬合一般的形狀。 如果我們要在一個單獨的測試集上評估我們的模型(而且您應該!),那麼具有較小隱藏層大小的模型由於更好的泛化可能會更好。 我們可以用更強的正則化來抵消過度擬合,但是爲隱藏層選擇正確的大小是一個更“economical”的解決方案。

英文原博網址在:
http://www.wildml.com/2015/09/implementing-a-neural-network-from-scratch/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章