☰
BP神经网络分类实验:从鸢尾花到红酒数据集的全流程解析
2026/10/6 8:13:08 网站建设 项目流程

简介:面向机器学习初学者的BP神经网络分类实验包,涵盖鸢尾花与红酒两个经典数据集的分类实现,适配课程作业与实验教学场景,可帮助读者掌握BP网络从数据预处理、模型搭建到训练评估的完整流程。包内共18个文件,压缩包大小约630KB,主要包含Python脚本与Jupyter Notebook源码、xls/xlsx格式的数据集表格、算法实践说明文档及实验课件,代码、数据、文档分离,便于直接运行和对照学习;其中基础网络定义与分类脚本分层组织,方便复用和改造。两个分类脚本分别针对不同数据集,可对比BP网络在不同特征维度下的表现,Notebook交互版本也便于逐段调试理解;实验文档与PPT则补充了实验背景、步骤和结果分析思路,整体目录紧凑、上手成本低。已有1034人学习浏览,适合正在完成神经网络相关课程作业或入门深度学习的学生参考。

1. BP神经网络分类实验:鸢尾花与红酒数据集资源包拆解

BP神经网络这个词在机器学习课程里几乎必考,但真正把它跑在鸢尾花和红酒两份数据集上,再交一份能对答如流的实验报告,中间隔着预处理、反向传播推导和调参三道坎。这份资源是一次完整实验沉淀:BP.py 是核心算法实现,iris_classify.py 和 winquality_classify.py 是两份数据的分类入口,配套 xls/xlsx 原始数据、实验文档《实验2-BP算法实践》和讲解 PPT。它解决的不仅是交作业,更提供一套从数据读取到结果评估都能跑通的基线代码。适合刚学完神经网络基础、想对照代码改参数的同学,也适合想快速复用 BP 分类管线的入门开发者。下面按拆包顺序,把数据、算法、调参和踩过的坑逐个说清楚。

2. 数据读取与预处理:两份数据集的差异、归一化与切分细节

2.1 鸢尾花与红酒质量数据集:结构差异决定预处理方式

先说数据集本身。iris_data.xlsx 是经典鸢尾花数据,150 条样本,4 个数值特征对应花萼长宽和花瓣长宽,3 个类别 setosa、versicolor、virginica 各 50 条,分布非常均衡。这个规模对 BP 网络来说属于闭着眼都能收敛的级别,适合用来验证反向传播写没写对。我在第一次拆包时先用鸢尾花把整条链路跑通,确认没有语法和维度错误,再切换到红酒数据。

winequality_data.xlsx 的情况完全不同。它是从 UCI 流传出来的红酒质量数据,特征从 fixed acidity、volatile acidity 一直到 alcohol 共 11 个连续指标,标签 quality 是一个 3 到 9 的整数评分,不是干净的类别标签。如果直接拿原始评分做多分类,等于让 BP 去拟合一个偏回归的评分分布,准确率会很难看。课程作业最常见的做法是重映射成二分类:quality 大于等于 7 记为优质 1,否则记为普通 0;也有老师要求三分类,按低中高拆区间。

这个差异直接决定了后续处理路径:鸢尾花可以用原始标签直接做 one-hot 编码,红酒必须先做数值映射再编码。我第一次跑的时候偷懒,直接拿原始评分当类别,结果准确率在 40% 附近晃荡,排查到最后才发现问题不在网络结构,而在标签定义。所以拿到数据集的第一件事永远是看分布,而不是直接塞进模型。

2.2 pandas读取xls/xlsx:引擎、表头与缺失值处理

资源包同时放了 xls 和 xlsx 两种格式,pandas 读取方式基本一致,但底层引擎不同:老格式 xls 依赖 xlrd,xlsx 依赖 openpyxl。版本不匹配是高频翻车点,后面避坑章节会专门展开。先看标准读取流程:

import pandas as pd df_iris = pd.read_excel('iris_data.xlsx', sheet_name=0) print(df_iris.shape) # (150, 5) print(df_iris.head()) df_wine = pd.read_excel('winequality_data.xlsx') print(df_wine.shape) # 行数取决于文件实际记录数 print(df_wine.info()) # 检查列类型、空值、非数值列

