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

全國(guó)咨詢/投訴熱線:400-618-4000

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

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

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

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]:

# 導(dǎo)包
import matplotlib.pyplot as plt
import numpy as np
import sklearn
import sklearn.datasets
import sklearn.linear_model
import matplotlib
# 設(shè)置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>

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

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

1.2 邏輯回歸

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

In [3]:

# 訓(xùn)練邏輯回歸分類器
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]:

# 這是個(gè)幫助函數(shù),這個(gè)函數(shù)的作用是用來(lái)畫決策邊界的,如果看不懂函數(shù)內(nèi)容不用介意。
def plot_decision_boundary(pred_func):
    # 設(shè)置邊界最大最小值
    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
    # 生成一個(gè)點(diǎn)間網(wǎng)格,它們之間的距離為h
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # 預(yù)測(cè)
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # 繪制輪廓和訓(xùn)練示例
    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')

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


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

1.3 訓(xùn)練神經(jīng)網(wǎng)絡(luò)

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

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

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

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


因?yàn)槲覀兿M窠?jīng)網(wǎng)絡(luò)最終輸出概率值,所以輸出層的激活函數(shù)使用softmax(https://en.wikipedia.org/wiki/Softmax_function)這只是將原始分?jǐn)?shù)轉(zhuǎn)換為概率的一種方法。同時(shí)如果熟悉邏輯函數(shù),可以認(rèn)為softmax可以做多分類。

1.3.2 參數(shù)學(xué)習(xí)

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

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

1.3.3 實(shí)現(xiàn)代碼

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

In [6]:

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

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

In [7]:

# 幫助函數(shù)用來(lái)評(píng)估數(shù)據(jù)集上的總體損失
def calculate_loss(model):
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # 前向傳播來(lái)計(jì)算預(yù)測(cè)值
    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)
    # 計(jì)算損失值
    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

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

In [8]:

# 幫助函數(shù)用來(lái)預(yù)測(cè)輸出類別(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)

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

In [9]:

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

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

    # 這個(gè)是最終返回的值
    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)

        # 添加正則化項(xiàng)(b1和b2沒有正則化項(xiàng))
        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}

        # 選擇打印損失,這個(gè)操作開銷很大,因?yàn)樗褂谜麄€(gè)數(shù)據(jù)集,所以不要頻繁做這個(gè)操作。
        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)絡(luò)

下面來(lái)看看如果我們訓(xùn)練隱藏層大小為3的網(wǎng)絡(luò)會(huì)發(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')

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

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

2 改變隱藏的圖層大小

在上面的示例中,我們?cè)O(shè)置了隱藏層大小3,接著看看改變隱藏層大小對(duì)結(jié)果的影響。

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

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

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

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

猜你喜歡

什么是Python?最全的python百科

學(xué)會(huì)python可以做什么?這些好處你想象不到

抽樣和抽樣方法介紹

Flask的響應(yīng)處理圖文介紹

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

0 分享到:
和我們?cè)诰€交談!