BP神经网络及其改进算法;自适应学习速率BP;周志华《机器学习》课后题5.6

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

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值