资讯动态

BP神经网络分类实战:鸢尾花与红酒数据集完整实现

发布时间:2026/10/1 12:53:35 来源:尧图企业网站定制
简介神经网络是机器学习中处理分类任务的重要工具而BP神经网络凭借其强大的非线性拟合能力成为入门深度学习的经典算法。其核心原理是通过前向传播计算预测结果再借助反向传播算法逐层调整权重从而最小化损失函数。在实际工程中BP网络的价值体现在对复杂特征关系的建模能力尤其适用于中小规模数据集的结构化分类问题。无论是鸢尾花这类经典数据集还是红酒品质这样的多维特征数据都需要先进行特征标准化与标签编码以提升模型收敛速度与准确率。从环境配置、数据预处理到网络结构设计整个流程具有极强的可复现性适合课程设计、期末作业以及工业场景中的快速原型验证。本文以鸢尾花和红酒数据集为例完整拆解了BP神经网络分类项目的代码实现、避坑要点与答辩技巧帮助读者直接上手并理解底层原理。1. BP神经网络分类实战鸢尾花和红酒数据集这份资源为什么能直接上手如果你正在做期末大作业或者课程设计选题是“基于BP神经网络模型的分类”那你大概率已经搜过一圈代码了。网上BP神经网络源码不少但真正能直接用在自己数据集上的不多——要么是MNIST手写识别的魔改版要么注释稀碎根本不敢往实验报告里贴。这套资源不一样它把鸢尾花和红酒数据集两套分类完整跑通附带实验报告和答辩PPT代码注释写到新手能看懂的程度属于那种下载下来简单部署就能出结果的项目。适合三类人一是时间紧需要快速交作业的二是想拿高分但不太会写报告和做PPT的三是想通过一个完整案例搞懂BP反向传播原理的。2. 环境准备与数据预处理xls和xlsx格式处理、特征标准化一并解决2.1 Python环境与依赖库版本选择和安装顺序先确认你本机的Python版本。这套源码在Python 3.6环境下运行过资源包里能看到BP.cpython-36.pyc的缓存文件但我实际测试Python 3.8、3.9、3.10都没问题核心依赖是numpy、pandas、matplotlib、scikit-learn四个库。这里有个关键点不是所有库都直接pip install就行xlrd这个库对Excel文件格式非常敏感——旧版本支持xls但不支持xlsx新版本支持xlsx但放弃了xls。资源包里同时存在. xls和.xlsx两种格式的数据文件所以你要装对版本组合才能把数据读进去。pip install numpy pandas matplotlib scikit-learn pip install xlrd1.2.0 pip install openpyxlxlrd固定装1.2.0是为了兼容xls格式openpyxl用来兜底xlsx格式。如果直接装最新版xlrd 2.x读xls会报“Excel xlsx file; not supported”或者直接不支持xls这是最常见的翻车点之一。装完之后建议在Python里跑一句import xlrd; print(xlrd.__version__)确认版本号再往下走。2.2 数据集读取与格式转换pandas读取Excel的完整姿势鸢尾花数据集是经典的iris_data150条样本、4个特征、3个类别红酒数据集是winequality_data特征数量比鸢尾花多不少标签是葡萄酒品质评分。两个文件都有xls和xlsx两种版本读取方式统一用pandas的read_excel函数靠engine参数控制底层解析器。import pandas as pd # 读取xls格式engine指向xlrd iris_xls pd.read_excel(iris_data.xls, enginexlrd) # 读取xlsx格式engine指向openpyxl iris_xlsx pd.read_excel(iris_data.xlsx, engineopenpyxl) # 统一以xlsx为准先看前几行和结构 data pd.read_excel(iris_data.xlsx, engineopenpyxl) print(data.head()) print(data.info()) print(data.columns.tolist())这段代码的逻辑是先分别验证两种格式能否正常读入再以其中一种作为后续训练的数据源。print结构信息是为了确认列名和数据类型因为鸢尾花数据集的列名可能是英文sepal_length、sepal_width等也可能是中文花萼长度、花瓣宽度等得先看清再写特征选择代码。pandas的info()方法还会告诉你有没有空值iris数据集一般没有但红酒数据集偶尔会出现缺失值后面要处理。engine参数必须按上述方式指定否则pandas会根据文件后缀自动选引擎后缀和实际格式不一致时就会读错。2.3 特征标准化与标签编码训练前必须做的两步操作BP神经网络对输入特征的数值范围很敏感。鸢尾花的4个特征单位都是厘米数值量纲一致但红酒数据集的11个特征里固定酸度、挥发性酸度、柠檬酸、残糖、氯化物等指标的量纲差异很大有的在0到1之间有的能达到十几甚至几十。如果不做标准化梯度下降会在某些维度上震荡得厉害、收敛极慢。常见的做法是用Z-score标准化每个特征减去均值再除以标准差让所有特征都落在相近的数值区间。import numpy as np from sklearn.preprocessing import StandardScaler, LabelEncoder # 分离特征和标签 def load_and_prepare(filepath, label_col): df pd.read_excel(filepath, engineopenpyxl) # 标签列单独拿出来 y_raw df[label_col].values # 其余列作为特征 X_raw df.drop(columns[label_col]).values # 特征标准化 scaler StandardScaler() X scaler.fit_transform(X_raw) # 标签编码把字符串类别转成0,1,2这样的数字 encoder LabelEncoder() y encoder.fit_transform(y_raw) return X, y, encoder # 鸢尾花最后一列是类别名 X_iris, y_iris, enc_iris load_and_prepare(iris_data.xlsx, species) print(X_iris.shape, y_iris.shape, enc_iris.classes_)这个函数里有两个关键参数要说明。label_col指定哪一列是标签鸢尾花是species列红酒数据集如果是winequality数据标签列一般是quality或者评分列需要根据实际Excel内容确定。StandardScaler的fit_transform会先计算均值和标准差再转换测试集后续要用同一个scaler做transform不能重新fit否则数据分布不一致会影响结果。LabelEncoder的作用是把类别字符串转成整数鸢尾花的setosa、versicolor、virginica会被映射成0、1、2训练输出层的神经元个数就由类别数决定。3. BP神经网络核心实现网络结构设计与反向传播逐行拆解3.1 网络结构设计输入层、隐藏层、输出层的维度怎么定BP神经网络的基础结构是三层输入层、隐藏层、输出层。输入层节点数等于特征数量鸢尾花是4红酒数据集是11输出层节点数等于类别数量两个数据集都是3类输出。隐藏层节点数没有标准答案需要根据经验调。一般从(输入层节点数 输出层节点数) / 2附近开始试效果不好就往上加。资源包里iris_classify.py的主程序专门留了一个HIDDEN_SIZE变量目的就是方便你跑不同隐藏层数做对比。import numpy as np class BPNetwork: def __init__(self, input_size, hidden_size, output_size, learning_rate0.1): self.input_size input_size self.hidden_size hidden_size self.output_size output_size self.lr learning_rate # 权重初始化用标准差为0.1的随机数避免过大导致梯度饱和 self.W1 np.random.randn(input_size, hidden_size) * 0.1 self.b1 np.zeros((1, hidden_size)) self.W2 np.random.randn(hidden_size, output_size) * 0.1 self.b2 np.zeros((1, output_size))初始化参数是BP网络最容易踩坑的地方。如果直接把权重初始化为0所有神经元在反向传播时会同步更新整个网络的表达能力就废了如果初始化为过大的随机数结合sigmoid激活函数会让神经元很快进入饱和区梯度趋近于零训练直接卡死。用np.random.randn() * 0.1生成标准差为0.1的正态分布随机数是中小型BP网络比较稳妥的做法。偏置b1和b2初始化为零没问题因为偏置的梯度不受对称性问题影响。learning_rate0.1是这份资源里经过验证的默认值实际调参可以先看loss曲线再决定放大还是缩小。3.2 前向传播与激活函数sigmoid在隐藏层和输出层的作用区别前向传播就是数据从输入层流向输出层的过程。每个隐藏层神经元接收输入的加权求和再加上偏置然后经过激活函数做非线性变换。输出层同样做一次计算得到每个类别的预测值。经典BP实现里一般用sigmoid作为激活函数它能把任意实数压缩到0到1之间适合做二分类或者多分类的概率输出。def sigmoid(self, x): # 数值稳定的sigmoid写法避免exp溢出 return 1 / (1 np.exp(-x)) def sigmoid_derivative(self, x): # 用sigmoid输出值本身计算导数省一次计算 return x * (1 - x) def forward(self, X): # 输入层到隐藏层 self.z1 np.dot(X, self.W1) self.b1 self.a1 self.sigmoid(self.z1) # 隐藏层到输出层 self.z2 np.dot(self.a1, self.W2) self.b2 self.a2 self.sigmoid(self.z2) return self.a2sigmoid_derivative这里用了一个技巧sigmoid函数的导数可以表示为sigma(x) * (1 - sigma(x))所以在反向传播时直接传已经算好的激活值a1、a2就能少做一次指数运算。注意sigmoid(x)里我用了np.exp(-x)而不是写1/(1np.e**(-x))两者数学上等价但前者计算精度更好。前向传播的过程里self.z1、self.a1、self.z2、self.a2都被存为成员变量这是为了在反向传播时复用它们本质上就是网络各层的中间状态。3.3 反向传播与梯度更新核心公式落到代码的一对一映射反向传播是BP神经网络的灵魂逻辑上分三步计算输出层的误差、计算隐藏层的误差、按误差梯度更新权重。输出层的误差用预测值和真实标签的差值衡量隐藏层的误差则是将输出层误差加权回传。写成代码时很多人会卡在delta的计算公式上其实对照着三层网络的链式求导图逐个变量对齐就行。def backward(self, X, y): m X.shape[0] # 样本数量 # 输出层误差预测值减真实值 delta2 (self.a2 - y) * self.sigmoid_derivative(self.a2) # 隐藏层误差输出层误差回传 delta1 np.dot(delta2, self.W2.T) * self.sigmoid_derivative(self.a1) # 梯度更新 self.W2 - self.lr * np.dot(self.a1.T, delta2) / m self.b2 - self.lr * np.sum(delta2, axis0, keepdimsTrue) / m self.W1 - self.lr * np.dot(X.T, delta1) / m self.b1 - self.lr * np.sum(delta1, axis0, keepdimsTrue) / m def train(self, X, y, epochs): loss_history [] for epoch in range(epochs): output self.forward(X) # 均方误差作为损失 loss np.mean((output - y) ** 2) loss_history.append(loss) self.backward(X, y) if epoch % 100 0: print(fepoch {epoch}, loss: {loss:.4f}) return loss_history这段代码有几个细节值得说明。delta2的计算里(self.a2 - y)是平均绝对意义上的误差乘上sigmoid_derivative是因为chain rule需要这一个乘法同时也是输出层激活函数求导的体现。delta1通过np.dot(delta2, self.W2.T)把输出误差映射回隐藏层依然是链式法则的矩阵形式。权重更新公式sigmoid前面出现的self.lr是学习率每次更新的步长就是它乘上梯度因为这里用的是全批量梯度下降所以最终除以m取平均保证梯度值不随样本量变化而变得过大。loss_history用于后续画曲线观察收敛情况如果loss在某个epoch后不再下降甚至反弹说明学习率需要调小或者网络结构需要调整。4. 两份数据集的分类实战iris_classify.py与winquality_classify.py对比跑通4.1 鸢尾花分类150条样本的三分类训练主流程iris_classify.py是这份资源的主入口文件流程是固定的读取数据、预处理、切分训练集和测试集、建立BP网络、训练、评估准确率。鸢尾花数据集很小150条样本所以不需要mini-batch全批量梯度下降一次迭代的计算量也很小几百个epoch就能收敛。下面这段代码对应主流程的核心部分。from sklearn.model_selection import train_test_split # 使用前面load_and_prepare得到的X_iris和y_iris # 按7:3比例切分random_state固定保证结果可复现 X_train, X_test, y_train, y_test train_test_split( X_iris, y_iris, test_size0.3, random_state42 ) # 把标签转成one-hot编码输出层需要 def one_hot(y, num_classes): n y.shape[0] res np.zeros((n, num_classes)) res[np.arange(n), y] 1 return res y_train_onehot one_hot(y_train, 3) y_test_onehot one_hot(y_test, 3) # 建立网络并训练 net BPNetwork(input_size4, hidden_size5, output_size3, learning_rate0.1) loss_hist net.train(X_train, y_train_onehot, epochs500)train_test_split的random_state42是刻意固定的目的是让每次运行切分方式相同实验报告里的准确率数字可以复现。标签从整数转成one-hot编码的过程很关键BP网络的输出层是3个神经元每个神经元对应一个类别的预测概率真实标签用one-hot表示后损失函数计算的是预测概率分布和真实分布之间的误差如果直接把整数标签塞给输出层维度对不上代码会在反向传播时报错。hidden_size5是参考(43)/2≈3.5再上探到5这个值在鸢尾花数据集上通常能达到95%以上的测试准确率。4.2 红酒品质分类特征更多、标签不均衡的处理差异winquality_classify.py和iris_classify.py用的是同一个BPNetwork类区别主要在数据预处理和类别处理上。红酒数据集的特征列有11个输入层节点数从4变成11隐藏层节点数需要相应增大否则模型容量不够、欠拟合。另一个重要区别是红酒数据集的标签不是字符串类别名而是整数的品质评分LabelEncoder依然能用但要注意评分可能存在类别不均衡的现象。资源包里已经处理好了这个问题但建议你自己跑一遍看看各类别的样本分布。# 红酒数据集加载以winequality_data.xlsx为例 df_wine pd.read_excel(winequality_data.xlsx, engineopenpyxl) # 注意红酒数据集里可能有非数值列或空值行先做清洗 print(df_wine.isnull().sum().sum()) # 如果标签列的取值是连续整数直接按整数编码 y_wine_raw df_wine[quality].values # 类别数由唯一值个数决定 num_wine_classes len(np.unique(y_wine_raw)) print(红酒数据集类别数:, num_wine_classes) # 特征标准化直接复用StandardScaler X_wine_raw df_wine.drop(columns[quality]).values scaler_wine StandardScaler() X_wine scaler_wine.fit_transform(X_wine_raw)红酒数据集的预处理比鸢尾花多了几步因为工业采集的数据清洗成本更高。检查isnull().sum().sum()是为了确认有没有空值如果有可以用pandas的dropna处理或者用均值填充。num_wine_classes是基于数据动态计算的比起写死一个数字更稳妥因为不同来源的红酒数据集quality取值可能不同有的版本只有3、4、5、6、7、8六档有的版本会少一档。特征量纲差异的问题前面已经提过这里用同样的StandardScaler处理scale之后11个特征都在相近区间BP网络训练速度会明显变快。数据集本身的类别不均衡——比如中间评分样本很多、极端评分样本很少——训练时准确率会偏向多数类这个问题列入后面的避坑章节详细说。4.3 训练过程可视化从loss曲线判断模型有没有学好资源包里两个ipynb文件iris_classify.ipynb和wine_classify.ipynb就是用来边跑边看结果的建议你把训练代码在Jupyter里跑一遍重点关注loss曲线和最终准确率。loss下降的形态比准确率更能说明问题正常收敛的loss曲线是单调下降然后逐渐平缓的如果曲线反复震荡说明学习率偏大如果下降速度极慢说明学习率偏小或者隐藏层节点不够。import matplotlib.pyplot as plt # loss_hist来自train方法的返回值 plt.plot(range(len(loss_hist)), loss_hist) plt.xlabel(epoch) plt.ylabel(loss) plt.title(BP训练损失曲线) plt.grid(True) plt.show()画loss曲线这一步很多人会忽略但它其实是最省力气的诊断工具。如果在实验报告里贴一张平滑下降的loss曲线再配上最终准确率导师通常就会认为你真正理解了训练过程。plot的两个列表长度必须一致如果你训练了500个epochloss_hist里就有500个值range(len(loss_hist))刚好一一对应。网格线grid(True)在答辩PPT的截图里会让曲线更容易读算是个展示的小技巧。5. 避坑指南数据读取到收敛判断的五个常见翻车现场5.1 现象xlrd读xlsx一直报错运行pd.read_excel(iris_data.xlsx)时报xlrd.biffh.XLRDError: Excel xlsx file; not supported但是项目里明明提供了xlsx文件。原因是xlrd 2.0以上版本移除了xlsx支持只保留xls解析能力。解决方法是把xlrd降级到1.2.0或者读xlsx时显式指定engineopenpyxl。我一般会在代码开头就统一用engineopenpyxl读xlsx、用enginexlrd读xls彻底屏蔽自动引擎选择的问题。5.2 现象训练准确率徘徊在30%到40%跟随机猜差不多代码逻辑看起来没问题train和predict都正常执行了但准确率怎么都上不去。排查半天发现是标签在预处理阶段没有同步训练集用LabelEncoder编码成0、1、2但预测结果却是原始字符串准确率计算时拿数字和字符串比较全部对不上。解决方法是把encoder保存下来在预测后调用encoder.inverse_transform()把数字标签转回原名或者干脆用数字标签做准确率计算两种方式选一种固定下来。5.3 现象loss曲线震荡得很厉害不降反升学习率设置不合理是首要嫌疑。BP网络用的梯度下降对学习率很敏感设0.5甚至更高的时候权重的更新步长太大loss会在最小值附近来回跳动甚至发散。解决办法是把learning_rate从0.1降到0.01或0.001观察曲线是否变平滑。如果降学习率之后收敛速度太慢可以适当增加epoch数量来弥补。5.4 现象红酒数据集的测试准确率虚高一细看全是多数类某些quality取值对应的样本量特别大网络学会了把所有样本都预测成那个值整体准确率看起来有70%多但少数类的准确率是0。这类问题在实验报告里扣分很严重。解决方式有两个一是查看每个类别的召回率recall而不是只看整体准确率二是训练时按类别比例分配样本或者对少数类加权。作为课程设计能发现这个问题并给出解决方案反而是加分项。5.5 现象隐藏层节点数怎么调都一个样隐藏层节点数属于BP网络里公认的“玄学”问题没有万能公式。改变hidden_size后记得同时观察训练集和测试集表现训练集准确率低说明模型容量不够往上加节点训练集准确率很高但测试集明显低说明过拟合往下减节点或者增加测试集比例。原始资源里鸢尾花用的hidden_size5是个不错的起点新手可以在这个基础上分别跑5、10、20做对比实验然后挑一个测试集表现最好的值放进报告。6. 验证与答辩技巧混淆矩阵、指标解读和老师提问的话术准备6.1 用混淆矩阵做模型验证比单一准确率更有说服力课程设计答辩时导师问得最多的一个问题就是“你这个模型到底表现怎么样”。只回一句“准确率95%”太单薄了最好能拿出混淆矩阵展示每个类别的分类细节。混淆矩阵是n×n的表格行代表真实类别列代表预测类别对角线上的数字是正确分类的数量其他位置是错分的情况。把混淆矩阵画出来贴进实验报告老师一眼就能看到哪些类别容易混淆。from sklearn.metrics import confusion_matrix, classification_report # 预测完整测试集 y_pred net.predict(X_test) # 返回one-hot格式转回整数标签 y_pred_labels np.argmax(y_pred, axis1) y_true_labels np.argmax(y_test_onehot, axis1) # 直接打印混淆矩阵数组 cm confusion_matrix(y_true_labels, y_pred_labels) print(混淆矩阵:) print(cm) # 打印每类的精确率、召回率、F1 print(classification_report(y_true_labels, y_pred_labels))混淆矩阵里如果发现某些类别经常互相错认比如鸢尾花的versicolor和virginica混淆较多可以在答辩时主动解释这两个类别在原始特征上有重叠区域可以在隐藏层加节点或者叠加新特征来尝试改进。classification_report输出的三项指标——precision、recall、F1-score是答辩加分的关键词熟练说出“对某一类的召回率偏低是因为样本不均衡”这种话老师会认为你对结果有深入理解。6.2 答辩PPT的讲解顺序和三个高频问题资源包里的答辩PPT我翻过一遍结构很完整封面、背景与意义、算法原理、实验设计、结果分析、总结与展望。PPT里有BP网络结构图和loss曲线截图基本都是可以直接用的素材。答辩时从算法原理入手讲起重点讲清楚“为什么BP网络能分类”然后用一张PPT的时间展示数据预处理过程最后把准确率和混淆矩阵放出来。三个最高频的提问方向提前准备第一个是“BP为什么不用ReLU”合适的回答是BP全连接网络用sigmoid是经典方案ReLU主要解决深层网络的梯度消失问题三层网络用sigmoid已经足够第二个是“学习率怎么定的”要说明试了0.5到0.01几组值最后选0.1是loss收敛速度和稳定性折中的结果第三个是“隐藏层节点数是怎么选的”回答时要讲清楚对比实验的过程而不是说凭空定的。把这几个问题都准备好整个答辩基本就稳了。这套资源的源码、实验报告和答辩PPT是完整配套的下载后解压就能直接开始改。从那以后我每次做这类分类大作业都会强制自己先跑通一个最小数据集、画好loss曲线再做正式训练和结果分析这个习惯让我少走了很多弯路。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