Backprop

反向傳播

具體推導(dǎo)見:后向傳播推導(dǎo)

現(xiàn)在我們來到了如何讓多層神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)的問題上。之前我們了解了如何用梯度下降來更新權(quán)重。反向傳播算法是它的一個(gè)延伸,用鏈?zhǔn)椒▌t來找到誤差與輸入層到輸入層鏈接的權(quán)重(兩層神經(jīng)網(wǎng)絡(luò))。
要更新輸入到隱藏層的權(quán)重,你需要知道隱藏層節(jié)點(diǎn)的誤差對(duì)最終輸出的影響是多大。輸出是由兩層之間的權(quán)重決定的,這個(gè)誤差是輸入跟權(quán)重在網(wǎng)絡(luò)中正向傳播的結(jié)果。既然我們知道輸出誤差,我們可以用權(quán)重來反向傳播到隱藏層。
例如,輸出層的話,在每一個(gè)輸出節(jié)點(diǎn) k 的誤差是 δ_?k^?o?? 。隱藏節(jié)點(diǎn) j 的誤差來自輸出層和隱藏層之間的權(quán)重(以及梯度)。

梯度下降跟之前一樣,只是用上式計(jì)算出的新的誤差:


w_?ij?? 是輸入和隱藏層之間的權(quán)重, x?i?? 是輸入值。這個(gè)形式可以表示任意層數(shù)(h)。權(quán)重更新步長(zhǎng)等與步長(zhǎng)乘以層輸出誤差再乘以這層的輸入值。

這里,你有了輸出誤差,δ?output??,從高層反向傳播這些誤差。V?in?? 是對(duì)這一層的輸入,經(jīng)過隱藏層激活后到輸出節(jié)點(diǎn)。

通過一個(gè)實(shí)際案例學(xué)習(xí)

讓我們一起過一遍計(jì)算一個(gè)簡(jiǎn)單的兩層網(wǎng)絡(luò)權(quán)重的更新過程。假設(shè)有兩個(gè)輸入值,一個(gè)隱藏節(jié)點(diǎn),一個(gè)輸出節(jié)點(diǎn),隱藏層和輸出層的激活函數(shù)都是 sigmoid 。下圖描述了這個(gè)網(wǎng)絡(luò)。(注意:輸入在這里顯示為圖最下方的節(jié)點(diǎn),網(wǎng)絡(luò)的輸出標(biāo)記為頂端的 ?y?^??,輸入本身不算做層,這也是為什么這個(gè)網(wǎng)絡(luò)結(jié)構(gòu)被稱作兩層網(wǎng)絡(luò)。)

假設(shè)我們?cè)囍斎胍恍┒诸悢?shù)據(jù),目標(biāo)是 y=1。我們從正向傳導(dǎo)開始,首先計(jì)算輸入到隱藏層

隱藏層的輸出

把它作為輸出層的輸入,神經(jīng)網(wǎng)絡(luò)的輸出是:

有了這個(gè)輸出,我們就可以開始反向傳播來計(jì)算兩層的權(quán)重更新了。sigmoid 函數(shù)導(dǎo)數(shù)特性

輸出誤差為:

現(xiàn)在我們要通過反向傳播來計(jì)算隱藏層的誤差。這里我們把輸出誤差與隱藏層到輸出層的權(quán)重 W 相乘。
隱藏層的誤差:

這里因?yàn)橹挥幸粋€(gè)隱藏節(jié)點(diǎn),這就比較簡(jiǎn)單了

有了誤差,就可以計(jì)算梯度下降步長(zhǎng)了。隱藏層到輸出層權(quán)重步長(zhǎng)是學(xué)習(xí)率乘以輸出誤差再乘以隱藏層激活值。

從輸入到隱藏層的權(quán)重 w?i??,是學(xué)習(xí)率乘以隱藏節(jié)點(diǎn)誤差再乘以輸入值。

