BP神经网络分类实验:从鸢尾花到红酒数据集的全流程解析 简介面向机器学习初学者的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 依赖 xlrdxlsx 依赖 openpyxl。版本不匹配是高频翻车点后面避坑章节会专门展开。先看标准读取流程import pandas as pd df_iris pd.read_excel(iris_data.xlsx, sheet_name0) 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_name0 表示读取第一个工作表如果 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_size0.3, random_state42, stratifyy_iris ) mean X_tr.mean(axis0) std X_tr.std(axis0) X_tr (X_tr - mean) / std X_te (X_te - mean) / std这段代码的核心是顺序先切分再用训练集的均值方差去归一化测试集。测试集模拟的是未来新数据新数据只能沿用训练集的统计量。如果先归一化全量数据再切分测试集的分布信息就通过均值方差泄露出去了测试准确率会虚高答辩时一问一个准。参数说明test_size0.3 是常用比例random_state42 固定切分结果保证多次运行切分一致stratifyy_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_classes2三分类就按区间映射成 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, lr0.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, lr0.1)红酒是 BP(11, 12, 2, lr0.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(axis0) self.w1 - self.lr * (X.T delta1) / m self.b1 - self.lr * delta1.mean(axis0)逻辑说明delta2 里的 (self.a2 - y_onehot) 是预测与真实标签的残差乘以 a2*(1-a2) 是 sigmoid 在当前输出值上的导数两者逐元素相乘得到输出层误差信号。delta1 用 delta2 乘 w2 转置得到对隐藏层的反向误差再乘隐藏层的 sigmoid 导数。更新公式里a1.T delta2 得到的是损失对所有 w2 的梯度除以 m 取平均再乘学习率从旧权重里减去这就是批量梯度下降的标准写法。参数说明这里整批训练m 是训练集样本数如果改成 mini-batchm 换成 batch_size循环里额外做切片。学习率 lr 的取值直接决定收敛速度和稳定性0.01 到 0.5 之间都有解但每个数据集的最优区间不同下一章给一组参考对比。4. 训练与调参学习率、隐藏层神经元数与收敛判断的试错记录4.1 训练循环与损失曲线什么才算真正收敛有了 forward 和 backward训练循环就是重复「前向算损失 → 反向更新参数」跑够轮数。iris_classify.py 里的训练入口大致是这样的结构def train(model, X, y_onehot, epochs500, verbose10): 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(fepoch {epoch:4d} loss {loss:.6f}) return loss_history逻辑说明批量梯度下降下只要学习率合适loss 应该逐轮单调下降最后进入平台期。loss_history 保存每一轮的损失值后面用 matplotlib 画成曲线它是判断收敛最直观的手段。参数说明epochs500 对鸢尾花来说 200 轮基本到底红酒样本更多、特征维度更高我通常开到 1000 轮左右。verbose10 是每 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, axis1) y_pred predict(model, X_te) acc np.mean(y_pred y_te) print(ftest 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, engineopenpyxl) 读 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, dpi150) np.savez(model_params.npz, w1model.w1, b1model.b1, w2model.w2, b2model.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 画收敛曲线dpi150 保证报告里插图清晰。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, encodingutf-8) as f: json.dump(config, f, indent2)参数说明indent2 让 JSON 有缩进可读encodingutf-8 防止中文注释乱码。accuracy 用前面 predict 的最终结果写入统一口径别手填。这份资源拆下来之后BP.py 可以单独抽出复用在其他小数据集上iris_classify.py 和 winquality_classify.py 是两份数据的完整入口iris_classify.ipynb 和 wine_classify.ipynb 是 Jupyter 版本适合逐格看中间变量实验文档和 PPT 里还带着公式推导交报告前对着补一遍推导细节就行。从那以后我每次跑 BP 实验都强制走一遍「固定种子 → 切分 → 归一化 → 训练 → 画曲线 → 存参数 → 写配置」的流程前后不到十分钟但任何关于可复现性的提问都能接住。这个习惯帮我避开了不少自己埋的坑希望帮到你。本文还有配套的精品资源点击获取