1.前言
课时有限,并没有能系统的学习第五章神经网络的部分,此课后习题也是在网络上找的代码来学习的,python的基础也不是很好,故还有很多缺陷,在原代码的基础上补充了很多注释,在文末也会给出实验结果和待补充的地方,供学习参考。参考材料会在文末附录给出,包括原代码以及数据链接。
本文针对的是周志华《机器学习》课后题5.6的编程。任务如下:试设计一个BP改进算法,能通过动态调整学习率显著提升收敛速度。编程实现该算法,并选择两个UCI数据集与标准BP算法进行实验比较。
关于python,使用的是3.9的版本,实际是以anaconda实现的python环境
最后,该作业是和我的好伙伴一起写的,非常感谢他们~
2.BP神经理论介绍
这里只做简单的介绍,相信大家都是有基础的。BP主要分成三个部分,详细推导过程课本上有。
2.1向前计算输出
2.2反向传播误差
2.3反向传播误差
2.4改进BP——自适应学习速率
通常调整学习速率的准则是:检查权值的修正值是否真正降低了误差函数,如果确实如此,则说明所选取的学习速率值小了,可以对其增加一个量;如果不是这样,则产生了过调,那么就应该减小学习速率的值。与采用附加动量法时的判断条件相仿, 当新误差超过旧误差一定的倍数时,学习速率将减小。 此方法可以保证网络总是以最大的可接受的学习速率进行训练。当一个较大的学习速率仍能够使网络稳定学习,使其误差继续下降时,则增加学习速率,使其以史大的学习速率进行学习。 一旦学习速率调得过大而不能保证误差继续减小,则减小学习速率直到使其学习过程稳定为止。 下式给出了一种自适应学习速率的调整公式,即:
特别要提出的是,在实际操作中,此处的几个系数,1.05,07,1.04并不是固定的,在实际中的使用效果也不好,可自行调整。
3.BP神经网络代码
把bp_code.py,text.py和数据放在一个文件夹目录下即可
bp_code.py
该部分包括激活函数、神经网络类、训练函数fit(内有改进BP部分),已做注释(python的多行注释与取消快捷键为shift+k+c和shift+k+u),自行替换即可。其中重要的是标准BP设定的固定的0.2的学习速率在实验后发现是一个比较快的设定。
# coding=utf-8
import numpy as np
def tanh(x):
return np.tanh(x)
def tanh_deriv(x):
return 1.0 - np.tanh(x) * np.tanh(x)
def logistic(x):
return 1.0 / (1.0 + np.exp(-x))
def logistic_derivative(x):
return logistic(x) * (1.0 - logistic(x))
class NeuralNetwork:
def __init__(self, layers, activation='logistic'):
"""
"""
if activation == 'logistic':
self.activation = logistic
self.activation_deriv = logistic_derivative
elif activation == 'tanh':
self.activation = tanh
self.activation_deriv = tanh_deriv
self.weights = []
#append()方法用于在列表末尾添加新的对象
# 初始化权重(随机)
self.weights.append((2 * np.random.random((layers[0] + 1, layers[1] - 1)) - 1) * 0.25)
for i in range(2, len(layers)):
self.weights.append((2 * np.random.random((layers[i - 1], layers[i])) - 1) * 0.25)
#fit传入训练集数据开始训练模型
def fit(self, X, y, learning_rate=0.1, epochs=10000):#定义训练周期数epochs,是权重的更新次数
B= np.zeros(epochs)#定义了一个存储误差的数组
X = np.atleast_2d(X)
# atlest_2d函数:确认X至少二位的矩阵,每一行代表一个实例
temp = np.ones([X.shape[0], X.shape[1] + 1])#只输出行数:x.shape[0];只输出列数:x.shape[1]
# 初始化偏置值,初始化矩阵全是1(行数,列数+1是为了有B这个偏向)
temp[:, 0:-1] = X
# 行全选,第一列到倒数第二列,即所有的Attribute Values
X = temp
y = np.array(y)
# 数据结构转换
for k in range(epochs):
# 抽样梯度下降epochs抽样
i = np.random.randint(X.shape[0])#生成随机数
a = [X[i]]
# print(self.weights)
for l in range(len(self.weights) - 1):
# 正向进行计算更新,把第一层的输出,作为下一层的输入,此处用了一个小递归,a[l]
b = self.activation(np.dot(a[l], self.weights[l]))
b = b.tolist()
b.append(1)
b = np.array(b)
a.append(b)
a.append(self.activation(np.dot(a[-1], self.weights[-1])))
# 向前传播,得到每个节点的输出结果
error = y[i] - a[-1]# a[-1]就是我们最终预测的输出
#print(error)
B[k]=((error[0])**2+(error[1])**2+(error[2])**2)**0.5 #计算误差矩阵的2范数
#print(B[k])
# 最后一层错误率
deltas = [error * self.activation_deriv(a[-1])]
#反向传播
#print(len(a) #len(a)=3
for l in range(len(a) - 2, 0, -1): # 从倒数第二层到第0层,每次回退一层
deltas.append(deltas[-1].dot(self.weights[l].T) * self.activation_deriv(a[l]))
deltas.reverse()# 从后往前计算出所有的delta,然后反转
#权值更新
for i in range(len(self.weights) - 1):
layer = np.atleast_2d(a[i])
delta = np.atleast_2d(deltas[i])
delta = delta[:, : -1]
#改进BP
if k==0:
self.weights[i] += learning_rate * layer.T.dot(delta)#.T就是对矩阵的转置
elif k>=1 and B[k]<B[k-1]:
learning_rate=1.01*learning_rate
#print("1")
self.weights[i] += learning_rate * layer.T.dot(delta)
elif k>=1 and B[k]>B[k-1]:
#print(learning_rate)
#print("2")
learning_rate=0.9882*learning_rate
self.weights[i] += learning_rate * layer.T.dot(delta)
else:
self.weights[i] += learning_rate * layer.T.dot(delta)
#改进BP
#标准BP
self.weights[i] += learning_rate * layer.T.dot(delta)
#标准BP
#最后
layer = np.atleast_2d(a[-2])
delta = np.atleast_2d(deltas[-1])
self.weights[-1] += learning_rate * layer.T.dot(delta)
def predict(self, x): #模型预测
x = np.atleast_2d(x)
# atlest_2d函数:扩展为二维数组
temp = np.ones(x.shape[1] + 1)
# 初始化矩阵全是1(行数,列数+1是为了有B这个偏向)
temp[:4] = x[0, :]
a = temp
# print(self.weights)
for l in range(len(self.weights) - 1):
b = self.activation(np.dot(a, self.weights[l]))
b = b.tolist()
b.append(1)
b = np.array(b)
a = b
a = self.activation(np.dot(a, self.weights[-1]))
return (a)
text.py
该文件为主函数,其中包括了数据导入(这里存在pyhton命令和excel文件的版本不匹配的问题,参考博文),我是把表格数据存成了2003版的.xls
from bp_code import NeuralNetwork
import numpy as np
from openpyxl import load_workbook
import xlrd
import datetime
# 程序代码段运行STRAT
start=datetime.datetime.now()
nn = NeuralNetwork([4, 15, 3], 'tanh')
import openpyxl
# 打开excel文件,获取工作簿对象,对数据进行处理
data = xlrd.open_workbook('BbezdekIris.xls')
table = data.sheets()[0]
nrows = table.nrows # 行数
ncols = table.ncols # 列数
train_nrows=(int(nrows/10)*8) #9:1分配训练集和测试集
test_nrows=nrows-train_nrows
datamatrix = np.zeros((nrows, ncols - 1)) #定义了全维数据大小的空矩阵
for k in range(ncols - 1):
cols = table.col_values(k) #获取第k列的值(返回数组),Attribute Values
minVals = min(cols)
maxVals = max(cols)
cols1 = np.matrix(cols) # 把list转换为矩阵进行矩阵操作
ranges = maxVals - minVals
b = cols1 - minVals
normcols = b / ranges # 数据进行归一化处理
datamatrix[:, k] = normcols # 把数据(全部列的全部行)进行存储
# print(datamatrix)
datalabel = table.col_values(ncols - 1) #分类标签
for i in range(nrows):
if datalabel[i] == 'Iris-setosa':
datalabel[i] = [1, 0, 0]
if datalabel[i] == 'Iris-versicolor':
datalabel[i] = [0, 1, 0]
if datalabel[i] == 'Iris-virginica':
datalabel[i] = [0, 0, 1]
#开始训练
train_datamatrix1 =table.col_values(colx=1,start_rowx=0,end_rowx=train_nrows)#构建train_nrows数据规模的数组
train_datalabel=table.col_values(colx=1,start_rowx=0,end_rowx=train_nrows)
for i in range(train_nrows):
train_datamatrix1[i] = datamatrix[i]
train_datalabel[i] = datalabel[i]
train_x = train_datamatrix1 #训练数据
train_y = train_datalabel
nn.fit(train_x,train_y)
CategorySet = ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
#训练结束
#开始测试
#初始化
test_datamatrix1 =table.col_values(colx=1,start_rowx=train_nrows,end_rowx=nrows)
test_datalabel=table.col_values(colx=1,start_rowx=train_nrows,end_rowx=nrows)
P = np.zeros((1, test_nrows))
for i in range(0,test_nrows):
test_datamatrix1[i] = datamatrix[i+train_nrows]
test_datalabel[i] = datalabel[i+train_nrows]
test_x = test_datamatrix1
test_y = test_datalabel
#测试结束
#测试结果和真实结果的对比——评价正确率
for i in range(len(test_x)):
Predict = nn.predict(test_x[i])
Predict = Predict.tolist() #将数组或者矩阵转换为列表
Index = Predict.index(max(Predict, key=abs))
Real = test_y[i]
Category = Real.index(max(Real, key=abs))
if Index == Category:
test_y[i] = 1
print('样本', i + 1, ':', test_x[i], ' ', '实际类别', ':', CategorySet[Category], ' ', '预测类别', ':', CategorySet[Index],
' ', '预测正确')
else:
test_y[i] = 0
print('样本', i + 1, ':', test_x[i], ' ', '实际类别', ':', CategorySet[Category], ' ', '预测类别', ':', CategorySet[Index],
' ', '预测错误')
print('准确率', ':', sum(test_y) / len(test_y))
# 程序代码段运行END
end=datetime.datetime.now() #时间格式 2022-10-16 17:05:03.860025
print('运行耗时: %s Seconds'%(end-start))
4.结果与结论
图分别是鸢尾花数据集的标准和改进BP;汽车评估的标准和改进BP
1、代码中没有画图的部分,这使得在对比标准BP和改进BP时没有很好的效果。
2、经常会遇到标准BP和改进BP一样的学习速率的结果,甚至是标准BP更好的结果。 这是因为该代码每次完整的训练次数是固定的10000次,那么存在改进BP提前收敛而标准BP收敛慢的情况,但最后准确率一样好的情况。所以可以绘制10000次—误差变化的曲线图来直观地展示。
3、每次执行都有偶然性,建议加个for循环多运行几次,看看代码输出的稳定性等等。
知识补充
1.Python numpy.atleast_2d函数方法的使用。https://www.cjavapy.com/article/876/
2.在python中,出现“Unexpected indent”原因可能:函数或哪一行的缩进出了问题
3.python创建数组的方法。http://t.zoukankan.com/dylancao-p-10019528.html
4.Python中 if 语句及其使用。https://blog.csdn.net/weixin_42570192/article/details/123611484
5.Python中范数计算np.linalg.norm()用法实例总结。https://www.jb51.net/article/256754.htm
6.BP神经网络参考博文汇总
https://www.lmlphp.com/user/58359/article/item/2101179/
附录
1.文章的主代码和图片来源。https://www.cnblogs.com/PengLaoShi/p/13778056.html
2.文章最终使用的代码和数据链接:https://pan.baidu.com/s/1bthlBcww2-wT4jCDFDT8Lw?pwd=1234
提取码:1234
3.UCI数据集。http://archive.ics.uci.edu/ml/index.php
使用的鸢尾花数据集http://archive.ics.uci.edu/ml/datasets/Iris
使用的汽车评估数据集http://archive.ics.uci.edu/ml/datasets/Car+Evaluation