這個(gè)例子你可以看出用 sigmoid 做激活函數(shù)的效果。sigmoid 函數(shù)最大的導(dǎo)數(shù)是 0.25(f*(1-f)的最大值),輸出層的誤差被至少減少了75%,隱藏層的誤差被減少了至少93.75%!你可以看出,如果你有很多層,用 sigmoid 激活函數(shù)函數(shù)會(huì)很快把權(quán)重降到靠近輸入的細(xì)小值。這被稱作梯度消失問題。后面的課程中你會(huì)學(xué)到其它的激活函數(shù)在這方面表現(xiàn)比它好,也被用于最新的網(wǎng)絡(luò)架構(gòu)中。

用 NumPy 來實(shí)現(xiàn)

現(xiàn)在你已經(jīng)有了大部分用 NumPy 來實(shí)現(xiàn)反向傳導(dǎo)的知識(shí)。
但是之前接觸的是一個(gè)元素的誤差項(xiàng)。現(xiàn)在在權(quán)重更新時(shí),我們需要考慮隱藏層每個(gè)節(jié)點(diǎn)的誤差:

首先,這里會(huì)有不同數(shù)量的輸入和隱藏節(jié)點(diǎn),所以試圖把誤差與輸入當(dāng)作行向量來乘會(huì)報(bào)錯(cuò)

hidden_error*inputs
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-22-3b59121cb809> in <module>()
----> 1 hidden_error*x

ValueError: operands could not be broadcast together with shapes (3,) (6,)

同時(shí),w?_ij 現(xiàn)在是一個(gè)矩陣,所以右側(cè)對(duì)應(yīng)也應(yīng)該有跟左側(cè)同樣的維度。幸運(yùn)的是,NumPy 這些都能搞定。如果你用一個(gè)列向量序列和一個(gè)行向量序列乘,它會(huì)把列向量的第一個(gè)元素與行向量的每個(gè)元素相乘,然后第一行是一個(gè)二維序列。列向量的每一個(gè)元素會(huì)這么做,所以你會(huì)得到一個(gè)二維序列,維度是
(len(column_vector), len(row_vector))

hidden_error*inputs[:,None]
array([[ -8.24195994e-04,  -2.71771975e-04,   1.29713395e-03],
       [ -2.87777394e-04,  -9.48922722e-05,   4.52909055e-04],
       [  6.44605731e-04,   2.12553536e-04,  -1.01449168e-03],
       [  0.00000000e+00,   0.00000000e+00,  -0.00000000e+00],
       [  0.00000000e+00,   0.00000000e+00,  -0.00000000e+00],
       [  0.00000000e+00,   0.00000000e+00,  -0.00000000e+00]])

這正好是我們?nèi)绾斡?jì)算權(quán)重更新的步長(zhǎng)。跟以前一樣,如果你的輸入是一個(gè)一行的二維序列,你可以用hidden_error*inputs.T,如果 inputs 是一維序列,就不行了(一維序列的轉(zhuǎn)置還是本身)。

反向傳播練習(xí)

接下來你會(huì)用代碼來實(shí)現(xiàn)一次兩個(gè)權(quán)重的反向傳播更新。我們提供了正向傳播的代碼,你來實(shí)現(xiàn)反向傳播的代碼。

要做的事

  • 計(jì)算網(wǎng)絡(luò)輸出誤差
  • 計(jì)算輸出層誤差的梯度
  • 用反向傳播計(jì)算隱藏層誤差
  • 計(jì)算權(quán)重更新步長(zhǎng)
import numpy as np

def sigmoid(x):
    """
    Calculate sigmoid
    """
    return 1 / (1 + np.exp(-x))

x = np.array([0.5, 0.1, -0.2])
target = 0.6
learnrate = 0.5

weights_input_hidden = np.array([[0.5, -0.6],
                                 [0.1, -0.2],
                                 [0.1, 0.7]])

weights_hidden_output = np.array([0.1, -0.3])

# forward pass
hidden_layer_input = np.dot(x, weights_input_hidden)
hidden_layer_output = sigmoid(hidden_layer_input)

output_layer_in = np.dot(hidden_layer_output, weights_hidden_output)
output = sigmoid(output_layer_in)

# backward pass
# todo: calculate output error 
error = target - output

del_err_output = error * output * (1 - output)

del_err_hidden = np.dot(del_err_output, weights_hidden_output) * \
                 hidden_layer_output * (1 - hidden_layer_output)

