尧图精选

Keras构建MLP诊断糖尿病:Pima数据集实战与避坑指南

🕒 发布时间:2026/10/2 14:17:16 📁 来源:尧图网络
简介一份基于Keras实现多层感知器MLP的机器学习入门资源面向希望掌握深度学习框架实际应用的初学者与数据科学爱好者。资源聚焦印第安糖尿病数据集的二分类诊断任务完整演示从数据预处理、模型结构设计到训练评估的流程适合作为神经网络入门的对照练习素材。压缩包体积仅11KB包含3个文件分别是糖尿病数据集csv、Python实现代码py以及数据集说明文档txt其中代码含详细注释便于逐行理解关键API用法。已有342人浏览学习说明其内容具备一定的参考价值。通过学习这份资料读者可快速上手Keras的Sequential模型、Dense层配置、sigmoid激活函数、binary_crossentropy损失函数及Adam优化器的实际调用方式并了解针对医学数据的缺失值处理与标准化方法为后续构建更复杂网络打下基础。1. 用 Keras 构建多层感知器诊断印第安糖尿病为什么这个经典项目值得认真做一遍用 Keras 构建多层感知器MLP诊断印第安糖尿病标题已经把任务说得很直白拿经典 Pima Indians Diabetes 数据集搭一个前馈神经网络做二分类预测一个人五年内患糖尿病的风险。这是人工智能项目实战里几乎必过的一道坎网上随便搜就是一大把能跑的代码但大部分跑完就扔很少有人讲清楚数据预处理里的零值陷阱、隐藏层为什么不能乱堆、准确率为什么不能当唯一指标。这篇笔记就照实战路径走一遍从数据集体检开始到 Keras 搭 MLP、调参训练、评估指标最后落到模型保存和复现顺带把常见翻车点列出来。适合正在做人工智能大作业、想用 Keras 练手完整分类项目、或者在简历里放一个可复现案例的人。标题里的.rar只是打包格式解压后真正重要的是数据文件有没有表头、列顺序是否一致——这是第一个坑。2. 先把数据弄明白Pima 数据集的 8 个特征和“零值陷阱”Pima 数据集来自 UCI 机器学习库共 768 条记录每条包含 8 个数值特征和 1 个标签。特征分别是怀孕次数、口服葡萄糖耐量两小时后的血浆葡萄糖浓度、舒张压、三头肌皮褶厚度、两小时血清胰岛素、身体质量指数 BMI、糖尿病谱系函数和年龄标签用 0 和 1 表示“五年内是否确诊糖尿病”。这个数据集规模不大、全是数值特征所以特别适合作为 Keras 入门时理解多层感知器行为的载体。但要把它用好第一关其实是数据体检而不是急着 import keras。2.1 数据体检三步走shape、dtypes 和零值分布拿到数据后的第一件事不是建模型而是先确认数据长什么样。常见做法是用 Pandas 读 CSV然后依次看形状、列类型和描述性统计。很多 Keras 报错都和 dtype 不一致、列数对不上有关提前看一眼能省下大量排查时间。import pandas as pd df pd.read_csv(pima-indians-diabetes.csv, headerNone) df.columns [pregnancies, glucose, blood_pressure, skin_thickness, insulin, bmi, diabetes_pedigree, age, outcome] print(shape:, df.shape) print(df.dtypes) print(df.describe().T) print(zero counts:\n, (df 0).sum())这段代码的逻辑顺序是先给 8 个特征加上列名再用describe看每个特征的均值、标准差、最小值和分位数最后用(df 0).sum()统计每一列有多少个 0 值。headerNone是因为 UCI 原始 CSV 没有表头如果直接read_csv第一行数据会被当成列名后面所有特征列都会错位。Kaggle 上部分版本的 CSV 自带表头所以拿到文件先打开看一眼比直接跑代码更靠谱。dtypes这一步容易被忽略。原始数据偶尔会有?或空字符串被 Pandas 读成 object 类型后续astype(float32)会直接报错。如果发现某列是 object需要用pd.to_numeric(df[insulin], errorscoerce)强制转换转换后原本的非数值位置会变成 NaN方便后续统一处理。2.2 零值不是真实测量值血糖、血压、皮脂、胰岛素和 BMI 里的 0 要谨慎处理体检结果会揭示一个经典问题glucose、blood_pressure、skin_thickness、insulin、bmi 这五列的最小值都是 0。从医学常识看一个人的血糖、血压、皮脂厚度、BMI 不可能为 0所以这些 0 值实际上是“未测量”或“测量失败”的编码属于隐式缺失。如果不处理直接喂给网络模型会学到“血压为 0 的人有某种规律”这完全是噪声还会拉低各特征的真实分布。常见处理策略是按列用均值或中位数替换 0 值我一般倾向中位数因为它对离群点更鲁棒。这里有一个容易踩的坑替换操作必须在切分训练集和测试集之前完成否则测试集的统计信息会混进训练集造成数据泄露后面评估出来的准确率是虚高的。下面这段代码用SimpleImputer做替换from sklearn.impute import SimpleImputer cols_to_impute [glucose, blood_pressure, skin_thickness, insulin, bmi] imputer SimpleImputer(missing_values0, strategymedian) df[cols_to_impute] imputer.fit_transform(df[cols_to_impute]) print((df[cols_to_impute] 0).sum())missing_values0告诉它哪些值算缺失strategymedian表示用该列中位数填充。fit_transform是在整个 DataFrame 上执行的因为此时还没切分数据集所以不涉及数据泄露。替换完再统计一次 0 值数量应该全部归零。除了中位数也有人用均值或 KNN 插值在这个数据集上中位数和均值对最终 AUC 的影响不大真正影响大的是“有没有做替换”这件事本身。2.3 归一化和数据切分为什么 StandardScaler 只在训练集上 fitMLP 的 Dense 层在做矩阵乘法时会把输入和权重直接相乘如果某个特征落在 0 到 800 的范围比如胰岛素另一个落在 0 到 80比如年龄梯度更新时量纲差异会主导学习过程训练会变得很不稳定。因此特征归一化是必须的。常用方案是 StandardScaler把每个特征变成均值 0、方差 1也有人用 MinMaxScaler 把范围压到 0 到 1。我建议用 StandardScaler它在分类任务里更通用。from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split X df.drop(outcome, axis1).values.astype(float32) y df[outcome].values.astype(float32) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy) scaler StandardScaler().fit(X_train) X_train scaler.transform(X_train) X_test scaler.transform(X_test)这里有两个细节值得记下来。第一个是stratifyy它让训练集和测试集里的正负样本比例和原始数据一致避免随机切分导致某个集合里病人只占 10%Pima 数据集大约只有 35% 的阳性样本不做分层抽样很容易切出分布失衡的训练集。第二个是StandardScaler只fit在训练集上测试集直接用训练集的均值方差去transform这仍然是防数据泄露。另外把所有值转成float32是因为 Keras 在 CPU 和 GPU 上对 float32 支持最好float64 不仅慢有些算子还会悄悄降精度。这套流程用在人工智能大作业或毕业设计里作为数据预处理部分完全够用。3. 用 Keras 搭建多层感知器结构设计、编译参数与实现数据准备好之后核心工作就是把多层感知器搭起来。MLP 的“多层”指的是至少有一个隐藏层位于输入和输出之间。对这个数据集来说输入层是 8 个神经元对应 8 个特征输出层是 1 个神经元用 sigmoid 激活输出 0 到 1 之间的概率代表患糖尿病的风险。中间的隐藏层层数和每层神经元数量是这个项目里最需要花心思调的部分也是最容易放飞自我的地方。3.1 为什么 Pima 数据集不适合堆太深的网络样本量与隐藏层的经验法则很多新手一上来就堆 5 层隐藏层每层 128 个神经元结果要么过拟合要么训练特别慢最后准确率还不如一个简单的 3 层网络。原因在于 Pima 数据集只有 768 条样本划分后训练集 600 条左右这个规模根本撑不起大网络。我一般会从两个隐藏层开始第一层 16 到 32 个神经元第二层 8 到 16 个激活函数用 ReLU。这个规模在 MLP 里算轻量级但已经足够建模血糖、BMI、年龄这些特征之间的非线性关系。ReLU 激活在隐藏层是默认选择因为它计算简单、能缓解梯度消失输出层必须用 sigmoid因为它把网络输出压缩到 0 到 1可以直接解释成概率。这里不要用 softmaxsoftmax 适合多分类二分类用单个输出加 sigmoid 更自然。还有一个容易忽略的点神经元数量不是越多越好而是越少越容易排查问题。如果第一层 32 个神经元训练不出来把 32 换成 128 大概率也训练不出来反而更难定位是数据问题还是结构问题。3.2 用 Sequential 实现 MLPDense、Dropout 和 BatchNormalization 怎么搭配Keras 里最直观的建模方式是用 Sequential 把层一层层拼起来。每个 Dense 层的第一个参数是输出维度第二个参数是激活函数。在隐藏层之间加 BatchNormalization 可以让每层输入保持稳定加 Dropout 可以随机丢弃一部分神经元起到正则化作用。但这两个组件在 768 样本的小数据集上要慎用Dropout 丢太多会让模型学不动BatchNormalization 在训练初期会让 loss 曲线变得奇怪。from tensorflow import keras from tensorflow.keras import layers model keras.Sequential([ layers.Dense(32, activationrelu, input_shape(8,), kernel_initializerhe_normal), layers.Dense(16, activationrelu), layers.Dropout(0.2), layers.Dense(1, activationsigmoid) ]) model.summary()这个模型的结构是输入层 8 个特征第一层 32 个 ReLU 神经元第二层 16 个 ReLU 神经元然后一个 Dropout 层最后是 1 个 sigmoid 输出。input_shape(8,)告诉 Keras 每个样本有 8 个特征这个参数只在第一层写。kernel_initializerhe_normal是 ReLU 的常见搭配如果换成默认的 glorot_uniform深层网络可能出现激活值逐渐衰减的问题虽然这个浅网络不一定表现出来但养成好习惯很重要。model.summary()会打印每层参数数量可以用来核对计算是否正确第一层的参数量是 8 乘 32 加 32 等于 288。Dropout 放在第二个隐藏层和输出层之间0.2 表示训练时随机保留 80% 的神经元。测试阶段 Dropout 自动关闭不需要手动处理这是 Keras 框架封装好的行为。如果你发现训练集上 loss 下不去可以先去掉 Dropout等模型能过拟合了再加回来调力度。参数规模参考下表层输出维度参数量激活函数Dense 132288reluDense 216528reluDropout160无Dense 3117sigmoid3.3 编译这一步决定训练成败Adam、BinaryCrossentropy 和 metrics 的选型模型搭好后要 compile这一步决定网络用什么方式更新权重、用什么标准衡量误差。二分类问题的标准损失函数是 BinaryCrossentropy它在数学上对应最大似然估计比均方误差更适合分类。优化器选 Adam它的自适应学习率让新手不用手动调整 learning rate 就能训练但我建议显式设 learning rate 为 0.001 而不是用默认值这样之后调试时有基线可对比。model.compile(optimizerkeras.optimizers.Adam(learning_rate0.001), losskeras.losses.BinaryCrossentropy(), metrics[accuracy, keras.metrics.AUC(nameauc)])Adam 的三个核心参数里learning rate 0.001 是最常用起点beta_1 和 beta_2 控制动量一般不用动。lossBinaryCrossentropy()是二分类的正确选型如果用了CategoricalCrossentropy标签必须改成 one-hot 编码SparseCategoricalCrossentropy则要求整数标签。这三种损失对应不同的标签格式混用是 Keras 报错的常见原因。metrics 里同时加 accuracy 和 AUC是因为在诊断场景中只看准确率会踩坑具体原因放在下一章展开。4. 训练与评估准确率会骗人AUC 和混淆矩阵才是诊断项目的核心模型编译好之后就是训练和评估。很多教程的执行方式是把全部数据拿去 fit训完打印一个 accuracy 就算结束。在真实工作流里这样做有两个问题一是没有留出验证集模型在训练数据上的表现被当成真实水平会严重高估二是只看 accuracy会掩盖模型对少数类别的预测能力。糖尿病数据集中阳性样本约占 35%一个把所有样本都预测为阴性的模型准确率也有 65%但这个模型完全没有诊断价值。4.1 用 EarlyStopping、ModelCheckpoint 和 class_weight 稳住训练过程在这个项目上我不建议固定 epochs 跑到底。因为样本量小模型很容易在几十个 epoch 后开始过拟合表现为训练 loss 继续下降而验证 loss 开始上升。EarlyStopping 会在验证 loss 连续多个 epoch 不再改善时自动停止训练ModelCheckpoint 会把最好的权重存下来方便之后回滚。这两个回调组合起来是 Keras 训练里最常用的“后悔药”。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience30, restore_best_weightsTrue), ModelCheckpoint(diabetes_model.h5, monitorval_loss, save_best_onlyTrue, verbose1) ] history model.fit( X_train, y_train, validation_split0.15, epochs500, batch_size32, callbackscallbacks, class_weight{0: 1.0, 1: 1.3}, verbose1 )这组参数里validation_split0.15表示 Keras 自动从训练集末尾切 15% 当验证集。注意它是在train_test_split之后再切所以完整数据流是原始集先切出训练集和测试集训练集再切出一部分做验证集。epochs 设 500 只是上限实际会在连续 30 个 epoch 验证 loss 没改善时停掉。class_weight{0: 1.0, 1: 1.3}把少数类的损失权重提高让模型更在意把病人查出来。1.3 是一个起始值想细调可以用网格搜索在 1.1 到 1.5 之间扫一遍。4.2 用 classification_report、AUC 和混淆矩阵评估诊断能力训练结束后在测试集上评估。对糖尿病诊断这个场景最危险的错误是漏诊一个血糖异常的人被模型说成没事比让一个正常人去做进一步检查后果严重得多。因此召回率比准确率重要AUC 比召回率更稳健因为它不依赖判定阈值。from sklearn.metrics import confusion_matrix, roc_auc_score, classification_report y_prob model.predict(X_test).ravel() y_pred (y_prob 0.5).astype(int) print(classification_report(y_test, y_pred, target_names[non-diabetic, diabetic])) print(AUC:, roc_auc_score(y_test, y_prob)) print(confusion_matrix(y_test, y_pred))这段代码的思路是先用 0.5 做阈值把概率转成类别标签同时保留原始概率来计算 AUC。classification_report输出 precision、recall、f1-scorediabetic 那一行的 recall 含义是“真正患病的人里有多大比例被查出来了”。AUC 则看完所有阈值衡量模型把患病和健康样本分开的能力0.5 是随机水平0.8 到 0.9 在这个数据集上属于可用级别。confusion_matrix能直观看到漏诊数也就是“标签为 1 但模型预测成 0”的数量这是诊断项目里最不能放过的数字。4.3 阈值偏移与交叉验证医疗场景下的另一种调参思路0.5 是最直觉的阈值但不是最合理的选择。如果漏诊成本大于误诊成本就应该把判定阈值下调让模型更倾向于“怀疑有病”。常见做法是在验证集上画 precision-recall 曲线找一个让召回率不低于 80% 的最大精确率点。比如把判定阈值从 0.5 降到 0.35模型会输出更多阳性预测召回率明显提升代价是误报增加具体调多少取决于项目里更愿意接受哪类错误。另外一次 train_test_split 的测试集只有 150 条左右评估结果方差很大。更可靠的做法是 5 折交叉验证把训练过程重复 5 次统计每折 AUC 的均值和标准差这样汇报出来的指标才经得起推敲。Keras 里用KFold配合model.fit实现每次重新初始化模型权重最后把 5 个 AUC 取平均。这个口径写进人工智能项目实战报告里比单次跑的准确率有说服力得多。5. 避坑指南Pima 数据集 MLP 跑通的 5 个常见问题与排查这部分是我实际跑这个项目时积累的踩坑记录每一条都按“现象、原因、解决”来写方便你排查时对号入座。这些问题单独看都不难但组合在一起会浪费大量时间尤其是当你以为自己写的 Keras 代码有问题其实是前面某一步埋了雷。5.1 零值替换后准确率反而下降数据泄露已经埋下了现象不做零值处理时跑一个两层 MLPAUC 约 0.80用中位数替换零值之后 AUC 掉到 0.76看起来“处理数据”反而帮了倒忙。原因最常见的是在切分数据集之前把测试集也一起拿去fit_transform了。测试集的统计信息混进训练集模型在测试集上的表现被虚高一旦换成正确的流程原本虚高的数值就掉下来了。另一个原因是某列零值占比太高比如胰岛素列有接近一半的 0直接全列用中位数填会把原本的分布信息冲淡。解决先train_test_split再在训练集上fit填补器和标准化器最后才transform测试集。也可以对零值比例特别高的列单独处理比如胰岛素列结合血糖和 BMI 做回归插补但入门阶段用中位数已经足够。5.2 Keras 版本差异Sequential API、.h5 和 .keras 格式对不上现象在自己电脑上训练好的模型换一台机器load_model时报错提示 Unknown activation function 或无法解析文件格式。原因Keras 经历过几次大版本变动。如果你跟的是早期 keras 安装教程装的可能还是 Keras 1.x 或 2.x 的老 API而新版 TensorFlow 已经默认推荐 Keras 3save出来的默认格式从.h5变成了.keras。旧代码里model.save(diabetes_model.h5)在新版里仍然能保存但换机器加载时两端版本不一致就会出现解析失败或激活函数编号对不上的问题。解决统一用 Keras 3 的.keras格式保存load_model前先打印keras.__version__确认两端大版本一致。如果必须用.h5加载时明确指定compileFalse再手动model.compile能绕开部分兼容性报错。5.3 训练 loss 卡在 0.69 附近不动learning rate 和归一化都没对上现象训练一开始 loss 就在 0.68 到 0.70 之间震荡几百个 epoch 后也不下降看起来网络完全没在学。原因0.69 附近的 loss 对应的是“概率预测在 0.5 附近波动”的状态。最常见的有两个一是 learning rate 设得过大Adam 的默认 0.001 一般没问题但手滑设成 0.01 就可能在损失曲面里来回跳二是输入数据没归一化胰岛素这类量级大的特征主导了梯度更新其他特征的信号被淹没了。解决先把X_train.std()打印出来如果某些特征标准差是几十甚至几百说明 StandardScaler 没生效回查是不是把原始数据送进了fit。然后确认 learning rate 是 0.001再看训练曲线。如果还是不降把第一层神经元从 32 减到 8快速验证数据里有没有可学的信号。5.4 训练 AUC 接近 1、测试 AUC 只有 0.6过拟合的收敛策略现象训练集上 AUC 一路冲到 0.95 以上测试集 AUC 只有 0.6 左右模型把训练集背下来了。原因Pima 数据集样本量小隐藏层神经元多、训练轮数长神经网络很容易把噪声也当成规律。Dropout 设为 0.2 可能不够EarlyStopping 的 patience 设为 30 太久模型已经在过拟合区间多跑了十几个 epoch。解决把 Dropout 提升到 0.4EarlyStopping 的 patience 缩短到 10 到 15验证 loss 连续 10 个 epoch 不降就停。同时可以在 Dense 层加kernel_regularizerkeras.regularizers.l2(0.001)把权重约束在较小的范围。改完再对比测试集 AUC正常应该能回到 0.75 到 0.85 区间。5.5 每次跑结果都不一样随机性与可复现实验现象连续跑三次训练每次测试集 AUC 都不同波动范围甚至能到 0.05。原因神经网络训练本身有随机性包括权重初始化和数据打乱顺序。train_test_split里设了random_state42只控制了切分没有控制 Keras 内部的随机过程。解决在训练脚本开头设置全局随机种子random.seed(0)、np.random.seed(0)、tf.random.set_seed(0)三行一起写。但要注意即使设置了随机种子GPU 上的某些算子仍然不完全确定。最终评估时跑 5 次取 AUC 均值和标准差比单次结果更有参考价值这也是论文里通用的汇报方式。6. 模型保存、加载与复现把训练结果变成一个可复用的预测脚本训练完成后要保存两样东西模型权重和预处理参数。只保存模型而丢掉 scaler后续在真实样本上预测时会出现结果完全离谱的问题因为新样本没有经过和训练时相同的归一化。这一步是项目交付前最容易遗漏的环节也是“训练完了但没法用”的主要来源。6.1 保存模型与预处理器缺一不可model.save(diabetes_model.keras) import joblib joblib.dump(scaler, diabetes_scaler.joblib)model.save保存的是整个模型结构加权重加载后可以直接预测scaler用 joblib 保存成独立文件。如果你在第 5 章遇到过.h5的兼容性问题这里用.keras格式更省心。加载模型时配合keras.models.load_model就可以了。6.2 用保存的模型预测一条新样本loaded keras.models.load_model(diabetes_model.keras) scaler joblib.load(diabetes_scaler.joblib) new_sample [[6, 148, 72, 35, 0, 33.6, 0.627, 50]] scaled scaler.transform(new_sample) print(loaded.predict(scaled))new_sample对应 Pima 数据集里一个经典样本怀孕 6 次、血糖 148、血压 72、皮脂厚度 35、胰岛素 0、BMI 33.6、糖尿病谱系函数 0.627、年龄 50真实标签是阳性。用这个样本验证模型能确认保存和加载流程是否完整。预测输出是一个概率值落在 0 到 1 之间需要结合你自己设定的判定阈值来解释。如果输出接近 0.5说明模型对这个样本没有明确把握这也是单次预测天然的不确定性。我现在的习惯是每跑完一次实验都把 scaler、模型权重、训练曲线和测试集评估结果放在同一个目录里命名带上日期。这样过两个月回头看还能清楚还原当时的数据处理和超参数设置。这个项目最大的价值不在于把 AUC 刷到多高而在于它把“数据预处理、模型搭建、训练监控、评估口径、保存复现”这条完整链路串了起来。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →