反向传播
具体推导见:后向传播推导
现在我们来到了如何让多层神经网络学习的问题上。之前我们了解了如何用梯度下降来更新权重。反向传播算法是它的一个延伸,用链式法则来找到误差与输入层到输入层链接的权重(两层神经网络)。
要更新输入到隐藏层的权重,你需要知道隐藏层节点的误差对最终输出的影响是多大。输出是由两层之间的权重决定的,这个误差是输入跟权重在网络中正向传播的结果。既然我们知道输出误差,我们可以用权重来反向传播到隐藏层。
例如,输出层的话,在每一个输出节点 k 的误差是 δ_k^o 。隐藏节点 j 的误差来自输出层和隐藏层之间的权重(以及梯度)。
梯度下降跟之前一样,只是用上式计算出的新的误差:
w_ij 是输入和隐藏层之间的权重, xi 是输入值。这个形式可以表示任意层数(h)。权重更新步长等与步长乘以层输出误差再乘以这层的输入值。
这里,你有了输出误差,δoutput,从高层反向传播这些误差。Vin 是对这一层的输入,经过隐藏层激活后到输出节点。
通过一个实际案例学习
让我们一起过一遍计算一个简单的两层网络权重的更新过程。假设有两个输入值,一个隐藏节点,一个输出节点,隐藏层和输出层的激活函数都是 sigmoid 。下图描述了这个网络。(注意:输入在这里显示为图最下方的节点,网络的输出标记为顶端的 y^,输入本身不算做层,这也是为什么这个网络结构被称作两层网络。)
假设我们试着输入一些二分类数据,目标是 y=1。我们从正向传导开始,首先计算输入到隐藏层
隐藏层的输出
把它作为输出层的输入,神经网络的输出是:
有了这个输出,我们就可以开始反向传播来计算两层的权重更新了。sigmoid 函数导数特性
输出误差为:
现在我们要通过反向传播来计算隐藏层的误差。这里我们把输出误差与隐藏层到输出层的权重 W 相乘。
隐藏层的误差:
这里因为只有一个隐藏节点,这就比较简单了
有了误差,就可以计算梯度下降步长了。隐藏层到输出层权重步长是学习率乘以输出误差再乘以隐藏层激活值。
从输入到隐藏层的权重 wi,是学习率乘以隐藏节点误差再乘以输入值。
这个例子你可以看出用 sigmoid 做激活函数的效果。sigmoid 函数最大的导数是 0.25(f*(1-f)的最大值),输出层的误差被至少减少了75%,隐藏层的误差被减少了至少93.75%!你可以看出,如果你有很多层,用 sigmoid 激活函数函数会很快把权重降到靠近输入的细小值。这被称作梯度消失问题。后面的课程中你会学到其它的激活函数在这方面表现比它好,也被用于最新的网络架构中。
用 NumPy 来实现
现在你已经有了大部分用 NumPy 来实现反向传导的知识。
但是之前接触的是一个元素的误差项。现在在权重更新时,我们需要考虑隐藏层每个节点的误差:
首先,这里会有不同数量的输入和隐藏节点,所以试图把误差与输入当作行向量来乘会报错
hidden_error*inputs
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
in ()
----> 1 hidden_error*x
ValueError: operands could not be broadcast together with shapes (3,) (6,)
同时,w_ij 现在是一个矩阵,所以右侧对应也应该有跟左侧同样的维度。幸运的是,NumPy 这些都能搞定。如果你用一个列向量序列和一个行向量序列乘,它会把列向量的第一个元素与行向量的每个元素相乘,然后第一行是一个二维序列。列向量的每一个元素会这么做,所以你会得到一个二维序列,维度是
(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]])
这正好是我们如何计算权重更新的步长。跟以前一样,如果你的输入是一个一行的二维序列,你可以用hidden_error*inputs.T
,如果 inputs
是一维序列,就不行了(一维序列的转置还是本身)。
反向传播练习
接下来你会用代码来实现一次两个权重的反向传播更新。我们提供了正向传播的代码,你来实现反向传播的代码。
要做的事
- 计算网络输出误差
- 计算输出层误差的梯度
- 用反向传播计算隐藏层误差
- 计算权重更新步长
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]
实现反向传播
现在我们知道输出层的误差是
输入层误差是
现在我们只考虑一个简单神经网络,他只有一层隐藏层和一个输出节点。这是通过反向传播更新权重的算法概述:
- 把每一层权重更新的初始步长设置为 0
- 输入到隐藏层的权重是 Δwij=0
- 隐藏层到输出层的权重是 ΔWj=0
- 对训练数据当中的每一个点
它正向通过网络,计算输出 y^
- 计算输出节点的误差梯度
- 误差传播到隐藏层
更新权重步长:
- 重复这个过程e代
反向传播练习
现在你来实现一个通过反向传播训练的神经网络,数据集就是之前的研究生院录取数据。通过前面所学你现在有能力完成这个练习:
你的目标是:
- 实现一个正向传播
- 实现反向传播
- 更新权重
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))