资讯动态

基于Python的血糖预测项目全解析:从特征工程到模型融合

发布时间:2026/9/13 13:32:46 来源:尧图企业网站定制
简介基于Python实现的人工智能辅助糖尿病遗传风险预测项目面向Python学习者、毕业设计及期末大作业场景利用性别、体检日期、血常规、肾功能等临床指标预测血糖值。资源共38个文件以28个Python脚本为主体覆盖LightGBM、SVR、KMeans岭回归、朴素贝叶斯、简单神经网络等多种回归与分类模型另有2个Jupyter Notebook用于基础分析与离线实验2个CSV数据文件、2张特征重要性图表和Markdown文档说明整体仅6.67MB便于下载与快速部署。目前已有85人学习下载。读者可从中系统掌握赛题数据的预处理与特征工程、多模型交叉验证与调参、特征重要性分析、集成学习与模型融合等完整思路同时文档说明和批处理启动脚本可帮助在Windows下快速打开Notebook或运行代码降低复现门槛。项目目录结构清晰各模块按功能拆分既适合作为课程设计或期末大作业参考也适合对医疗健康领域人工智能应用感兴趣的开发者深入实践与二次开发。1. 从一份体检数据到血糖预测拆解一个可直接运行的 Python 项目体检报告上血糖值后面往往只有一个箭头但真正决定这个数值的是血常规、肾功能和性别共同作用的结果。基于 Python 实现的人工智能辅助糖尿病遗传风险预测源码包就围绕这个场景提供了完整预测管线数据放在 data/add_datasets特征工程在 feature.py 和 feature_relativity.py模型层有 LightGBM、CatBoost、SVR、ElasticNet、贝叶斯回归和简单神经网络集成层有 lgb_nn_ensembling.py 和 lgb_cvensembling.py连离线/在线拆分的评估脚本也齐了。对正在做毕业设计或期末大作业的人来说这份资源胜在可拆可改而不是只能整套跑通。下面先看它如何从原始字段中构造有效特征。2. 特征工程与基线建模从体检字段到特征重要性排序2.1 字段类型与缺失值处理先看数据组织。训练数据每一行对应一条病人体检记录字段包括性别、体检日期、血常规与肾功能检查项最后一列是血糖值。直接把这些字段塞进模型通常结果很差原因有三个日期字段是时间对象不能被树模型直接使用连续指标存在缺失尤其肾小球滤过率这类检查不是每项体检都开量纲差异大肾功能项目的数值范围可以比血常规差几个数量级。项目里 variables.py 的作用就是统一字段名feature.py 负责把原始表转成模型输入。一个常见做法是先把日期转成距基准日天数import pandas as pd import numpy as np def load_raw(path): df pd.read_csv(path) y df[血糖值].astype(float) X df.drop(columns[血糖值]) # 体检日期转成数值距数据集最早日期的天数 if 体检日期 in X.columns: d pd.to_datetime(X[体检日期]) base d.min() X[days_since_first] (d - base).dt.days X X.drop(columns[体检日期]) # 连续变量缺失值用中位数填充避免极端体检值拉偏均值 num_cols X.select_dtypes(include[np.number]).columns for c in num_cols: X[c] X[c].fillna(X[c].median()) # 性别是最重要的分组变量保留文本形式等拆分模型时直接过滤 return X, y关键参数是缺失值填充方式。中位数比均值抗异常原因是体检数据里肌酐、尿酸这类指标偶尔会出现录入错误比如多打一位数均值会被带偏中位数几乎不受影响。日期字段转成数值后还要注意体检日期跨度越大days_since_first 的分布越宽后续如果做 LGB 可以直接喂但做 SVR 前必须标准化。这里最容易被忽略的字段是体检日期很多人会直接删掉但在竞赛类数据中不同批次的体检数据采集标准可能有差异日期可以反映这种批次漂移所以保留成天数是有意义的。2.2 相关性分析feature_relativity.py 的筛选逻辑基础分析脚本 basic_analysis.ipynb 会先做单变量相关性。feature_relativity.py 则更进一步把每个特征与血糖值的 Pearson 相关、Spearman 相关都算一遍再对高相关特征做去冗余。对于体检数据Spearman 比 Pearson 更可靠因为很多指标不是线性关系比如白细胞计数与血糖的关系只在偏高区间才明显。下表总结了这类数据中常见字段的处理方式这也是阅读源码时对照的索引字段类别示例字段建议处理备注类别性别保留原值后续按性别拆分模型不能直接 0/1 编码后丢弃时间体检日期转为距基准日天数保留批次漂移信息连续血常规、肾功能指标中位数填充 异常值截断避免均值被录入错误带偏目标血糖值回归任务直接使用分类脚本按阈值二值化阈值建议取 7.0 mmol/LPearson 相关性筛选代码写法很简单关键是要把相关性结果和业务常识对照from scipy.stats import spearmanr corr_dict {} for c in X.columns: if X[c].dtype np.number: corr_dict[c] spearmanr(X[c], y).statistic corr_series pd.Series(corr_dict).sort_values() print(corr_series.head(10)) print(corr_series.tail(10))这段代码只输出相关系数真正有效的工作是对 tail 里的字段做共线性检查。比如尿素氮和肌酐都是肾功能指标两者相关系数经常超过 0.6同时放进线性模型会造成系数不稳。feature_relativity.py 的另一个输出就是特征间相关性矩阵用于剔除相关系数大于 0.8 的冗余字段。2.3 特征重要性普通树与条件推断树ctree的差别项目里 feature_importance_tree.csv 和 feature_importance_ctree.csv 分别是两种算法计算的特征重要性。第一种是 LightGBM 等树模型自带的 importance按分裂时平方误差的减少量累计。第二种 ctree全称 conditional inference tree在节点分裂前会先做独立性检验再选检验 p 值最小的变量分裂因此不偏好取值多的字段。在 Python 中复现 ctree 效果最方便的方式是用 sklearn 的 permutation_importancefrom lightgbm import LGBMRegressor from sklearn.inspection import permutation_importance model LGBMRegressor(n_estimators300, learning_rate0.05, random_state42) model.fit(X_train, y_train) # n_repeats 越大重要性估计越稳但耗时随特征数线性增长 perm permutation_importance( model, X_val, y_val, n_repeats20, scoringneg_mean_absolute_error, random_state42 ) for i in perm.importances_mean.argsort()[::-1]: print(X_val.columns[i], perm.importances_mean[i])这里用验证集而非训练集算置换重要性能避免树模型对训练集过拟合导致的重要性虚高。feature_importance_tree.png 和 feature_importance_ctree.png 是两张柱状图对照着看就能发现普通重要性可能把某个连续字段排得很高而 ctree 重要性会重新排序把性别、年龄这类低频取值字段的贡献体现出来。如果发现两种排序差异很大说明模型选出的高重要字段里有相当一部分是随机噪声需要在后续模型矩阵里用带正则的模型再验证一次。特征重要性只解决哪些字段有用的问题不能解决它们如何组合的问题后者正是下一章多模型矩阵要处理的。3. 模型矩阵LightGBM、SVR、ElasticNet 与贝叶斯回归的适用边界3.1 为什么同一份数据要跑六七个脚本源码包根目录下有一批让人眼花缭乱的文件lgb_classification.py、simple_lgb_cv.py、lgb_svr_cv.py、simple_cat_cv.py、simple_nn_cv.py、svr_cv.py、kmeans_ridge.py、kmeans_bayesian.py。这不是无意义的堆砌而是针对血糖回归的不同假设。树模型能捕捉特征交互比如肌酐对血糖的影响在低龄人群更明显线性模型在数据量小的时候更稳定能给出系数解释SVR 对局部非线性的拟合能力强但对量纲敏感。多模型交叉验证的真正目的是看不同结构的模型是否在同一批样本上犯相似的错。如果 LGB 和 SVR 在训练集上都表现好验证集都差那说明数据分布漂移如果一个好一个差则值得做集成。3.2 lgb_elastic_net_cv.py用弹性网先筛特征再交给 LightGBM这个脚本的逻辑很有代表性。ElasticNet 在 Lasso 的 L1 正则之外又加了 L2 正则适合特征数多且彼此相关的场景。体检指标恰好满足这两点字段数可能上百尿素氮和肌酐又高度相关。ElasticNetCV 会先把一部分回归系数压缩到 0再让 LightGBM 在剩余特征上训练能明显降低树模型对噪声字段的过拟合。from sklearn.linear_model import ElasticNetCV from lightgbm import LGBMRegressor from sklearn.model_selection import train_test_split X_tr, X_va, y_tr, y_va train_test_split( X, y, test_size0.2, random_state42 ) # l1_ratio 接近 1 时更接近 Lasso接近 0 时更接近 Ridge enet ElasticNetCV( l1_ratio[0.1, 0.5, 0.7, 0.9], alphas[0.001, 0.01, 0.1, 1.0], cv5, max_iter10000, random_state42 ) enet.fit(X_tr, y_tr) selected np.abs(enet.coef_) 1e-5 print(保留特征数:, selected.sum()) lgb LGBMRegressor( n_estimators500, learning_rate0.03, num_leaves31, reg_alpha0.5, reg_lambda1.0, random_state42 ) lgb.fit(X_tr.loc[:, selected], y_tr)注意 ElasticNetCV 的 l1_ratio 与 alpha 的交互。l1_ratio 决定惩罚项偏 L1 还是 L2alpha 决定正则整体强度。体检数据中字段量级差异大alpha 太大把有效特征也压成 0太小又起不到筛选作用。我处理这类数据时一般把 alpha 范围放宽到 [0.001, 10] 做对数均匀分布再用 CV 结果看保留特征数量是否稳定。如果 5 折 CV 选出的特征每次差很多说明特征本身就不稳应当回到上一章去处理共线性。3.3 SVR、CatBoost 与贝叶斯回归的调参要点svr_cv.py 的调参重点不是先调 C 和 gamma而是先做标准化。RBF 核的 gamma 对输入量纲极其敏感肾功能指标动辄几十血常规指标是个位数不标准化的话 gamma 会被大数值字段主导。下面表格整理了源码中几类模型在血糖预测场景里的核心参数和常见陷阱脚本模型关键参数常见陷阱svr_cv.pySVRC: [1,10,100], gamma: 0.01~0.1, epsilon: 0.1~0.5忘记 StandardScalergamma 失效simple_lgb_cv.pyLightGBMnum_leaves: 15~63, learning_rate: 0.03~0.1num_leaves 太大导致局部过拟合simple_cat_cv.pyCatBoostdepth: 6~8, l2_leaf_reg: 3~10类别特征不声明 cat_features 会被当数值kmeans_bayesian.pyBayesianRidgealpha_1, alpha_2 先验先验设置与字段量级不匹配simple_nn_cv.pyMLPhidden_layer_sizes, early_stopping未做标准化时收敛极慢CatBoost 脚本最容易踩坑的是性别字段。如果不把它在 fit 时通过 cat_features 参数声明CatBoost 会把男女按字符串排序映射成数值虽然树模型能处理但分裂点会丧失语义。SVR 的标准做法是from sklearn.preprocessing import StandardScaler from sklearn.svm import SVR from sklearn.model_selection import GridSearchCV scaler StandardScaler() X_tr_s scaler.fit_transform(X_tr) X_va_s scaler.transform(X_va) param_grid { C: [1, 10, 50], gamma: [0.01, 0.05, 0.1], epsilon: [0.05, 0.1, 0.2] } svr GridSearchCV( SVR(kernelrbf), param_grid, cv5, scoringneg_mean_absolute_error, n_jobs-1 ) svr.fit(X_tr_s, y_tr) print(best:, svr.best_params_)逻辑说明标准化必须在划分训练集之后 fit只对训练集计算均值和方差再 transform 验证集这是防止数据泄漏的最基本动作。GridSearchCV 的 scoring 选了负 MAE和医疗场景更贴近因为它不放大个别离群误差避免因为某个极端血糖值影响参数选择。体检数据样本量通常不大网格搜索在 60 组参数内可以接受。4. 性别拆分与模型融合从 KMeans 分组到 LGBNN 集成4.1 为什么 splittedgender_*.py 能降低误差在体检指标中性别不是传统意义上的弱特征而是划分参考区间的强先验。男性与女性在肌酐、血红蛋白、尿酸等指标上的分布差异显著统一模型的回归系数会落在一个不自然的均衡点上对男性偏低对女性偏高。splittedgender_svr.py、splittedgender_lgb.py、splittedgender_lasso.py、splittedgender_bayesian.py 直接按性别拆成两套数据各自训练最后合并预测结果。这种做法的收益是模型复杂度不增加但每个子模型的拟合目标更纯。拆分代码很短但要注意三个细节df_m df[df[性别] 男] df_f df[df[性别] 女] # 分别建模 def train_gender(df_sub): y_sub df_sub[血糖值] X_sub df_sub.drop(columns[血糖值, 性别]) # 这里不能用 df 整体的填充值要在子集上重新计算 X_sub X_sub.fillna(X_sub.median()) return X_sub, y_sub X_m, y_m train_gender(df_m) X_f, y_f train_gender(df_f)第一个细节是缺失值填充要在子集内做不能先填充整个表再切分否则女性样本的缺失值可能会被男性子集的中位数影响。第二个细节是切分后两个子集都做标准化scale 参数彼此独立。第三个细节是如果数据不均衡比如男性样本是女性的两倍需要给两个子模型不同的权重建议在集成阶段用岭回归学习权重而不是手工设定。4.2 KMeans 聚类与 Ridge/Bayesian 回归的耦合kmeans_ridge.py 和 kmeans_bayesian.py 做的事是在性别拆分之后再做一层非监督分组。它先把特征标准化用 KMeans 把样本聚成多个簇再在每个簇内训练岭回归或贝叶斯回归。这背后的假设是同一簇内的体检指标组合更接近同一代谢状态线性回归在局部簇里拟合得更准。from sklearn.cluster import KMeans from sklearn.linear_model import Ridge from sklearn.preprocessing import StandardScaler # 先标准化再聚类否则聚类会按量纲大的字段分 scaler StandardScaler() X_s scaler.fit_transform(X_m) best_k, best_score 0, -np.inf for k in range(2, 8): km KMeans(n_clustersk, n_init10, random_state42) groups km.fit_predict(X_s) scores [] for g in range(k): if (groups g).sum() 30: scores.append(-np.inf) continue ridge Ridge(alpha1.0) ridge.fit(X_s[groups g], y_m[groups g]) scores.append(-ridge.score(X_s[groups g], y_m[groups g])) mean_score np.mean(scores) if mean_score best_score: best_score mean_score best_k k这段代码用 Ridge.score 即 R2 做朴素筛选真实项目中建议换成交叉验证 MAE。聚类簇数 k 的最佳值往往在 3 到 5 之间因为体检人群的代谢状态大致可分成正常、边缘、异常三档。k2 太粗k6 以上会出现只有几十个样本的簇岭回归不稳定。还要注意 KMeans 对初始值敏感n_init 至少要 10否则同一份数据两次跑出来的分组不同最终模型不可复现。4.3 加权融合lgb_nn_ensembling.py 与 lgb_cvensembling.py 的设计这两个脚本代表了两种集成路线。lgb_nn_ensembling.py 是把 LightGBM 和简单神经网络做加权平均lgb_cvensembling.py 则是把同一份 LightGBM 在多个 CV fold 上训练的模型做融合。LightGBM 擅长特征交互神经网络能拟合平滑函数两者误差相关性低加权平均效果好于单一模型。加权系数不需要手工调整直接对验证集做岭回归即可from sklearn.linear_model import Ridge import numpy as np # lgb_pred, nn_pred 是同一组验证集的预测结果 stack_X np.column_stack([lgb_pred, nn_pred]) meta Ridge(alpha1.0) meta.fit(stack_X, y_val) # 预测时同样用学习到的系数加权 final_val meta.predict(np.column_stack([lgb_val, nn_val])) print(meta coef:, meta.coef_)这里常被忽略的是meta 模型仍然要防止过拟合。验证集大小只有 200 条时用一个两维输入的岭回归很安全如果换成三个模型以上需要把验证集再划出一部分或者用重复 K 折来评估融合权重。lgb_cvensembling.py 则把 K 折模型的预测取平均理论上相当于把树的方差压低适合在单模型已经足够好的情况下再挤出一点提升。在引入堆叠时最常犯的错误是把 K 折训练集的预测拿来做 meta 特征而没有保证每个 fold 的预测由未见过该 fold 的模型产生。项目里带 CV 后缀的脚本用 cross_val_predict 这类方式处理这一点放到第五章验证一起讲。5. 验证口径与误差分析离线/在线拆分下的血糖预测稳定性5.1 防数据泄漏按体检日期划分而不是随机划分源码包中 online/offline 两个目录以及 basic_analysisoffline 命名的文件提示这个项目刻意区分了离线训练和在线预测两个口径。如果体检数据是按批次产生的随机切分会让同一批次的相似样本同时出现在训练集和测试集测试误差被低估。正确做法是按体检日期排序取前面的日期作为训练集后面的作为验证集。df df.sort_values(体检日期) split_idx int(len(df) * 0.8) train df.iloc[:split_idx] test df.iloc[split_idx:]这样切分会让模型在验证集上的表现变差但更接近真实场景因为部署时要预测的是未来体检的人。5.2 回归误差指标的选择血糖预测报告里通常看 MAE 和 RMSE。MAE 对所有误差一视同仁RMSE 会放大离群样本。体检数据中偶尔会混入空腹血糖和餐后血糖一起的被测者产生 10 mmol/L 以上的离群标签RMSE 会被这类样本主导掩盖正常区间的预测能力。建议把两列都打印出来如果 RMSE 明显大于 1.5 倍 MAE说明数据里有值得检查的异常标签。指标用途血糖场景注意点MAE评估平均绝对误差正常范围 0.3~0.8 mmol/LRMSE放大离群误差大于 1.5 倍 MAE 时排查标签错误R2相对方差解释度体检预测通常 0.3~0.6 就可用临床风险混淆矩阵分类口径阈值 7.0 mmol/L 时看特异度5.3 对血糖标签做 log1p 变换再评估一个值得在最后落地的细节对标签做 log1p 变换可以让模型更关注血糖值偏低的密集区间而不是被少数高血糖样本带偏。训练时把 y 换成 np.log1p(y)预测后用 np.expm1 还原MAE 要基于还原后的数值计算。from sklearn.metrics import mean_absolute_error # 训练阶段 y_log np.log1p(y) model.fit(X_train, y_log) # 预测阶段 pred_raw model.predict(X_test) pred np.expm1(pred_raw) print(MAE:, mean_absolute_error(np.expm1(y_test_log), pred))注意如果测试集标签也是 log 后的直接算 MAE 会把正常水平附近的误差压缩得更小看起来很好看但没有业务意义。最终对外汇报或者写答辩文档时必须用还原后的血糖单位 mmol/L 计算误差。配合 splittedgender 和 KMeans 分组这个验证流程已经可以覆盖多模型集成的所有中间产物从 feature_importance_ctree.csv 到 lgb_cvensembling.py 的预测结果都能用同一套指标对齐。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价