这里 sheet_name=0 表示读取第一个工作表;如果 Excel 里附带说明页,不指定的话 pandas 默认读第一张表,容易读错。df_wine.info() 这一步别省,红酒数据容易出现个别空值和字符串混入,后面做 np.float 转换时会直接抛异常。数据读进来之后,按位置把特征和标签切开:

X_iris = df_iris.iloc[:, :4].values.astype(float) y_iris = df_iris.iloc[:, 4].values X_wine = df_wine.iloc[:, :-1].values.astype(float) y_wine = df_wine.iloc[:, -1].values

逻辑说明:iloc 按位置切片。鸢尾花前 4 列是特征、第 5 列是类别名;红酒前面全是特征,最后一列 quality 是评分。astype(float) 是防御性写法,避免 Excel 把数值单元格存成文本格式,转换失败时优先怀疑有非数值脏数据。

参数说明:.values 把 DataFrame 转成 numpy 数组,后面 BP 的矩阵运算全部依赖这个格式。直接用 DataFrame 参与矩阵乘法,性能和兼容性都会出问题。

2.3 归一化与标签编码:先切分再归一化是铁律

BP 网络配 sigmoid 激活时,输入特征量纲不统一直接影响收敛。红酒数据里 density 在 0.99 附近,alcohol 在 10 附近,差一个数量级。不归一化的话,部分特征会让 sigmoid 提前饱和,梯度消失,损失曲线从头到尾都下不来。归一化和切分的代码如下:

from sklearn.model_selection import train_test_split X_tr, X_te, y_tr, y_te = train_test_split( X_iris, y_iris, test_size=0.3, random_state=42, stratify=y_iris ) mean = X_tr.mean(axis=0) std = X_tr.std(axis=0) X_tr = (X_tr - mean) / std X_te = (X_te - mean) / std

这段代码的核心是顺序:先切分,再用训练集的均值方差去归一化测试集。测试集模拟的是未来新数据,新数据只能沿用训练集的统计量。如果先归一化全量数据再切分,测试集的分布信息就通过均值方差泄露出去了,测试准确率会虚高,答辩时一问一个准。

参数说明:test_size=0.3 是常用比例;random_state=42 固定切分结果,保证多次运行切分一致;stratify=y_iris 保证切分前后各类别比例一致。鸢尾花三类均衡,加不加 stratify 差别不大,但红酒二分类如果不加,可能出现训练集里全是多数类的情况,模型直接学偏。

标签编码用 numpy 一行搞定:

def one_hot(y, n_classes): return np.eye(n_classes)[y.astype(int)] y_tr_oh = one_hot(y_tr, 3) y_te_oh = one_hot(y_te, 3)

红酒数据如果映射成二分类,先执行 y_wine = (y_wine >= 7).astype(int),再传 n_classes=2;三分类就按区间映射成 0/1/2。映射规则建议写成一个独立函数,后面评估阶段要把预测结果从 one-hot 还原回类别,两处共用同一套规则,避免前后不一致。

提示:xls 和 xlsx 虽然文件格式不同,但读取后的 DataFrame 结构完全一致,不需要为两种格式写两套代码。

3. 手写BP网络核心:结构选型、前向传播与反向传播的代码实现

3.1 网络结构选型:输入层、隐藏层与输出层的参数怎么定

动手写代码之前先画一张 BP 神经网络结构图,这对理解后续矩阵维度很有帮助。BP 网络最常用的是三到四层,这份资源里的两个数据集用三层就够:输入层神经元数等于特征数,鸢尾花是 4,红酒是 11;输出层等于类别数,鸢尾花是 3,红酒映射后是 2 或 3。真正需要试的是隐藏层层数和每层神经元数。

常见做法是先用单隐藏层,神经元数取输入层和输出层之间的折中。鸢尾花特征少、类别可分性好,隐藏层 4 到 6 个神经元就够,多了反而在训练集上过拟合,测试准确率往下掉。红酒特征更多,我一般取 8 到 16,再往上对准确率贡献很小,训练时间却明显变长。激活函数选 sigmoid,原因是实验报告的公式推导展示的就是 sigmoid 求导过程;想换成 ReLU 也能跑,但反向传播推导部分会跟你手写的内容对不上。

3.2 前向传播与MSE损失:每一行矩阵运算在做什么

下面是资源里 BP.py 的核心类,我拆开逐段讲。先看初始化和前向传播:

