作为最经典的神经网络,BP网络是1986年由Rumelhart和McClelland为首的科学家提出的概念;
BP——back propagation,也就是逆向传播的意思,通俗的说就是:用模型得到的预测值与真实值之间的误差,来影响改变之前各层间的权重
一、BP算法介绍
先介绍几个符号代表的意思:(都是向量)
- X——输入值(数据项);Y——真实值(标签项)
- V——输入层连接隐藏层的权重;W——隐藏层连接输出层的权重
- L1——隐藏层的值(输入层的值X经过权重V作用后的输出值)
- L2——预测值(隐藏层的值L1经过权重W作用后的输出值,也就是模型预测值)
BP算法的工作原理:
- 将输入数据 X 与权重矩阵 V 相乘,经过激活函数后,得到隐藏层的输入值 L1。
- 由于 L1 是隐藏层的输出值,接着 L1 与权重矩阵 W 相乘,经过激活函数处理后,生成最终的预测值 L2。
- 使用梯度下降法计算输出层的误差调整量:即根据真实值 Y 与预测值 L2 的误差,求得 L2 的负梯度,从而得到输出层的误差调整量 L 2 δ L2_δ L2δ 。
- 使用梯度下降法计算隐藏层的误差调整量:将输出层的误差调整量 L 2 δ L2_δ L2δ 与权重 W 相乘,再乘以 L1 的负梯度,得到隐藏层的误差调整量 L 1 δ L1_δ L1δ。
- 将每层的误差调整量分别乘以学习率,再与各层的原始权重相加,得到更新后的权重矩阵 V n e w V_{new} Vnew和 W n e w W_{new} Wnew。
- 采用更新后的权重 V n e w V_{new} Vnew和 W n e w W_{new} Wnew进行迭代计算,重复以上步骤,直到满足收敛条件,输出最终的预测结果。
二、BP神经网络解决异或
import numpy as np
# 输入数据 X 和目标输出 Y
X = np.array([[1, 0, 0],
[1, 0, 1],
[1, 1, 0],
[1, 1, 1]])
Y = np.array([[0], [1], [1], [0]])
# 初始化权重和偏置
np.random.seed(0)
weights_input_hidden = np.random.rand(3, 2) - 0.5 # 输入到隐藏层的权重矩阵 (3x2)
weights_hidden_output = np.random.rand(2, 1) - 0.5 # 隐藏层到输出层的权重矩阵 (2x1)
bias_hidden = np.random.rand(1, 2) - 0.5 # 隐藏层的偏置
bias_output = np.random.rand(1, 1) - 0.5 # 输出层的偏置
learning_rate = 0.1
# 定义激活函数(Sigmoid)及其导数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
return x * (1 - x)
# 训练网络
for epoch in range(10000): # 迭代训练10000次
# 前向传播
hidden_input = np.dot(X, weights_input_hidden) + bias_hidden # 计算隐藏层输入
hidden_output = sigmoid(hidden_input) # 计算隐藏层输出
final_input = np.dot(hidden_output, weights_hidden_output) + bias_output # 计算输出层输入
final_output = sigmoid(final_input) # 计算输出层输出
# 计算误差
output_error = Y - final_output
output_delta = output_error * sigmoid_derivative(final_output) # 输出层误差调整量
hidden_error = output_delta.dot(weights_hidden_output.T) # 隐藏层误差
hidden_delta = hidden_error * sigmoid_derivative(hidden_output) # 隐藏层误差调整量
# 更新权重和偏置
weights_hidden_output += hidden_output.T.dot(output_delta) * learning_rate
weights_input_hidden += X.T.dot(hidden_delta) * learning_rate
bias_output += np.sum(output_delta, axis=0, keepdims=True) * learning_rate
bias_hidden += np.sum(hidden_delta, axis=0, keepdims=True) * learning_rate
# 测试网络
print("Final output after training:")
print(final_output)
解释
- hidden_input:计算输入层到隐藏层的输入值。
- hidden_output:通过激活函数(Sigmoid)处理,得到隐藏层的输出。
- final_input 和 final_output:计算隐藏层到输出层的输入和输出。
- output_error 和 output_delta:输出层的误差和误差调整量。
- hidden_error 和 hidden_delta:隐藏层的误差和误差调整量。
训练完成后,final_output 结果接近于目标值 [0, 1, 1, 0]
2018年代码如下:
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing
# -*- 2018/01/12;14:37
# -*- python3.5
import numpy as np
lr = 0.11 #学习速率
#输入数据分别:偏置值,x1,x2
X = np.array([[1,0,0],
[1,0,1],
[1,1,0],
[1,1,1]])
#标签
Y = np.array([[0,1,1,0]])
# 权重初始化,取值范围-1到1
V = np.random.random((3,4))*2-1
W = np.random.random((4,1))*2-1
#print('输入层连接隐藏层的权值V:',V)
#print('隐藏层连接输出层的权值W:',W)
def sigmoid(x):
return 1/(1+np.exp(-x))
def dsigmoid(x):
return x*(1-x)
#更新权重函数
def get_update():
global X,Y,W,V,lr
# L1:输入层传递给隐藏层的值;输入层3个节点,隐藏层4个节点
# L2:隐藏层传递到输出层的值;输出层1个节点
L1 = sigmoid(np.dot(X,V))
L2 = sigmoid(np.dot(L1,W))
# L2_delta:输出层的误差改变量
# L1_delta:隐藏层的误差改变量
L2_delta = (Y.T - L2)*dsigmoid(L2)
L1_delta = L2_delta.dot(W.T)*dsigmoid(L1)
# W_C:输出层对隐藏层的权重改变量
# V_C:隐藏层对输入层的权重改变量
W_C = lr * L1.T.dot(L2_delta)
V_C = lr * X.T.dot(L1_delta)
# 更新后的权重
W = W + W_C
V = V + V_C
def main():
for i in range(10000):
get_update()
if i%500 == 0:
L1 = sigmoid(np.dot(X, V))
L2 = sigmoid(np.dot(L1, W))
print('当前误差',np.mean(np.abs(Y.T - L2)))
L1 = sigmoid(np.dot(X, V))
L2 = sigmoid(np.dot(L1, W))
print('最后逼近值:',L2)
if __name__ == "__main__":
main()
效果展示:
可以看到经过迭代,误差越来越小,最后逼近的值很接近标签项[0,1,1,0]