简介基于机器学习的蘑菇分类项目以python源码为核心附带CSV数据集、Jupyter Notebook交互式分析、项目说明文档和演示PPT构成一套完整且可直接运行的分类任务实践包。面向计算机科学、数据科学与大数据技术、人工智能等专业学生既能支撑课程大作业、毕业设计也适合机器学习初学者开展实战练习。压缩包共5个文件整体仅1.79MB结构清晰main.py负责模型训练与预测mushrooms.csv提供蘑菇特征样本ipynb完整呈现数据探索、预处理与建模过程md说明文档梳理算法原理与调参思路PPT便于成果汇报。项目代码已经测试运行成功使用者对照文档即可复现二分类流程掌握数据清洗、特征编码、模型评估等关键技能并可在此基础上延伸更多分类实验。目前已有84人学习对希望快速获得可参考项目模板的同学具有较高的借鉴价值。1. 基于机器学习的蘑菇分类为什么这个选题适合作为毕设与课程设计课程设计和毕业设计里“基于机器学习”的分类任务很多蘑菇分类是其中门槛适中、又能把完整流程讲透的一个经典选题。它的需求非常直观拿到一份蘑菇外观特征数据判断每条记录对应的是可食用还是有毒。这个任务看起来简单但真正动手时从 csv 读入、缺失值处理、特征编码到模型选择和评估几乎覆盖了 sklearn 的完整主干。很多同学下载源码后都能跑出高准确率可一到答辩就讲不出“为什么”了。这套方案就是用来解决这件事的让源码不再是一个黑匣子而是你能说清每一步设计理由的完整项目。适合毕业设计、课程大作业也适合需要短期交付一个可演示原型的开发者。2. 总体架构设计从 CSV 到预测结果的五步数据流一个完整的蘑菇分类项目不是单个训练脚本就能代表的。它是从数据文件到预测结果的线性流程中间的每一步都值得单独设计。本章先把整体结构讲清楚再落到 main.py 的代码骨架。2.1 文件构成与职责划分每个文件扮演什么角色打开压缩包第一眼是五个文件。很多人只看 main.py却忽略了另外四个这是本末倒置。一次合格的答辩展示需要这四个部分互相配合。文件角色如下表文件/模块 | 在方案中的角色 | 交付时的用途 mushrooms.csv | 原始数据集 | 数据来源说明 Mushrooms_c.ipynb | 探索性分析环境 | 演示特征处理过程 main.py | 工程化训练脚本 | 一键复现最终模型 README.md | 运行说明与字段说明 | 验收文档 Presentation.pptx | 答辩 PPT 素材 | 展示结果与设计思路从这个表可以看出Notebook 和 main.py 是不同的东西Notebook 是开发环境main.py 是交付物。如果答辩老师要求“现场跑一下”你肯定跑 main.py 而不是重新执行 Notebook。所以main.py 的每一段都要能被解释流程要能完整跑通这也是后面几章反复强调工程化的原因。在模块设计上我也建议把代码拆成五个函数对应五步数据流load_data负责读文件preprocess负责清洗和编码train_model负责训练evaluate_model负责指标输出plot_features负责可视化。这样每个函数都短小且可单独测试。实践里很多初次做项目的人把代码全写在if __name__下面一旦某行出错就要从头跑一遍非常浪费时间。拆函数之后可以单独在交互环境调试某一环节而不影响其他部分。还要考虑依赖环境。main.py 运行时需要 pandas、scikit-learn、joblib 等库建议在交付目录里补一个 requirements.txt固定版本范围比如scikit-learn1.0,2.0。蘑菇分类本身不挑版本但 sklearn 的主版本变化会影响 joblib 模型文件的兼容性固定范围更稳妥。这个文件虽然不在原始压缩包里但作为交付补充十分必要也会给老师留下“考虑到了环境复现”的印象。2.2 main.py 骨架入口、随机种子与可复现性main.py 是给验收人看的所以它的结构要符合“可复现”这个最基本的工程要求。所谓可复现就是同一份代码在不同时间多次执行结果几乎一致。要达到这一点最重要的就是固定随机种子。sklearn 中很多环节都有随机性包括train_test_split、随机森林、SVM 的随机初始化如果不固定种子你这次看到准确率 0.98下次可能是 0.97解释起来非常被动。固定随机种子的常规写法是random_state42同时要在train_test_split和模型初始化里都用同一个值。这不是玄学而是让实验可以被复现的基本操作。一个可以照抄的入口结构如下import pandas as pd from sklearn.model_selection import train_test_split, cross_val_score from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix def load_data(path): # 问号也是缺失值必须在这里处理否则会被当成独立类别 df pd.read_csv(path, na_values[?, ]) print(data shape:, df.shape) return df def preprocess(df): # 具体编码逻辑见下一章这里保持函数接口稳定 X pd.get_dummies(df.drop(columns[class]), dummy_naTrue) y (df[class] p).astype(int) # p 表示有毒作为正类 return X, y def make_model(): # random_state 全局固定保证可复现 return RandomForestClassifier(n_estimators100, random_state42) def main(): df load_data(mushrooms.csv) X, y preprocess(df) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy) model make_model() model.fit(X_train, y_train) pred model.predict(X_test) print(classification_report(y_test, pred)) print(confusion_matrix(y_test, pred)) if __name__ __main__: main()这里值得留意的有三处na_values把?转 NaNstratifyy保证训练测试类别比例一致(df[class] p).astype(int)把有毒类显式标为正类。为什么要强调正类因为后面算召回率时默认正类是 1你不会搞混“召回率到底是对谁算的”。我在不少课程设计里都没有看到这个细节但答辩时一旦被提问就很容易露馅。运行方式也很简单在项目根目录执行python main.py。如果不在根目录或者 csv 路径带中文都可能报错。所以 main.py 里最好在开头把工作目录切换到脚本所在路径import os os.chdir(os.path.dirname(os.path.abspath(__file__)))加上这两行后命令行里无论从哪个目录进入都能正确找到mushrooms.csv。这个操作很小但能避免答辩现场最典型的路径翻车。另外建议在 README 里写清楚运行命令避免别人拿到源码后不知道怎么启动。2.3 Notebook 与 main.py 的分工探索、演示和交付Notebook 的价值在于过程透明。你可以把数据展示、空值统计、特征分布、模型对比分成多个 cell每运行一个 cell 就产生一个输出这些输出可以直接截图放进 PPT。main.py 的价值在于结果确定。我一般会遵循这样一个习惯在 Notebook 里一遍遍尝试参数确定最优方案后把最终版本固化到 main.py然后用 Presentation.pptx 呈现“最终结论”。需要注意的是两个环境不要维护两份不同的预处理代码。否则你在 Notebook 里做了一版编码main.py 里又写一遍很容易出现不一致。更好的做法是把公共函数放在一个 utils.py 里两边都 import。在这个压缩包的既定结构里没有独立 utils.py所以你可以在 main.py 定义函数后在 Notebook 里用%run导入或者手动保持同步。对于课程设计来说不建议为了省事而让 Notebook 代码和 main.py 代码各写各的因为答辩前连你自己也分不清哪份是最新的。实际发生过的翻车案例是Notebook 里准确率 99%main.py 里 93%最后发现是一处 OneHot 参数不一致。把公共代码集中才能避免这种结果对不上的情况。如果只交一个 main.py 行不行可以但会失去过程展示。老师更愿意看到你“试错过”先跑逻辑回归发现准确率不够高再换成随机森林最后展示对比结果。这些过程在 Notebook 里有着天然的时间顺序截图放在 PPT 里就是一套完整的实验记录。main.py 承担的是“最终版”Notebook 承担的是“实验过程”两者缺一不可。3. 关键实现特征筛选与数据清洗的实战细节这部分是最值得展开讲的地方。蘑菇分类的数据集大多是表格型特征都是类别属性比如菌盖形状、菌褶颜色、气味、孢子印颜色。如果不理解这些字段含义编码就会出错。而课程设计里老师最关注的正是你如何把“原始 csv”变成“能训练的矩阵”。3.1 先读懂数据用一行代码查看字段与分布拿到数据后不要马上建模。先运行下面几行建立对数据的整体认知。import pandas as pd df pd.read_csv(mushrooms.csv, na_values[?]) print(df.shape) print(df.dtypes) print(df.isnull().sum().sum()) print(df[class].value_counts())这段代码输出三件重要事情样本量和特征数、各列类型、缺失总量以及标签分布。当标签分布严重不均衡时后面要采取加权策略当缺失总量很高时要考虑是否删除列当dtypes全为 object 时说明所有特征都是类别型必须编码。为什么要先看字段语义举个例子odor列可能是决定性特征但它的取值是a、l、p这类字母分别代表茴香、柠檬、腐臭等。如果你不看 README就无法解释“为什么 odor 特征重要性最高”。在答辩时能够说出“有毒蘑菇中腐臭和刺鼻气味占多数”远比“随机森林说它重要”更有说服力。因此README 里这一段说明应该保留并读懂这是项目里最便宜的“背景调研”。3.2 类别特征编码OneHot 与 LabelEncoder 的适用边界蘑菇数据所有特征都是类别但没有哪个类别自带顺序。比如菌盖颜色 brown、black 之间不存在大小关系。如果用LabelEncoder把它们编码成 0、1、2等于强行给模型灌入顺序信息这在逻辑回归和 SVM 中是致命的在树模型中也有风险。所以我给出的编码方案是除了标签列用LabelEncoder转 0/1 之外所有特征列统一用pd.get_dummies做 OneHot。这样每个取值都成为独立的二元列模型学习的是“这个取值是否出现”而不是“这个取值比另一个大”。代码可以这样写def preprocess(df): # 标签p 为有毒记为正类 1e 为可食用记为 0 y (df[class] p).astype(int) # 特征列排除标签列剩余全部做 OneHot X df.drop(columns[class]) X pd.get_dummies(X, dummy_naTrue) # 删除出现次数极少的稀疏列避免过拟合 counts X.sum(axis0) X X.loc[:, counts 10] return X, y注意dummy_naTrue把缺失值作为一个独立类别而不是在 OneHot 时直接丢弃。然后X.sum(axis0)统计每个二元列的正例次数去掉出现次数少于 10 的列。这个阈值不是固定的当数据量是 8000 时10 是一个比较稳的下限如果数据量只有 1000我会降到 5。别小看这一步它能防止一个取值极其罕见的特征列把样本划分到只有一条数据的叶节点从而降低过拟合风险。编码完成后建议打印X.shape看一眼特征维度。通常蘑菇数据会从 22 个原始特征膨胀到 100 个左右的哑变量列。这个数字在答辩时可以主动说出来表示你知道特征膨胀的问题并且做出了控制。如果某个特征有三十多个取值OneHot 后会产生几十列这时候更需要用出现次数过滤来控制维度。3.3 缺失值处理不要把 ? 当成普通字符串很多数据集中缺失值不会以 NaN 形式出现而是用?或空字符串填上。直接pd.read_csv默认会把?当成一个正常类别这样模型会把“缺失”当成一种有意义的属性但它没有任何业务含义。正确的读入方式是df pd.read_csv(mushrooms.csv, na_values[?, NA, N/A])这样把多种缺失标记统一转为 NaN后续可以用df.isnull().sum()查看。处理缺失值不是只有删除这一个选择。当缺失比例低于 5% 时可以考虑删除这些行但当缺失集中于某一列且比例很高时比如超过 20%建议整列删掉。另一种做法也是我常用的是把缺失作为一种状态保留也就是前面代码里的dummy_naTrue。这样模型就能学到“该特征存在缺失”这一信息对预测不一定有害。这里最容易踩的坑是用fillna(unknown)把所有缺失填成 common 字符串。这会制造一个覆盖面很大的xxx_unknown列虽然能跑但会导致特征重要性被这个“假的类别”污染。你会在特征重要性 top 列表里看到很多_unknown列解释起来非常困难。所以缺失量小可以直接删行缺失列比例高可以删列中间状态保留缺失哑变量而不是统一填充。处理缺失值的原则是让“缺失”可被解释而不是被隐藏。3.4 特征重要性分析与筛选让模型结果可解释训练完随机森林后可以输出特征重要性并排序。这是最容易在论文和 PPT 里展示的成果。实现上import pandas as pd from sklearn.ensemble import RandomForestClassifier model RandomForestClassifier(random_state42) model.fit(X_train, y_train) imp pd.Series(model.feature_importances_, indexX_train.columns) imp.sort_values(ascendingFalse).head(10)在这个蘑菇分类问题里排名靠前的特征往往集中在气味相关的哑变量和孢子印颜色上。从业务角度解释很多有毒蘑菇的气味带有腐臭或辛辣味而孢子印颜色在部分毒蘑菇中表现为特定颜色。这样的解释能证明你不是只会调包。注意特征重要性不等于因果性答辩时如果被问到“这个特征排名高一定说明它和毒性有因果吗”可以回答“它说明模型在预测时主要依赖这些特征需要进一步结合生物学知识判断不能直接下因果结论。”这句话非常加分。关于是否要降维我的建议是对于课程设计不需要 PCA因为 PCA 后的维度没有可解释性你无法说清楚第一主成分是什么。使用随机森林特征重要性做筛选比 PCA 更适合表格类别数据。如果还想做消融实验可以只保留 top 10 特征重新训练对比准确率下降多少。这样你能得出“去掉次要特征后性能几乎不变”的结论正好支撑你的特征选择策略。通常这样操作后准确率可能只下降零点几个百分点但特征从上百个减到几十个模型更简洁故事也更好讲。4. 模型训练与评估从基准模型到可解释输出特征准备好后就到了最直观的模型环节。很多同学以为模型越复杂越好但蘑菇分类是典型的结构化小数据任务复杂模型未必占优。合理做法是多个模型对比再选一个解释性最好的作为最终方案。4.1 模型选型与对比三个模型各适合什么蘑菇分类是二分类问题结构化特征且样本量中等因此我一般对比逻辑回归、随机森林、SVM。它们正好代表了线性模型、集成树模型和核方法三种不同思路。逻辑回归优点是训练快、系数可解释适合作为基准随机森林对类别特征友好、有特征重要性适合作为主模型SVM 在特征维度扩展后也能跑出不错结果但训练速度慢且模型不可解释。三者对比表如下模型 | 特征编码要求 | 训练时间 | 可解释性 | 本任务典型表现 逻辑回归 | OneHot 后建议标准化 | 快 | 高系数 | 准确率较高 随机森林 | OneHot 即可 | 中 | 中重要性 | 准确率最高且稳定 SVM(RBF) | OneHot 标准化 | 慢 | 低黑匣子 | 接近随机森林注意随机森林不要求特征标准化因为树模型只用到了特征取值的比较而非距离。逻辑回归在特征维度高时可以加 L2 正则参数C1.0足够。SVM 如果用 RBF 核gamma需要调通常用默认值但如果特征多gamma过大容易过拟合。所以我不建议一上来就选 SVM先跑通逻辑回归和随机森林再做对比。4.2 训练流程分层划分、交叉验证、固定种子训练流程的第二件事是正确划分数据。如果直接train_test_split当类别不均衡时训练集和测试集的类别比例可能不一致影响评估。所以要加stratifyy。再用交叉验证来判断模型稳定性。from sklearn.model_selection import train_test_split, cross_val_score from sklearn.ensemble import RandomForestClassifier X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy) model RandomForestClassifier(n_estimators100, max_depthNone, random_state42) scores cross_val_score(model, X_train, y_train, cv5, scoringaccuracy) print(CV: %.4f ± %.4f % (scores.mean(), scores.std()))在这里max_depthNone表示树不限制深度。对蘑菇数据来说如果特征充足不限制深度有可能得到 100% 的训练准确率但测试集表现依然好。为什么因为数据本身有很强的区分信息这不是过拟合而是真实信号。但为了稳妥还是可以试max_depth10与不限制对比如果差异不大说明结果不依赖深度。交叉验证的±很关键。如果scores.std()超过 0.01说明数据划分对结果影响大。这时要检查类别比例、少数类的样本量以及是否有离群特征。记住单次测试集准确率 0.99 不如“五次交叉验证平均 0.987、标准差 0.003”有说服力后者排除了运气因素。如果还想进一步调优可以加一组网格搜索把关键参数列出来不要求穷举但能说明你尝试过from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [50, 100, 200], max_depth: [None, 10, 20], } grid GridSearchCV(model, param_grid, cv3, scoringaccuracy) grid.fit(X_train, y_train) print(grid.best_params_)网格搜索结果放在 PPT 里比直接说“我用随机森林”更有实验感。但要控制规模参数组合多了训练时间会成倍上涨课程设计里 3×3 候选已经足够。如果搜索出的最佳参数和默认参数结果几乎一样也别惊讶这说明问题本身简单模型不需要复杂调优。4.3 评估指标准确率、精确率、召回率、混淆矩阵蘑菇分类的错误代价不对称把有毒蘑菇预测成可食用是安全级错误把可食用预测成有毒只是经济损失。所以要多看召回率。代码from sklearn.metrics import classification_report, confusion_matrix pred model.predict(X_test) print(classification_report(y_test, pred, target_names[edible, poisonous])) print(confusion_matrix(y_test, pred))解释时注意正类是“poisonous”。召回率表示“真实有毒的蘑菇中被正确识别出的比例”精确率是“模型判断为有毒的样本中确实有毒的比例”。如果生产环境里更看重不漏报应优先保证召回率宁可误报一些可食用蘑菇。在答辩时可以主动说出这个 trade-off表示你考虑了业务场景而不只是刷准确率。另外推荐把混淆矩阵画出来放进 PPT格式为[[TN, FP], [FN, TP]]四格分别标注含义。老师一般会问“左下角和右上角代表什么”能准确回答说明你真的理解评估。不要只在代码里打印数字还要用seaborn.heatmap画成图因为图在 PPT 上的视觉冲击力远大于文本输出。4.4 模型保存与预测pipeline 让演示更稳定模型训练完成后不要只在内存里保存。用 joblib 保存整个 pipeline可以避免现场重新训练和特征对齐问题。import joblib from sklearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder # 将编码与模型封装成一个整体 pipeline Pipeline([ (onehot, OneHotEncoder(handle_unknownignore)), (clf, RandomForestClassifier(n_estimators100, random_state42)) ]) pipeline.fit(X_train, y_train) joblib.dump(pipeline, mushroom_pipeline.joblib)handle_unknownignore很重要当新数据出现训练集里没有的类别时编码器不会报错而是生成全 0 向量预测不会崩溃。这样演示时即使你临时换一条新样本结果也不会受到编码维度不一致的影响。加载后可以直接预测loaded joblib.load(mushroom_pipeline.joblib) sample pd.DataFrame([new_mushroom], columnsfeature_names) print(loaded.predict(sample))需要提醒的是new_mushroom必须保持与原始数据一致的列名和顺序。实际中你的新样本通常只有一行但列名要齐全。另外pipeline 里的 OneHotEncoder 输出的是稀疏矩阵随机森林能直接接受但如果你还想查看特征重要性需要取pipeline.named_steps[onehot].get_feature_names_out()来还原列名否则拿到的索引是对不上的。这些小细节如果遗漏答辩现场调起来时间失控。5. 常见问题与排查这五个坑几乎每个项目都会踩写这一章是因为我把这套方案做过多次每次都会遇到同样的问题。你会发现这些坑和算法本身关系不大更多是不注意工程细节造成的。每一条按“现象—原因—解决”来写可以直接对应到调试报告里。5.1 训练时准确率 100%测试时却大幅度下降现象是模型在训练集上完美测试集上掉到 80%显然容易出现被老师质疑过拟合的局面。原因是特征编码不一致或模型把某个强特征当成了唯一依据。在蘑菇数据中odor和spore-print-color区分度极强如果数据里存在个别缺失值模型在训练时从未见过该缺失类别测试时遇到缺失就会预测错误。解决方法是使用dummy_naTrue保留缺失状态并用 Pipeline 统一处理特征同时用交叉验证观察各折波动如果差异过大考虑剪枝设置max_depth和min_samples_leaf。出现这种现象时先检查训练集和测试集是不是用了同一套预处理这是最常见的根因。先打印两边特征维度是否一致如果不一致多半就是某处单独做了编码。5.2 运行报错 ValueError: could not convert string to float这个错在逻辑回归和 SVM 中很常见原因是 sklearn 直接把object类型特征交给了数值模型。现象是报错指向某一行但你看不出问题所在。原因通常是脚本里漏掉了get_dummies或编码只做在 Notebook 里main.py 没有对应代码。解决方式是检查X_train.dtypes确保全部是数值类型并统一编码逻辑。建议不要用LabelEncoder全部转换因为循环转换后你很难知道每个特征原来的取值对应关系写解释时容易出错。另一个相同报错的原因是train_test_split返回的顺序不对把y_train当成了X_train这种错误比较低级但排查时要敢于怀疑自己。先打印X_train.head()看前几列是不是数值再往后查。5.3 Notebook 运行正常命令行执行却找不到文件或编码错误现象是你在 Notebook 里能正常运行但打开终端执行python main.py时抛 FileNotFoundError 或 UnicodeDecodeError。原因有两点第一Notebook 的当前工作目录可能已经是数据集所在目录而命令行进入的是脚本所在目录之外的路径第二csv 可能是 GBK 编码而默认读取是 UTF-8。解决方法是两种都处理在 main.py 开头用os.chdir(os.path.dirname(os.path.abspath(__file__)))固定工作目录读取 csv 时先尝试 UTF-8如果抛 UnicodeDecodeError 则改为encodinggbk。一个更稳的做法是用errorsignore但我不建议因为会静默丢弃内容后面结果错了都发现不了。定位时可以直接加一句print(os.getcwd())看当前目录是不是项目根目录通常这一步就能找出问题。5.4 模型预测整体偏向可食用类别有毒样本被漏掉现象是混淆矩阵中“有毒样本被预测为可食用”的数量很多。原因通常是类别不均衡或模型对少数类样本不敏感。解决方法是先看class列的value_counts如果可食用类占 90% 以上则必须在划分数据时用stratifyy并在模型中加入class_weightbalanced。加入之后模型会给少数类更大权重召回率会明显提高。在报告里呈现时可以对比加入权重前后的混淆矩阵。这一结果比准确率上升更能打动老师因为说明你考虑了实际风险。如果加入权重后召回率上升但精确率下降明显可以把阈值往有毒类偏移一点用predict_proba调整判定边界而不是简单取 0.5。这种“阈值调整”是答辩中的一个亮点比单纯调参更能体现思考深度。5.5 模型文件在答辩现场加载失败现象是现场运行加载代码时 FileNotFoundError 或 ModuleNotFoundError。原因包括模型文件没拷贝到演示机、sklearn 版本不一致、当前路径不对。解决方法是提交目录中包含.joblib文件并写进 README 的目录清单在演示准备阶段先单独验证一次加载脚本不要等答辩时再加载。如果担心版本不兼容的问题可以在代码中打印sklearn.__version__确保与训练环境一致。更保险的做法是在 README 中给出pip freeze requirements.txt的步骤并在答辩前用虚拟环境全新安装后再跑一遍。加载报错时先检查文件是否存在再检查版本按这两步走基本都能解决。这些都属于“后悔药”花十分钟能省掉现场半小时的尴尬。6. 验收与答辩让项目经得起追问的四个准备一份源码交出去之前应该先过一遍验收而不是能出结果就行。我会把验收拆成三步第一源码验收在干净环境执行 main.py能够复现 README 中写的准确率并且生成预测结果第二文档验收README 写清楚环境依赖、运行命令、数据字段说明和常见报错PPT 覆盖了背景、数据、流程、结果和总结五块第三演示验收用已保存的 pipeline 对三行新数据做预测结果和预期一致。三步走完才叫“可以交付”。答辩时老师最常问的四类问题是为什么选这个数据集为什么用这个模型特征怎么处理的能不能继续优化。回答时不要背报告要用结果举证。比如问到特征可以说“气味相关哑变量在特征重要性排名中占前三位原始数据中气味为腐臭的样本绝大多数对应有毒类因此模型重点学习了这一信号”。如果被问到“为什么不用深度学习”回答“表格型小数据下集成树模型解释性更好、训练成本低深度学习更擅长图像和文本用在结构化特征上优势不明显”。在演示时请先跑一次完整的python main.py再展示模型加载预测最后翻到 PPT 的混淆矩阵结果。如果你已经采纳了前面的建议那么“固定随机种子、分层划分、交叉验证、pipeline 保存”这些词都可以自然出现在你的答辩台词里。我自己的习惯是不管时间多紧答辩前都会强制走一遍“删除所有输出 → 重新跑 main.py → 加载 joblib 预测新样本”的流程。因为之前有过一次现场演示时模型加载失败的翻车经历从那以后我再也不敢省略这一步。希望这份基于机器学习的蘑菇分类方案能帮到你祝答辩顺利。本文还有配套的精品资源点击获取