最近自己专研了一下神经网络中的BP算法,写下这篇文章一是为了记录下自己的想法以免以后忘了,二是重新理一下自己的思路。
长话短说,bp算法的基本原理就是:数据向前传播,到达输出层,通过误差方向传播更新权重。
一、向前传播
首先,为这个模型定义一些数据符号,以便下面操作:输入层---i、隐藏层---j、输出层---k、权重---表示第i层到第j层连线的权值,比如:表示第i层第一个节点和第j层第一个节点的连线。
从输入层传播到隐藏层。
图2中显示了一次传播过程,其中a、b、c代表输入层的三个节点,输出y为隐藏层一个节点的输出值,S阈值函数使用的是sigmoid (x)= (显示有些问题,为e的-x次方)。
所以:x = (第一个节点的输出*链接权重)+(第二个节点的输出*链接权重) +(第三个节点的输出*链接权重);
y = sigmoid (x)。
[注:这里省略了偏置bias,经过实验证明,是否添加偏置对预测结果影响不大]
所以隐藏层的三个节点的值为hidden_output = W·I,其中W、I都为矩阵,W为权重矩阵:每一行代表着三个输入层节点指向同一个节点的3个权重;I 是输入矩阵:由输出层三个节点组成的一个列矩阵。
从隐藏层传播到输出层
从隐藏层到输入层传播方式是一样的,final_output = sigmoid( W·hidden_output )。
二、反向传播。
相信有一定数学基础的人在向前传播这一步都没什么问题,BP算法的难点就在反向传播。
首先我们需要理解为什么可以反向传播更新权重W呢?在这之前我们需要理解误差error,error = targets-final_output,即误差=正确值-预测值,我们要做的就是让”error->0“。
这个表达式表示了当权重wj,k 改变时,误差E是如何改变的,正好符合我们的要求。
接下来我们对这个偏导数求导:
换一种表达方式:
其中为学习率,E_k为下一层的误差,O_J为前一层的输出的转置,O_k即为当前层的输出。
之后我们就可以进行反向传播了。
基于bp算法预测手写字的python代码如下:
import numpyas np
import scipy.special
#神经网络框架
class neuralNetwork:
#1、初始化神经网络函数
#1.1、为神经网络设置一些参数:输入节点、隐藏节点、输出节点、学习率
def __init__(self, inputnodes, hiddennodes, outputnodes, learning_rate):
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
self.lr = learning_rate
#1.2、初始化两层权值,self.wih、self.who ,初始化权重都位于(-0.5,0.5)
self.wih = (np.random.rand(self.hnodes ,self.inodes)-0.5)
self.who = (np.random.rand(self.onodes ,self.hnodes)-0.5)
# 1.3激励函数,即sigmoid函数
self.activation_function =lambda x: scipy.special.expit(x)
#2、训练神经网络函数
def train(self,inputs_list , targets_list):
#2.1以下6行代码是和query()函数中的一样的,正向传播过程。
inputs = np.array(inputs_list,ndmin=2).T
targets = np.array(targets_list,ndmin=2).T
hidden_inputs = np.dot(self.wih, inputs)
hidden_outputs =self.activation_function(hidden_inputs)
final_inputs = np.dot(self.who, hidden_outputs)
final_outputs =self.activation_function(final_inputs)
#-------------------------------------------------
#2.2核心步骤---反向更新
#2.2.1、计算目标值和实际值的误差error = targets - actual
output_errors = targets - final_outputs
#2.2.2、计算隐藏层的误差:隐藏层权值的转置 * 输出层的误差
errors_hidden = np.dot(self.who.T , output_errors)
#2.2.3更新权重: W(j,k) = lr * E_k * sigmoid(O_k) * ( 1-sigmoid(O_k) ) · O_j.T
self.who +=self.lr * np.dot( output_errors * final_outputs * (1-final_outputs ) , np.transpose(hidden_outputs) )
self.wih +=self.lr * np.dot( errors_hidden * hidden_outputs * (1-hidden_outputs ) , np.transpose(inputs) )
#3、预测神经网络函数
def query(self ,inputs_list):
#将输入转化为2d数组
inputs = np.array(inputs_list ,ndmin=2).T
# 3.1将信号传入隐藏层
# 激励函数的输入值:X_hidden =self.wih * I , 其中I为输入层的输入
hidden_inputs = np.dot(self.wih, inputs)
# 将输入值传入激励函数
hidden_outputs =self.activation_function(hidden_inputs)
# 3.2将信号从隐藏层传入输出层,隐藏层的输出*权值就是输出层的输入 即self.who * hidden_outputs = final_inputs
final_inputs = np.dot(self.who, hidden_outputs)
final_outputs =self.activation_function(final_inputs)
return final_outputs
#三层节点设置为784、200、10,学习率为0.1
n = neuralNetwork(784,200,10,0.1)
train_data_file =open("D:/Handwritten numerals/mnist_train.csv")
train_data_list = train_data_file.readlines()
train_data_file.close()
#训练网络1000次
for iin range(60000):
#随机找一行
j = np.random.randint(len(train_data_list))
#以逗号分开
all_value = train_data_list[j].split(',')
#将输入数字转化为[0.01——1]
inputs = (np.asfarray(all_value[1:])/255*0.99)+0.01
#目标数字
targets = np.zeros(10)+0.01
targets[int(all_value[0])]=0.99
n.train(inputs,targets)
#预测
test_data_file =open("D:/Handwritten numerals/mnist_test.csv")
test_data_list = test_data_file.readlines()
test_data_file.close()
#新建一个分数数组,用于存储预测正确的数
scored = []
for recordin test_data_list:
test_value = record.split(',')
inputs = (np.asfarray(test_value[1:]) /255 *0.99) +0.01
outputs = n.query(inputs)
#np.argmax()函数:取出一位数组中最大值的索引
label = np.argmax(outputs)
if label ==int(test_value[0]):
scored.append(1)
else:
scored.append(0)
#预测的准确率
scored_array =np.asfarray(scored)
print('准确率为:',scored_array.sum()/scored_array.size)
注:训练60000次大概可以达到95%的识别率,若是训练600000万次可以达到97%。
引用书籍:《Python神经网络编程》,[英]塔里克·拉希德著,林赐译。