ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于机器学习的糖尿病预测系统实战:从数据清洗到模型部署

基于机器学习的糖尿病预测系统实战:从数据清洗到模型部署 简介基于机器学习实现的糖尿病预测系统是一套面向毕设/课设场景的完整项目包含源代码与配套文档。项目采用Java与Scala混合开发基于Maven构建前端使用JSP和CSS清晰覆盖从数据处理、模型训练到Web展示的完整链路。资源共22个文件以Java、JSP、Scala源码为主辅以XML配置、properties配置文件及CSS样式压缩包仅24KB轻量但结构完整便于快速迁移与二次开发。适合计算机、人工智能等专业的学生用于毕设、课程设计或项目演示也适合初学者学习机器学习在医疗诊断中的应用。代码经测试运行成功答辩平均分达96分目前已有197人学习。下载后打开README或文档说明即可理清项目结构并在此基础上扩展新功能。1. 糖尿病预测系统不是算法比赛是数据工程和模型落地的组合拿到“基于机器学习实现的糖尿病预测系统源代码文档说明”这个标题很多人的第一反应是去找一个现成的分类模型然后跑个准确率出来。但真正把系统从脚本变成能用的预测服务难点从来不在模型本身而在数据质量、特征处理和系统封装这三个环节。糖尿病预测是一个典型的二分类问题但数据里往往存在零值、偏斜分布和特征间相关性问题这些都会让模型的泛化表现与训练指标严重脱节。这篇文章从数据清洗、特征工程、模型选择到接口实现按一线工程师做这类小系统的完整路径来讲。适合两类读者一类是机器学习入门阶段想看看完整项目怎么组织的新手另一类是已经能跑通单机训练但没做过预测服务封装的人。前者可以照着步骤复现后者可以重点看特征处理和模型边界这两个部分。下文所有代码基于 Python 3.9 和主流机器学习库的稳定版本数据集采用公开的 Pima Indians Diabetes 数据集这是业界用得最多的糖尿病预测基准数据之一。整个系统的知识结构可以拆成「数据理解 → 特征质量 → 分类模型 → 系统封装 → 验证与文档」五层。下面按这个顺序逐层展开。2. 数据清洗与特征工程机器学习预测系统的地基是数据质量回归到糖尿病的分类任务上公开数据集里最容易踩的坑是特征列里的非法值在 pandas 里是 NaN但在 Pima 数据集中零值才是真正需要处理的坏数据。比如 Glucose、BloodPressure、SkinThickness、Insulin、BMI 五列的值域在生物医学上都有明确下界出现 0 直接意味着测量缺失而不是真实生理指标。很多初学者把数据读进来直接丢给算法训练拿到一个虚高的 AUC 还以为是模型效果好实际上是模型学了一堆零值特征的模式。import pandas as pd df pd.read_csv(diabetes.csv) # Pima 数据集中 Glucose、BloodPressure 等特征的 0 值应当视为缺失 zero_valued_cols [Glucose, BloodPressure, SkinThickness, Insulin, BMI] for col in zero_valued_cols: df[col] df[col].replace(0, pd.NA)这段代码做的事情是把生物医学上不可能出现的 0 值统一标记为缺失交给后续插补逻辑处理。之所以不直接 drop 行是因为 Pima 数据只有 768 条样本丢一行就丢一份信息对小型数据集来说代价太高。先标记缺失再插补能够保留样本量的同时控制偏差。缺失值插补的常见做法有均值填充、中位数填充和按类别分组填充。糖尿病预测这个场景里按患病标签分组填充比全局填充更稳妥因为不同组别的生理指标分布本来就不一样用全局均值会把两组人的差异抹平一部分。实现上可以用一个循环加groupby().transform()完成代码不复杂但效果差异明显。特征交叉也不可忽视。BMI 与 Glucose 的乘积在高风险人群识别上往往比单独的 BMI 有效因为胰岛素抵抗的生理路径是两者协同作用的。另外Age 和 Pregnancies 也值得做一次交叉年龄偏大且妊娠次数多的人群患病风险会显著提升。这类人工特征虽然简单但对树模型和线性模型都有正向帮助。df[BMI_Glucose] df[BMI] * df[Glucose] df[Age_Pregnancies] df[Age] * df[Pregnancies]数据不平衡问题在 Pima 数据里不算严重正负样本比例大约是 1 比 1.8但如果你把系统用到真实体检数据上不平衡会非常明显。当测试集里负样本占比到了 90% 以上准确率这个指标就没有参考价值了。这时要去看召回率、精确率、F1 和 AUC。过采样方法比如 SMOTE 可以作为应对手段但要注意只能对训练集做绝对不能让合成样本泄漏到验证集里。处理方法上我一般建议做分层划分StratifiedKFold既控制了类别比例又能在交叉验证时保持数据分布的一致性。配合特征缩放所有数值型特征进入模型之前统一做标准化对逻辑回归这类依赖特征尺度的模型尤其重要。3. 模型选择与训练分类器的比较基准不能只看准确率数据准备完毕后进入模型选择阶段。糖尿病预测这个任务从机器学习入门到 Kaggle 竞赛都有大量实践积累比较成熟的方案集中在逻辑回归、随机森林和梯度提升树这三类分类器上。逻辑回归的优势是可解释性强能输出各个特征的权重系数适合医疗场景下需要向使用者交代判断依据的需求。随机森林对特征间非线性关系有天然的适应能力且不需要过度调参。XGBoost 和 LightGBM 则是追求更高精度的常见选择但代价是超参数多调参成本高。在实际项目的做法里先把逻辑回归当作 baseline再拿树模型去比较提升幅度。如果树模型相对逻辑回归的提升不超过两个点医疗场景里我倾向于保留逻辑回归原因是部署和维护成本低出了问题也更容易定位。from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.preprocessing import StandardScaler X df.drop(columns[Outcome]) y df[Outcome] # 先对特征做标准化再走分层五折交叉验证 scaler StandardScaler() X_scaled scaler.fit_transform(X) models { logistic_regression: LogisticRegression(max_iter1000), random_forest: RandomForestClassifier(n_estimators200, random_state42), xgboost: XGBClassifier( n_estimators200, max_depth4, learning_rate0.1, eval_metriclogloss, random_state42 ), } cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) for name, model in models.items(): scores cross_val_score(model, X_scaled, y, cvcv, scoringroc_auc) print(f{name}: AUC {scores.mean():.4f} ± {scores.std():.4f})这段代码的意图是把三个模型放到同一条起跑线上比较用 AUC 而不是准确率作为评分标准因为不平衡数据下准确率会掩盖模型在少数类上的真实表现。StratifiedKFold负责控制折与折之间正负样本比例一致cross_val_score的scoringroc_auc直接输出每一折的 AUC 值最后取均值和标准差。参数层面逻辑回归的max_iter1000保证在标准化后的特征空间里能收敛随机森林里n_estimators200是一个稳定性与计算成本的平衡点超过 500 棵的收益在 768 条样本上几乎可以忽略XGBoost 的max_depth4刻意限制了单棵树的深度配合 0.1 的学习率减少过拟合风险。交叉验证结果出来之后还有一个必须做的步骤查看逻辑回归的特征系数。排序后你会发现 Pregnancies 和 Age 的权重在不同模型之间符号可能不一致这提示特征之间存在共线性。处理方法是计算相关系数矩阵如果某两个特征的相关系数超过 0.7保留其中一个。典型的情况是 SkinThickness 和 BMI 相关性偏高二者分别和胰岛素抵抗相关但信息有重叠。最后把模型在完整训练集上重新拟合一次再对测试集获取最佳阈值。很多模型默认用 0.5 作为分类阈值但糖尿病预测里如果目标是筛查高风险人群调低阈值可以提高召回率同时牺牲一部分精确率。from sklearn.metrics import precision_recall_curve # 获取正类概率而不是直接拿 predict 结果 y_proba xgb_model.predict_proba(X_test)[:, 1] precisions, recalls, thresholds precision_recall_curve(y_test, y_proba) # 找到 recall 不低于 0.85 的最高 precision 对应阈值 valid [t for t, r in zip(thresholds, recalls) if r 0.85] best_threshold max(valid) print(f最佳阈值{best_threshold:.3f})这里的核心思路是predict_proba输出概率后不要急着切 0.5而是结合业务预期去选择操作点。如果系统定位是门诊初筛工具召回率优先如果系统定位是确诊辅助工具精确率优先。这个调阈值的过程往往比换模型更能改变实际的预测表现。4. 预测系统的工程化实现从模型文件到可调用的预测接口模型训练完成后下一步是把模型从 Jupyter Notebook 或者训练脚本里捞出来封装成一个可以被其他系统调用的预测服务。这个环节在 CSDN 上几乎每个糖尿病预测项目源码里都有但大部分质量很差问题集中在三处没有做输入校验、没有加载时的异常处理、没有把预处理逻辑和模型封装在同一套代码里。标准做法是先把训练好的两个关键对象序列化保存特征标准化器和模型本身。标准化器必须和模型同时保存因为预测接口收到的原始输入是未缩放的得先走训练时同一套标准化逻辑再进入模型。序列化格式方面joblib比 pickle 更适合 sklearn 对象因为它对 numpy 数组的内存映射处理更高效。import joblib # 保存训练时的标准化器和模型 joblib.dump(scaler, artifacts/scaler.joblib) joblib.dump(best_model, artifacts/diabetes_model.joblib)保存好模型工件之后用 FastAPI 写一个预测接口是当前最主流的选择之一。FastAPI 自带请求体校验和数据验证比 Flask 少写很多防御代码而且性能在异步场景下也表现更好。接口设计上POST /predict 接收 JSON 格式的 8 个指标字段返回预测结果、风险概率和建议字段。from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field import joblib import numpy as np app FastAPI() # 预先加载模型工件而不是每次请求都读文件 scaler joblib.load(artifacts/scaler.joblib) model joblib.load(artifacts/diabetes_model.joblib) class DiabetesFeatures(BaseModel): pregnancies: int Field(..., ge0, le20) glucose: float Field(..., gt0, le300) blood_pressure: float Field(..., gt0, le200) skin_thickness: float Field(..., ge0, le100) insulin: float Field(..., ge0, le1000) bmi: float Field(..., gt0, le80) diabetes_pedigree: float Field(..., gt0, le3) age: int Field(..., ge1, le120) app.post(/predict) def predict(features: DiabetesFeatures): try: data np.array([[ features.pregnancies, features.glucose, features.blood_pressure, features.skin_thickness, features.insulin, features.bmi, features.diabetes_pedigree, features.age ]]) # 输入数据经过与训练时相同的标准化处理 data_scaled scaler.transform(data) prob model.predict_proba(data_scaled)[0, 1] result int(prob 0.5) return {prediction: result, probability: round(prob, 4)} except Exception as e: raise HTTPException(status_code500, detailstr(e))这段接口实现里有几个关键点值得单独交代。第一Field里的取值范围约束不是摆设实际体检数据里偶尔会出现血糖值等于 0 或者年龄为负值的脏数据接口直接拒绝比模型给出一个错误预测更负责任。第二模型产物在模块加载时通过joblib.load一次性载入内存避免了每次请求都去读磁盘的开销。第三预测概率先返回给调用方而不是只给一个 0/1 标签这样上层系统可以根据自身需要调节判定阈值而不需要重新部署接口。写完接口之后要做的第一个验证是发一个真实请求。用 curl 模拟一遍调用确认字段命名、返回格式和数据管道都通畅。另一个容易被忽略的问题是缺失特征的处理预测接口必须要求调用方传全所有字段不允许模型自己去补。因为在线预测时做缺失插补是一件风险极高的事情训练时的插补逻辑和在线时往往不一致会导致预测分布发生偏移。接口之外还应当生成一个requirements.txt把依赖锁定住。常见的治疗是直接pip freeze requirements.txt这会把一堆与环境相关的包打进去正确做法是手动列出核心依赖并标注主版本区间。numpy1.24,2.0 pandas2.0,3.0 scikit-learn1.3,2.0 xgboost2.0,3.0 fastapi0.104,1.0 uvicorn0.24,1.0 joblib1.3,2.0 pydantic2.4,3.0依赖版本区间控制在一个大版本内是因为 xgboost 2.x 和 1.x 在部分接口上有不兼容调整sklearn 的 API 也在持续变化。不锁版本过半年项目可能根本跑不起来锁太死又会阻碍安全补丁更新。5. 模型验证与文档落地机器学习项目的完整度体现在交付物里模型训练好了、接口也通了这时候才到了「带源代码和文档说明」这个标题真正区别于普通脚本的地方。代码有了还要回答几个问题这个模型到底能不能用边界在哪里后来的人怎么接手衡量维度上除了交叉验证时看的 AUC还需要确定最终模型的混淆矩阵和分类报告。用一个独立测试集来做最终评估计算精确率、召回率、F1-score 和 AUC 四个指标。同时也要检查模型在不同年龄段上的表现差异。糖尿病预测里有一个普遍规律65 岁以上人群的召回率通常高于 40 岁以下人群因为老年人的症状特征更明显而年轻患者的生理指标往往处于临界状态。阈值校准上用前面章节里的precision_recall_curve方法找到适合筛查场景的阈值把这个阈值连同模型、标准化器一起保存成配置文件。这里有一个常见错误是把阈值硬编码在业务代码里换个环境就找不到在哪里改。正确做法是把阈值放进配置文件或者环境变量与模型文件保持同等地位。文档方面一个完整的糖尿病预测系统至少要带三类文档README 说明、训练脚本注释和数据集说明。README 应当写清楚环境依赖、训练命令、启动预测服务的命令、接口字段含义和示例请求。文档的意义在于让第二个接手的人能在 15 分钟内跑通整个流程不需要靠猜。数据集说明里要用表格把 8 个输入特征的名称、单位、正常范围列出这是很多源码包缺失的一环。没有数据字典的机器学习项目就像是没有接口文档的 API使用者只能靠猜。特征字典示例特征名含义单位/范围缺失时的处理建议Pregnancies怀孕次数0-20保留 0 值Glucose口服葡萄糖耐量试验 2 小时血浆葡萄糖浓度40-300 mg/dL0 视为缺失按分组中位数填充BloodPressure舒张压30-200 mmHg0 视为缺失按分组中位数填充SkinThickness三头肌皮褶厚度0-100 mm0 视为缺失按分组中位数填充Insulin2 小时血清胰岛素0-1000 mu U/ml0 视为缺失按分组中位数填充BMI体质指数10-80 kg/m²0 视为缺失按分组中位数填充DiabetesPedigreeFunction糖尿病遗传函数0.1-2.5极少缺失直接保留Age年龄21-120 岁保留原值模型上线后还需要建立一个简单的监控机制。常见做法是记录请求日志每隔一段时间统计输入特征的均值和标准差如果某个特征的分布相比训练集发生了显著偏移说明线上数据和环境已经漂移需要重新训练。这个环节不需要一开始就做得很复杂在日志里定期输出特征摘要即可。验证与交付是一个糖尿病预测系统从能跑到能用的分界线。代码加上文档才构成一个可以让别人复现和接管的完整项目。本文还有配套的精品资源点击获取
返回列表