简介面向机器学习与人工智能方向的糖尿病预测系统毕业设计源码包适用于计算机、人工智能、自动化、电子信息等专业的在校学生、老师或企业员工用于毕设、课设、作业或项目初期演示。代码已经过完整测试并成功运行答辩评审平均分达96分可作为可靠的学习参考也支持在原有基础上修改实现其他功能。包内共22个文件以Java源文件3个java、JSP页面2个jsp、Scala文件、Maven工程文件pom.xml、Properties配置和XML资源文件为主压缩包整体仅24KB结构精简便于快速阅读与部署。目前已有199人学习浏览。内容涵盖核心预测模型实现、前后端交互页面、工程项目配置信息以及IDEA工作区设置可帮助读者快速把握系统模块划分与调用关系理解从数据接入到结果展示的完整流程适合具备一定编程基础的学生作为毕业设计或课程设计的起点。1. 糖尿病预测系统 MoDiabetes一套能跑通机器学习全流程的 Java 参考工程糖尿病预测系统听起来像是专业医学软件实际拆开后会发现它就是一个典型的二分类机器学习工程。MoDiabetes 就是这样一个用 Java 实现的参考项目读取患者体检数据用机器学习算法判断患病风险通过桌面界面展示结果。它解决的核心问题不是代替医生做诊断而是把「数据清洗、特征归一化、模型训练、效果评估」这一整条链路封装成可运行的代码。适合两类读者一类是计算机相关专业要做课程设计或毕业演示范例的学生另一类是基础不错、想快速看到机器学习如何落地的入门工程师。项目结构清晰依赖集中顺着入口代码就能读完训练和预测过程是一个很适合直接下载下来对照着跑的工程。2. 从源码结构到技术选型pom.xml 里的依赖说明答案打开一个没读过源码的工程我习惯先看构建文件。MoDiabetes 是一个 Maven 项目入口不是看 java 文件而是先读 pom.xml 和资源目录的摆放方式。这两样东西能快速告诉你这是一个桌面应用还是 Web 服务用到了什么机器学习框架数据放在哪里。比漫无目的地翻代码高效得多。2.1 项目骨架与构建方式Maven 工程和 UI 设计器留下的线索从工程根目录的文件看.idea 下保存的是集成开发环境的工程配置其中 uiDesigner.xml 明确记录着 Swing 界面是用 IDE 的 UI Designer 插件生成的。这说明 MoDiabetes 的展示层大概率是 Swing 窗口而不是控制台。Maven 项目的标准结构是 src/main 下分 java、resources加上 pom.xml 定义依赖和打包方式。dataSources 目录名容易让人以为是数据库连接但在没有数据库配置文件的情况下它更可能是放置训练数据文件的目录比如 CSV 或 ARFF。Maven 依赖决定了技术栈。对于「糖尿病预测」这种典型的表格分类任务Java 生态里最经济的方案就是用 Weka 机器学习库。Weka 在学术界和教育场景使用广泛内置了数据预处理、分类器、聚类、关联规则、可视化等完整工具链。一个合格的课程设计与其手写矩阵运算去实现逻辑回归不如把 Weka 封装起来把主要精力放在数据清洗和模型参数的解释上——因为评分更看重的是你知不知道数据为什么要清洗、参数为什么要这样设而不是能否从零写出梯度下降。举一个最小依赖的例子dependency groupIdnz.ac.waikato.cms.weka/groupId artifactIdweka-stable/artifactId version3.8.6/version /dependency这段依赖会把 Weka 的 Instances数据集对象、Filter过滤器、Classifier分类器、Evaluation评估器全部带进来。3.8.6 是稳定版中 API 比较保守的版本不容易因为版本升级导致序列化模型不兼容。如果你的 pom 中还有 mysql-connector说明工程支持从数据库读数据MoDiabetes 的 dataSources 目录更像是本地文件数据源因为当前没有看到连接池配置的痕迹。有了依赖之后再去看 src/main 下的类文件心里就会有大致的分类一类是读取数据的工具类一类是训练方法一类是 Swing 窗口类。顺着调用关系读下去很快就能定位到核心训练代码。实际运行前要留意代码里读文件的路径是硬编码还是选择框弹窗我在类似项目里见过路径写死成绝对路径导致别人电脑上直接报错的情况。2.2 机器学习流程与模型选择的底层逻辑为什么先看二分类、再看两个分类器糖尿病预测本质上是二分类输入是体检特征输出是患病或未患病。这种问题在机器学习里是最成熟、最容易解释的。模型选择上有两条路线性模型和非线性模型。线性模型代表是逻辑回归。它把特征线性加权求和后经过 Sigmoid 函数映射到 [0,1] 区间输出可以直接解释为患病概率。公式不复杂P1/(1exp(-(w·xb)))。训练时通过最大似然估计更新权重 w。逻辑回归的优势是可解释性极强每个特征的系数大小直接反映它对患病概率的影响方向和强度。在医学场景里可以拿系数去和临床经验对照所以它是预测类课程设计的默认基线。非线性模型代表是随机森林。它由多棵决策树组成每棵树在训练时用 Bootstrap 抽样生成不同的样本子集并且每次分裂只随机选择一部分特征最后对结果投票。这种 Bagging 机制让它在特征维度不高、样本量不大的表格数据上非常稳健不容易过拟合也不需要像逻辑回归那样把特征归一化到固定尺度。随机森林还能输出特征重要性分数在写课程设计报告时很有用。为什么不选 SVMSVM 调参麻烦核函数、C 值、gamma而且标准 SVM 输出的不是概率做预测解释时不如逻辑回归直观。为什么不选深度学习这种小规模表格数据根本喂不饱深度网络而且训练环境要求高课程设计没必要。整个流程的正确顺序如下加载数据集检查字段类型设置类属性标签列通常是最后一列处理缺失值和异常值比如血糖为 0 的记录视为缺失对数值特征做标准化或归一化随机打乱并划分训练集、测试集训练分类器用交叉验证或测试集评估记录准确率、AUC、混淆矩阵。这套顺序里最容易出错的是第 1 步到第 4 步。很多人加载完 CSV 就直接训练结果发现字段类型不对模型输出一塌糊涂。接下来专门讲数据加载和类标签这个「第一坑」。2.3 数据加载与类标签设置Weka 的 Instances 对象里藏着第一个坑在 Weka 里所有数据都封装在 Instances 对象中。如果你手里的是 ARFF 文件第一行 relation 声明关系名attribute 声明每个字段的名字和类型然后才是数据。工业界更常用 CSV因为 CSV 没有类型声明Weka 只能靠内容猜只要某一列里出现过字符串比如用 NA 表示缺失这一整列都会被认定为 nominal。明明血糖是数值一旦混入一个 NA后边的所有数值计算都会报错。因此我不建议直接用 CSV 喂给训练代码更稳妥的做法是把数据统一整理成 ARFF并在 attribute 里直接声明每个字段的类型relation diabetes attribute Pregnancies numeric attribute Glucose numeric attribute BloodPressure numeric attribute SkinThickness numeric attribute Insulin numeric attribute BMI numeric attribute DiabetesPedigreeFunction numeric attribute Age numeric attribute Outcome {0,1} dataJava 加载 ARFF 的代码很简单import weka.core.Instances; import java.io.BufferedReader; import java.io.FileReader; public class DataLoader { public static void main(String[] args) throws Exception { BufferedReader reader new BufferedReader( new FileReader(data/diabetes.arff)); Instances data new Instances(reader); reader.close(); // 关键告诉 Weka 哪一列是分类标签 data.setClassIndex(data.numAttributes() - 1); System.out.println(数据集大小: data.numInstances()); System.out.println(特征数量: data.numAttributes()); } }setClassIndex 必须在所有预处理之前设置因为很多过滤器会依赖类属性。这里 numAttributes() 返回属性个数减一是让最后一列Outcome作为标签。如果不设置后面训练和评估都会直接报错或结果毫无意义。参数说明构造函数 new Instances(reader) 解析 ARFF/CSV 文件setClassIndex 的参数从 0 开始因此最后一列是 numAttributes() - 1。这个操作是所有后续步骤的前提我习惯把它写成一个独立方法防止每次跑实验都忘。3. 数据预处理与特征归一化糖尿病数据集的四个脏点与处理顺序数据预处理决定了模型的上限算法只是逼近这个上限。这一章从实际数据处理出发把最麻烦的四个点讲清楚类型推断、缺失值、零值、特征量纲。3.1 特征字段与标签定义理解字段含义、类型以及是否需要离散化糖尿病预测数据集虽然版本不同但典型字段是固定的。下面是一张常用字段表字段含义典型范围是否需要处理Pregnancies怀孕次数0~170 为正常不能替换Glucose空腹血糖0~200值为 0 时视为缺失BloodPressure舒张压0~122值为 0 时视为缺失SkinThickness三头肌皮褶厚度0~99值为 0 时视为缺失Insulin2小时血清胰岛素0~846缺失较多处理要谨慎BMI身体质量指数0~67值为 0 时视为缺失DiabetesPedigreeFunction糖尿病遗传函数0.08~2.42数值小建议缩放Age年龄21~81无需特殊处理Outcome标签0 或 10 或 1类属性需设为 nominalMoDiabetes 的 dataSources 里的文件可能有细微差别但处理原则一致先区分数值特征和标签再看有没有「无意义的 0」。所谓无意义的 0指的是医学检验指标在正常情况下不可能为 0。血糖、血压、BMI 为 0 的记录显然缺失或填写错误。如果直接丢给模型这些 0 值会形成一个虚假分布峰值逻辑回归的系数会被带偏。常见做法是把 0 替换成该列的中位数或均值或者直接删除整条样本。替换中位数比均值更稳健不易受其他异常值影响。另外Pregnancies 为 0 表示未怀孕是有意义的不能动。所以写代码时要对每一列单独配置不能一刀切替换。在动手前可以用一段小代码统计哪些列存在零值for (int i 0; i data.numAttributes() - 1; i) { int zeros 0; for (int j 0; j data.numInstances(); j) { if (data.instance(j).value(i) 0.0) { zeros; } } System.out.println(列 data.attribute(i).name() 零值数量: zeros); }这段代码遍历所有非标签列统计每个字段中值为 0 的样本数。输出后你就能决定哪些列需要把 0 当成缺失来替换。注意 Attribute.name() 返回的是 ARFF 中 attribute 声明的名称。3.2 数据分割与归一化参数训练集/测试集比例和 Standardize 的选择在模型训练前有两道工序必须固定下来分割数据集和特征缩放。先说分割。常见做法是 70% 训练、30% 测试或者直接上十折交叉验证。对于几百条样本的数据集十折交叉验证比固定分割更稳定它把数据均分成 10 份轮流 9 份训练 1 份验证最终取 10 次结果的平均。固定分割容易因为抽样不均匀导致某次实验准确率虚高或虚低。用 Java 实现 70/30 分割时要先用随机种子打乱数据再取前 70%// 按 70/30 划分训练集和测试集 Random random new Random(42); // 固定种子结果可复现 data.randomize(random); int trainSize (int) Math.round(data.numInstances() * 0.7); int testSize data.numInstances() - trainSize; Instances train new Instances(data, 0, trainSize); Instances test new Instances(data, trainSize, testSize);new Random(42) 的 42 是种子换一个数字会得到不同划分但同一个种子在不同机器上产生相同的随机序列。这样你在报告里写「随机种子为 42」别人能复现结果。不推荐使用 new Random() 无参构造因为每次运行结果都不同实验结果不可复现。然后是归一化。Weka 提供了两个常用过滤器Normalize 和 Standardize。它们有本质区别过滤器变换公式输出范围适用场景Normalize(x - min) / (max - min)[0,1]已知边界不需要保持方差Standardize(x - mean) / std理论无界逻辑回归、KNN、SVMNormalize 对异常值比较敏感因为 min 和 max 会被极端值拉偏Standardize 用均值和标准差相对稳定。逻辑回归通常用 Standardize因为它让每个特征分布接近标准正态梯度下降收敛更稳。随机森林不需要缩放但如果你要在同一个流程里对比多个模型统一标准化也不会造成负面影响树模型无所谓。具体使用import weka.filters.Filter; import weka.filters.unsupervised.attribute.Standardize; Standardize standardize new Standardize(); standardize.setInputFormat(train); Instances trainStd Filter.useFilter(train, standardize);这段代码先创建 Standardize 过滤器setInputFormat(train) 让过滤器计算训练集的均值和标准差useFilter 生成标准化后的训练集。这里有个关键点测试集不能参与统计量计算否则会有数据泄露。标准做法是训练集上 fit测试集上 transform// 训练集上计算标准化参数测试集复用同一组参数 Instances testStd Filter.useFilter(test, standardize);第二次 useFilter 不会重新计算统计量而是直接应用第一次得到的均值和标准差。这在 Weka 的 Filter 设计里已经处理好了但很多人不知道直接把整个数据集先标准化再切分导致训练集和测试集信息互相污染评估结果虚高。3.3 处理缺失值的完整流程替换中位数比均值更稳Weka 自带一个 ReplaceMissingValues 过滤器它默认用均值填充数值特征、用众数填充标称特征。均值的问题是对异常值敏感比如 Insulin 列有个极端值 846均值会被拉高大量缺失值会被填成偏高的数值。在医学数据里我更倾向用中位数填充。下面的代码演示了如何自定义填充缺失或零值使用中位数import weka.core.Instances; import java.util.Arrays; // 复制一份不修改原对象 Instances processed new Instances(data); for (int i 0; i processed.numAttributes() - 1; i) { if (processed.attribute(i).isNumeric()) { // 提取该列所有非缺失值排序后取中位数 double[] vals processed.attributeToDoubleArray(i); Arrays.sort(vals); double median vals[vals.length / 2]; for (int j 0; j processed.numInstances(); j) { // 把缺失值或 0 替换为中位数需要根据业务决定哪些列可替换 if (processed.instance(j).isMissing(i) || processed.instance(j).value(i) 0.0) { processed.instance(j).setValue(i, median); } } } }代码逻辑先复制数据集然后逐列判断是否为数值类型。attributeToDoubleArray 取出该列所有值排序后取中位数。接着遍历样本把缺失值或值为 0 的样本替换成中位数。注意这段代码将「值为 0」也视为缺失所以只适合血糖、血压、BMI 这种 0 没有医学含义的列。对于 Pregnancies你要在循环里加一个列名判断跳过它。填充完之后再执行 3.2 的分割和标准化整个数据管道就完整了。到这里训练数据已经是干净、标准化的结构化表格可以进入模型训练环节。4. 模型训练与评估用十折交叉验证检验逻辑回归与随机森林模型训练本身在 Weka 里很简单难的是评估方法的理解。这一章先用代码跑通逻辑回归和随机森林然后用交叉验证给出一份可信的成绩单。4.1 在 Java 代码里训练分类器从 Logistic 到 RandomForest 的构建流程Weka 的分类器接口是 Classifier所有算法的 buildClassifier 方法接受 Instances 训练集。先看逻辑回归import weka.classifiers.functions.Logistic; import weka.classifiers.Evaluation; import weka.core.Instances; // train 是已经处理过的训练集 Logistic lr new Logistic(); lr.setRidge(1.0E-8); // 默认正则化系数一般不用改 lr.buildClassifier(train);buildClassifier 执行后lr 对象里保存了训练得到的权重向量。调用 classifyInstance 就能给新样本打标签。setRidge 控制 L2 正则化强度1.0E-8 是 Weka 给出的默认值它在数据量大时可能会出现一些问题但这里保持默认即可。随机森林的构建方式类似import weka.classifiers.trees.RandomForest; RandomForest rf new RandomForest(); rf.setNumIterations(100); // 树的数量 rf.setMaxDepth(0); // 0 表示不限制深度 rf.setNumFeatures(0); // 0 表示使用默认特征数 rf.buildClassifier(train);这三个参数是随机森林最重要的调节旋钮。numIterations 太少容易欠拟合太多训练时间变长100 是通用起点。maxDepth 设为 0意味着每棵树可以长到纯节点在噪声大的数据上容易过拟合可以限制到 20 以内。numFeatures 设为 0 时Weka 自动使用 floor(log2(features)1) 作为每次分裂的随机特征数这是公认的默认经验值。训练完分类器马上用测试集评估。一个完整的两模型对比代码如下// 直接评估逻辑回归 Evaluation evalLR new Evaluation(test); evalLR.evaluateModel(lr, test); System.out.println(逻辑回归准确率: evalLR.pctCorrect()); // 直接评估随机森林 Evaluation evalRF new Evaluation(test); evalRF.evaluateModel(rf, test); System.out.println(随机森林准确率: evalRF.pctCorrect());pctCorrect() 返回正确分类样本的百分比。但这种一次性测试集评估有方差运气好测试集容易准确率就虚高。更靠谱的是十折交叉验证相当于做 10 次训练和评估对结果平均Evaluation cv new Evaluation(data); cv.crossValidateModel(lr, data, 10, new Random(1)); System.out.println(逻辑回归十折准确率: cv.pctCorrect());crossValidateModel 的第一个参数是未训练的 classifier 实例第二个参数是完整数据集第三个是折数第四个是随机种子。它内部自己划分数据不需要手动打乱。十折输出的指标通常比随机 70/30 划分更稳定也常用于课程设计报告。4.2 评估指标与混淆矩阵Accuracy、AUC、F1 到底看哪个很多项目里只打印 Accuracy这不够。糖尿病数据集中正例患病比例通常低于 40%。如果一个模型把所有样本都预测为 0未患病准确率可能到 60% 以上看起来不错实际一个病人都没识别出来。所以不能只看准确率。要输出完整指标用下面代码System.out.println(cv.toClassDetailsString()); System.out.println(cv.toMatrixString()); System.out.println(AUC (class 0): cv.auc(0)); System.out.println(AUC (class 1): cv.auc(1)); System.out.println(加权平均 AUC: cv.weightedAreaUnderROC());toClassDetailsString 打印每个类别的精确率、召回率、F1toMatrixString 打印混淆矩阵cv.auc(1) 得到患病类别的 AUC这是最重要的指标。AUC 的意义是随机取一个患病样本和一个未患病样本模型给患病样本打出更高风险分数的概率。AUC 达到 0.8 以上就有实用价值。混淆矩阵是一个 2x2 表格预测患病预测未患病实际患病TPFN实际未患病FPTN我们要关注的不是对角线总数而是 FN漏诊和 FP误诊的代价。在疾病筛查场景漏诊的代价通常更高宁可把健康人送去做进一步检查也不能把病人放过去。所以如果 FN 过高可以适当下调决策阈值哪怕牺牲一些精确率来换取召回率。在代码层面分类器默认以 0.5 概率阈值决定类别。你可以通过 distributionForInstance 拿到概率后自行改阈值这部分在最后一章会讲到。评估时除了 Acuracy至少要看类别 1 的 AUC 和 F1这两个指标对类别不平衡更鲁棒。5. 避坑与常见问题排查从数据导入到模型导出的五个真实翻车现场这一章记录的是我拆解类似项目时真实踩过的坑每一条都按「现象 → 原因 → 解决」写清楚希望对你有用。5.1 现象CSV 里的 NA 被读成 nominal导致数值运算报错现象加载一份 CSV其中空腹血糖列有少量缺失值写成了 NA在 Weka 的界面里该列变成了标称类型而不是数值类型后续逻辑回归直接拒绝运行报错信息提示属性不支持数值运算。原因Weka 从 CSV 加载数据时没有类型声明它按整列内容推断类型。只要一列中出现 NA 这种非数字字符串整列就会被当成 nominal。即使只有一两个缺失值也会让整列丧失数值语义。解决把数据整理成 ARFF 文件用符号?表示缺失并在 attribute 声明里明确写 numericattribute Glucose numeric attribute Outcome {0,1} data , ?这样加载后 Glucose 就是 numeric缺失值用?表示后续 ReplaceMissingValues 过滤器才能正确处理。如果必须保留 CSV可以先把 NA 替换成空字符串再加载但空字符串同样可能触发类型判断错误所以最稳妥的还是 ARFF。5.2 现象训练时抛出 Cant find class attribute现象调用 evaluateModel 或 buildClassifier 时程序直接抛 IllegalStateException 或提示找不到类属性。原因忘记在数据上调用 setClassIndexWeka 不知道哪一列是标签。这个问题在新建了 Instances 对象后特别常见尤其是从多个数据源拼接数据时新对象不会继承类索引。解决在任何训练代码之前执行if (data.classIndex() -1) { data.setClassIndex(data.numAttributes() - 1); }同时确认类属性类型是 nominal。如果标签列是 0 和 1要在 ARFF 里声明成{0,1}否则 Weka 会把它当作数值回归问题处理。5.3 现象测试集准确率很高但实际预测全是 0现象报告里准确率到了 82%看了混淆矩阵才发现所有患病样本都没被认出来预测结果全是 0。原因数据集类别不平衡正例占比低模型学到的规律偏向多数类。另一个常见原因是训练集和测试集划分没有打乱比如按原始顺序取前 70% 训练、后 30% 测试。如果数据按科室顺序排列测试集分布会和训练集完全不同。解决先打乱再分割固定随机种子观察混淆矩阵而非只看准确率把评估指标换成召回率和 AUC。对于不平衡数据可以尝试对少数类过采样或者使用 Weka 的 CostSensitiveClassifier 调整误分类代价。5.4 现象Swing 界面打开后中文乱码或控件叠在一起现象在集成开发环境里运行 MoDiabetes窗口标题和按钮文字变成乱码或者控件错位重叠看都看不清楚。原因IDE 的 UI Designer 生成代码时使用 UTF-8 编码但运行时默认编码可能是 GBK中文字符解析错误。另一个原因是窗口布局依赖绝对坐标不同系统字体测量偏差导致控件重叠。解决在运行配置的 VM options 里加上-Dfile.encodingUTF-8并确保项目全局编码是 UTF-8。如果还乱码把源码里的中文硬编码改成资源文件 properties统一转码加载。对于布局问题检查 JFrame 是否设置了合适的 LayoutManager比如 BorderLayout 或 GridBagLayout不建议用绝对布局。5.5 现象模型保存后再次加载报类找不到现象训练完用 ObjectOutputStream 把模型写进文件重启项目读取报weka.classifiers.functions.Logistic相关的 ClassNotFoundException。原因Weka 分类器通过 Java 序列化保存反序列化时要求加载序列化文件中的类。如果本地 Weka 版本与训练时不一致或者项目依赖引入了多个版本的 Weka jar类加载器找不到匹配的类。解决统一依赖版本固定 pom.xml 里的 weka 版本保存和加载都使用 Weka 自带的工具类import weka.core.SerializationHelper; // 保存 SerializationHelper.write(diabetes-lr.model, lr); // 加载 Logistic model (Logistic) SerializationHelper.read(diabetes-lr.model);SerializationHelper 是 Weka 官方提供的序列化辅助类比裸用 ObjectOutputStream 更可靠对类路径兼容性做了一些处理。如果仍然报错用mvn dependency:tree排查是否有多个 weka jar 冲突。6. 进阶把模型导出并在新数据上完成批量预测在 MoDiabetes 这样的参考工程里完成训练和评估只是第一步。评审常问的进阶问题是模型训练好了怎么应用到新样本上这里给出一个可以立刻用起来的做法——保存模型写一个独立的预测类接收新记录输出患病概率。完整的预测流程是加载模型 → 加载新数据的结构 → 逐样本调用分布概率 → 解析结果。新数据必须和训练数据拥有相同的属性定义否则概率算出来的位置就是错的。SerializationHelper.write(models/diabetes-lr.model, lr); // 后续任意时刻加载 Logistic clf (Logistic) SerializationHelper.read(models/diabetes-lr.model); // 加载新数据结构和训练集一致 BufferedReader reader new BufferedReader( new FileReader(new_patients.arff)); Instances newData new Instances(reader); newData.setClassIndex(newData.numAttributes() - 1); reader.close(); for (int i 0; i newData.numInstances(); i) { Instance inst newData.instance(i); double pred clf.classifyInstance(inst); double[] dist clf.distributionForInstance(inst); System.out.printf(样本 %d: 预测类别%d, 患病概率%.4f%n, i 1, (int) pred, dist[1]); }classifyInstance 返回的 pred 是 0 或 1对应类标签索引。distributionForInstance 返回长度为 2 的数组dist[0] 是未患病概率dist[1] 是患病概率。如果不想用默认的 0.5 阈值可以自己改比如 dist[1] 大于 0.4 就提示复查这在筛查场景更合理。还有一个细节如果训练时用了 Standardize预测时新样本也必须先过同样的过滤器。你可以把过滤器和模型一起保存或者确保 newData 的数值范围与训练集相当。我在每次做这件事时都会故意把训练集中的样本重新输入到预测脚本里检查输出和训练时的预测是否一致用这个方法可以快速识别特征列顺序、过滤器、类索引不一致的低级错误。在 MoDiabetes 这个工程里dataSources 目录就是放置训练数据和新样本的位置。拿到源码后建议先确认数据文件的路径再跑通这个批量预测脚本。这样后续替换成自己的 CSV 时只需要保证列名和类型一致即可。从那以后我每次做这类预测项目都会强制走一遍「保存模型 加载模型 新样本验证」的流程哪怕只是随机挑三条数据验算一下也能提前暴露特征列顺序、过滤器、类索引不一致的低级错误避免在答辩演示时才翻车。希望这次的项目拆解过程能帮到你。本文还有配套的精品资源点击获取