简单分享一下本学期的机器学习课程论文:
本学期学习了机器学习这门课,然后期末作业是写一篇论文,我抽到了bp神经网络的论文
最开始不知道写什么,(发现写其他的太难了),就上网简单找了个鸢尾花的分类的数据集合,用python写了个bp神经网络预测鸢尾花的分类
分享一下自己的学习成果(论文最后有实现代码)
论文:
基于BP神经网络对鸢尾花的分类的研究
摘要
鸢尾(Iris tectorum Maxim.)鸢尾科鸢尾属的多年生草本植物,根茎粗壮;花蓝紫色,上端膨大成喇叭形。鸢尾花主要有三个品种,setosa、versicolor、virginnica(山鸢尾、变色鸢尾和维吉尼亚鸢尾)。在进行分类时,主要依据是花瓣的长度(Petal Length)、宽度(Petal Width),花萼的长度(Sepal Length)和宽度(Sepal Width)。如何通过机器根据鸢尾花花瓣的长度、宽度(Petal Width)、花萼的长度(Sepal Length)和宽度(Sepal Width)的去快速、准确的辨别出鸢尾花的类别呢?针对以上问题,提出基于 BP(Back Propagation)神经网络方法构建鸢尾花类别预测模型, 旨在通过鸢尾花的花瓣的长度,宽度,花萼的长度,宽度去快速、准确的辨别鸢尾花的类别。本模型训练的训练集共有数据150条数据,其属性分别为(花瓣的长度、宽度,花萼的长度、宽度以及花的类别),4 个属性作为输入特征:花萼长、花萼宽、花瓣长、花瓣宽;类别作为标签,首先将标签转为数字,其规则为:0 代表山鸢尾,1 代表变色鸢尾,2 代表弗吉尼亚鸢尾。然后,整理数据为训练集,测试集把输入特征 和 标签 做成数据对,即每一行输入特征有与之对应的类别;一共150行数据;其中80%作为训练集,即120行;20%作为测试集,即后30行。从数据中分析出,有4个输入特征,所以输入层有4个节点;鸢尾花有3种类别,所以输出层有3个节点. 我们需要初始化网络中的参数(权值、偏置)。通过前向传播计算,即从输入层到输出层迭代计算,预测出是那个类别的鸢尾花,对比是否预测正确(通过损失函数计算出 预测值和真实值的偏差,这个偏差越小代表预测越接近真实;最终选择最优的参数)。通过对鸢尾花(iris)训练集的500次迭代训练,构建鸢尾花类别预测模型,并最后最每轮模型学习概率、损失值差进行可视化,最终模型对测试数据的预测准确度达到100%,训练误差未超过 0 . 019,提出基于BP神经网络的鸢尾花数据具有良好的预测精度、实用性和推广性,可以成为预测鸢尾花的模型,为鸢尾花的识别提供精准预测。
关键词: BP 神经网络 鸢尾花分类预测 数据预处理
一、数据介绍
鸢尾花多分类问题是tensorflow 官方文档里面的一个tensorflow入门教程,鸢尾花(Iris)数据集是一个著名的统计学资料,被机器学习研究人员大量使用。实验选取的是比较典型特点的三种鸢尾花:山鸢尾Iris setosa(0)、变色鸢尾Iris versicolor (1)、维吉尼亚鸢尾Iris virginica(2),各自都是线性可分的。从图一可以看出三种鸢尾花区别很明显,主要体现在花瓣和花萼上。
图1 鸢尾花示例
其中每类各含50个数据,每条记录有4个输入特征如图:花萼长度(SepalLength)、花萼宽度(SepalWidth)、花瓣长度(PetalLength)、花瓣宽度(PetalWidth),种类(Class),通过这4个特征和标签预测鸢尾花卉属于哪一品种。
图二 数据集特征
二、BP 神经网络
BP 神经网络[12] 是一种 3 层或 3 层以上的带误差反馈机制的前向神经网络 , 属于有监督的学习方法 , 其结构如图 三 所示。
图 三 BP 神经网络结构示意图
BP 神经网络的构建( 训练学习) 过程包括信号( 样本数据) 前向传播和误差反向传播两部分 。信号前 向传播时 , 输入信号从输入层经隐藏层到输出层 , 逐层进行非线性变换 , 产生输出信号 。其中 , 每层神 经元的状态只影响下一层神经元的状态 。如果在输出层得到的实际输出与期望不符 , 则进行误差的反向 传播 。在误差反馈阶段 , 通过将误差信号从输出到输入的方向逐层反馈 , 并将误差分摊给各层所有神经 元 。根据各层获得的误差调整该层各神经元的权值和阈值 , 使误差沿梯度方向下降 。经过反复训练网络 , 直到输出误差或训练次数达到预设时 , BP 神经网络构建完成。
利用输入的训练样本训练网路 , BP 神经网络可以构建从输入到输出的非线性映射关系 。因其具有 结构简单、适应性强的特点 , 在函数非线性逼近等问题上具有很强的复演能力 。虽然当前有诸如卷积神 经网络等更为高性能的算法或网络结构 , 但考虑到出发点和运算复杂度等因素 , 基于 BP 神经网络,挖掘鸢尾花种类与特征:花萼长、花萼宽、花瓣长、花瓣宽的关系 , 并以此构建鸢尾花种类预测模型。
-
模型的建立与求解
3.1 实验流程
图5 实验流程结构
3.2 数据的导入
- # 导入所需模块
- import tensorflow as tf
- from sklearn import datasets
3.3 数据集的乱序
为保证数据的随机性,提升预测的可信度,我们将x_data以及y_data数据进行随机清洗。
- np.random.seed(116) # 使用相同的seed,保证输入特征和标签一一对应
- np.random.shuffle(x_data)
- np.random.seed(116)
- np.random.shuffle(y_data)
264行使用相同的seed,使输入特征/标签一一对应,seed使用一次就失效。shuffle函数可以实现打乱列表顺序,打乱的规则由seed决定。
可输出查看y_data形式:
图6 打乱后的y_data
3.4 BP 神经网络相关参数
BP 神经网络参数设置直接决定该神经网络的性能, 但网络参数如何设置目前仍未有明确依据。 因此,考虑到问题实际情况和运算时间、代价等因素, 将BP神经网络设为经典的 3 层结构, 即隐藏层数 量为1。其他参数设置如下:
1) 输入层和输出层的节点数量分别为 4 和 3(生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元);
2) 隐藏层神经元数量为 5(神经元数量= 2*4-3);
3) 激活函数为为 sigmoid型函数;
4) 最大训练次数为 500, 训练要求精度为 0.01。
其余参数均为基于 Python 的 BP 神经网络默认参数。
3.5 参数跟新
- grads = tape.gradient(loss, [w1, b1])
- # 实现梯度更新 w1 = w1 - lr * w1_grad b = b - lr * b_grad
- w1.assign_sub(lr * grads[0]) # 参数w1自更新
- b1.assign_sub(lr * grads[1]) # 参数b自更新
虽然(w1*X)和b1的维度不同,但可以相加。使用tanh作为第一层的激活函数,使用sigmoid作为第二层的激活函数。实现梯度自动跟新,实现梯度更新 ,w1和b参数跟新公式:
w1 = w1 - lr * w1_grad …(1)
b = b - lr * b_grad …(2)
3.6 数据测试
- pred = tf.argmax(y, axis=1) # 返回y中最大值的索引,即预测的分类
- pred = tf.cast(pred, dtype=y_test.dtype)
- correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
划分数据集合为训练集和测试集,在每一次迭代中,用测试数据去测试训练的BP神经网络模型,计算出每轮的学习概率,和损失值并记录
3.8 实验结果
训练开始:
图7 训练过程图
图8 测试数据预测结果
由图七和图八可以明显看出,随着训练次数的增加,BP神经网络模型的预测精准度逐步提高,经过500次训练,训练的精度以达到1,对测试数据全部预测正确,由此可见BP 算法在鸢尾花分类预测方面的有效性和稳定性
图9 测试数据预测结果 图10 测试数据预测结果
图 9 和图 10 中横坐标和纵坐标分别为用于构建 BP 网络的训练样本数量和模型预测出的分类同真实 分类间的误差 。实验表明 BP 神经网络在鸢尾花分类方面具有极高的实用性、稳定性和准确性 。首先 , 由图 9 和图 10 中可以 看出 , 无论是训练性能还是测试性能都呈现浮动性 , 这不同于随着训练样本的增加性能有所改变的特 点 , 说明 BP 算法在鸢尾花分类预测方面具有不需要过多的训练样本且受样本数量影响较低的优点 , 使基于 BP 算法的鸢尾花分类预测模型可以在较少样本前提下构建 , 胜任数据集合太少的分类预测工作 。其次 , 训练样本的训练误差未超过 0 . 019 , 换成百分制即误差低于 1 . 9 分 。最后 , 对测试样本 , 预测误差未超过0 . 04 , 若 换成百分制即误差在[ -4 ,4] 分之间 。优异的预测效果可以帮助计算机实现精准的鸢尾花分类预测
四:结论
本实验采用简单的神经网络反向传播运算实现对鸢尾花数据的分类,很多常用的机器学习算法可以解决线性分类的问题,如支持向量机等等,但是不可否认,在客观世界中,许多系统的输入与输出之间存在着复杂的非线性关系,对于这类系统,往往很难用传统的数理方法建立其数学模型。设计合理地神经网络通过对系统输入输出样本对进行自动学习,能以任意精度逼近任何复杂的非线性映射。神经网络的这一优点能使其可以作为多维非线性函数的通用数学模型。
五、 参考文献
[1] 百度百科 . BP神经网络 [EB/OL]. (2023-03-27)
[2] 关于BP神经网络隐藏层节点数_神经网络隐藏层节点个数-CSDN博客
[3] 鸢尾花(Iris)数据集_iris数据集-CSDN博客
[4] 孙守规.论权移动平均法的预测效果[J].预测,1985,(第 A1 期).
[5] Wamg 潇潇.“时间序列模型 (二):移动平均法”CSDN 博客. 2019.
https://blog.csdn.net/qq_29831163/article/details/89440426.
[6] 司守奎,孙玺菁.数学建模算法与应用[M].北京:国防工业出版社,2011.8
[7] Blanche117. “数学建模——多目标规划模型”. CSDN 博客.2020.
https://blog.csdn.net/weixin_45745854/article/details/107433168.
[8] Mr._Hou. “利用 NSGA-II 算法求解多元多目标函数优化问题”. CSDN 博客.
2019. https://blog.csdn.net/hsk6543210/article/details/103152592.
[9] 赵明著. 城市应急物流设施选址[M]. 北京:北京邮电大学出版社, 2020.06.
六、附件
6.1 BP神经网络解决鸢尾花分类代码
- # -*- coding: UTF-8 -*-
- # 利用鸢尾花数据集,实现前向传播、反向传播,可视化loss曲线
- # 导入所需模块
- import tensorflow as tf
- from sklearn import datasets
- from matplotlib import pyplot as plt , font_manager
- import numpy as np
- from matplotlib import rcParams
- import numpy as np
- import seaborn as sns
- import os
- # 设置全局字体为支持中文的字体,例如SimHei
- rcParams['font.family'] = 'SimHei'
- my_font = font_manager.FontProperties(fname="C:/WINDOWS/Fonts/STSONG.TTF")
- # 导入数据,分别为输入特征和标签
- x_data = datasets.load_iris().data
- y_data = datasets.load_iris().target
- y_data = list(y_data)
- print(y_data.count(0))
- print(y_data.count(1))
- print(y_data.count(2))
- # 随机打乱数据(因为原始数据是顺序的,顺序不打乱会影响准确率)
- # seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样(为方便教学,以保每位同学结果一致)
- np.random.seed(116) # 使用相同的seed,保证输入特征和标签一一对应
- np.random.shuffle(x_data)
- np.random.seed(116)
- np.random.shuffle(y_data)
- tf.random.set_seed(116)
- # s = str(y_data)
- # print(s[:len(s)//3])
- # print(s[len(s)//3:2*(len(s)//3)])
- # print(s[2*(len(s)//3):])
- # exit()
- # 将打乱后的数据集分割为训练集和测试集,训练集为前120行,测试集为后30行
- x_train = x_data[:-30]
- y_train = y_data[:-30]
- x_test = x_data[-30:]
- y_test = y_data[-30:]
- # 转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
- x_train = tf.cast(x_train, tf.float32)
- x_test = tf.cast(x_test, tf.float32)
- # from_tensor_slices函数使输入特征和标签值一一对应。(把数据集分批次,每个批次batch组数据)
- train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
- test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)
- # 生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元
- # 用tf.Variable()标记参数可训练
- # 使用seed使每次生成的随机数相同(方便教学,使大家结果都一致,在现实使用时不写seed)
- w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
- b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))
- lr = 0.1 # 学习率为0.1
- train_loss_results = [] # 将每轮的loss记录在此列表中,为后续画loss曲线提供数据
- test_acc = [] # 将每轮的acc记录在此列表中,为后续画acc曲线提供数据
- epoch = 500 # 循环500轮
- loss_all = 0 # 每轮分4个step,loss_all记录四个step生成的4个loss的和
- # 训练部分
- for epoch in range(epoch): # 数据集级别的循环,每个epoch循环一次数据集
- for step, (x_train, y_train) in enumerate(train_db): # batch级别的循环 ,每个step循环一个batch
- with tf.GradientTape() as tape: # with结构记录梯度信息
- y = tf.matmul(x_train, w1) + b1 # 神经网络乘加运算
- y = tf.nn.softmax(y) # 使输出y符合概率分布(此操作后与独热码同量级,可相减求loss)
- y_ = tf.one_hot(y_train, depth=3) # 将标签值转换为独热码格式,方便计算loss和accuracy
- loss = tf.reduce_mean(tf.square(y_ - y)) # 采用均方误差损失函数mse = mean(sum(y-out)^2)
- loss_all += loss.numpy() # 将每个step计算出的loss累加,为后续求loss平均值提供数据,这样计算的loss更准确
- # 计算loss对各个参数的梯度
- grads = tape.gradient(loss, [w1, b1])
- # 实现梯度更新 w1 = w1 - lr * w1_grad b = b - lr * b_grad
- w1.assign_sub(lr * grads[0]) # 参数w1自更新
- b1.assign_sub(lr * grads[1]) # 参数b自更新
- # 每个epoch,打印loss信息
- print("Epoch {}, loss: {}".format(epoch, loss_all / 4))
- train_loss_results.append(loss_all / 4) # 将4个step的loss求平均记录在此变量中
- loss_all = 0 # loss_all归零,为记录下一个epoch的loss做准备
- # 测试部分
- # total_correct为预测对的样本个数, total_number为测试的总样本数,将这两个变量都初始化为0
- total_correct, total_number = 0, 0
- for x_test, y_test in test_db:
- # 使用更新后的参数进行预测
- y = tf.matmul(x_test, w1) + b1
- y = tf.nn.softmax(y)
- pred = tf.argmax(y, axis=1) # 返回y中最大值的索引,即预测的分类
- # 将pred转换为y_test的数据类型
- pred = tf.cast(pred, dtype=y_test.dtype)
- # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
- correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
- # 将每个batch的correct数加起来
- correct = tf.reduce_sum(correct)
- # 将所有batch中的correct数加起来
- total_correct += int(correct)
- # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
- total_number += x_test.shape[0]
- # 总的准确率等于total_correct/total_number
- acc = total_correct / total_number
- test_acc.append(acc)
- print("Test_acc:", acc)
- print("--------------------------")
- # 绘制 loss 曲线
- plt.title('损失函数图') # 图片标题
- plt.xlabel('训练次数') # x轴变量名称
- plt.ylabel('损失值') # y轴变量名称
- plt.plot(train_loss_results, label="损失函数曲线") # 逐点画出trian_loss_results值并连线,连线图标是Loss
- plt.legend() # 画出曲线图标
- plt.show() # 画出图像
- # 绘制 Accuracy 曲线
- plt.title('训练精度图') # 图片标题
- plt.xlabel('训练次数') # x轴变量名称
- plt.ylabel('精度') # y轴变量名称
- plt.plot(test_acc, label="训练精度曲线") # 逐点画出test_acc值并连线,连线图标是Accuracy
- plt.legend()
- plt.show()