教育行業(yè)A股IPO第一股(股票代碼 003032)

全國咨詢/投訴熱線:400-618-4000

手把手教你實現(xiàn)神經(jīng)網(wǎng)絡

更新時間:2021年03月10日17時52分 來源:傳智教育 瀏覽次數(shù):

在這篇文章中,我們將從頭開始實現(xiàn)一個簡單的3層神經(jīng)網(wǎng)絡。假設你熟悉基本的微積分和機器學習概念,例如:知道什么是分類和正規(guī)化。理想情況下,您還可以了解梯度下降等優(yōu)化技術的工作原理。 但是為什么要從頭開始實施神經(jīng)網(wǎng)絡呢?它可以幫助我們了解神經(jīng)網(wǎng)絡的工作原理,這對于設計有效模型至關重要。

1.1 生成數(shù)據(jù)集

這里我們首先生成后面要用的數(shù)據(jù)集。生成數(shù)據(jù)集可以使用scikit-learn (http://scikit-learn.org/)里面的make_moons (http://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_moons.html)函數(shù)。

In [1]:

# 導包
import matplotlib.pyplot as plt
import numpy as np
import sklearn
import sklearn.datasets
import sklearn.linear_model
import matplotlib
# 設置matplot參數(shù)
%matplotlib inline
matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)

In [2]:

# 生成數(shù)據(jù)集并用plot畫出
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)

Out[2]:

<matplotlib.collections.PathCollection at 0x1a1ee64f60>

實現(xiàn)神經(jīng)網(wǎng)絡01

這個數(shù)據(jù)集有兩個類別,分別是用紅色和藍色表示。我們的目標是使用機器學習的分類器根據(jù)x, y坐標預測出正確的類別。注意這里的數(shù)據(jù)并不是線性可分的。我們不能畫一條直線把這個數(shù)據(jù)集分成兩個類別。這就意味著,線性分類器,比如邏輯回歸無法對我們的數(shù)據(jù)行擬合,換言之就是無法用線性分類器對這個數(shù)據(jù)集行分類。除非手動構造非線性特征,比如多項式。事實上這正是神經(jīng)網(wǎng)絡的主要優(yōu)點之一。使用神經(jīng)網(wǎng)絡我們不用去做特征工程 (http://machinelearningmastery.com/discover-feature-engineering-how-to-engineerfeatures-and-how-to-get-good-at-it/)。神經(jīng)網(wǎng)絡的隱藏層會自動的學習這些特征。

1.2 邏輯回歸

這里為了演示,我們使用邏輯回歸行分類。輸入是數(shù)據(jù)集里的x, y坐標,輸出是預測的類別(0或者1)。為了方便我們直接使用scikit-learn 中的邏輯回歸。

In [3]:

# 訓練邏輯回歸分類器
clf = sklearn.linear_model.LogisticRegressionCV(cv=5)
clf.fit(X, y)

Out[3]:

LogisticRegressionCV(Cs=10, class_weight=None, cv=5, dual=False, fit_intercept=True, intercept_scaling=1.0, max_iter=100, multi_class='warn', n_jobs=None, penalty='l2', random_state=None, refit=True, scoring=None, solver='lbfgs', tol=0.0001, verbose=0)

In [4]:

# 這是個幫助函數(shù),這個函數(shù)的作用是用來畫決策邊界的,如果看不懂函數(shù)內容不用介意。
def plot_decision_boundary(pred_func):
    # 設置邊界最大最小值
    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
    # 生成一個點間網(wǎng)格,它們之間的距離為h
    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)

In [5]:

plot_decision_boundary(lambda x: clf.predict(x))
plt.title("Logistic Regression")

Out[5]: Text(0.5, 1.0, 'Logistic Regression')

實現(xiàn)神經(jīng)網(wǎng)絡02


這個圖顯示了通過邏輯回歸學習到的決策邊界。這里的直線已經(jīng)盡可能的把數(shù)據(jù)集分成兩部分,但是分的效果還是不理想,還是有些分錯類別的。

1.3 訓練神經(jīng)網(wǎng)絡

現(xiàn)在我們構建一個3層神經(jīng)網(wǎng)絡,其中包含一個輸入層,一個隱藏層和一個輸出層。輸入層中的節(jié)點數(shù)由我們的數(shù)據(jù)的維數(shù)確定的,這里是2。輸出層中的節(jié)點數(shù)由我們擁有的類別數(shù)量決定,這里也是2。因為我們只有兩個類 實際上只用一個輸出節(jié)點可以預測0或1,但是有兩個可以讓網(wǎng)絡更容易擴展到更多的類。 網(wǎng)絡的輸入將是x和y坐標,其輸出將是兩個概率,一個用于類別0,一個用于類別1。 神經(jīng)網(wǎng)絡如圖所示:

實現(xiàn)神經(jīng)網(wǎng)絡03

我們可以選擇隱藏層的維度也就是節(jié)點數(shù)。隱藏層的節(jié)點越多,得到的神經(jīng)網(wǎng)絡功能就越復雜。但更高的維度需要付出代價。首先,學習網(wǎng)絡參數(shù)和預測就需要更多的計算量。同時更多參數(shù)也意味著我們得到的模型更容易過擬合。 如何選擇隱藏層的大小?雖然有一些指導方針,但實際上具體問題需要具體分析,稍后我們將改變隱藏層中的節(jié)點數(shù)量來查看它如何影響我們的輸出。

實現(xiàn)神經(jīng)網(wǎng)絡04


