反向傳播是多數神經網絡進行參數更新的基本方法,它的本質是巧妙利用了高數中的鍊式法則,下面對這一方法進行推導:
(1)符号說明

:神經元的激活函數
:神經網絡的權重向量
:神經網絡的偏置向量
:某層的輸入向量
:某層的輸出向量
(2)損失函數
假設神經網絡的損失函數為
,那麼定義其損失函數為:
其中,
為期望的輸出值,
是神經網絡的預測輸出值。
方向傳播算法是通過改變網絡中的權重參數
和偏置
來改變損失函數的方法。
(3)4個基本方程推導
1. 輸出層誤差
首先定義第
層的第
個神經元的誤差為:
在這裡可能很多人會覺得不能了解,具體可以參考這篇文章 http://neuralnetworksanddeeplearning.com/chap2.html
根據定義,對損失函數求導可得:
其中
度量了損失函數在神經元輸出的函數的變化程度,
則度量了激活函數在神經元權重輸入處的變化程度。
對輸出層誤差向量化,得到輸出層的誤差公式BP1,如下:
2. 第
層誤差
非輸出層的誤差依賴于下一層的誤差,也就是網絡第
層的誤差依賴于第
層的誤差,則有:
上式利用了鍊式法則,前一次的輸出可以作用于後一層的輸入,又因為:
是以有:
帶入之前的算是可以得到網絡第
層的第
個神經元誤差為:
向量化後可以得到網絡中第1層的誤差公式BP2:
公式BP2充分展現了誤差反向傳播的特點,隻要知道
層的誤差就可以推導出
層的誤差,以此類推,最後能夠從網絡的輸出層倒推直到輸入層的誤差,這就是誤差反向傳播的含義,另外從誤差傳播的公式中我們可以注意到含有激活函數的導師,一般我們采用的sigmoid的激活函數的導數最大為0.25,每反向傳播一次梯度最多儲存25%,是以當神經網絡隐藏層增多後會産生梯度消失問題。
3. 損失函數關于偏置
的偏導
同理,根據鍊式求導法則可得:
又因為:
帶入得到:
最終損失函數在網絡中任意偏置等于該神經元上的誤差,可以通過誤差直接得到偏置的倒數。
4. 損失函數關于權值
的偏導
從公式中可以知道損失函數關于權重的偏導,連接配接了第1層第j個神經元的誤差以及上一層第i個神經元的輸出。
至此反向傳播的4個基本公式都已經推導完畢。
(4)反向傳播算法的實作代碼示例
import numpy as np
import pprint
pp = pprint.PrettyPrinter(indent=4)
# 定義神經網絡的模型架構 [input, hidden, output]
network_sizes = [3,4,2]
# 初始化該神經網絡的參數
sizes = network_sizes
num_layers = len(sizes)
biases = [np.random.randn(h, 1) for h in sizes[1:]]
weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]
def loss_der(network_y, real_y):
"""
傳回損失函數的偏導,損失函數使用 MSE
L = 1/2(network_y-real_y)^2
delta_L = network_y-real_y
"""
return (network_y - real_y)
def sigmoid(z):
"""激活函數使用 sigmoid."""
return 1.0 / (1.0 + np.exp(-z))
def sigmoid_der(z):
"""sigmoid函數的導數 derivative of sigmoid."""
return sigmoid(z) * (1 - sigmoid(z))
def backprop(x, y):
"""
根據損失函數 C通過反向傳播算法傳回
"""
"""Return a tuple "(nabla_b, nabla_w)" representing the
gradient for the cost function C_x. "nabla_b" and
"nabla_w" are layer-by-layer lists of numpy arrays, similar
to "self.biases" and "self.weights"."""
# 初始化網絡參數的導數 權重w的偏導和偏置b的偏導
delta_w = [np.zeros(w.shape) for w in weights]
delta_b = [np.zeros(b.shape) for b in biases]
# 向前傳播 feed forward
activation = x # 把輸入的資料作為第一次激活值
activations = [x] # 存儲網絡的激活值
zs = [] # 存儲網絡的權重輸入值 (z=wx+b)
for w, b in zip(weights, biases):
z = np.dot(w, activation) + b
activation = sigmoid(z)
activations.append(activation)
zs.append(z)
# 反向傳播 back propagation
# BP1 計算輸出層誤差
delta_L = loss_der(activations[-1], y) * sigmoid_der(zs[-1])
# BP3 損失函數在輸出層關于偏置的偏導
delta_b[-1] = delta_L
# BP4 損失函數在輸出層關于權值的偏導
delta_w[-1] = np.dot(delta_L, activations[-2].transpose())
delta_l = delta_L
for l in range(2, num_layers):
# BP2 計算第l層誤差
z = zs[-l]
sp = sigmoid_der(z)
delta_l = np.dot(weights[-l + 1].transpose(), delta_l) * sp
# BP3 損失函數在l層關于偏置的偏導
delta_b[-l] = delta_l
# BP4 損失函數在l層關于權值的偏導
delta_w[-l] = np.dot(delta_l, activations[-l - 1].transpose())
return (delta_w, delta_b)
training_x = np.random.rand(3).reshape(3,1)
training_y = np.array([0, 1]).reshape(2,1)
print("training data x:\n{},\n training data y:\n{}".format(training_x, training_y))
backprop(training_x, training_y)
輸出的結果為:
training data x:
[[0.69316998]
[0.16969983]
[0.51637141]],
training data y:
[[0]
[1]]
([array([[-0.00122639, -0.00030024, -0.00091359],
[-0.0252308 , -0.00617693, -0.01879548],
[-0.0039671 , -0.00097121, -0.00295526],
[ 0.01921057, 0.00470307, 0.01431076]]),
array([[ 0.05610549, 0.02035511, 0.06627431, 0.01132567],
[-0.11372293, -0.04125875, -0.13433461, -0.02295656]])],
[array([[-0.00176925],
[-0.03639915],
[-0.00572312],
[ 0.02771408]]), array([[ 0.07169137],
[-0.14531471]])])
參考:
《深度學習原理與實踐》第2章