本文基于tensorflow搭建和训练单隐层网络,分别实现标准BP算法和累计BP算法。
标准BP算法每次仅针对一个训练样例更新连接权和阈值,也就是说,BP算法中的更新规则是基于单个的 推导而得,如果类似的推导出基于累积误差最小化的更新规则,就得到了累积误差逆传播算法。
标准BP算法和累积BP算法的区别类似于随机梯度下降(Stochastic gradient descent,简称 SGD)与标准梯度下降之间的区别。本文通过改变batch_size的大小来覆盖全部样本或者单个样本,进而体现出标准BP算法和累计BP算法的区别。
基于tensorflow搭建的单隐层神经网络
import tensorflow as tf
import numpy as np
from sklearn.metrics import accuracy_score
class BPNN(object):
def __init__(self,input_n,hidden_n,output_n,lambd):
"""
这是BP神经网络类的构造函数
:param input_n:输入层神经元个数
:param hidden_n: 隐藏层神经元个数
:param output_n: 输出层神经元个数
:param lambd: 正则化系数
"""
self.Train_Data = tf.placeholder(tf.float64,shape=(None,input_n),name='input_dataset') # 训练数据集
self.Train_Label = tf.placeholder(tf.float64,shape=(None,output_n),name='input_labels') # 训练数据集标签
self.input_n = input_n # 输入层神经元个数
self.hidden_n = hidden_n # 隐含层神经元个数
self.output_n = output_n # 输出层神经元个数
self.lambd = lambd # 正则化系数
self.input_weights = tf.Variable(tf.random_normal((self.input_n, self.hidden_n),mean=0,stddev=1,dtype=tf.float64),trainable=True) # 输入层与隐含层之间的权重
self.hidden_weights = tf.Variable(tf.random_normal((self.hidden_n,self.output_n),mean=0,stddev=1,dtype=tf.float64),trainable=True) # 隐含层与输出层之间的权重
self.hidden_threshold = tf.Variable(tf.random_normal((1,self.hidden_n),mean=0,stddev=1,dtype=tf.float64),trainable=True) # 隐含层的阈值
self.output_threshold = tf.Variable(tf.random_normal((1,self.output_n),mean=0,stddev=1,dtype=tf.float64),trainable=True) # 输出层的阈值
# 将层与层之间的权重与偏置项加入损失集合
tf.add_to_collection('loss', tf.contrib.layers.l2_regularizer(self.lambd)(self.input_weights))
tf.add_to_collection('loss', tf.contrib.layers.l2_regularizer(self.lambd)(self.hidden_weights))
tf.add_to_collection('loss', tf.contrib.layers.l2_regularizer(self.lambd)(self.hidden_threshold))
tf.add_to_collection('loss', tf.contrib.layers.l2_regularizer(self.lambd)(self.output_threshold))
# 定义前向传播过程
#activation function为sigmoid
self.hidden_cells = tf.sigmoid(tf.matmul(self.Train_Data,self.input_weights)+self.hidden_threshold)
self.output_cells = tf.sigmoid(tf.matmul(self.hidden_cells,self.hidden_weights)+self.output_threshold)
# 定义损失函数,并加入损失集合
self.MSE = tf.reduce_mean(tf.square(self.output_cells-self.Train_Label))
tf.add_to_collection('loss',self.MSE)
# 定义损失函数,均方误差加入L2正则化
self.loss = tf.add_n(tf.get_collection('loss'))
def train_test(self,Train_Data,Train_Label,Test_Data,Test_Label,learn_rate,epoch,iteration,batch_size):
"""
这是BP神经网络的训练函数
:param Train_Data: 训练数据集
:param Train_Label: 训练数据集标签
:param Test_Data: 测试数据集
:param Test_Label: 测试数据集标签
:param learn_rate: 学习率
:param epoch: 时期数
:param iteration: 一个epoch的迭代次数
:param batch_size: 小批量样本规模
"""
train_loss = [] # 训练损失
test_loss = [] # 测试损失
test_accarucy = [] # 测试精度
with tf.Session() as sess:
datasize = len(Train_Label)
self.train_step = tf.train.GradientDescentOptimizer(learn_rate).minimize(self.loss)
sess.run(tf.global_variables_initializer())
for e in np.arange(epoch):
for i in range(iteration):
start = (i*batch_size)%datasize
end = np.min([start+batch_size,datasize])
sess.run(self.train_step,
feed_dict={self.Train_Data:Train_Data[start:end],self.Train_Label:Train_Label[start:end]})
if i % 1000 == 0:
total_MSE = sess.run(self.MSE,
feed_dict={self.Train_Data:Train_Data,self.Train_Label:Train_Label})
print("第%d个epoch中,%d次迭代后,训练MSE为:%g"%(e+1,i+1000,total_MSE))
# 训练损失
_train_loss = sess.run(self.MSE,feed_dict={self.Train_Data:Train_Data,self.Train_Label:Train_Label})
train_loss.append(_train_loss)
# 测试损失
_test_loss = sess.run(self.MSE, feed_dict={self.Train_Data:Test_Data, self.Train_Label: Test_Label})
test_loss.append(_test_loss)
# 测试精度
test_result = sess.run(self.output_cells,feed_dict={self.Train_Data:Test_Data})
test_accarucy.append(self.Accuracy(test_result,Test_Label))
return train_loss,test_loss,test_accarucy
def Accuracy(self,test_result,test_label):
"""
这是BP神经网络的测试函数
:param test_result: 测试集预测结果
:param test_label: 测试集真实标签
"""
predict_ans = []
label = []
for (test,_label) in zip(test_result,test_label):
test = np.exp(test)
test = test/np.sum(test)
predict_ans.append(np.argmax(test))
label.append(np.argmax(_label))
return accuracy_score(label,predict_ans)
测试代码:
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import Normalizer
from BP3 import BPNN
def Load_Data(fileName):
"""
这是导入数据的函数
:return: 数据集
"""
data = []
label = []
datas = pd.read_csv(fileName)
datas['one'] = 1.0
data = np.mat(datas[['one','density','sugar']])
label = np.mat(datas[['label']])
data = np.array(data,dtype=np.float64)
label = np.array(label,dtype=np.float64)
return data,label
def run_main():
"""
这是主函数
"""
# 导入数据
#path = './data.txt'
Data,Label = Load_Data('waterMalon.csv')
# 分割数据集,并对数据集进行标准化
Train_Data,Test_Data,Train_Label,Test_Label = train_test_split(Data,Label,test_size=1/5,random_state=10)
Train_Data = Normalizer().fit_transform(Train_Data)
Test_Data = Normalizer().fit_transform(Test_Data)
# 设置网络参数
input_n = np.shape(Data)[1]
output_n = np.shape(Label)[1]
hidden_n = int(np.sqrt(input_n*output_n))
lambd = 0.001
batch_size = 64#标准BP算法和累计BP算法的区别可以理解为随机梯度下降(SGD)与经典的梯度下降法的区别,SGD可以理解为这里的mini_batch,而batch_size的不同则表示当前是采用了SGD还是经典的梯度下降
learn_rate = 0.01
epoch = 5
iteration = 1000
# 训练并测试网络
bpnn = BPNN(input_n,hidden_n,output_n,lambd)
train_loss,test_loss,test_accuracy = bpnn.train_test(Train_Data,Train_Label,Test_Data,Test_Label,learn_rate,epoch,iteration,batch_size)
# 解决画图是的中文乱码问题
mpl.rcParams['font.sans-serif'] = [u'simHei']
mpl.rcParams['axes.unicode_minus'] = False
# 结果可视化
col = ['Train_Loss','Test_Loss']
epoch = np.arange(epoch)
plt.plot(epoch,train_loss,'r')
plt.plot(epoch,test_loss,'b-.')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.grid(True)
plt.legend(labels = col,loc='best')
plt.savefig('./累计BP训练与测试损失.jpg')
plt.show()
plt.close()
plt.plot(epoch, test_accuracy, 'r')
plt.xlabel('Epoch')
plt.ylabel('Test Accuracy')
plt.grid(True)
plt.legend(loc='best')
plt.savefig('./累计BP测试精度.jpg')
plt.show()
plt.close()
if __name__ == '__main__':
run_main()
这里的batch_size = 64
表示的就是每次更行参数覆盖了所有的样本,即实现的是累计BP算法,而当batch_size = 1
则表示的是标准BP算法,每次参数更新仅针对一个训练样例。
训练分别得到了两种BP算法的结果(如下)
标准BP训练与测试损失 | 标准BP测试精度 |
累计BP训练与测试损失 | 累计BP测试精度 |
从结果可以看出累计BP与标准BP有以下几个不同点:
结论:标准BP训练快但效果不一定高,累计BP训练慢但效果好,因此可以采用先用标准BP再用累计BP的思路来达到最优解,这大概也就是为什么现在都是用的batch gradient descent,采用两者中间的一个mini batch来估计整体的效果,更准确也更快。