import numpy as np class BP: def __init__(self, n_in, n_hidden, n_out, lr=0.1): self.w1 = np.random.randn(n_in, n_hidden) * 0.1 self.b1 = np.zeros(n_hidden) self.w2 = np.random.randn(n_hidden, n_out) * 0.1 self.b2 = np.zeros(n_out) self.lr = lr def sigmoid(self, x): return 1 / (1 + np.exp(-x)) def forward(self, X): self.z1 = X @ self.w1 + self.b1 self.a1 = self.sigmoid(self.z1) self.z2 = self.a1 @ self.w2 + self.b2 self.a2 = self.sigmoid(self.z2) return self.a2 def mse_loss(self, y_true, y_pred): return np.mean((y_true - y_pred) ** 2)

逻辑说明:init里 np.random.randn 生成标准正态分布权重,乘以 0.1 把初始值压到小尺度,这是防止 sigmoid 饱和的关键一步。forward 里 @ 是矩阵乘法,z1 是隐藏层净输入,sigmoid 之后得到激活值 a1,继续传到输出层得到 z2 和 a2。a2 的 shape 是 (样本数, 类别数),每一行是当前样本在各类别上的预测概率。mse_loss 对预测和 one-hot 标签逐元素求均方差,得到标量损失。

参数说明:n_in、n_hidden、n_out 分别对应三层神经元数,鸢尾花场景实例化是 BP(4, 6, 3, lr=0.1),红酒是 BP(11, 12, 2, lr=0.1)。lr 是学习率,下面反向传播更新权重时会用到。

3.3 反向传播与权重更新:delta公式与梯度下降的落地

反向传播的本质是链式法则。输出层的误差项 delta2 由预测误差乘以 sigmoid 导数得到,隐藏层的 delta1 通过权重转置把误差传回去。

def backward(self, X, y_onehot): m = X.shape[0] delta2 = (self.a2 - y_onehot) * self.a2 * (1 - self.a2) delta1 = (delta2 @ self.w2.T) * self.a1 * (1 - self.a1) self.w2 -= self.lr * (self.a1.T @ delta2) / m self.b2 -= self.lr * delta2.mean(axis=0) self.w1 -= self.lr * (X.T @ delta1) / m self.b1 -= self.lr * delta1.mean(axis=0)

逻辑说明:delta2 里的 (self.a2 - y_onehot) 是预测与真实标签的残差,乘以 a2*(1-a2) 是 sigmoid 在当前输出值上的导数,两者逐元素相乘得到输出层误差信号。delta1 用 delta2 乘 w2 转置得到对隐藏层的反向误差,再乘隐藏层的 sigmoid 导数。更新公式里,a1.T @ delta2 得到的是损失对所有 w2 的梯度,除以 m 取平均,再乘学习率从旧权重里减去,这就是批量梯度下降的标准写法。

参数说明:这里整批训练,m 是训练集样本数;如果改成 mini-batch,m 换成 batch_size,循环里额外做切片。学习率 lr 的取值直接决定收敛速度和稳定性,0.01 到 0.5 之间都有解,但每个数据集的最优区间不同,下一章给一组参考对比。

4. 训练与调参:学习率、隐藏层神经元数与收敛判断的试错记录

4.1 训练循环与损失曲线:什么才算真正收敛

有了 forward 和 backward,训练循环就是重复「前向算损失 → 反向更新参数」,跑够轮数。iris_classify.py 里的训练入口大致是这样的结构:

def train(model, X, y_onehot, epochs=500, verbose=10): loss_history = [] for epoch in range(epochs): pred = model.forward(X) loss = model.mse_loss(y_onehot, pred) model.backward(X, y_onehot) loss_history.append(loss) if epoch % verbose == 0: print(f"epoch {epoch:4d} loss {loss:.6f}") return loss_history

逻辑说明:批量梯度下降下,只要学习率合适,loss 应该逐轮单调下降,最后进入平台期。loss_history 保存每一轮的损失值,后面用 matplotlib 画成曲线,它是判断收敛最直观的手段。

参数说明:epochs=500 对鸢尾花来说 200 轮基本到底;红酒样本更多、特征维度更高,我通常开到 1000 轮左右。verbose=10 是每 10 轮打印一次,避免终端刷屏。更省事的做法是加早停:连续 50 轮 loss 下降幅度小于 1e-4 就 break,不用死等全部轮数。

