Fork me on GitHub

至简

Be simple

为了寻找你,我把自己搬进鸟的眼睛,经常盯着路过的风。


BP神经网络权重更新及推导


链式求导

假如BP神经网络有三层,即输入层、隐藏层和输出层,根据链式求导法则,那么输出层的权重更新公式为: $$\boldsymbol{W_{t+1}^{o}}=\boldsymbol{W_{t}^{o}}-\eta \frac{\partial \boldsymbol{L}}{\partial \boldsymbol{W_{o}}}=\boldsymbol{W_{t}^{o}}-\eta \boldsymbol{\frac{\partial L}{\partial \sigma_{o}}}\frac{\partial \boldsymbol{\boldsymbol{\sigma_{o}}} }{\partial \boldsymbol{z_{o}}}\boldsymbol{\frac{\partial z_{o}}{\partial W_{o}}}$$ 隐藏层的权重公式为: $$\boldsymbol{W_{t+1}^{h}}=\boldsymbol{W_{t}^{h}}-\eta \frac{\partial \boldsymbol{L}}{\partial \boldsymbol{W_{h}}}=\boldsymbol{W_{t}^{h}}-\eta \boldsymbol{\delta_{h}}\boldsymbol{\frac{\partial z_{h}}{\partial W_{h}}}$$ 其中,$\eta$为权重更新步长,即学习因子;$\boldsymbol{L}$为损失函数;$\boldsymbol{\sigma}$为激活函数;$\boldsymbol{z}$是关于权重$\boldsymbol{W}$、特征$\boldsymbol{X}$的函数;$\boldsymbol{\delta_{h}}$是隐藏层的误差项,其值反映了该层神经元对最终误差的影响,不失一般性,$k$ 层神经元的误差项是由 $k+1$ 层误差项乘以 $k+1$ 层权重再乘以 $k$ 层激活函数导数得到,这就是误差的反向传播。

实例推导

假如输出层的损失函数为 $$\boldsymbol{L\left ( \boldsymbol{\sigma _{o}} \right )}=\frac{1}{2}\left ( \boldsymbol{\sigma _{o}} -\boldsymbol{\widehat{y}}\right )^{2}$$ 其中 $\boldsymbol{\widehat{y}}$ 为训练集真值。
假如输出层的激活函数为 $$\boldsymbol{\sigma _{o}({z _{o}})}=\frac{1}{1+e^{-\boldsymbol{z _{o}}}}$$ 又 $$\boldsymbol{z _{o}}=\boldsymbol{W _{o}}\boldsymbol{X _{o}^{T}}+\boldsymbol{b _{o}}$$ 根据前面的链式求导,可以推导出输出层的权重更新公式为 $$\boldsymbol{W _{t+1}^{o}}=\boldsymbol{W _{t}^{o}}-\eta \left ( \boldsymbol{\sigma _{o}} -\boldsymbol{\widehat{y}} \right )\boldsymbol{\sigma _{o}}\left ( 1- \boldsymbol{\sigma _{o}}\right )\boldsymbol{X _{o}^{T}}$$ 下面推导一下隐藏层的误差项 $$\boldsymbol{\delta _{h}}= \boldsymbol{\frac{\partial L}{\partial z _{h}}}=\boldsymbol{\frac{\partial L}{\partial z _{o}}}\boldsymbol{\frac{\partial z _{o}}{\partial X _{o}}}\boldsymbol{\frac{\partial X _{o}}{\partial z _{h}}}=\boldsymbol{\delta _{o}}\boldsymbol{W _{o}}\boldsymbol{\frac{\partial \sigma _{h}}{\partial z _{h}}}$$ 上式中,隐藏层的激活函数输出 $\boldsymbol{\sigma _{h}}$ 就是输出层的输入 $\boldsymbol{X _{o}}$,从中可以看出,隐藏层神经元的误差项是由输出层误差项乘以输出层权重再乘以隐藏层激活函数导数得到,那么隐藏层权重更新公式为 $$\boldsymbol{W _{t+1}^{h}}=\boldsymbol{W _{t}^{h}}-\eta \boldsymbol{W _{o}^{T}} \left ( \boldsymbol{\sigma _{o}} -\boldsymbol{\widehat{y}} \right )\boldsymbol{\sigma _{h}}\left ( 1- \boldsymbol{\sigma _{h}}\right )\boldsymbol{X _{i}^{T}}$$ 上式中,$\boldsymbol{X _{i}^{T}}$ 为输入层特征的转置。

核心代码

output_vector1 = np.dot(self.weights_hidden_in, input_vector)
output_vector_hidden = sigmoid(output_vector1)
output_vector2 = np.dot(self.weights_hidden_out, output_vector_hidden)
output_vector_network = sigmoid(output_vector2)

#更新输出层权重
output_errors = -self.target_vec + output_vector_network
tmp = output_errors * output_vector_network * (1.0 - output_vector_network)
tmp = self.learning_rate  * np.dot(tmp, output_vector_hidden.T)
self.weights_hidden_out -= tmp

#更新隐藏层权重
hidden_errors = np.dot(self.weights_hidden_out.T, output_errors)
tmp = hidden_errors * output_vector_hidden * (1.0 - output_vector_hidden)
x = np.dot(tmp, input_vector.T)[:-1,:] #有偏置量
self.weights_hidden_in -= self.learning_rate * x

完整代码请点击这里