资讯动态

基于BP神经网络的鲍鱼性别分类:从数据清洗到模型调参的完整实战

发布时间:2026/10/13 12:36:13 来源:尧图企业网站定制
简介这份资源是面向高校学生与机器学习入门者的课程设计级项目包围绕鲍鱼性别分类任务采用Python语言与BP神经网络完成从数据处理到模型训练、评估的完整流程适合用作机器学习期末大作业、课程设计或自学练手。压缩包为zip格式整体约192.11MB内含项目源码与实验报告等文件源码附有注释便于新手理解网络结构、参数设置与训练逻辑。目前已有305人学习下载说明该方案在同类作业中具有一定参考价值。读者可从中获得一套可直接部署运行的分类项目对照实验报告梳理数据预处理、特征选择、模型搭建与结果分析思路并借助注释快速定位关键代码完成调试与二次修改降低大作业从零搭建的门槛。1. 鲍鱼性别分类这个课设到底在练什么拿到「机器学习大作业-基于BP神经网络实现鲍鱼的性别分类」这个题目很多人第一反应是去搜现成源码跑通交差。但如果你真想把这份作业变成简历上能讲的东西得先搞清楚它到底在练什么。鲍鱼数据集Abalone来自UCI原始任务是预测鲍鱼年龄而把它改造成性别分类Infant/Female/Male三分类本质上是一次完整的监督学习流程演练数据清洗、特征工程、标签编码、BP网络搭建、训练调参、评估可视化。它不考验你调库的熟练度考验的是你对「一份脏数据怎么变成可用模型」的理解。适合刚学完机器学习课程、想找一个能从头跑到尾、又不至于太复杂的项目练手的人。下面我按实际做一遍的顺序把每个环节拆开讲。2. 数据到手先别急着喂网络鲍鱼数据集的清洗与特征处理2.1 先看清数据长什么样鲍鱼数据集原始文件通常叫abalone.data没有表头字段依次是Sex、Length、Diameter、Height、Whole weight、Shucked weight、Viscera weight、Shell weight、Rings。原始Sex只有M、F、I三类这正好就是我们要分类的目标。但注意原始任务里Sex是特征Rings才是标签做性别分类时要把这个关系反过来——Sex变成标签Rings可以保留为特征也可以直接丢掉。我一般会保留Rings因为它和性别有一定相关性丢掉反而浪费信息。加载数据时用pandas最省事但因为没有表头得手动指定列名import pandas as pd import numpy as np # 原始文件无表头手动指定列名 columns [Sex, Length, Diameter, Height, Whole weight, Shucked weight, Viscera weight, Shell weight, Rings] df pd.read_csv(abalone.data, namescolumns) # 看一眼分布确认三类是否均衡 print(df[Sex].value_counts()) print(df.describe())这段代码做了两件事一是把无表头数据读成带列名的DataFrame二是用value_counts()和describe()快速摸清类别分布和数值范围。参数上没什么可调的read_csv的names参数是关键不传的话第一行数据会被当成表头吃掉。跑完你会看到I类幼体数量明显多于M和F大概比例是1.3:1:1左右不算严重失衡但后面评估时不能只看准确率。2.2 缺失值和异常值的处理鲍鱼数据集本身缺失值很少但Height字段存在个别为0的记录这在实际测量中不可能——鲍鱼不可能没有高度。这些0值就是异常值直接删掉比填充更安全因为数量极少通常个位数删了不影响整体分布。# Height为0的是测量异常直接剔除 df df[df[Height] 0] # 检查是否还有缺失 print(df.isnull().sum())这里用布尔索引过滤逻辑简单但有效。注意不要用均值填充Height的0值因为0不是缺失是错误记录填充会引入虚假信息。另外Whole weight等重量字段偶尔会出现极端大值可以用IQR方法看一眼但一般不需要处理BP网络对量纲敏感后面统一归一化就行。2.3 标签编码与特征归一化BP神经网络的输出层要做三分类标签必须转成数值。常见做法有两种LabelEncoder转成0/1/2配合softmax输出或者OneHotEncoder转成三维向量配合sigmoid输出。我推荐前者因为softmax交叉熵在三分问题上更稳定代码也更简洁。from sklearn.preprocessing import LabelEncoder, StandardScaler # 标签编码I/F/M - 0/1/2 le LabelEncoder() df[Sex_encoded] le.fit_transform(df[Sex]) print(dict(zip(le.classes_, le.transform(le.classes_)))) # 特征与标签分离 feature_cols [Length, Diameter, Height, Whole weight, Shucked weight, Viscera weight, Shell weight, Rings] X df[feature_cols].values y df[Sex_encoded].values # 标准化BP网络对输入尺度敏感必须做 scaler StandardScaler() X_scaled scaler.fit_transform(X)LabelEncoder按字母顺序编码I0F1M2这个映射关系要记下来后面画混淆矩阵时用得上。StandardScaler做的是z-score标准化把每个特征变成均值0、方差1。注意fit_transform只能用在训练集上测试集要用训练集的scaler做transform否则会数据泄露。这里为了演示先整体做了实际划分训练测试集时要分开处理。3. 用PyTorch搭一个能跑的BP网络结构、参数与训练循环3.1 网络结构怎么定BP网络的核心是全连接层加非线性激活。输入维度是88个特征输出维度是3三个类别。隐藏层设几层、每层多少神经元这是调参的重点。我的经验是这种小数据集一层隐藏层足够神经元数量取输入维度的2到3倍也就是16到24之间。两层隐藏层容易过拟合而且训练慢没必要。import torch import torch.nn as nn class BPNet(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super(BPNet, self).__init__() self.fc1 nn.Linear(input_dim, hidden_dim) self.relu nn.ReLU() self.fc2 nn.Linear(hidden_dim, output_dim) def forward(self, x): x self.fc1(x) x self.relu(x) x self.fc2(x) return x model BPNet(input_dim8, hidden_dim20, output_dim3) print(model)结构很简单输入层到隐藏层用ReLU激活隐藏层到输出层不加激活因为后面接CrossEntropyLoss它内部会做softmax。hidden_dim20是我试出来的一个平衡点设16准确率略低设32开始过拟合。注意输出层不要加softmax否则和CrossEntropyLoss冲突这是新手常翻车的地方。3.2 训练循环与关键参数训练循环里要关注四个东西优化器、学习率、batch size、epoch数。优化器用Adam学习率0.001起步batch size设32或64都行epoch先跑200看收敛曲线。from torch.utils.data import TensorDataset, DataLoader from sklearn.model_selection import train_test_split # 划分训练集和测试集stratify保证类别比例一致 X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.2, random_state42, stratifyy) # 转成Tensor X_train_t torch.FloatTensor(X_train) y_train_t torch.LongTensor(y_train) X_test_t torch.FloatTensor(X_test) y_test_t torch.LongTensor(y_test) train_ds TensorDataset(X_train_t, y_train_t) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) for epoch in range(200): model.train() total_loss 0 for batch_x, batch_y in train_loader: optimizer.zero_grad() output model(batch_x) loss criterion(output, batch_y) loss.backward() optimizer.step() total_loss loss.item() if (epoch 1) % 50 0: print(fEpoch {epoch1}, Loss: {total_loss/len(train_loader):.4f})stratifyy是关键参数保证训练集和测试集里I/F/M的比例和原始数据一致否则测试集可能偏向某一类评估结果不可信。shuffleTrue在每个epoch打乱数据顺序防止模型学到顺序相关的虚假模式。学习率0.001是Adam的经典值如果loss震荡厉害就降到0.0005如果收敛太慢就升到0.002但别超过0.01否则容易发散。3.3 评估与混淆矩阵训练完不能只看准确率三分类问题必须看混淆矩阵否则你不知道模型是把F错分成M还是I。from sklearn.metrics import confusion_matrix, classification_report model.eval() with torch.no_grad(): test_output model(X_test_t) _, predicted torch.max(test_output, 1) pred_np predicted.numpy() print(confusion_matrix(y_test, pred_np)) print(classification_report(y_test, pred_np, target_namesle.classes_))torch.no_grad()关闭梯度计算节省显存并加速推理。torch.max取输出向量最大值对应的索引就是预测类别。classification_report会给出每个类别的precision、recall、f1-score重点看I类的recall因为I类样本最多模型容易偏向它。如果F和M的recall明显低于I说明模型没学好区分成体和幼体需要检查特征里是不是少了关键信息。4. 调参这件事隐藏层、学习率和正则化怎么配合4.1 隐藏层神经元数量的影响我做过一组对比实验固定其他参数只改hidden_dim看测试集准确率hidden_dim测试准确率训练时间秒80.6212160.6514200.6615320.6418640.6125从表里能看出20左右是个拐点再往上准确率反而降这是典型的过拟合。训练时间随神经元增加线性增长但准确率不增反降说明模型容量已经超过数据能支撑的复杂度。我一般会从16开始试每次加4找到验证集准确率最高的点就停。4.2 学习率与batch size的搭配学习率和batch size是耦合的。batch size越大梯度估计越准但更新次数少需要更大的学习率来补偿。反过来batch size小梯度噪声大学习率要小一点防止震荡。我的经验组合是batch_size32配lr0.001batch_size64配lr0.002batch_size16配lr0.0005。如果换了组合发现loss曲线剧烈抖动先把学习率减半试试。# 换一组超参对比 model2 BPNet(input_dim8, hidden_dim20, output_dim3) optimizer2 torch.optim.Adam(model2.parameters(), lr0.002) train_loader2 DataLoader(train_ds, batch_size64, shuffleTrue) # 训练循环同上略注意每次换超参要重新初始化模型不能复用已经训练过的权重否则结果不可比。4.3 加Dropout和L2正则化如果发现训练集准确率远高于测试集比如训练集0.85、测试集0.62那就是过拟合了。最直接的解法是加Dropout层在隐藏层后面随机丢弃一部分神经元。class BPNetWithDropout(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim, dropout_rate0.3): super(BPNetWithDropout, self).__init__() self.fc1 nn.Linear(input_dim, hidden_dim) self.relu nn.ReLU() self.dropout nn.Dropout(dropout_rate) self.fc2 nn.Linear(hidden_dim, output_dim) def forward(self, x): x self.fc1(x) x self.relu(x) x self.dropout(x) x self.fc2(x) return xdropout_rate设0.3到0.5之间太小没效果太大欠拟合。另外可以在优化器里加weight_decay参数做L2正则化Adam(model.parameters(), lr0.001, weight_decay1e-4)这个值不要超过1e-3否则模型学不动。Dropout和L2选一个就行两个一起加容易矫枉过正。5. 避坑与排查鲍鱼性别分类里最容易翻车的五个地方5.1 现象测试准确率永远在0.5左右上不去原因标签没做编码或者编码后类别顺序和评估时对不上。LabelEncoder按字母顺序编码I0、F1、M2但如果你手动映射成F0、M1、I2后面classification_report的target_names顺序就错了看起来准确率正常实际混淆矩阵全乱。解决统一用LabelEncoder并且把le.classes_存下来评估时传给target_names。不要手动写映射字典。5.2 现象训练loss下降但测试loss上升原因过拟合。鲍鱼数据集只有四千多条特征8个模型容量一大就记住训练集了。解决先减hidden_dim从20降到12试试不行再加Dropout(0.3)还不行就加L2正则化。三步走下来基本能压住。5.3 现象Height字段有0值导致标准化后出现极端负值原因前面清洗时漏掉了Height0的记录StandardScaler把0当成正常值标准化后变成很大的负数网络学到一个虚假的强特征。解决在df[df[Height] 0]这一步之后打印一下df[Height].min()确认最小值大于0。如果还有0检查是不是浮点数精度问题用df[df[Height] 1e-6]更保险。5.4 现象换了随机种子结果波动很大原因数据集小train_test_split的随机划分对结果影响大尤其是I类样本多如果测试集里I类偏少准确率会虚高。解决固定random_state42并且用stratifyy保证类别比例。如果还波动做5折交叉验证取平均但课设作业一般固定种子就够了。5.5 现象预测时输入单个样本报维度错误原因训练时输入是二维(batch_size, 8)预测单个样本时给了一维(8,)PyTorch的Linear层要求至少二维。解决用x.unsqueeze(0)把一维扩成(1, 8)预测完再squeeze()回去。或者直接用model(X_test_t)批量预测别一个个喂。6. 把实验报告写出技术含量可视化与结果解读的进阶做法课设实验报告最容易写成流水账加载数据、建模型、跑结果、贴准确率。但如果你想拿高分或者放简历里讲得在可视化和结果解读上做出深度。我一般会加三样东西训练过程loss曲线、混淆矩阵热力图、特征重要性分析。loss曲线用matplotlib画把训练loss和验证loss画在同一张图上能直观看出过拟合从第几个epoch开始。代码很简单import matplotlib.pyplot as plt train_losses [] # 在训练循环里每个epoch append val_losses [] plt.plot(train_losses, labelTrain Loss) plt.plot(val_losses, labelVal Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.legend() plt.savefig(loss_curve.png, dpi150)混淆矩阵热力图用seaborn的heatmap比直接打印数字直观得多。重点看对角线上的值以及F和M之间的误分类。如果F被大量预测成M说明这两个类别在特征空间里重叠严重可以考虑加特征或者换模型。特征重要性分析对BP网络来说没有直接的方法但可以用permutation importance把某一列特征打乱看准确率下降多少下降越多说明该特征越重要。这个分析能帮你解释为什么模型在某些类别上表现差——比如Shell weight被打乱后准确率骤降说明它是区分性别的关键特征而Length可能影响很小。最后实验报告里一定要写清楚你试过哪些参数组合、哪些失败了、为什么失败。比如「hidden_dim64时训练集准确率0.92但测试集只有0.61判断为过拟合最终选用hidden_dim20加Dropout0.3」。这种记录比单纯贴一个最终结果有说服力得多。我自己的习惯是每跑一组实验就在notebook里记一行参数、训练准确率、测试准确率、耗时、备注。最后整理成表格放进报告读者一眼就能看出你的调参逻辑。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