因為我們希望神經(jīng)網(wǎng)絡最終輸出概率值,所以輸出層的激活函數(shù)使用softmax(https://en.wikipedia.org/wiki/Softmax_function)這只是將原始分數(shù)轉換為概率的一種方法。同時如果熟悉邏輯函數(shù),可以認為softmax可以做多分類。

1.3.2 參數(shù)學習

實現(xiàn)神經(jīng)網(wǎng)絡05

實現(xiàn)神經(jīng)網(wǎng)絡06

1.3.3 實現(xiàn)代碼

現(xiàn)在我們把具體代碼實現(xiàn)來,這里先定義一些后面求梯度會用到的參數(shù):

In [6]:

num_examples = len(X) # 訓練集大小
nn_input_dim = 2 # 輸入層維度
nn_output_dim = 2 # 輸出層維度
# 梯度下降參數(shù),這兩個參數(shù)是?為設定的超參數(shù)
epsilon = 0.01 # 梯度下降的學習率
reg_lambda = 0.01 # 正則化強度

首先我們實現(xiàn)上面定義的損失函數(shù),這里用它來評估我們的模型的好壞:

In [7]:

# 幫助函數(shù)用來評估數(shù)據(jù)集上的總體損失
def calculate_loss(model):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # 前向傳播來計算預測值
    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)
    # 計算損失值
    corect_logprobs = -np.log(probs[range(num_examples), y])
    data_loss = np.sum(corect_logprobs)
    # 為損失添加正則化
    data_loss += reg_lambda/2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
    return 1./num_examples * data_loss

這里實現(xiàn)了一個幫助函數(shù)來計算網(wǎng)絡的輸出。它按照上面的定義行前向傳播,并返回具有最高概率的類別。

In [8]:

# 幫助函數(shù)用來預測輸出類別(0或者1)
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)
    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)

最后這個函數(shù)是訓練神經(jīng)網(wǎng)絡。這個函數(shù)李我們用前面定義的的反向傳播導數(shù)實現(xiàn)批量梯度下降。

In [9]:

# 這個函數(shù)學習神經(jīng)網(wǎng)絡的參數(shù)并返回模型。
# - nn_hdim: 隱藏層中的節(jié)點數(shù)
# - num_passes: 通過梯度下降的訓練數(shù)據(jù)的次數(shù)

# - print_loss: 如果為True,則每1000次迭代打印一次損失值
def build_model(nn_hdim, num_passes=20000, print_loss=False):
    # 將參數(shù)初始化為隨機值。模型會學習這些參數(shù)。
    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))

    # 這個是最終返回的值
    model = {}

    # 梯度遞降
    for i in range(0, num_passes):

        # 前向傳播
        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)

        # 反向傳播
        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)

        # 添加正則化項(b1和b2沒有正則化項)
        dW2 += reg_lambda * W2
        dW1 += reg_lambda * W1

        # 梯度下降參數(shù)更新
        W1 += -epsilon * dW1
        b1 += -epsilon * db1
        W2 += -epsilon * dW2
        b2 += -epsilon * db2

        # 為模型分配新參數(shù)
        model = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}

        # 選擇打印損失,這個操作開銷很大,因為它使用整個數(shù)據(jù)集,所以不要頻繁做這個操作。
        if print_loss and i % 1000 == 0:
            print("Loss after iteration %i: %f" % (i, calculate_loss(model)))
return model

1.3.4 隱藏層大小為3的神經(jīng)網(wǎng)絡

下面來看看如果我們訓練隱藏層大小為3的網(wǎng)絡會發(fā)生什么。

In [10]:

# 隱藏層大小為3
model = build_model(3, print_loss=True)

# 繪制決策邊界
plot_decision_boundary(lambda x: predict(model, x))
plt.title("Decision Boundary for hidden layer size 3")

Loss after iteration 0: 0.432387
Loss after iteration 1000: 0.068947
Loss after iteration 2000: 0.068901
Loss after iteration 3000: 0.071218
Loss after iteration 4000: 0.071253
Loss after iteration 5000: 0.071278
Loss after iteration 6000: 0.071293
Loss after iteration 7000: 0.071303
Loss after iteration 8000: 0.071308
Loss after iteration 9000: 0.071312
Loss after iteration 10000: 0.071314
Loss after iteration 11000: 0.071315
Loss after iteration 12000: 0.071315
Loss after iteration 13000: 0.071316
Loss after iteration 14000: 0.071316
Loss after iteration 15000: 0.071316
Loss after iteration 16000: 0.071316
Loss after iteration 17000: 0.071316
Loss after iteration 18000: 0.071316
Loss after iteration 19000: 0.071316

Out[10]: Text(0.5, 1.0, 'Decision Boundary for hidden layer size 3')

實現(xiàn)神經(jīng)網(wǎng)絡07

這看起來很不錯。我們的神經(jīng)網(wǎng)絡能夠找到一個成功分離兩個類別的決策邊界。

2 改變隱藏的圖層大小

在上面的示例中,我們設置了隱藏層大小3,接著看看改變隱藏層大小對結果的影響。

In [11]:

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

實現(xiàn)神經(jīng)網(wǎng)絡08

實現(xiàn)神經(jīng)網(wǎng)絡09

我們可以看到,隱藏層在低維度時可以很好地擬合數(shù)據(jù)的總體趨勢,更高的維度容易過擬合。當隱藏層維度過大時,模型嘗試著去“記住”數(shù)據(jù)的形狀而不是擬合他們的一般形狀。通常情況我們還需要一個單獨的測試集來評估我們的模型,隱藏層維度較小的模型在這個測試集上的表現(xiàn)應該更好,因為這個模型更加通用。我們也可以通過更強的正則化來抵消過度擬合,但是選擇一個合適的隱藏層大小是一個比較劃算的解決方案。

猜你喜歡

什么是Python?最全的python百科

學會python可以做什么?這些好處你想象不到

抽樣和抽樣方法介紹

Flask的響應處理圖文介紹

Python+數(shù)據(jù)分析課程

0 分享到:
和我們在線交談!