机器学习分类实现肥胖与心血管疾病风险预测
2026/9/16 18:37:57 网站建设 项目流程

简介:这是一份面向机器学习初学者与数据分析从业者的完整实战资源,以肥胖与心血管疾病风险分类为任务,覆盖数据清洗、EDA可视化、特征工程、模型训练评估与超参数调优全流程。压缩包共22个文件,包含20个Python源代码、1个CSV数据集(约257KB)和1个说明文档,整体约111KB;代码除了随机森林、XGBoost等主流分类模型外,还加入3D PCA投影、KMeans聚类、TensorFlow神经网络、SHAP/LIME可解释性分析等多样化实现,便于读者对照学习不同算法在同一数据集上的表现。目前已有112人浏览学习。数据集与代码均完整打包,所有脚本为手工整理且无语法错误,可直接运行复现;借助这些代码,读者可以快速掌握分类项目从探索到部署的常见思路,也能够基于同一数据自行扩展实验,适合作为毕业设计、课程作业或技能提升的参考资料。

1. 为什么 AI 会把肥胖和心血管疾病风险预测做成分类任务

心血管疾病的发生率和肥胖指标之间并不是一条简单直线。BMI 处于正常区间却检出高血脂的人员在门诊并不少见,而肥胖合并高血压的人群又会因为年龄、遗传背景和生活习惯的差异呈现出完全不同的风险走向。把每位体检者的身高、体重、腰围、血压、血糖、家族史等记录拼成一张结构化表格,再让机器学习模型去学习“高风险”和“低风险”两组样本的边界,本质上就是一个分类任务。传统统计方法能做单变量相关分析,但当特征之间互相纠缠时,模型的价值就体现在捕捉非线性组合上。这也是标题里“分类数据分析预测”的实际含义:先横向比较多个候选特征,再输出一个 0 到 1 的风险概率。整个流程适合有 Python 基础、想完整过一遍数据清洗、特征工程、模型训练和评估的 AI 入门者,也适合需要快速搭建健康风险筛查原型的算法工程师。

2. 数据预处理:把健康档案转成可训练的分类张量

2.1 标签定义与样本分布检查

拿到项目压缩包后,第一步不是急着打开模型脚本,而是先用 pandas 看一遍表的形状和字段类型。健康类数据集的常见字段包括年龄、性别、吸烟史、饮酒频率、运动次数、BMI、腰围、血压、空腹血糖以及心脑血管事件标记。目标列可能是布尔值“是否有心血管风险”,也可能是多分类的风险等级。为了让后续训练有意义,先确认标签列是什么、长什么样。

import pandas as pd df = pd.read_csv("obesity_cvd_data.csv") print(df.shape) print(df.dtypes) print(df["label"].value_counts(normalize=True))

这段代码做三件事:查看样本总量和特征数量,检查字段类型,统计标签列的正负样本占比。如果正例比例低于 10%,就属于明显的类别不平衡。257.47 KB 的原始数据集量级通常在 5000 到 20000 行之间,这个体量对表格学习足够,但对少数类的覆盖却可能不够稳。遇到这种情况,我一般不会立刻用 SMOTE,而是先看正样本的绝对数量。正样本少于 200 条时,重采样会让验证集失去真实性,更好的做法是保留类别权重并在评估时关注召回率。

2.2 数值列与分类列的编码策略

结构化健康数据里,年龄、血压、BMI 是连续数值,性别、吸烟史、饮酒习惯是分类文本。直接把字符串交给模型会报错,正确做法是对分类列做独热编码,对数值列做标准化。独热编码之后,决策树模型可以直接使用原始数值,而逻辑回归和神经网络对特征的尺度敏感,必须先经过 Z-score 标准化。

from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer num_cols = ["age", "height", "weight", "systolic_bp", "diastolic_bp", "blood_sugar"] cat_cols = ["gender", "smoking", "drinking", "family_history"] preprocessor = ColumnTransformer([ ("num", StandardScaler(), num_cols), ("cat", OneHotEncoder(handle_unknown="ignore"), cat_cols), ], remainder="drop")

