任务: 根据前三个人是否看电影的意愿,预测小强是否去看电影
手推正向传播和反向传播的公式:
我们直接根据前4行数据,可以很明确的分析出只要如花去看电影,小明必然去看电影,下面我们根据上图的推导,用程序实现这一预测过程
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def main():
x = np.array([[0,0,1],
[1,1,1],
[1,0,1],
[0,1,1]])
y = np.array([[0,1,1,0]]).T
np.random.seed(1) # 随机种子,使得每次随机产生的weight都是一样的
weights = 2 * np.random.random((3,1)) - 1 # 随机生成(-1,1)的3*1的向量
learning_rate = 10
# 训练网络
for it in range(1000):
# 根据三个人的意愿,计算第四个人的意愿
z = np.dot(x, weights)
# 利用sigmoid函数,将第四个人的意愿转换到(0,1)
y_hat = sigmoid(z)
# 对比实际值与预测值,计算误差,实际上是MSE的对y_hat的导数
error = y_hat - y
# 计算梯度,也即激活函数的导数
slope = y_hat * (1 - y_hat)
delta = error * slope # 计算增量
# 更新三个人对第四个人的权重
weights -= learning_rate * x.T.dot(delta)
print(weights)
if __name__ == '__main__':
main()
通过一个神经元预测,我们可以看到如花对小强的影响最大(正相关),而小倩和小明对小强的影响是负相关。
重构代码
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 正向传播,根据输入值以及当前权重计算预测值
def fp(x, weights):
# 根据三个人的意愿,计算第四个人的意愿
z = np.dot(x, weights)
# 利用sigmoid函数,将第四个人的意愿转换到(0,1)
y_hat = sigmoid(z)
return y_hat
# 根据真实值以及预测值计算权重的增量
def bp(y, y_hat):
# 对比实际值与预测值,计算误差,实际上是MSE的对y_hat的导数
error = y_hat - y
# 计算梯度,也即激活函数的导数
slope = y_hat * (1 - y_hat)
delta = error * slope # 计算增量
return delta
def main():
x = np.array([[0,0,1],
[1,1,1],
[1,0,1],
[0,1,1]])
y = np.array([[0,1,1,0]]).T
np.random.seed(1) # 随机种子,使得每次随机产生的weight都是一样的
weights = 2 * np.random.random((3, 1)) - 1 # 随机生成(-1,1)的3*1的向量
learning_rate = 10
# 训练网络
for it in range(1000):
y_hat = fp(x, weights)
delta = bp(y, y_hat)
# 更新三个人对第四个人的权重,朝着梯度的反方向更新
weights -= learning_rate * x.T.dot(delta)
print(weights)
if __name__ == '__main__':
main()
下面我们根据已经训练好的模型(即weight),预测三个人中只有一个人去,对小强的影响
print(fp([[1, 0, 0]],weights)) # 如花去
print(fp([[0, 1, 0]], weights)) # 小倩去
print(fp([[0, 0, 1]], weights)) # 小明去
很明显,基本上如花去,小强是必去的
上面预测结果是由如花一个人决定的,因此可以使用一个神经元进行预测,然而下面的例子则不能使用一个神经元进行预测
分析一下,可以发现小强是否去看电影,取决于如花和大美两个人,是她们意愿进行亦或后得到小强是否去看电影,然而 线性模型不能解决异或问题,此时我们引入 隐藏层
,得到如下神经网络:
权重的矩阵表示:
关于前向传播(Forward Propagation)
其中:
-
z
z
z 表示所有的输入值与对应权值的乘积和偏置值的和,即
z = w ∗ x + b z=w*x+b z=w∗x+b - ∂ z ∂ w = − 1 \frac{\partial z}{\partial w}=-1 ∂w∂z=−1 表示 z z z 对权值的偏导数就等于对应的输入值
- 同理, z z z 对输入的值求偏导的结果就是对应的权值
关于反向传播(Backward Propagation)
其中:
- l l l 表示位于最末端的某个神经元的输出与真实值的距离(即损失)
- ∂ a ∂ z \frac{\partial a}{\partial z} ∂z∂a 表示激活函数的输出值对输入值的导数, a a a表示激活函数输出值, z z z表示激活函数输入值,若 a = σ ( z ) a=\sigma(z) a=σ(z),则 ∂ a ∂ z = σ ′ ( z ) \frac{\partial a}{\partial z}=\sigma'(z) ∂z∂a=σ′(z)
- ∂ z ′ ∂ a \frac{\partial z'}{\partial a} ∂a∂z′ 表示中间某时刻,输入值与对应权值的乘积和偏置值的和对某个输入的偏导数,其结果就是一个权重 。 z ′ z' z′ 表示中间某时刻,输入值与对应权值的乘积和偏置值的和, a a a 表示中间某时刻的一个输入值,由图可知 a a a 同时也表示上一个神经元的输出
于是我们得到 ∂ l ∂ z \frac{\partial l}{\partial z} ∂z∂l 的 计算公式:
可画出下图进行理解:
当图中最后一层不是输出层时,我们递归的求 ∂ l ∂ z \frac{\partial l}{\partial z} ∂z∂l,直到到达输出层
当图中最后一层就是输出层时
最后将上面两个式子代入 ∂ l ∂ z \frac{\partial l}{\partial z} ∂z∂l的计算公式即可
到这里,我们计算出了 ∂ l ∂ z \frac{\partial l}{\partial z} ∂z∂l,而 ∂ l ∂ w = ∂ l ∂ z ∗ ∂ z ∂ w \frac{\partial l}{\partial w}=\frac{\partial l}{\partial z}*\frac{\partial z}{\partial w} ∂w∂l=∂z∂l∗∂w∂z, ∂ z ∂ w = a \frac{\partial z}{\partial w}=a ∂w∂z=a
我们更新权值的表达式为: w = w − l r ∗ ∂ l ∂ w w=w-lr*\frac{\partial l}{\partial w} w=w−lr∗∂w∂l,所以到这里反向传播的更新权值的工作就完成了。
本例手推公式:
更多详细的公式推导参考:反向传播算法详细推导
由此,我们写出代码:
import numpy as np
np.random.seed(1) # 随机种子,使得每次随机产生的weight都是一样的
w1 = 2 * np.random.random((3, 4)) - 1 # 表示3个输入,4个神经元
w2 = 2 * np.random.random((4, 1)) - 1 # 表示4个神经元,1个输出
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def derivative(x):
return sigmoid(x) * (1 - sigmoid(x))
# 前向传播,根据输入值以及当前权重,计算第四个人的意愿值
def fp(x):
z1 = np.dot(x, w1) # 汇聚第一层的输入 (8, 4) = (8, 3) * (3, 4)
l1 = sigmoid(z1) # 经过第一层激活函数
z2 = np.dot(l1, w2) # 汇聚第二层的输入 (8, 1) = (8, 4) * (4, 1)
l2 = sigmoid(z2) # 经过第二层激活函数
return l1, l2
# 反向传播,根据第四个人实际的意愿值以及预测的值,计算增量,以便于更新权重
# l1,l2指第一层和第二层神经网络输出
def bp(y, x, l1, l2):
l2_error = l2 - y # 实际上是MSE对第二层神经元输出的导数 (8, 1)
l2_slope = l2 * (1 - l2) # 实际上是sigmoid函数的输出对输入的导数 (8, 1)和(8, 1)的向量每一位对应相乘
l2_delta = np.dot(l1.T, l2_error * l2_slope) # (4, 1) = (4, 8) * (8, 1)
l1_error = np.dot(l2_error * l2_slope, w2.T) # 实际上是MSE对第一层神经元输出的导数 (8, 4) = (8, 1) * (1, 4)
l1_slope = l1 * (1 - l1) # (8, 4)
l1_delta = np.dot(x.T, l1_error * l1_slope) # (3, 4) = (3, 8) * (8, 4)
return l1_delta, l2_delta
def main():
global w1
global w2
x = np.array([[0, 0, 1],
[0, 1, 1],
[1, 0, 1],
[1, 1, 1],
[0, 0, 0],
[0, 1, 0],
[1, 0, 0],
[1, 1, 0]])
y = np.array([[0, 1, 1, 0, 0, 1, 1, 0]]).T
learning_rate = 10
# 训练网络
for it in range(1000):
l1, l2 = fp(x) # 前向传播,得到第一层和第二层的输出
l1_delta, l2_delta = bp(y, x, l1, l2) # 反向传播,得到增量,用于更新权重
w2 -= learning_rate * l2_delta
w1 -= learning_rate * l1_delta
if __name__ == '__main__':
main()
预测当如花去、大美不去看电影时,小强去的概率:
print(fp([[1, 0, 0]])[1])
很显然,训练的模型满足异或关系。