delta_w_h_o = hidden_layer_output * learnrate * del_err_output

delta_w_i_h = learnrate * del_err_hidden * x[:,None]

實(shí)現(xiàn)反向傳播

現(xiàn)在我們知道輸出層的誤差是

輸入層誤差是

現(xiàn)在我們只考慮一個(gè)簡(jiǎn)單神經(jīng)網(wǎng)絡(luò),他只有一層隱藏層和一個(gè)輸出節(jié)點(diǎn)。這是通過反向傳播更新權(quán)重的算法概述:

  • 把每一層權(quán)重更新的初始步長(zhǎng)設(shè)置為 0
    • 輸入到隱藏層的權(quán)重是 Δwij=0
    • 隱藏層到輸出層的權(quán)重是 ΔWj=0
  • 對(duì)訓(xùn)練數(shù)據(jù)當(dāng)中的每一個(gè)點(diǎn)
    • 它正向通過網(wǎng)絡(luò),計(jì)算輸出 y^

    • 計(jì)算輸出節(jié)點(diǎn)的誤差梯度

      這里
      輸入到輸出節(jié)點(diǎn)。
    • 誤差傳播到隱藏層
    • 更新權(quán)重步長(zhǎng):

  • 重復(fù)這個(gè)過程e代

反向傳播練習(xí)

現(xiàn)在你來實(shí)現(xiàn)一個(gè)通過反向傳播訓(xùn)練的神經(jīng)網(wǎng)絡(luò),數(shù)據(jù)集就是之前的研究生院錄取數(shù)據(jù)。通過前面所學(xué)你現(xiàn)在有能力完成這個(gè)練習(xí):

你的目標(biāo)是:

  • 實(shí)現(xiàn)一個(gè)正向傳播
  • 實(shí)現(xiàn)反向傳播
  • 更新權(quán)重
import numpy as np
from data_prep import features, targets, features_test, targets_test

np.random.seed(21)

def sigmoid(x):
    """
    Calculate sigmoid
    """
    return 1 / (1 + np.exp(-x))


# Hyperparameters
n_hidden = 2  # number of hidden units
epochs = 900
learnrate = 0.005

n_records, n_features = features.shape
last_loss = None

# Initialize weights
weights_input_hidden = np.random.normal(scale=1 / n_features ** .5,
                                        size=(n_features, n_hidden))
weights_hidden_output = np.random.normal(scale=1 / n_features ** .5,
                                         size=n_hidden)

for e in range(epochs):
    del_w_input_hidden = np.zeros(weights_input_hidden.shape)
    del_w_hidden_output = np.zeros(weights_hidden_output.shape)
    for x, y in zip(features.values, targets):
        
        ## Forward pass ##
        hidden_input = np.dot(x, weights_input_hidden)
        hidden_output = sigmoid(hidden_input)
        
        output_layer_in = np.dot(hidden_output, weights_hidden_output)
        output = sigmoid(output_layer_in)

        ## Backward pass ##
        error = y - output

        output_error = error * output * (1 - output)

        hidden_error = np.dot(output_error, weights_hidden_output) * \
                 hidden_output * (1 - hidden_output)

        del_w_hidden_output += output_error * hidden_output
        del_w_input_hidden += hidden_error * x[:, None]

    weights_input_hidden += learnrate * del_w_input_hidden / n_records
    weights_hidden_output += learnrate * del_w_hidden_output / n_records

    # Printing out the mean square error on the training set
    if e % (epochs / 10) == 0:
        hidden_output = sigmoid(np.dot(x, weights_input_hidden))
        out = sigmoid(np.dot(hidden_output,
                             weights_hidden_output))
        loss = np.mean((out - targets) ** 2)

        if last_loss and last_loss < loss:
            print("Train loss: ", loss, "  WARNING - Loss Increasing")
        else:
            print("Train loss: ", loss)
        last_loss = loss

# Calculate accuracy on test data
hidden = sigmoid(np.dot(features_test, weights_input_hidden))
out = sigmoid(np.dot(hidden, weights_hidden_output))
predictions = out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容