ColumnTransformer 把两类列的处理流程封装进同一个管道中,之后无论更换模型还是做交叉验证,都不用再单独维护处理逻辑。handle_unknown="ignore"防止预测阶段出现训练集里没见过的类别枚举时直接报错,这在后续接入新数据时非常关键。

一个常见的误用是给年龄、血压做分箱。年龄分箱确实能提升个别模型的局部表现,但会折断连续变量的梯度信息,尤其在集成模型里,分箱边界往往成为人为噪声。除非业务方明确要求按年龄区间出报告,否则我倾向保留原始数值并新增交叉特征。

2.3 构造医学上可解释的交叉特征

原始数值列不足以表达肥胖与心血管风险之间的组合关系。临床评估里常用的腰高比可以比 BMI 更好反映腹型肥胖,而 BMI 本身又是由身高体重计算得到的。把这类领域知识变成特征列,通常比多叠加一个弱模型更有效。

df["bmi"] = df["weight"] / (df["height"] / 100) ** 2 df["waist_height_ratio"] = df["waist"] / df["height"] df["age_bmi_interact"] = df["age"] * df["bmi"]

交叉特征不一定越多越好。这里构造的前两个特征有明确医学意义,第三个特征则属于模型自适应的交互项。随机森林和梯度提升对这类手动交互项并不敏感,因为树模型自己能构造分裂规则,所以第三个特征主要服务于逻辑回归这类线性模型。特征构造完成后,用df.isnull().sum()检查缺失比例,超过 30% 的列优先删除而不是填充。

提示:健康数据的缺失值不一定是随机的。如果缺失集中在饮酒量和吸烟记录偏多的样本上,直接把行删除会造成选择性偏差,此时应考虑单独标记缺失状态。

3. 分类模型的选择:逻辑回归基线到集成模型

3.1 逻辑回归作为下界参考

分类问题起步时先跑一个逻辑回归,作用不是用它交差,而是给后续复杂模型提供一个参照系。如果逻辑回归在验证集上已经拿到 0.75 的 AUC,那么复杂模型至少要超过这个数才有意义,否则说明数据本身没有强复杂信号。

from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score clf = LogisticRegression(max_iter=2000, class_weight="balanced") clf.fit(X_train, y_train) y_prob = clf.predict_proba(X_val)[:, 1] print("Logistic Regression AUC:", roc_auc_score(y_val, y_prob))

max_iter 设置为 2000 是因为特征经过独热编码后数量膨胀,默认迭代次数往往不收敛。class_weight="balanced"按类别频率自动调整权重,让少数类在梯度更新时获得更高惩罚,这比手动复制样本更稳。逻辑回归适合特征和目标接近线性可分的数据,当“肥胖程度对风险的影响取决于年龄”这类交互效应明显时,逻辑回归的上限就被卡住了。

3.2 随机森林与梯度提升方法

表格数据上,随机森林最大的优点是不需要标准化,分类特征经过独热编码后也能直接使用。min_samples_leaf=8控制叶子节点的最小样本数,对患者数量有限的数据集来说,这个参数能显著降低过拟合风险。

from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier( n_estimators=300, max_depth=12, min_samples_leaf=8, class_weight="balanced_subsample", n_jobs=-1, random_state=42, ) rf.fit(X_train, y_train) print("RF AUC:", roc_auc_score(y_val, rf.predict_proba(X_val)[:, 1]))

梯度提升目前在结构化数据竞赛里往往更稳,XGBoost 和 LightGBM 是常见选择。这类模型的优劣取决于学习率和树数量之间的平衡,学习率设太低会拉长训练时间,太高则容易在验证集上提前过拟合。我习惯先用一个浅树、学习率 0.1 的配置跑通全流程,再决定要不要加深树结构。

