ARTICLE DETAIL

资讯详情

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

Keras + BoxCox 糖尿病风险预测:从数据预处理到可视化交付

Keras + BoxCox 糖尿病风险预测:从数据预处理到可视化交付 简介面向医疗健康数据研究者、AI竞赛选手及机器学习初学者的糖尿病遗传风险预测系统源于天池大数据竞赛任务基于Keras框架实现神经网络预测模型并采用BoxCox变换优化输入数据分布配合可视化分析组件覆盖糖尿病早期筛查与风险评估场景可帮助理解从数据清洗到模型评估的完整流程。资源包共159个文件压缩后约21.09MB核心包括34个Python脚本、8个CSV数据集、2个Jupyter Notebook及大量PNG可视化结果图另有PDF/Word说明文档与训练好的模型文件兼顾代码实现、数据样例与文档讲解。目前已有44人学习下载适合希望借助真实竞赛案例提升实践能力的读者。资源内提供原始训练与测试数据、预处理后的特征文件以及数据处理过程说明配合可视化图表和Notebook可复现赛题全流程。无论用于课程设计、论文实验还是参赛备练都能直接参考网络结构、特征处理与调参思路是一款紧凑实用的学习工具包。1. 天池糖尿病遗传风险预测这套 Keras BoxCox 方案到底解决什么问题体检报告里空腹血糖偏高、糖化血红蛋白超标的人群医生常会说再观察观察但风险往往藏在遗传背景和多项代谢指标的综合趋势里。这个标题指向的是一类用天池大数据竞赛公开数据训练Keras 搭前馈神经网络、BoxCox 做预处理最终输出患病概率和可视化报告的糖尿病早期筛查工具。它把有没有风险这个模糊问题量化成 0 到 1 的概率再换算成低危、中危、高危档位供体检中心或健康管理平台初筛。这件事适合谁想拿天池竞赛做完整项目的算法工程师和做健康管理风险分层产品的开发者。提醒一句模型定位是早期筛查辅助不替代临床诊断。我见过有人先急着调网络结构AUC 卡在 0.6 上不去复盘后才发现瓶颈全在数据预处理。下面按从预处理到训练、再到可视化交付的顺序逐段拆解。2. 为什么是 BoxCox 而不是标准化数据分布处理的前置决策天池这类二分类医学竞赛里特征多半是体检指标血糖、甘油三酯、BMI、血压、家族史编码。它们有一个共同特点右偏。大部分人集中在正常区间少数异常值拖出长尾。神经网络对输入分布很敏感极端值会在训练早期把梯度拉偏。所以拿到数据后第一件事不是建模而是逐列看偏度决定哪些列需要做 BoxCox哪些列直接放行。2.1 BoxCox 做了什么用极大似然估计拉正右偏分布BoxCox 变换本质上是一个带参数的幂函数族。对任意正数 x变换定义为 (x^λ - 1)/λλ 0 时取自然对数 ln(x)。关键点在于 λ 不用手调而是对数据做极大似然估计得到目标是让变换后的分布最接近正态。对右偏的医学指标λ 通常落在 0 到 0.5 之间相当于把平方根变换或对数变换自动选出来不需要人工试算。用 scipy 一行就能做先看效果再讲参数import numpy as np from scipy import stats rng np.random.default_rng(42) # 模拟一个右偏的代谢指标比如甘油三酯 col rng.lognormal(mean1.2, sigma0.8, size2000) print(变换前偏度:, stats.skew(col)) transformed, lam stats.boxcox(col) print(最优 lambda:, round(lam, 4)) print(变换后偏度:, round(stats.skew(transformed), 4))逻辑说明stats.skew衡量分布偏斜程度正值越大右偏越严重stats.boxcox返回两个值第一个是变换后的数组第二个是通过极大似然估计得到的最优 λ。参数说明lognormal生成的数据全部为正可以直接变换真实体检数据里若有 0 值或负值必须先加偏移量这一步在第三章会专门处理。实际操作中我会先算所有数值列的偏度|skew| 大于 0.5 的列才做 BoxCox接近正态的列直接放行。BoxCox 有一个硬限制输入必须严格为正。如果某列最小值是 0 或负数需要对该列加一个偏移量 shift |min| 0.001再执行变换。这个偏移量要和 λ 一样记录下来验证集转换时复用不能重新估计。2.2 对比实验标准化遇到长尾数据会失效很多人习惯一上来就StandardScaler这在特征近似正态时没问题但遇到右偏长尾会出岔子。标准化的本质是减均值、除标准差异常值会把均值拉偏正常样本被压缩到很窄的区间而异常样本仍然离群。拿同一份模拟数据做对比from sklearn.preprocessing import StandardScaler import pandas as pd df pd.DataFrame({raw: col}) df[zscore] StandardScaler().fit_transform(df[[raw]]) df[boxcox] transformed print(df.describe().T)逻辑说明看describe输出zscore列的极值仍然离均值 3 个标准差以上而boxcox列的分布更紧凑。这说明标准化管住了均值和方差但管不住偏度BoxCox 先让分布对称再交给标准化做缩放。这两步在我的流程里是串联关系不是二选一。下面这张表是我在做特征工程时常用的方法选型参考方法解决的问题主要局限适用场景StandardScaler量纲差异对偏度无能为力特征已接近正态MinMaxScaler数值范围受异常值影响大树模型或固定区间输入Log1p右偏分布只能处理正数无法自适应偏度稳定、快速验证时BoxCox右偏和部分左偏只能处理正数需估计 λ连续值列分布形态未知什么情况下可以不碰 BoxCox如果某列是均匀分布或已经相当对称变换收益很低。另外对 0/1 编码的家族史列、性别列做 BoxCox 没有意义这些离散列直接保留或单独缩放即可。还要注意变换是针对单个连续列不是对整个 DataFrame 一次性做否则会把离散列也卷进幂函数里。2.3 表格数据为什么选前馈神经网络标题里点名的神经网络在这个场景下不是越复杂越好。糖尿病风险数据是典型表格数据几十列特征、几千条样本特征之间没有图像的空间结构也没有文本的序列结构。卷积神经网络和循环神经网络在这里用不上真正合适的是三层左右的前馈神经网络也就是全连接网络。前馈神经网络的优势有三点直接输出 sigmoid 概率方便接阈值判断模型体积小部署到筛查系统里几乎零成本和 BoxCox、StandardScaler 串成一个预处理管线很顺手。Transformer 这类大结构在几千条样本上很容易过拟合图神经网络则根本没有图结构可用。至于和 XGBoost、LightGBM 怎么选树模型对特征缩放不敏感也不需要 BoxCox是强劲基线但树模型输出的概率是台阶状的嵌入平台前常常要做 Calibration而前馈神经网络输出天然光滑配合 Keras 保存后的模型文件跨语言部署更省事。2.4 预处理顺序是前置决策不是后置细节BoxCox 的 λ、标准化的均值和方差都属于从训练集学到的统计量。如果先对全量数据做变换再划分训练集和验证集验证集的信息就已经混进预处理参数里了这就是数据泄露。泄露不会让代码报错但会让验证 AUC 虚高提交到天池榜单后分数直接掉一截。我的固定顺序是先按标签分层划分再在训练集上估计 λ、均值和方差最后用同一套参数转换验证集。判断有没有泄露有个笨办法把验证集的 BoxCox 参数换成全量数据重新估计的参数再训一个模型对比两次验证 AUC。如果波动超过 0.03基本可以怀疑预处理顺序有问题。这个顺序在第三章的代码里落地。3. 把 BoxCox 和 Keras 串成完整数据管线从 CSV 到模型输入上一章把选型逻辑立住了这一章进入动手环节。常见做法是先写一个清洗流程再做划分和变换最后组装成 numpy 数组给 Keras。我一般不用 DataFrame 直接喂模型而是转成 float32 的数组DataFrame 有数据类型和索引的额外开销训练时容易出隐蔽 bug。3.1 读取数据与清洗天池医学数据的检查清单天池发布的糖尿病相关数据一般是 CSV。拿到后先看 shape、列名、dtype、缺失比例再决定丢弃还是填充import pandas as pd import numpy as np df pd.read_csv(diabetes_data.csv) # 换成实际路径 print(shape:, df.shape) print(列名:, df.columns.tolist()) print(缺失情况:) print(df.isna().mean().sort_values(ascendingFalse)) label_col label # 标签列名以实际数据为准 print(标签分布:) print(df[label_col].value_counts())逻辑说明isna().mean()能一眼看出每列缺失比例超过 30% 的列要重新考虑是否保留value_counts()看类别不平衡程度。参数说明这类竞赛的label通常取 0/1 两个值如果读到的是字符串先做df[label_col].astype(int)。清洗阶段我不会用df.dropna()一把梭。医学数据里的缺失常常不是随机的某项检查没做、家族史未填写这些缺失本身有业务含义。常见做法是先标记缺失、再用中位数填充让模型有机会学到缺失这个信息。3.2 分层划分、缺失标记与 BoxCox 参数复用from sklearn.model_selection import train_test_split from scipy import stats train, val train_test_split( df, test_size0.2, stratifydf[label_col], random_state42 ) # 缺失值处理先记录缺失标记再用训练集中位数填充 for c in num_cols: med train[c].median() train[c _isna] train[c].isna().astype(np.float32) train[c] train[c].fillna(med) val[c _isna] val[c].isna().astype(np.float32) val[c] val[c].fillna(med) # BoxCox逐列估计 lambda 和 shift验证集复用同一套参数 lambdas, shifts {}, {} for c in num_cols: shift 0.0 min_val train[c].min() if min_val 0: shift abs(min_val) 1e-3 shifts[c] shift adj train[c] shift train[c _boxcox], lambdas[c] stats.boxcox(adj) # 验证集只做 transform不再重新估计 for c in num_cols: val_adj val[c] shifts[c] val[c _boxcox] stats.boxcox(val_adj, lmbdalambdas[c])逻辑说明stratify按标签比例分层避免某一折里全是正样本或全是负样本stats.boxcox要求输入严格为正所以先对训练集算 shift 并记录到字典验证集用stats.boxcox(x, lmbda...)时只应用训练集学到的 λ不重新估计。参数说明random_state42固定随机种子保证实验可复现shift 的 1e-3 是防止零值更稳妥的做法是留 0.01 余量避免验证集出现比训练集更小的新最小值时再次报错。注意λ 和 shift 都属于训练集统计量验证集只能转换不能重新估计这和 StandardScaler 的 fit/transform 是同一套逻辑。3.3 标准化与数组组装给 Keras 喂 float32from sklearn.preprocessing import StandardScaler feat_cols [f{c}_boxcox for c in num_cols] [f{c}_isna for c in num_cols] scaler StandardScaler() X_train scaler.fit_transform(train[feat_cols].values) X_val scaler.transform(val[feat_cols].values) y_train train[label_col].values.astype(np.float32) y_val val[label_col].values.astype(np.float32)逻辑说明BoxCox 解决偏度StandardScaler 解决量纲两者串联后特征大致落在 [-3, 3] 区间这对 relu 激活和 adam 优化器最友好。fit_transform只在训练集调用一次验证集只用transform和 BoxCox 遵循同一原则。参数说明如果特征列里有 one-hot 编码的家族史列不要让它们进 BoxCox直接拼到feat_cols里顺序必须固定np.concatenate拼接时列顺序错位是后期图表对不上的经典原因。3.4 预处理阶段就要画分布对比图预处理不是闷头跑代码每做一步都要可视化确认。我会把 BoxCox 前后的直方图放在同一张画布里对比import matplotlib.pyplot as plt import seaborn as sns fig, axes plt.subplots(1, 2, figsize(12, 4)) sns.histplot(train[bmi], bins50, kdeTrue, axaxes[0]) axes[0].set_title(before boxcox) sns.histplot(train[bmi_boxcox], bins50, kdeTrue, axaxes[1]) axes[1].set_title(after boxcox) plt.tight_layout() plt.savefig(boxcox_compare.png, dpi150)逻辑说明肉眼确认右偏是否缓解比单看偏度数值更直观。参数说明kdeTrue叠加密度曲线适合观察分布形状如果变换后出现双峰说明该列可能是混合分布要再考虑分桶或加交互特征。这些图就是数据可视化分析交付物的一部分不是临时草稿。4. 用 Keras 搭前馈神经网络结构、损失函数与三个训练关键参数数据管线准备好后模型部分反而简洁。Keras 的安装教程网上很多核心只有一句话先装 TensorFlow再确认from tensorflow.keras import ...能跑通。TF 2.x 里tf.keras和独立 Keras 是两套入口混用会导致报错莫名其妙我的建议是全程用from tensorflow.keras避免直接 import keras。4.1 网络结构三层全连接为什么比深网络稳医学表格数据量通常是几千条深网络在这里没有优势反而更容易过拟合。我一般用两个隐层宽度从 64 开始逐层减半from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense, Dropout, BatchNormalization model Sequential([ Dense(64, activationrelu, input_shape(X_train.shape[1],)), BatchNormalization(), Dropout(0.3), Dense(32, activationrelu), Dropout(0.3), Dense(1, activationsigmoid), ])逻辑说明input_shape取X_train.shape[1]即预处理后的特征数BatchNormalization 放在第一个隐层之后控制 relu 输出尺度避免梯度在多层传播中失控Dropout 放在每个隐层之后是几千条样本下最主要的防过拟合手段。参数说明Dropout 比例 0.3 是常用起点验证集仍然过拟合时可以提到 0.5隐层宽度 64/32 对这个量级的数据已经有余量加到 256/128 通常只会增加训练时间不会带来 AUC 提升。4.2 损失函数与评估指标监控 AUC 而不是 accuracy分类任务默认用binary_crossentropy但评估指标我选 AUC。原因很简单正样本占比低于 30% 时accuracy 的基线本身就很高模型全部预测为 0 也能拿到 70% 的正确率这个数字没有参考价值。AUC 对类别分布不敏感反映的是正负样本排序质量更适合筛查场景。from tensorflow.keras.metrics import AUC model.compile( optimizeradam, lossbinary_crossentropy, metrics[AUC(nameauc)], )逻辑说明nameauc给指标起名后面早停回调里monitorval_auc会用到。参数说明这里显式实例化AUC而不是传字符串AUC旧版 Keras 的字符串行为受全局 backend 影响显式实例化在不同 TensorFlow 版本下行为更一致。如果想看更细的指标可以加Precision、Recall但早停只看 AUC 一个指标就够。4.3 三个训练关键参数batch size、epochs、类别权重from tensorflow.keras.callbacks import EarlyStopping neg_num int((y_train 0).sum()) pos_num int((y_train 1).sum()) early_stop EarlyStopping( monitorval_auc, modemax, patience20, restore_best_weightsTrue, ) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs200, batch_size64, class_weight{0: 1.0, 1: neg_num / pos_num}, callbacks[early_stop], verbose1, )逻辑说明class_weight把正样本的损失权重调到负样本比例的倒数让模型不偏向多数类EarlyStopping监控val_auc连续 20 轮不涨就停并回滚到最佳权重。参数说明patience太大浪费时间太小容易在 AUC 抖动时提前停20 是这个量级数据下的经验值epochs200只是上限正常情况早停会在 50 到 100 轮内结束。batch_size64是内存和梯度稳定性的折中样本量只有几千时也可以直接用 32。如果neg_num / pos_num算出来太大比如超过 10我一般会改用0.5 * (neg_num pos_num) / pos_num让权重温和一些。提示restore_best_weightsTrue会让模型回滚到 val_auc 最高的权重别改成 False否则拿到的是一轮中途的权重提交效果会打折扣。4.4 训练曲线可视化看 loss 和 auc 是否同步import matplotlib.pyplot as plt plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.legend() plt.subplot(1, 2, 2) plt.plot(history.history[auc], labeltrain_auc) plt.plot(history.history[val_auc], labelval_auc) plt.legend() plt.savefig(training_curve.png, dpi150)逻辑说明两列并排左边看 loss右边看 AUC。参数说明如果val_auc在高位震荡说明学习率偏大把optimizeradam换成Adam(learning_rate0.0003)重跑如果train_auc接近 1 而val_auc不到 0.75说明过拟合优先调 Dropout 而不是加层。这两类情况从曲线上看非常明显不要等到提交榜单才发现。5. 糖尿病风险预测常见问题与排查五个容易翻车的地方前四章讲的是完整流程这一章集中写踩坑记录。下面每一条我都实际遇到过按现象、原因、解决的顺序写方便你对照排查。5.1 BoxCox 报错 Data must be positive 或变换后出现 NaN现象跑stats.boxcox直接抛ValueError或者代码没报错但变换后的列里有 NaN。原因数据里有 0 值或负值另一种情况是列里含缺失值没填充就做了变换。解决先看df[col].describe()确认最小值按第三章的做法加 shift缺失值先用中位数填充再变换。shift 必须从训练集计算并记录验证集不能重新算。如果某列最小值是负数且波动大abs(min_val) 1e-3可能不够我给0.01留余量验证集出现新最小值时不容易再报错。5.2 验证集 AUC 一直在 0.5 附近晃现象训练很快收敛但val_auc始终在 0.5 上下波动跟抛硬币一样。原因最常见的有三个。一是标签和特征行错位train_test_split之后没有reset_index(dropTrue)导致y和X索引对不上二是验证集的 BoxCox 或标准化又重新 fit 了一遍分布错乱三是class_weight没设置正样本太少时模型输出全部接近 0。解决先画标签分布和特征分布确认y_val顺序和X_val对得上再检查预处理参数是否复用训练集最后加class_weight。按这个顺序排查比盲目改网络结构高效得多。5.3 训练和验证曲线越拉越开train_auc 接近 1现象train_auc一路涨到 0.98val_auc停在 0.72 涨不动两条曲线呈喇叭口。原因过拟合。表格数据样本量小、特征噪声大模型把训练集里的个体噪声也记住了。解决优先加大 Dropout 到 0.4 到 0.5再不行就缩减隐层宽度从 64/32 降到 32/16。还可以给 Dense 层加kernel_regularizerl2(1e-4)我一般从 1e-4 起步效果不明显再上调。数据增强不适用于这类表格任务不用在这个方向浪费时间。5.4 可视化图表和最终指标对不上现象相关性热力图显示某列与标签高度相关但模型训练时删了这列或者最终报告里的分布图和训练用的数据不是同一版。原因预处理和可视化用了不同的 DataFrame。常见是我在 BoxCox 时原地改了train[c]回头可视化又去读原始 DataFrame或者拼接特征时列顺序调整图表里的列索引和模型输入不一致。解决统一约定。预处理产生的新列用_boxcox后缀存进 train原始列不覆盖可视化只从处理后的 DataFrame 取数拼接特征用显式列表维护列顺序不依赖 DataFrame 的隐式顺序。5.5 本地验证 AUC 很高天池榜单分数明显偏低现象本地val_auc0.82提交后分数只有 0.74落差很大。原因预处理参数泄露是常见原因之一另一个常见原因是测试集和训练集分布不完全一致这类赛题会刻意让测试集来自不同批次本地单次划分的验证集不能完全代表线上分布还有可能是提交的预测概率做了截断或精度不足。解决训练时用 5 折交叉验证替代单次划分平均 AUC 比单次验证更接近线上预测概率保留 6 位小数用np.round(pred, 6)避免精度损失交叉验证时每一折单独估计 BoxCox 参数不跨折复用。如果线上分数还是低就加一个 LightGBM 对照模型判断是预处理问题还是神经网络不适合这个数据分布。6. 让模型输出变成筛查结论概率阈值、风险分层与可视化交付模型训练完只是第一步筛查系统真正需要的是一个可解释的结论。我把最后一步分成两件事选阈值、做分层。6.1 用约登指数在验证集上选最优阈值from sklearn.metrics import roc_curve fpr, tpr, thresholds roc_curve(y_val, pred_val) youden tpr - fpr best_idx int(np.argmax(youden)) best_threshold thresholds[best_idx] print(best threshold:, round(float(best_threshold), 4)) risk_labels np.where(pred_val best_threshold, 高危, 低危)逻辑说明约登指数等于灵敏度加特异度减一取值越大说明该阈值下误诊和漏诊的综合损失越小。医疗筛查里如果更看重不漏掉患者可以把阈值往下调 0.05牺牲一点特异度换召回率。参数说明roc_curve返回的 thresholds 按降序排列直接用索引对齐即可。6.2 风险分层与 echarts 可视化交付拿到概率后我习惯分三档低危小于 0.3中危 0.3 到 0.7高危大于 0.7而不是只用单阈值二分。这样体检报告能输出更细的建议低危定期复查中危加测糖化血红蛋白高危转内分泌科。可视化方面matplotlib 画完图只是中间产物项目交付最好用 echarts 做动态面板概率分布直方图、三档人数占比、每个特征 BoxCox 前后对比图放在同一个看板上。图表数据直接从验证集统计结果导出不重新生成避免出现第五章说的对不上。我的习惯是每次交付前把feat_cols、lambdas、shifts、scaler、best_threshold用joblib.dump存起来上线时加载同一套预处理管线保证训练和推理完全一致。还有一句提醒这类模型只能做早期筛查辅助预测概率不是诊断结论给业务方讲清楚模型边界比提高 0.01 的 AUC 更重要。希望帮到你。本文还有配套的精品资源点击获取
返回列表