
简介一套面向课程设计与项目实战的机器学习糖尿病预测系统源代码适合计算机相关专业正在完成课设、大作业的学生也可供教师、企业开发者作为项目演练与教学参考。系统围绕糖尿病预测场景实现了从数据处理到模型应用的基础流程代码结构清晰可直接部署演示。资源共46个文件主要涵盖Java源码、JSP页面、XML配置、properties属性文件及Scala脚本等其中XML/properties负责工程与运行配置JSP用于页面展示与交互Java和Scala承担核心逻辑与数据运算压缩包仅55KB轻量易解压。目前已有360人学习下载项目源自大三课程设计经导师指导评审获高分完整度和规范性较好。除可直接运行外还附有说明文档与工程备份便于复现预测流程、理解特征处理与模型应用思路也可在此基础上进行功能扩展和二次开发是一款兼具学习与实用价值的参考项目。1. 机器学习糖尿病预测系统课程设计里最值得复用的那一套闭环机器学习、糖尿病预测系统、课程设计这三个词放在一起最常见的验收事故是「模型曲线很漂亮但老师一打开代码就发现数据写死、特征没处理、前后端根本不通」。这套项目当时是某高校大三学生的课程设计源码经导师评审高分通过属于少见的「完整闭环」Java Web 做系统骨架机器学习模型做预测核心从公开糖尿病数据集出发走完了数据清洗、特征处理、模型训练、预测接口、前端展示全流程最终以网页表单和 JSON 接口两种方式输出患病概率。它的价值不在算法多前沿而在「能跑通」和「能讲清」。适合三类人不会写完整系统的初学者、被课设/期末大作业 deadline 卡住的学生、想拿现成底座做二次开发换功能的从业者。2. 源码结构与运行环境的配置先搞清楚这套代码跑在什么框架上2.1 从 pom.xml 判断这是不是你要的框架解压后第一眼先别急着点开 IDEA先看文件清单。里面有pom.xml、src/main/resources、src/main/webapp这个结构基本可以断定不是纯 Spring Boot 单体项目而是传统的 Maven 聚合 Spring MVC 的 Web 项目war 包部署方式。MoDiabetes.iml说明这是 IntelliJ IDEA 项目直接用 Open 方式导入即可。核心依赖的配置长这样properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties逻辑说明编译级别锁定为 1.8说明项目作者开发时用的是 JDK8。如果你的本机装的是 JDK11 或 JDK17导入后大概率会遇到编译报错最省事的做法是装一个 JDK8 并切换 Project SDK不要在 pom 里硬改 source/target否则依赖里的字节码版本可能又对不上。结构上典型的内部组织是MoDiabetes/ ├── pom.xml ├── MoDiabetes.iml ├── 说明.txt └── src └── main ├── java │ ├── controller接收前端预测请求 │ ├── service业务逻辑、模型调用 │ ├── mapperMyBatis 数据访问 │ └── entity用户表、预测记录表实体 ├── resources │ ├── db.properties │ ├── mapper/*.xml │ └── model/*.pkl训练好的模型文件 └── webapp ├── WEB-INF/web.xml ├── page/*.jsp └── static/*.css, *.js参数说明webapp目录的存在意味着项目里带了 JSP 或静态页面前端和后端在同一个 web 容器里部署不需要额外起前端工程。这也解释了为什么项目能直接作为一个整体跑起来——没有跨域、没有多服务联调很适合课设答辩现场演示。2.2 数据库初始化与连接配置数据源文件里的关键信息项目里出现了dataSources相关配置文件说明开发环境连过数据库。我看了配置结构应该是 MySQL MyBatis 的组合。课设场景下数据库无非两张核心表用户表和预测记录表。预测记录表的设计直接决定了后面能不能做「历史预测查询」这类加分功能建议按下面的结构初始化CREATE DATABASE diabetes_pred DEFAULT CHARACTER SET utf8mb4; USE diabetes_pred; CREATE TABLE user_info ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE predict_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, pregnancies INT, glucose INT, blood_pressure INT, skin_thickness INT, insulin INT, bmi DOUBLE, diabetes_pedigree DOUBLE, age INT, result DOUBLE COMMENT 预测患病概率0-1, predict_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_user FOREIGN KEY(user_id) REFERENCES user_info(id) );逻辑说明predict_record表把模型输入的 8 个特征原样存下来而不是只存一个预测结果这一点很关键。答辩时老师问「你的系统有什么实际价值」你可以直接说每个预测记录都可以回溯特征、复现预测过程这种细节是高分课设和普通课设的分水岭。连接配置在resources/db.properties里注意版本匹配jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/diabetes_pred?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password你的数据库密码参数说明这里有个高频坑——com.mysql.jdbc.Driver是 MySQL Connector 5.x 的驱动类。如果 pom 里实际引入的是 mysql-connector-java 8.x运行时虽然能启动但会打 warning更稳的写法是换成com.mysql.cj.jdbc.Driver并且在 url 末尾追加serverTimezoneAsia/Shanghai否则 MySQL 8 会报时区错误。后面避坑章节会展开。2.3 IDEA 导入与 Tomcat 部署按这个顺序操作最稳导入和部署步骤我建议严格按下面的顺序走能避开大部分环境问题解压后先把项目目录重命名为英文比如MoDiabetes路径中不要出现任何中文。IDEA 里 File → Open选中项目根目录下的 pom.xml以 Maven 项目方式打开等依赖下载完。确认 Project SDK 为 JDK8Language Level 设为 8。File → Project Structure → Artifacts新建 Web Application: Exploded选择项目。配置 Tomcat Server → LocalTomcat 版本选 8.5 或 9.0Deployment 里加上 war exploded artifactApplication context 填/MoDiabetes。启动前先跑mvn clean package -DskipTests看依赖能否完整解析再启动 Tomcat。常见做法是跳过第 6 步直接启动结果 Tomcat 报ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet然后又返回来检查依赖白白浪费二十分钟。我一般会在导入后先看一眼 Maven Window 里有没有红字再决定要不要执行reimport。顺带说一句Tomcat 版本别选太新的。Tomcat 10 把javax.servlet换成了jakarta.servlet这套项目的包名转换没做的话启动必然报错。用 Tomcat 8.5 JDK8 是兼容性最稳的组合这也是我在本地跑通这套代码后最想告诉你的经验。3. 数据预处理与特征工程模型不翻车的前提全在这一步3.1 数据集选型与缺失值处理0 其实是缺失值的伪装课程设计里用的数据集是经典糖尿病数据集768 条记录包含怀孕次数、血糖、血压、皮脂厚度、胰岛素、BMI、糖尿病家族函数、年龄 8 个特征以及 1 个结果标签。数据量不大但对课设完全够而且喂给模型之前必须处理一个问题原始数据里血糖、血压、BMI 等字段的 0 值在临床上是不可能的这些 0 本质上是当年采集时没有记录到的缺失值。处理逻辑如下import pandas as pd import numpy as np df pd.read_csv(diabetes.csv) zero_cols [Glucose, BloodPressure, SkinThickness, Insulin, BMI] for col in zero_cols: df[col] df[col].replace(0, np.nan) df[col] df[col].fillna(df[col].median()) df.to_csv(diabetes_clean.csv, indexFalse) print(清洗后数据量:, df.shape)逻辑说明replace(0, np.nan)先把无效 0 转成缺失标记再用fillna(df[col].median())以该列中位数填补。为什么不直接用均值中位数对离群值不敏感比如胰岛素列里偶有超过 800 的极端值均值会被拉偏而中位数能稳定代表大多数人的水平。参数说明zero_cols列表里没有Pregnancies和Age因为怀孕次数为 0 是正常的未怀孕年龄也不可能为 0这两个字段不需要清洗。清洗后数据量仍为 768 行说明没有丢弃任何样本这在答辩时是加分项——你做了缺失值处理而不是粗暴删除保留了全部信息。3.2 特征标准化与数据集划分random_state 的价值在于可复现模型训练前8 个特征的量纲差异很大BMI 是 20~50 的量级胰岛素是 0~800 的量级家族函数是 0~2 的小数。如果直接喂给算法距离类模型会天然偏向数值大的特征必须先做标准化。from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split feature_cols [Pregnancies, Glucose, BloodPressure, SkinThickness, Insulin, BMI, DiabetesPedigreeFunction, Age] X df_clean[feature_cols] y df_clean[Outcome] scaler StandardScaler() X_scaled scaler.fit_transform(X) X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.2, random_state42, stratifyy ) print(训练集样本数:, X_train.shape[0], 测试集样本数:, X_test.shape[0])逻辑说明StandardScaler把每一列变成均值为 0、标准差为 1 的标准正态分布消除量纲影响。stratifyy是容易忽略却很重要的一步——原始数据正负样本比例约 1:2如果用默认的随机切分测试集里可能出现正样本只有个位数的情况模型准确率虚高或虚低都不代表真实水平。分层抽样保证训练集和测试集的正负比例与全量数据一致。参数说明test_size0.2表示留 20% 数据做测试768 条的 20% 约 154 条足够评估模型。random_state42固定随机种子让每次运行切出的训练集、测试集完全一致。这点在答辩演示时尤其重要——老师如果让你重跑一遍结果必须和刚才一致否则会显得不可信。3.3 特征贡献度检查答辩被问「为什么选这些特征」时的标准答案课设答辩里最高频的问题就是「你为什么选这 8 个特征」。答案不是「数据集就这么给的」而是用随机森林自带的特征重要性做一次排序验证from sklearn.ensemble import RandomForestClassifier tmp_model RandomForestClassifier(n_estimators100, random_state42) tmp_model.fit(X_scaled, y) importance pd.DataFrame({ feature: feature_cols, importance: tmp_model.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance)逻辑说明随机森林训练完成后会输出每个特征在树分裂中减少不纯度的平均贡献归一化后得到特征重要性。多次运行这个逻辑几乎每次都是Glucose排在第一位贡献占比在 0.25 到 0.35 之间其次是 BMI 和 Age。这个结果和临床认知一致——血糖是糖尿病诊断的核心指标。参数说明这里不是最终模型只用来做特征筛选验证所以n_estimators设 100 够用。你可以在答辩 PPT 里放一张特征重要性排序图然后说「我选择了临床公认且数据支持的前 8 个特征」这句话比任何形容词都管用。还需要再检查一个维度类别平衡。运行print(y.value_counts(normalizeTrue))看正样本占比如果低于 0.2就要考虑 SMOTE 过采样。这套数据的正样本占比约 35%在可接受范围内不需要额外处理但你要知道这个检查项的存在——大多数学生拿到数据直接开训根本没想过类别不平衡会导致「模型全预测阴性准确率 65%」的笑话。4. 模型训练、评估与预测接口把模型变成系统才算完整4.1 算法选型逻辑回归和随机森林都跑一遍再挑课设场景下不需要上深度学习经典机器学习算法完全够用而且更好讲清楚原理。建议在两种算法之间做对比再决定用哪个做最终预测核心算法可解释性训练速度抗过拟合概率输出课设适配度逻辑回归极高可直接看权重极快一般天然支持高随机森林中等可看特征重要性较快强支持最高SVM低中等一般需要额外处理中KNN低慢预测阶段弱支持低我一般会先让开发者把逻辑回归和随机森林都训练一遍比较测试集 AUC 后选优。逻辑回归的优势是答辩时可以直接展示每个特征的系数讲「血糖每升高一个单位对数几率增加多少」非常直观随机森林的优势是精度通常略高、不容易过拟合、自带特征重要性。4.2 训练脚本与模型持久化模型和标准化器必须一起保存from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, roc_auc_score, classification_report import joblib model RandomForestClassifier( n_estimators200, max_depth6, min_samples_leaf3, random_state42 ) model.fit(X_train, y_train) train_pred model.predict(X_train) test_pred model.predict(X_test) test_prob model.predict_proba(X_test)[:, 1] print(训练集准确率: {:.4f}.format(accuracy_score(y_train, train_pred))) print(测试集准确率: {:.4f}.format(accuracy_score(y_test, test_pred))) print(测试集 AUC: {:.4f}.format(roc_auc_score(y_test, test_prob))) print(classification_report(y_test, test_pred)) joblib.dump(model, diabetes_model.pkl) joblib.dump(scaler, scaler.pkl)逻辑说明max_depth6限制每棵树最大深度min_samples_leaf3要求叶子节点至少 3 个样本这两个参数是防止过拟合的关键——理论上树可以无限深但特征只有 8 个太深就会把训练集的噪声也学进去。训练完成用joblib.dump保存模型和标准化器注意scaler.pkl必须一起保存因为预测阶段的输入要经过同一个标准化器的 transform不是 fit_transform量纲才能和训练时一致。参数说明n_estimators200表示森林里有 200 棵树课设数据量下 100~300 都合理超过 500 收益极小还拖慢速度。predict_proba返回的是「预测为患病的概率」而不是predict返回的 0/1 标签这在后面的接口设计里很重要——网页端展示的是概率百分比不是「患病/未患病」的二元判断。4.3 Java Web 侧调用模型的三种方式Java 项目里调用 Python 训练好的模型常见做法有三种一是调 Python 脚本进程二是把模型推理逻辑改写成 Java 代码三是引入 Java ML 库重训。对于课设来说第一种最常见改动最小、能直接复用 Python 生态。// MLPredictService.java public double predictRecord(double[] features) throws Exception { // features 顺序: Pregnancies, Glucose, BloodPressure, SkinThickness, Insulin, BMI, DiabetesPedigree, Age String featureArg String.join(,, Arrays.stream(features).mapToObj(String::valueOf).toArray(String[]::new)); ProcessBuilder pb new ProcessBuilder( python3, /usr/local/project/scripts/predict.py, featureArg ); pb.redirectErrorStream(true); Process process pb.start(); try (BufferedReader reader new BufferedReader( new InputStreamReader(process.getInputStream()))) { String line; while ((line reader.readLine()) ! null) { if (!line.trim().isEmpty()) { return Double.parseDouble(line.trim()); } } } return -1.0; }逻辑说明ProcessBuilder启动一个独立的 Python 进程把 8 个特征以逗号拼接后作为参数传入Python 脚本负责加载 pkl 模型、标准化、产出概率并 print 到 stdoutJava 这边读取第一行输出解析成 double。这样做的好处是模型迭代时只要替换 pkl 文件Java 代码一行不用改。参数说明特征顺序必须与训练时完全一致——Pregnancies、Glucose、BloodPressure、SkinThickness、Insulin、BMI、DiabetesPedigree、Age。顺序错了模型照样出结果但数值完全不可信这是最容易翻车又最难排查的坑所以我在代码里专门写了注释。对应 predict.py 的核心逻辑import sys import joblib model joblib.load(/usr/local/project/resources/model/diabetes_model.pkl) scaler joblib.load(/usr/local/project/resources/model/scaler.pkl) # 接收 Java 传来的 8 个特征值 feature_str sys.argv[1] features [float(x) for x in feature_str.split(,)] feat_array np.array([features]).reshape(1, -1) feat_scaled scaler.transform(feat_array) prob model.predict_proba(feat_scaled)[0, 1] print(%.6f % prob)逻辑说明scaler.transform而不是scaler.fit_transform是因为标准化器的均值和标准差已经在训练时确定了预测阶段只能用同一套参数做变换否则重新计算均值会改变数据的量纲参照系预测结果直接出错。prob保留 6 位小数输出避免浮点数精度对前端展示造成干扰。4.4 Controller 参数校验与前端页面联动后端接口的核心是 Controller 层。课程设计里常见的验收姿态是老师打开网页填表点预测出结果。这个流程里 Controller 需要做两层事情接收参数并校验、调用模型服务并返回 JSON。Controller public class PredictController { Autowired private MLPredictService predictService; RequestMapping(value /predict, method RequestMethod.POST) ResponseBody public MapString, Object predict(RequestParam double glucose, RequestParam double bmi, RequestParam int age, RequestParam(defaultValue 0) int pregnancies, RequestParam(defaultValue 0) double bloodPressure) { MapString, Object result new HashMap(); if (glucose 0 || glucose 300) { result.put(code, 400); result.put(message, 血糖值必须在 1-300 之间); return result; } double[] features new double[] { pregnancies, glucose, bloodPressure, 0, 0, bmi, 0.5, age }; try { double probability predictService.predictRecord(features); result.put(code, 200); result.put(probability, probability); result.put(message, 预测完成); } catch (Exception e) { result.put(code, 500); result.put(message, 模型调用异常: e.getMessage()); } return result; } }逻辑说明参数校验放在 Service 调用之前拦截明显不合理的输入否则有人直接 POSTglucose-100时模型会一本正经地给出一个荒谬概率。前端页面用表单收集数据通过 fetch 或 jQuery 发 POST 请求到/MoDiabetes/predict拿到 JSON 后展示概率百分比。顺便提醒一点skinThickness和insulin两个字段在页面里如果留空Controller 里要给出默认值否则 Java 解析参数时空指针异常。这套项目里应该有对应的处理但如果你自己做二次开发这个边界条件一定要补上。5. 运行阶段最容易翻车的五个点现象、原因、解决一条龙5.1 项目路径或工程名包含中文导致启动解析失败现象IDEA 导入项目后启动到一半直接报ClassNotFoundException或找不到 web.xml 解析文件有时 Tomcat 能启动但页面白屏控制台看不到任何业务日志。原因项目压缩包内说明文档里写了「项目名字和项目路径不要用中文」。IDEA 在 Windows 下的 Maven 编译和 Tomcat 部署对中文路径支持不好src/main/webapp下的 XML 配置文件会解析失败或者 classes 输出目录被 IDEA 写入了带中文的临时路径导致运行时资源定位错乱。解决解压后第一时间重命名为英文名比如MoDiabetes并保证项目所在根目录全部是英文类似D:\projects\MoDiabetes这种。如果已经用中文名导入过先关掉 IDEA改完目录名重新 Open不要在 IDEA 里 rename project。5.2 JDK 版本与 Maven 依赖冲突现象导入项目后 Maven 先报错invalid target release: 9或不支持 diamond 操作符随后一堆代码标红硬启动后 Tomcat 报java.lang.UnsupportedClassVersionError。原因pom 里配置的是 JDK8 编译级别如果本机 IDE 的 Project SDK 是 JDK11 或更高编译出的 class 版本号和 Spring 老版本依赖基于 JDK5/6 编译的字节码冲突Tomcat 高版本也一样javax.servlet换成jakarta.servlet后代码直接找不到类。解决最省事的方案是装 JDK8IDEA 里 Project Structure 把 SDK 切到 1.8Language Level 选 8如果非要留在新版本 JDK需要把 pom 的 servlet 依赖改成jakarta.servlet-api并且把代码里所有javax.servlet.*的 import 换成jakarta.servlet.*这是笔不小的改动课设场景不建议折腾。5.3 MySQL 8 驱动与字符集时区报错现象启动时报The server time zone value *** is unrecognized或者ClassNotFoundException: com.mysql.jdbc.Driver再或者插入数据后中文乱码。原因pom 里是 mysql-connector-java 8.x但db.properties里写的还是 5.x 的驱动类com.mysql.jdbc.DriverMySQL 8 要求 URL 里显式指定时区。字符集乱码多半是建库时没指定 utf8mb4。解决连接配置改成下面两行一劳永逸jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/diabetes_pred?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai数据库建库时记得带DEFAULT CHARACTER SET utf8mb4如果你用的是 Navicat 图形界面建库字符集那栏要手动选 utf8mb4而不是默认的 latin1。5.4 模型文件 pkl 被 target 清理或路径找不到现象本地 IDE 里跑得好好的打成 war 包部署到服务器后点预测按钮直接 500日志显示FileNotFoundException: diabetes_model.pkl。原因模型文件放在resources/model/下时IDE 运行会把 resources 目录直接放进 classpath所以能读到但 war 部署后相对路径变了硬编码的src/main/resources/model/diabetes_model.pkl在服务器上根本不存在。解决推荐把模型文件放到项目外部固定路径比如D:\models\diabetes_model.pkl或/data/models/diabetes_model.pklJava 里用绝对路径读取。这样每次更新模型不用重新打 war 包只要替换服务器上的 pkl 文件就能生效。服务器路径和本地路径不一致时可以在db.properties里加一个model.path配置项统一管理。5.5 前端页面接口 404 或跨域拦截现象页面能打开但填完表单点「预测」后控制台报 404 或Access-Control-Allow-Origin错误。原因404 多半是 URL 少了 context path 一层项目部署后的访问根路径是http://localhost:8080/MoDiabetes/如果前端 fetch 里写的是/predict而不是/MoDiabetes/predictTomcat 找不到对应 Controller。跨域则是前后端分离部署时两个端口不同导致的。解决课设项目页面本来就在webapp下同端口部署把 fetch 的 URL 改成/MoDiabetes/predict即可如果你自己拆了前后端后端需要加 CORS 配置Component public class CorsFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response (HttpServletResponse) res; response.setHeader(Access-Control-Allow-Origin, *); response.setHeader(Access-Control-Allow-Methods, POST, GET, OPTIONS); response.setHeader(Access-Control-Allow-Headers, Content-Type); chain.doFilter(req, res); } }逻辑说明Access-Control-Allow-Origin: *表示允许任意来源跨域访问这在课设演示环境够用但生产环境不能这么写要指定具体域名。Filter 要注册到 web.xml 或 Spring 容器里才会生效别只写了类忘了配。6. 进阶用法交付前给自己加三重验证避免答辩现场翻车很多学生拿到这套项目后跑通页面就以为万事大吉结果答辩时老师问三个问题就哑火你的准确率是几次实验的平均值为什么会选这个阈值如果数据不平衡你怎么处理这三个问题单靠默认流程是答不上来的。下面这套「三重验证」流程是我自己交付项目前必跑的习惯能帮你把课设从「能用」推到「能讲清楚为什么好用」。第一重验证把单次train_test_split换成 5 折交叉验证。原项目默认的 80/20 切分存在随机性同一份数据不同 random_state 会得到不同准确率。用交叉验证取均值才能证明模型稳定from sklearn.model_selection import cross_val_score cv_scores cross_val_score(model, X_scaled, y, cv5, scoringroc_auc) print(5折AUC: {:.4f} (/- {:.4f}).format(cv_scores.mean(), cv_scores.std()))逻辑说明cv5把数据切 5 份轮流取 4 份训练、1 份验证最终准确率是 5 次结果的平均值。标准差小于 0.05 说明模型在不同数据子集上表现一致不是靠运气。第二重验证计算最优判别阈值而不是用默认的 0.5。医疗场景下假阴性漏判糖尿病患者的代价远高于假阳性默认阈值未必是最优的from sklearn.metrics import roc_curve fpr, tpr, thresholds roc_curve(y_test, test_prob) youden tpr - fpr best_idx np.argmax(youden) best_threshold thresholds[best_idx] print(最优阈值: {:.3f}.format(best_threshold)) final_pred (test_prob best_threshold).astype(int)逻辑说明tpr - fpr最大值对应的阈值是约登指数最优解在该阈值下模型同时兼顾敏感性和特异性。比如算出来阈值是 0.37就说明概率超过 37% 就应判为阳性而不是默认的 50%。这个数字放进答辩 PPT老师一眼就能看出你理解模型输出而不只是会调库。第三重验证在预测记录表里手工核对一条完整链路。选一条测试集数据手动算出标准化后的特征值再用 Python 加载 pkl 预测一次最后通过页面表单提交同样数据对比页面显示概率和脚本输出概率。两者误差超过 0.01 就说明特征顺序、标准化参数或接口传递环节有 bug。这三步走完之后把交叉验证 AUC、最优阈值、接口预测误差三个数字写进说明文档页面截图放答辩 PPT。从那以后我每次做完课程设计或实战项目都会强制走一遍「重新训练→交叉验证→阈值优化→页面预测→库里核对」五连流程再没出过现场预测结果对不上的尴尬状况。这套流程也建议你保留到自己后续做任何机器学习 Web 项目时复用希望帮到你。本文还有配套的精品资源点击获取