最近几天在研究BP神经网络网时,发现网上对它的介绍很多,但是在编程实现的时候,总感觉很多博客介绍BP的公式的时候没有介绍清楚,参考了不少博客还是感觉模棱两可,最后参考了周志华老师的《机器学习》这本书,再结合之前在网上看到的,自己用python实现了一个标准的BP网络。在这里记录一下学习过程。
1,BP神经网络
在参考其他博客的时候,感觉都没有把BP网络的图以及变量表示清楚。导致我自己在看博客代码实现的时候,变量全部都搞混了。后来参考了周志华老师的《机器学习》感觉上面把变量定义的很清楚,在此参考。
1.1 正向传播
BP的网络如下(这里直接拍照了 QAQ):
图中定义了一个3层神经网络。输入层,隐藏层,输出层个数分别为
d,q,l
。原始的输入为
xi
,每次输入d个变量,也可以理解为每次输入一个向量有d维。输入层与隐藏层之间的元素的节点的权值为
vih
,隐藏层元素接收到的输入为
ah
,
ah
的公式为
ah=∑divihxi
,隐藏层的阀值表示为
γh
。隐藏层输出值用
bh
表示,一般是
ah
减去阀值然后放入一个函数中。通常会选择s型函数,s型函数的定义为
f(x)=11−e−x
,s型函数的导数为
f,(x)=f(x)∗(1−f(x))
,之后在反向修改权值和阀值的时候会用到。隐藏层的输出
bn
的公式为
bh=f(ah−γh)
。再从隐藏层到输出层时,每一隐藏层元素与输出层的元素之间的权值为
whj
,每个输出层元素的输入为
βj
。公式为
βj=∑qhwhjbh
。最后输出层的输出用
yj^
表示。每个输出层的元素也有一个阀值用
θj
表示。
yj^
的公式为
yjˇ=f(βj−θj)
。
公式符号全部就介绍完了。是不是搞晕了。在这里总结一下:
s型函数:
f(x)=f(x)∗(1−f(x))
,导数:
f,(x)=f(x)∗(1−f(x))
最初输入:
xi
输入层和隐藏层权值:
vih
隐藏层输入:
ah
,公式:
ah=∑divihxi
隐藏层阀值:
γh
隐藏层输出:
bh
,公式:
bh=f(ah−γh)
隐藏层与输出层权值:
whj
输出层输入:
βj
,公式:
βj=∑qhwhjbh
输出层阀值:
θj
输出层输出值:
yj^
,公式:
yj^=f(βj−θj)
当有一个输入
xi
的时候,先计算隐藏层输入
ah
。再计算隐藏层输出
bh
,然后计算输出层输入
βj
,最后计算得到输出值
yj
。正向传播的过程大致就是这样了。
1.2 反向传播
在正向过程计算完成后,然后就要通过误差的反向传播(error backpropagation)修改权值和阀值了。误差使用每次正向传播的输出值与真实值的平方差得到
E=12(yreal−yj^)2
。从误差的公式以及之前的正向传播的定义可以将误差E看成是关于权值和阀值的函数,利用梯度下降的思想分别求出权值的梯度
Δwhj
和阀值的梯度
Δθj
(这里以隐藏层和输出层的权值阀值为例),得出梯度下降的方向,然后新的权值
whj=whj−ηΔwhj
。新的阀值
θj=θj−ηΔθj
。
η
为学习率,一般定为0.1。
这里以隐藏层与输出层的权值阀值为例,先求关于权值的偏导
∂Ek∂whj
,这里就直接上图了,《机器学习》书中给出了大致的推导过程,具体想要了解数学推导的同学,再看看其他的博客或书。
在此总结一下反向传播的各个参数:
隐藏层与输出层之间权值的梯度:
Δwhj=ηgjbh
gj
的公式:
gj=y^kj(1−y^kj)(ykj−y^kj)
输出层阀值的梯度:
Δθj=−ηgj
输入层与隐藏层之间的权值梯度:
Δvih=ηehxi
eh
的公式:
eh=bh(1−bh)∑ljwhjgj
隐藏层的阀值梯度:
Δγh=−ηeh
至此BP的公式就介绍完了。
2,代码实现
代码实现的过程中有些技巧,在参考的https://www.cnblogs.com/Finley/p/5946000.html博客中,博主没有考虑输出层的阀值,只是考虑了输入层的阀值。而在http://blog.csdn.net/acdreamers/article/details/44657439这篇博客中,博主使用的是c++写的。没有将权值用矩阵表示。完全是利用for循环写的QAQ。
所以我在实现自己的神经网络时,考虑了隐藏层阀值以及输出层阀值,并且利用权值矩阵将输入层与隐藏层以及隐藏层和输出层的权值和阀值用两个矩阵input_weights以及output_weights表示。
代码如下:
#! /usr/bin/python
# -*- encoding:utf8 -*-
import numpy as np
def rand(a, b):
return (b - a) * np.random.random() + a
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
def sigmoid_derivative(x):
return x * (1 - x)
class BP:
def __init__(self, layer, iter, max_error):
self.input_n = layer[0] # 输入层的节点个数 d
self.hidden_n = layer[1] # 隐藏层的节点个数 q
self.output_n = layer[2] # 输出层的节点个数 l
self.gj = []
self.eh = []
self.input_weights = [] # 输入层与隐藏层的权值矩阵
self.output_weights = [] # 隐藏层与输出层的权值矩阵
self.iter = iter # 最大迭代次数
self.max_error = max_error # 停止的误差范围
# for i in range(self.input_n + 1):
# tmp = []
# for j in range(self.hidden_n):
# tmp.append(rand(-0.2, 0.2))
# self.input_weights.append(tmp)
#
# for i in range(self.hidden_n + 1):
# tmp = []
# for j in range(self.output_n):
# tmp.append(rand(-0.2, 0.2))
# self.output_weights.append(tmp)
# self.input_weights = np.array(self.input_weights)
# self.output_weights = np.array(self.output_weights)
# 初始化一个(d+1) * q的矩阵,多加的1是将隐藏层的阀值加入到矩阵运算中
self.input_weights = np.random.random((self.input_n + 1, self.hidden_n))
# 初始话一个(q+1) * l的矩阵,多加的1是将输出层的阀值加入到矩阵中简化计算
self.output_weights = np.random.random((self.hidden_n + 1, self.output_n))
self.gj = np.zeros(layer[2])
self.eh = np.zeros(layer[1])
# 正向传播与反向传播
def forword_backword(self, xj, y, learning_rate=0.1):
xj = np.array(xj)
y = np.array(y)
input = np.ones((1, xj.shape[0] + 1))
input[:, :-1] = xj
x = input
# ah = np.dot(x, self.input_weights)
ah = x.dot(self.input_weights)
bh = sigmoid(ah)
input = np.ones((1, self.hidden_n + 1))
input[:, :-1] = bh
bh = input
bj = np.dot(bh, self.output_weights)
yj = sigmoid(bj)
error = yj - y
self.gj = error * sigmoid_derivative(yj)
# wg = np.dot(self.output_weights, self.gj)
wg = np.dot(self.gj, self.output_weights.T)
wg1 = 0.0
for i in range(len(wg[0]) - 1):
wg1 += wg[0][i]
self.eh = bh * (1 - bh) * wg1
self.eh = self.eh[:, :-1]
# 更新输出层权值w,因为权值矩阵的最后一行表示的是阀值多以循环只到倒数第二行
for i in range(self.output_weights.shape[0] - 1):
for j in range(self.output_weights.shape[1]):
self.output_weights[i][j] -= learning_rate * self.gj[0][j] * bh[0][i]
# 更新输出层阀值b,权值矩阵的最后一行表示的是阀值
for j in range(self.output_weights.shape[1]):
self.output_weights[-1][j] -= learning_rate * self.gj[0][j]
# 更新输入层权值w
for i in range(self.input_weights.shape[0] - 1):
for j in range(self.input_weights.shape[1]):
self.input_weights[i][j] -= learning_rate * self.eh[0][j] * xj[i]
# 更新输入层阀值b
for j in range(self.input_weights.shape[1]):
self.input_weights[-1][j] -= learning_rate * self.eh[0][j]
return error
def fit(self, X, y):
for i in range(self.iter):
error = 0.0
for j in range(len(X)):
error += self.forword_backword(X[j], y[j])
error = error.sum()
if abs(error) <= self.max_error:
break
def predict(self, x_test):
x_test = np.array(x_test)
tmp = np.ones((x_test.shape[0], self.input_n + 1))
tmp[:, :-1] = x_test
x_test = tmp
an = np.dot(x_test, self.input_weights)
bh = sigmoid(an)
# 多加的1用来与阀值相乘
tmp = np.ones((bh.shape[0], bh.shape[1] + 1))
tmp[:, : -1] = bh
bh = tmp
bj = np.dot(bh, self.output_weights)
yj = sigmoid(bj)
print yj
return yj
if __name__ == '__main__':
# 指定神经网络输入层,隐藏层,输出层的元素个数
layer = [2, 4, 1]
X = [
[1, 1],
[2, 2],
[1, 2],
[1, -1],
[2, 0],
[2, -1]
]
y = [[0], [0], [0], [1], [1], [1]]
# x_test = [[2, 3],
# [2, 2]]
# 设置最大的迭代次数,以及最大误差值
bp = BP(layer, 10000, 0.0001)
bp.fit(X, y)
bp.predict(X)