4.2 参数组合对比:四组常见配置的准确率差异

我在自己机器上把这组参数跑过一遍,结果整理如下,注意这是参考区间而不是标准答案:

数据集隐藏层神经元学习率训练轮数测试准确率
鸢尾花40.130096.7%
鸢尾花60.150097.8%
鸢尾花60.550093.3%
红酒120.1100081.2%
红酒160.1100080.5%

几个结论。鸢尾花在隐藏层 5 到 8 范围内表现都不错,学习率 0.1 明显比 0.5 稳定,0.5 下损失曲线会先冲高再回落,偶尔掉进震荡。红酒准确率到 80% 附近基本到头,原因是质量评分本身带噪声:同一批酒不同品酒师打分可能差 1 到 2 分,BP 学不动这部分随机性。表里的数值受随机种子影响,你自己跑波动 1% 到 2% 都正常,别拿它当验收标准。

提示:调参时一次只改一个变量。先固定隐藏层数把学习率扫一遍,再反过来固定学习率扫隐藏层数,不然出问题根本定位不了是哪个参数引起的。

4.3 分类评估:准确率、混淆矩阵与误分类分析

训练完成后的评估要回答「错在哪」,不能只看准确率。标准的评估代码是这样:

def predict(model, X): out = model.forward(X) return np.argmax(out, axis=1) y_pred = predict(model, X_te) acc = np.mean(y_pred == y_te) print(f"test accuracy = {acc:.4f}") from sklearn.metrics import confusion_matrix print(confusion_matrix(y_te, y_pred))

逻辑说明:np.argmax 取输出层概率最大的下标作为预测类别,和真实标签逐样本比较算准确率。confusion_matrix 的行是真实类、列是预测类,对角线之和就是正确分类数。鸢尾花常见的误分类集中在 versicolor 和 virginica 之间,这两类在花瓣特征上有重叠,BP 很难完全分开,看到这种结果不用慌,属于数据本身的可分性问题。

参数说明:红酒二分类场景下,准确率高不代表模型好。如果优质样本只占 15%,模型全部预测普通类也能拿到 85% 准确率。这时候要看少数类的召回率或者 F1,用 classification_report 输出 precision、recall、f1-score 一组指标,比准确率诚实得多。

5. 避坑排查:BP训练中五个高频翻车点的现象、原因与解法

5.1 损失值不降反升:初始权重与学习率的连锁反应

现象:第一轮 loss 是 0.25,跑 50 轮之后变成 0.38,中间还出现尖峰,损失越来越大。

原因:两个因素叠加。初始权重直接用了 np.random.randn() 没乘系数,初始化尺度偏大;学习率又设成 1.0,梯度步长过大,参数在损失曲面两侧来回弹。权重尺度太大时,sigmoid 输入落在饱和区,梯度趋近于 0,更新完全失效。

解决:把初始权重乘 0.1 或者 0.01,学习率降到 0.1 以下。我之前翻车一次是把权重尺度拉到 5 左右,前向输出全 1,反向梯度全 0,改回小权重初始化立刻恢复。

5.2 准确率震荡不收敛:标签编码与输出层激活不匹配

现象:鸢尾花的 loss 从 0.3 降到 0.1 后就不再下降,准确率在 80% 和 90% 之间来回跳。

原因:最典型的错误是标签没有 one-hot,直接把 0/1/2 整数当真实值去算 MSE。输出层是三元素概率向量,拿向量和单个数字求均方差,梯度方向是错的。另一个相关错误是评估时没用 np.argmax,直接拿输出向量和整数标签比较。

解决:检查 y_tr_oh 的 shape 是 (样本数, 类别数),可以用 print(y_tr_oh[:5]) 确认每行只有一个 1。评估时统一走 predict 函数,保证 argmax 逻辑只写一遍。

5.3 红酒数据集准确率偏低:类别不平衡与质量分数映射

现象:红酒数据训练 1000 轮,测试准确率只有 55%,和瞎猜差不多。

原因:没做标签映射,直接拿原始 quality 评分当多分类类别;或者映射成二分类时阈值定得离谱,正负样本比例严重失衡,模型学会了预测多数类但不学特征。

解决:先执行 df_wine['quality'].value_counts().sort_index() 看评分分布,再定阈值。质量评分中位数通常在 6,取大于等于 7 映射为优质,大约能得到 15% 左右的正样本,这个比例偏但还能训练。如果取大于等于 5,正样本占 80% 以上,模型学不到区分信息。

