尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

机器学习糖尿病预测系统:从数据处理到模型部署

机器学习糖尿病预测系统:从数据处理到模型部署 简介一个基于机器学习实现的糖尿病预测系统是面向计算机、人工智能、通信工程、自动化、电子信息等专业的毕业设计源码包适合在校学生、教师或企业开发人员用于毕设、课设、课程作业或项目初期立项演示。系统采用Java与Scala混合开发前端基于JSP与CSS构建展示界面后端由Java逻辑处理与配置文件支撑整体通过Maven管理依赖和构建能够完成从数据预处理、模型训练到预测结果展示的完整闭环。压缩包共22个文件主要文件类型包括xml配置、properties属性文件、java源码、jsp页面、scala脚本和css样式资源大小仅24KB轻量但结构清晰适合快速导入IDE进行学习和二次开发。附带说明文档有助于理解项目设计思路和运行流程。目前已有197人学习下载适合需要参考机器学习Web应用实现的学习者。1. 基于机器学习的糖尿病预测系统从分类实验到可复用交付物你在搜索引擎里搜“基于机器学习实现的糖尿病预测系统源代码文档说明”真正想解决的往往不是某个算法细节而是怎么在有限时间里拿到一整套能跑、能讲、能扩展的交付物。糖尿病预测是一个典型二分类任务但它覆盖了机器学习项目最关键的完整链路原始数据可信度、类别不平衡、模型评估口径、对外接口与可复现文档。Pima 数据集只有 768 条样本和真实临床数据相比很小反而放大了过拟合、指标失真和阈值不当三个问题。这篇文章从数据形态与标签含义入手依次走完特征工程、多模型比对与参数优化、Flask 接口落地全过程最后补上阈值移动和模型解释两个极易被跳过但决定交付质量的步骤。2. 特征口径与预处理糖尿病预测的上限定在数据侧任何一个机器学习预测系统的效果上限都由数据决定模型只是在兑现这个上限。Pima Indians Diabetes 格式的 CSV 是这类项目中常见的训练语料真正需要花时间的地方在于字段怎么解析、缺失值怎么定义、特征数值怎么统一量纲。2.1 字段解析与数据分布诊断Pima 数据集包含 8 个特征和 1 个标签列标签 Outcome 取 0 或 11 表示携带糖尿病阳性诊断。8 个特征分别是 Pregnancies、Glucose、BloodPressure、SkinThickness、Insulin、BMI、DiabetesPedigreeFunction 和 Age每个特征都有明确的临床含义。特征名含义说明医学上允许为 0 吗Glucose口服葡萄糖耐量试验 2 小时血浆葡萄糖浓度不允许BloodPressure舒张压mm Hg不允许SkinThickness肱三头肌皮褶厚度mm不允许Insulin2 小时血清胰岛素mu U/ml不允许BMI体质指数体重 kg / 身高 m 的平方不允许DiabetesPedigreeFunction糖尿病家族遗传函数值允许Pregnancies怀孕次数允许Age年龄岁允许拿到的 CSV 第一件事不是建模而是做基础诊断。pandas 的info()和describe()可以快速暴露读取类型错误和数值异常import pandas as pd df pd.read_csv(data/diabetes.csv) print(df.dtypes) print(df.describe().T)df.dtypes用于确认 8 个特征都按 float/int 解析而不是被误读成 objectdescribe().T把转置后的 min、max、均值与四分位数铺开打印一眼能看到 Glucose、BMI 这类字段的最小值是否为 0。这个 0 是问题的起点上述 5 个“医学上不允许为 0”的字段一旦出现 0 值意味着这是一条缺失记录而不是真实测量值直接用 0 参与建模会同时干扰树模型的分裂点和线性模型的系数估计。2.2 缺失值校正与分层填充策略把医学上不合理的 0 值替换为 NaN再按标签分组填充是糖尿病预测系统预处理里最基础也最重要的一步。整体均值填充会把正样本组偏高的血糖水平拉低导致模型学不到两类之间应有的差异。COLS_ZERO_MISSING [Glucose, BloodPressure, SkinThickness, Insulin, BMI] for col in COLS_ZERO_MISSING: df[col] df[col].replace(0, pd.NA) # 按 Outcome 分层填充保留正负样本各自的真实中位数水平 for col in COLS_ZERO_MISSING: df[col] df.groupby(Outcome)[col].transform( lambda s: s.fillna(s.median()) )第二次循环的逻辑是用groupby(Outcome)把样本按标签分开每个分组内各自用中位数填充然后用transform把填充结果映射回原表。这样患病组和健康组的特征中心互不污染。Insulin 缺失比例偏大如果换成全局中位数正样本的胰岛素水平会被明显拉低后续树模型的特征重要性也随之失真。填充完成后还要观察偏度。Insulin 和 DiabetesPedigreeFunction 都是右偏分布Insulin 在部分样本上达到几百甚至上千的数值直接进入线性模型会主导距离计算。要不要做对数变换可以看偏度系数一般绝对值大于 1.5 时优先考虑 np.log1p 后再标准化。2.3 特征标准化与训练集划分用 Pipeline 规避数据泄漏逻辑回归、SVM、KNN 这类距离敏感的机器学习模型必须做标准化随机森林和 XGBoost 对量纲不敏感但多模型对比时统一走标准化更容易形成公平的对照环境。更关键的是标准化的拟合时机只能从训练集学习均值和方差再应用到测试集否则测试集的信息会间接参与训练。from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression X df.drop(columns[Outcome]) y df[Outcome] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) pipe_lr Pipeline([ (scaler, StandardScaler()), (lr, LogisticRegression(max_iter1000)) ]) pipe_lr.fit(X_train, y_train)stratifyy保证训练和测试集合中正负样本比例一致。768 条样本里阳性约占 35%不做分层抽样时随机切分完全可能把测试集正样本比例抽到 30% 以下后续所有评估指标都建立在一个不稳定基数上这一步对数据量较小的糖尿病预测系统至关重要。将 StandardScaler 放入 Pipelinefit阶段只对训练集计算均值与标准差transform阶段再对测试集套用同一组参数这是防止数据泄漏的常用做法。提示数据预处理阶段的每改动都要对应一次源码级别的备注。后面写文档说明时把“0 值视为缺失并分层中位数填充”这一条写清楚交付可信度直接上一个台阶。3. 模型选型与训练调优先定评估指标再谈分类器准确率机器学习项目最常见的翻车点不是模型选错而是把准确率当作唯一裁判。糖尿病预测的场景里假阴性意味着漏掉一个潜在病人假阳性只会带来一次额外复查两种错误的代价明显不对等评估口径必须和业务方向绑定。3.1 评估指标选择混淆矩阵、精确率、召回率与 F1一个永远预测“健康”的哑巴模型在 Pima 数据上也能拿约 65% 的准确率。要看真实能力至少同时盯住 precision、recall、F1 和混淆矩阵四项。糖尿病筛查场景下我一般优先保 recall宁可把疑似病例圈进来也不要因为追求准确率而漏掉阳性样本。from sklearn.metrics import classification_report, confusion_matrix y_pred pipe_lr.predict(X_test) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, target_names[健康, 患病]))classification_report给出精确率、召回率和 F1 三列指标confusion_matrix返回一个 2x2 矩阵行代表真实标签列代表预测标签。当输出形如[[80, 7], [17, 50]]时左下角的 17 是假阴性样本数正在压垮模型在患病组上的召回率。出现这种情况不要直接换模型先检查阈值。roc_auc 也应该作为第二指标加入文档。AUC 不依赖具体阈值反映的是模型区分正负样本的排序能力适合横向比较多个分类器而 precision 和 recall 则用于回答“某个固定阈值下系统表现如何”这一实际问题。3.2 多模型基线对比逻辑回归、随机森林与 XGBoost对 768 条样本的小数据集我一般同时跑三个分类器作为基线逻辑回归代表线性模型随机森林代表 Bagging 集成XGBoost 代表 Boosting 集成。三个模型的评估指标表放进文档选型依据自然成立。from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier pipe_rf Pipeline([ (scaler, StandardScaler()), (rf, RandomForestClassifier(n_estimators200, random_state42)) ]) pipe_xgb Pipeline([ (scaler, StandardScaler()), (xgb, XGBClassifier( n_estimators200, max_depth3, eval_metriclogloss, random_state42 )) ]) for name, model in [(LR, pipe_lr), (RF, pipe_rf), (XGB, pipe_xgb)]: model.fit(X_train, y_train) y_pred model.predict(X_test) report classification_report(y_test, y_pred, output_dictTrue) print(name, { acc: round(report[accuracy], 4), recall_患病: round(report[患病][recall], 4), f1_患病: round(report[患病][f1-score], 4) })n_estimators200在小样本上已经足够盲目增加到 1000 对性能提升微乎其微只会拖慢训练速度。XGBoost 的max_depth3防止树过深后在小数据上迅速过拟合eval_metriclogloss显式指定评估函数避免不同版本默认值不一致带来告警。如果运行环境装不了 xgboost用 sklearn 自带的GradientBoostingClassifier替代也能得到接近的基线水平。三种模型对比后经常出现的结果是随机森林与 XGBoost 的准确率接近但逻辑回归在患病组上的召回率并不落后全部。树模型在数值特征上会自动做非线性切分而糖尿病数据里多数特征和标签的关系接近线性累积所以线性模型往往是稳定且可解释的兜底选择源码里三条管道并列也让后期替换特征预处理方式更顺手。3.3 网格搜索与交叉验证把调参变成训练流程的一部分默认参数不等于最优参数但调参的目标不是把验证集分数刷到最高而是找到泛化能力与可解释性的平衡。网格搜索是一种朴素但可靠的搜索策略参数组合全面、实现成本低适合数据量不大的场景。from sklearn.model_selection import GridSearchCV param_grid { rf__n_estimators: [100, 200, 300], rf__max_depth: [3, 5, None], rf__min_samples_leaf: [1, 2, 4] } grid GridSearchCV( pipe_rf, param_grid, scoringf1, cv5, n_jobs-1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)scoringf1是关键参数。若默认使用 accuracy 作为搜索目标网格搜索会偏爱把多数类全部判对的模型召回率反而下降。cv5对 768 条样本是一个合理折中折数再多训练集会明显缩小每折模型方差变大n_jobs-1启用全部 CPU 核心并行训练能显著压缩搜索时间。调参方向推荐设置原因树模型最大深度3 至 5小样本下太深容易记住噪声随机森林树数量200 左右再增加收益边际递减训练耗时上升逻辑回归惩罚项L2 默认防止特征系数过大、稳定性更好交叉验证折数5平衡训练集大小与评估方差调参完成后用测试集跑一次最终评估但绝对不能用测试集反馈来反复调整网格参数。测试集只能用来做最后验证每次修改参数都回到交叉验证重新搜索否则得到的分数是过拟合测试集的结果不具备任何交付说服力。4. 系统实现与交付模型持久化、Flask 接口与文档说明的组装方式训练代码只是项目源代码的一部分。让“糖尿病预测系统”变成可运行的交付物至少还需要模型持久化、对外预测接口、依赖版本固定和一篇能根据说明复现环境的文档。4.1 使用 joblib 持久化最优模型训练完成后第一件事是将模型序列化到磁盘。joblib 和 pickle 都可以完成该任务但对于包含 numpy 数组和 sklearn Pipeline 的对象joblib 对内存映射和大型数组的存储效率更高加载速度也更稳定。import joblib # 取网格搜索后的最优模型 best_model grid.best_estimator_ joblib.dump(best_model, models/diabetes_model.joblib) # 冒烟测试加载模型并对原始测试集前 5 条做预测 loaded_model joblib.load(models/diabetes_model.joblib) y_probe loaded_model.predict(X_test.head(5)) print(y_probe)joblib.dump第一个参数是模型对象第二个是落盘路径。保存路径建议显式放到models/目录不要把生成的 .joblib 文件与训练代码混在一起。加载后的predict冒烟测试能确认序列化前后模型行为一致cPickle 或版本变化带来的隐性问题在这一步就会被发现。保存模型还需要同步记录三件事训练数据规模、特征顺序、生成日期。特征顺序尤其容易被忽略训练时 Pipeline 的输入列顺序是[Pregnancies, Glucose, BloodPressure, SkinThickness, Insulin, BMI, DiabetesPedigreeFunction, Age]预测接口接到的输入如果按另外的顺序拼接数组模型不会报错但结果会整体错位这类错误最难排查。4.2 用 Flask 搭建糖尿病预测 APIJSON 请求与响应Flask 是搭建轻量预测接口最直接的工具之一启动快、单文件可运行非常适合做源码交付。接口核心逻辑分为两步服务启动时加载已保存模型收到 POST 请求时把 JSON 中的特征数组按固定顺序拼成二维矩阵传入predict_proba。from flask import Flask, request, jsonify import joblib import numpy as np app Flask(__name__) model joblib.load(models/diabetes_model.joblib) FEATURES [ Pregnancies, Glucose, BloodPressure, SkinThickness, Insulin, BMI, DiabetesPedigreeFunction, Age ] app.route(/predict, methods[POST]) def predict(): data request.get_json() if not data or features not in data: return jsonify({error: missing features key}), 400 values data[features] if len(values) ! len(FEATURES): return jsonify({ error: fexpected {len(FEATURES)} values, got {len(values)} }), 400 x np.array(values, dtypefloat).reshape(1, -1) proba model.predict_proba(x)[0][1] label int(proba 0.5) return jsonify({ prediction: label, probability: round(proba, 4), features: FEATURES }) if __name__ __main__: app.run(host0.0.0.0, port5000)接口中的参数校验不是多余代码。request.get_json()解析请求体后先检查features键是否存在、长度是否为 8任何异常输入都返回 400 和明确错误消息而不是让 numpy 抛类型异常。reshape(1, -1)把一维数组转为 1x8 矩阵这是 sklearnpredict_proba的标准输入格式。返回里的probability是对应标签 1 的预测概率比只返回 0/1 更利于前端展示和后续阈值调整。本地验证接口curl -X POST http://127.0.0.1:5000/predict \ -H Content-Type: application/json \ -d {features: [6, 148, 72, 35, 0, 33.6, 0.627, 50]}返回示例{features: [...], prediction: 1, probability: 0.8912}。这里 features 数组的拼接顺序必须与训练时完全一致一旦错位所有后续调试都会在错误位置上浪费时间。4.3 文档说明的固定结构环境、目录与复现步骤一份合格的 README 不做知识科普只固定项目运行时的事实。我通常按顺序组织四块内容运行依赖、目录结构、启动步骤、输入输出示例。requirements.txt 关键依赖 pandas2.0.3 scikit-learn1.3.0 flask3.0.0 xgboost2.0.0 joblib1.3.2版本号固定比写 latest 可靠。sklearn 从 1.0 到 1.3 有多次默认参数变化xgboost 的 eval_metric 也经历过默认行为变更如果不锁版本接收方在另一台机器上复现时行为可能不一致。文档里直接给一行pip install -r requirements.txt即可。目录树只需要覆盖核心路径data/放原始 CSVmodels/放 joblib 模型文件app.py放 Flask 接口train.py放训练与调参代码。README 里写清“先运行 train.py 生成模型再运行 app.py 启动服务”并用三五行说明各文件职责。文档说明最后的示例请求应直接使用第 4.2 节里的 curl 命令让接收方在不打开代码的情况下也能验证系统已跑通。5. 阈值移动、样本不平衡与 SHAP 解释交付前必须补齐的三块拼图系统能跑通不等于模型决策可靠。把源代码交给别人之前还需要处理三个在训练环节不会显式暴露的问题固定阈值 0.5 是否合理、样本不平衡是否扭曲了评估结论、模型输出能否给出符合医学直觉的解释。5.1 阈值移动用验证集寻找更贴合筛查场景的决策点逻辑回归默认用 0.5 作为概率切分点这对平衡数据集是合理的近似但对不到 35% 阳性率的糖尿病数据0.5 会让模型整体偏向输出“健康”。糖尿病预测系统的业务目标是筛查宁可在概率相持区间多标记出几例阳性也不要让高风险样本从预测缝隙里漏掉。probas pipe_lr.predict_proba(X_test)[:, 1] for t in [0.3, 0.35, 0.4, 0.45, 0.5, 0.55]: y_t (probas t).astype(int) tp ((y_t 1) (y_test 1)).sum() fp ((y_t 1) (y_test 0)).sum() fn ((y_t 0) (y_test 1)).sum() recall tp / (tp fn) if (tp fn) else 0 precision tp / (tp fp) if (tp fp) else 0 print(fthreshold{t:.2f} precision{precision:.3f} recall{recall:.3f})这段代码手动枚举阈值并计算对应的精确率与召回率观察两者随阈值移动的权衡关系。阈值从 0.5 降到 0.35 时召回率上升但精确率下降此时接口返回的prediction判定逻辑也要同步修改为int(proba 0.35)。调试这类接口时最容易出错的地方是 sklearn 1.2 以上版本在加载模型时对特征名做校验如果训练与预测时的列顺序不一致它会直接报错而不是静默运行遇到这类错误先打印loaded_model.feature_names_in_核对字段顺序。5.2 样本不平衡class_weight 与 SMOTE 的取舍Pima 数据 35% 的阳性率属于中间态不平衡真实电子病历场景下阳性率有可能低于 10%那时大多数分类器会滑向多数类。解决途径通常是两条线一是在模型内部通过 class_weight 放大少数类损失权重二是在数据层面用 SMOTE 合成少数类样本。from imblearn.over_sampling import SMOTE sm SMOTE(random_state42) X_train_res, y_train_res sm.fit_resample(X_train, y_train)SMOTE 对特征空间插值生成新样本之后接任何分类器都可以提升正向类覆盖但生成的样本不是真实测量值预估精度会下降且重采样只会对训练集执行验证集和测试集必须保持原始分布。class_weight 与 SMOTE 也不是只能二选一实际项目里可以先用 class_weight 看基线效果不足再引入 SMOTE并同步在文档中记录两次实验结果。SHAP 在这里可以做交叉验证确认重采样后模型的特征方向没有被合成样本扭曲。5.3 用 SHAP 验证特征贡献方向与医学常识的一致性模型可解释性在交付环节的价值是帮助我们建立信任基线。训练完成后用几个关键特征快速做一组对比血糖偏高的样本是否更容易被判为患病BMI 与年龄是否与预测概率正相关。如果模型给出的结论与临床常识相悖问题几乎肯定出在特征编码或缺失值处理而不是什么高级参数没有调优。import shap explainer shap.TreeExplainer(pipe_rf.named_steps[rf]) shap_values explainer.shap_values(X_test[:50]) shap.summary_plot(shap_values, X_test.iloc[:50])TreeExplainer接收随机森林模型结构shap_values给出每个样本下各特征的正负贡献summary_plot的横轴符号表示该特征值对预测结果的推动方向正值代表推向患病类别。如果 Glucose 的贡献方向为负说明该特征值和患病风险成反比和医学知识冲突需要立即回到数据预处理环节排查。SHAP 输出也适合直接截取两张典型样本的力图放进文档作为模型可解释性的辅助材料。这样整套糖尿病预测系统的交付物才真正具备了从代码、模型到诊断结论的完整闭环。本文还有配套的精品资源点击获取
返回列表