反向傳播
具體推導(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)的誤差梯度
-
誤差傳播到隱藏層
更新權(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))