from lightgbm import LGBMClassifier lgb = LGBMClassifier( n_estimators=500, learning_rate=0.05, max_depth=6, num_leaves=31, class_weight="balanced", random_state=42, ) lgb.fit(X_train, y_train) print("LGBM AUC:", roc_auc_score(y_val, lgb.predict_proba(X_val)[:, 1]))

LightGBM 的叶子生长策略和传统按层分裂不同,它优先选择增益最大的叶子细分,因此相同深度下表达能力更强,但也更需要早停和正则化。健康数据特征维数在几十到数百之间,这个量级对 LightGBM 完全适配,不需要额外压缩。至于 Transformer 那类深度网络,更适合序列或图像输入,直接套在表格特征上通常不会超过 LightGBM,除非把特征改造成时序视图或嵌入表示,否则不建议优先尝试。

3.3 评估指标和业务风险的权衡

AUC 只能告诉你正负样本的整体排序质量,不能直接告诉你在某个拦截阈值下会漏掉多少高风险患者。要落地到筛查场景,还要看精确率、召回率和 F1 随阈值变化的曲线。

模型AUC精确率召回率适用场景
逻辑回归0.780.710.62需要向业务方逐条解释特征权重的报表场景
随机森林0.850.790.74特征列多且需要重要性排序的探索阶段
LightGBM0.870.820.78追求预测上限,部署条件允许的正式服务

表中数值是同类健康数据集上具有代表性的相对水平,实际数据集的绝对值会不同,重点在于三列一起看。心血管风险筛查倾向于召回率优先,宁可把部分低风险者送进复查,也不希望漏掉真正的高危样本。所以调阈值时我会把概率阈值从默认的 0.5 降到 0.3 甚至更低,再检查召回率变化,而不是只盯着 accuracy。

提示:不要用训练集上的精确率评估模型。逻辑回归训练集准确率可能到 0.90,但验证集只有 0.72,这类差距在中小数据集上非常常见,最终判断只认交叉验证或独立测试集结果。

4. 交叉验证与超参数优化:避免在固定划分上自嗨

4.1 分层 K 折交叉验证

单次 train_test_split 的划分方式会直接影响模型得分。为了减少运气成分,我一般使用五折分层交叉验证。分层策略保证每一折的正负比例和整体一致,这对正例占比不高的健康数据集尤其重要。K 折结果会输出五个模型的平均值和标准差,标准差值一旦大于 0.04,说明模型边界不稳定。

from sklearn.model_selection import StratifiedKFold, cross_val_score skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(lgb, X_all, y_all, cv=skf, scoring="roc_auc") print("CV AUC mean:", scores.mean(), "std:", scores.std())

这里有个细节要特别注意:cross_val_score 内部只会切分数据,不会替你重新拟合预处理器。如果直接把已经标准化和独热编码完成的 X_all 传入,那么测试折的均值和方差信息已经从训练阶段泄漏进来。正确做法是把 preprocessor 和模型拼成 sklearn 的 Pipeline,再把 Pipeline 传入 cross_val_score。

from sklearn.pipeline import Pipeline pipe = Pipeline([ ("prep", preprocessor), ("clf", LGBMClassifier( n_estimators=200, learning_rate=0.05, class_weight="balanced", random_state=42, )), ]) cv_scores = cross_val_score(pipe, df_feat, df["label"], cv=skf, scoring="roc_auc") print(cv_scores.mean(), cv_scores.std())

Pipeline 保证每一折在训练阶段从头拟合 scaler 和 one-hot 编码器,测试折的信息不会以任何统计量形式进入训练步骤,这是交叉验证结果可信的前提。

4.2 超参数搜索区间与先后顺序

参数搜索范围建议起点
n_estimators200~800300
learning_rate0.01~0.10.05
max_depth4~106
num_leaves16~6431
min_child_samples10~3020

网格搜索把参数两两组合全部跑一遍,参数数量一旦超过 4 组就很容易膨胀。更实际的做法是先做粗粒度随机搜索,确定参数的大致区间后再用网格收窄。随机搜索会从给定分布中抽固定次数,避免把所有组合枚举一遍。