5.4 测试集准确率虚高:归一化顺序导致的数据泄漏

现象:测试准确率 99%,比训练准确率还高,肉眼判断不合理。

原因:先用全量数据算均值方差做归一化,再切分训练测试集。测试集的统计信息混进了归一化过程,相当于模型提前接触了测试集分布,这不是模型的真实泛化能力。

解决:严格按「先切分,再 fit 训练集统计量,transform 测试集」的顺序执行。这个坑在答辩时最容易被问到,被问「归一化参数从哪里来」回答不上来,前面所有工作都会打折扣。

5.5 xls文件读取报错:xlrd与openpyxl的引擎问题

现象:pd.read_excel('winequality_data.xls') 直接报 XLRDError,提示 Excel xlsx file; not supported。

原因:文件扩展名是 xls,但实际内容是 xlsx 格式;或者本地 xlrd 版本过新,不再支持老版 xls 格式。资源包里还留着 BP.cpython-36.pyc 缓存,说明原环境是 Python 3.6,版本偏新的环境更容易踩这类依赖坑。

解决:先确认文件真实格式,不只看后缀。简单做法是统一指定引擎:pd.read_excel(path, engine='openpyxl') 读 xlsx;真正老的 xls 文件需要装 xlrd 小于 2.0 的版本。你也可以直接用包里自带的 xlsx 版本,绕开这个问题。

6. 验证与存档:把课程作业做成别人也能复跑的实验记录

课程作业交上去之后,最怕答辩时老师让你复现一遍,结果换个机器换次环境结果全变了。让实验可复现不需要复杂工具,三个习惯就够了。

固定随机种子。BP 的权重初始化是随机的,不固定种子跑两次结果就有差异。脚本开头写 np.random.seed(42),或者用自己的学号,结果即可稳定。答辩时被问到随机性,也能直接说清楚种子值。

保存损失曲线和模型参数。训练轮数一多,内存里的模型说没就没。把 loss_history 画成图存 PNG,把 w1、b1、w2、b2 用 np.savez 落盘,之后加载参数直接做预测,不用二次训练:

import matplotlib.pyplot as plt import numpy as np plt.plot(loss_history) plt.xlabel('epoch') plt.ylabel('MSE loss') plt.savefig('loss_curve.png', dpi=150) np.savez('model_params.npz', w1=model.w1, b1=model.b1, w2=model.w2, b2=model.b2) params = np.load('model_params.npz') model.w1, model.b1 = params['w1'], params['b1'] model.w2, model.b2 = params['w2'], params['b2']

逻辑说明:plt.plot 用训练时记录的 loss_history 画收敛曲线,dpi=150 保证报告里插图清晰。np.savez 把四个参数数组存成一个 npz 文件,np.load 按名字取回,三步完成参数持久化。文件名建议带上数据集和参数标识,比如 iris_nhidden6_lr01_loss.png,多个实验放一起不会混淆。

再存一份实验配置。把数据集、隐藏层数、学习率、epoch 数、最终准确率写成 JSON,和曲线图、参数文件放同一目录,下次调参直接读配置对比,不用翻代码回忆:

import json config = { "dataset": "iris", "n_hidden": 6, "lr": 0.1, "epochs": 500, "accuracy": 0.978 } with open("experiment_config.json", "w", encoding="utf-8") as f: json.dump(config, f, indent=2)

参数说明:indent=2 让 JSON 有缩进可读;encoding="utf-8" 防止中文注释乱码。accuracy 用前面 predict 的最终结果写入,统一口径,别手填。

这份资源拆下来之后,BP.py 可以单独抽出复用在其他小数据集上,iris_classify.py 和 winquality_classify.py 是两份数据的完整入口,iris_classify.ipynb 和 wine_classify.ipynb 是 Jupyter 版本,适合逐格看中间变量,实验文档和 PPT 里还带着公式推导,交报告前对着补一遍推导细节就行。

从那以后我每次跑 BP 实验都强制走一遍「固定种子 → 切分 → 归一化 → 训练 → 画曲线 → 存参数 → 写配置」的流程,前后不到十分钟,但任何关于可复现性的提问都能接住。这个习惯帮我避开了不少自己埋的坑,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询