from sklearn.model_selection import RandomizedSearchCV param_grid = { "clf__learning_rate": [0.01, 0.03, 0.05, 0.1], "clf__max_depth": [4, 6, 8, 10], "clf__num_leaves": [16, 24, 31, 48], } rs = RandomizedSearchCV( pipe, param_grid, n_iter=30, cv=skf, scoring="roc_auc", n_jobs=-1 ) rs.fit(df_feat, df["label"]) print(rs.best_params_) print(rs.best_score_)

注意参数名称带clf__前缀,这是 Pipeline 中的命名约定,前缀对应步骤名,双下划线后接具体参数。RandomizedSearchCV 里的 n_iter 是采样组合数,不是迭代轮数。搜索结束后如果最优参数落在边界上,说明搜索区间设置偏了,要扩大范围再跑一轮。

4.3 特征重要性和数据泄漏检查

树模型训练完要马上查看 feature_importance,一方面解释哪些因素影响风险判断,另一方面排查异常高的无关特征。比如“姓名长度”或者“采样批次号”的重要性排在前列,说明有泄漏风险。

import matplotlib.pyplot as plt imp = pd.Series(rs.best_estimator_._final_estimator.feature_importances_, index=preprocessor.get_feature_names_out()) imp.sort_values(ascending=False).head(15).plot.barh() plt.tight_layout() plt.savefig("feature_importance.png")

preprocessor 输出的是带前缀的特征名,例如num__agecat__smoking_1,直接作为索引可以和原始字段对上号。检查时重点关注两类特征:日期相关字段和样本唯一 ID。体检类数据如果按月份分文件,模型可能学到的是时间段规律而不是疾病规律,因此日期列建议显式删除,或改造成“距上次体检间隔天数”这种业务特征。

交叉验证均值与单一验证集得分差距过大也值得警惕。如果五折均值 0.80,而临时划分的验证集得分 0.90,大概率是验证集划分不够随机,或是有与标签高度相关的字段混进了特征。排查方法是逐个删除可疑特征,观察得分是否有明显回落。

5. 把模型接入实际预测流程

5.1 模型持久化与外层封装

训练完成后把预处理器和模型一起打包,后续预测时直接加载,避免临时重跑清洗步骤。

import joblib joblib.dump({ "preprocessor": preprocessor, "model": rs.best_estimator_._final_estimator, }, "obesity_cvd_pipeline.pkl")

5.2 输入一条新样本得到风险概率

实际使用阶段,传入的是体检记录字典,函数内部自动完成派生特征计算、预处理和概率输出。

def predict_risk(record: dict) -> float: sample = pd.DataFrame([record]) sample["bmi"] = sample["weight"] / (sample["height"] / 100) ** 2 sample["waist_height_ratio"] = sample["waist"] / sample["height"] X = preprocessor.transform(sample) return float(rs.best_estimator_.predict_proba(X)[0][1])

风险等级判定依据评估阶段固定好的阈值。一般可以分三档:概率低于 0.3 为低风险,0.3 到 0.7 为中风险,高于 0.7 为高风险。中风险及以上人群建议进入进一步检查,这符合筛查场景的召回偏好。

5.3 业务侧验证与模型迭代

分类模型对极端肥胖样本返回的概率往往偏高,这与样本集中极值 BMI 数据点过少有关。上线后的模型需要定期用新体检数据做对比验证,把实际发病记录和预测概率合并起来重画校准曲线。如果曲线出现系统性偏差,说明样本分布已经漂移,应把新样本加入训练集迭代训练,而不是继续沿用固定管线。

迭代时一个快捷技巧:优先微调阈值和 n_estimators,不重建特征工程。如果新一轮数据出现了预处理器没见过的分类枚举,比如问诊流程里新增了职业暴露史标签,再考虑扩展 ColumnTransformer 的类别列并重新训练。健康风险筛查场景中,可解释性和召回率共同决定模型能否留在线上,这两点需要写在每一次迭代的验收标准里。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询