FEATURED · 精选文章

使用Keras构建多层感知器进行糖尿病诊断:从数据处理到模型评估的完整实战

发布时间 / 2026/8/27 3:41:57
来源 / 创域科博编辑部
栏目 / 资讯中心
使用Keras构建多层感知器进行糖尿病诊断:从数据处理到模型评估的完整实战 简介多层感知器MLP作为深度学习的基础模型通过模拟人脑神经元连接实现复杂模式识别。其核心原理在于前向传播计算与反向传播优化能够自动学习数据中的非线性关系。在工程实践中MLP凭借其强大的特征提取能力成为解决分类、回归问题的关键技术。尤其在医疗健康等垂直领域MLP可用于构建辅助诊断模型处理如印第安人糖尿病数据集这类结构化医疗数据。通过特征工程处理缺失值与异常值并利用Dropout等正则化技术防止过拟合可以构建出稳健的预测模型。本项目以糖尿病诊断为例完整展示了使用Keras构建MLP的流程涵盖了数据预处理、模型构建、训练调优及基于精确率与召回率的评估为机器学习在现实场景中的应用提供了实践范本。1. 项目背景与核心价值为什么用Keras诊断糖尿病如果你正在寻找一个能快速上手、又能体现机器学习核心流程的实战项目那么“使用Keras构建多层感知器MLP诊断印第安人糖尿病”绝对是一个经典选择。这不仅仅是一个“大作业”或“练手项目”它背后串联了从数据处理、模型构建到结果评估的完整机器学习生命周期。印第安人糖尿病数据集Pima Indians Diabetes Dataset在机器学习社区里几乎无人不知它包含了8个医学特征和1个二分类标签是否患病数据量适中特征含义清晰是入门监督学习分类任务的绝佳“试金石”。而Keras作为构建在TensorFlow之上的高级神经网络API以其极简的语法和模块化的设计让构建一个多层感知器变得像搭积木一样简单。你不再需要从零开始编写复杂的反向传播算法只需关注网络的结构设计和数据流向。这个项目的核心价值在于它能让你在短时间内亲身体验到如何将原始的、带噪声的医疗数据通过一系列标准化的数据处理和建模步骤转化为一个具备初步诊断能力的预测模型。对于初学者这是理解人工智能在垂直领域如医疗健康应用逻辑的绝佳窗口对于有一定经验的开发者这也是一个验证新想法、测试不同网络架构的快速实验平台。2. 环境搭建与数据初探万事开头细在开始构建任何模型之前一个稳定、兼容的开发环境是基石。很多人卡在第一步往往是因为环境配置的混乱。2.1 构建纯净的Python虚拟环境我强烈建议你为这个项目创建一个独立的虚拟环境。这能避免与系统中其他项目的Python包发生版本冲突。使用conda或venv都可以这里以venv为例因为它更轻量且是Python标准库的一部分。# 创建名为keras_diabetes的虚拟环境 python -m venv keras_diabetes # 激活环境Windows keras_diabetes\Scripts\activate # 激活环境macOS/Linux source keras_diabetes/bin/activate环境激活后你的命令行提示符前会出现(keras_diabetes)字样这表示你已进入该独立环境。2.2 核心库的精准安装接下来安装核心库。版本的选择至关重要不兼容的版本组合是后续无数报错的根源。基于长期的稳定性和兼容性考虑我推荐以下组合# 安装TensorFlow和Keras。注意现代Keras已直接集成在TensorFlow中。 pip install tensorflow2.15.0 # 安装数据处理和科学计算全家桶 pip install numpy1.24.3 pandas2.0.3 scikit-learn1.3.0 matplotlib3.7.2注意这里固定了版本号。TensorFlow 2.15是一个长期支持版本与上述NumPy、Pandas版本兼容性很好。直接pip install tensorflow会安装最新版但最新版有时会引入不兼容的改动对于学习项目稳定压倒一切。2.3 加载与审视印第安人糖尿病数据集这个数据集通常内置于scikit-learn的datasets模块中但更常见的是从UCI机器学习仓库获取的CSV文件。我们假设你有一个名为diabetes.csv的文件。首先让我们用Pandas看看它的“长相”。import pandas as pd # 加载数据 df pd.read_csv(diabetes.csv) # 查看数据前5行和基本信息 print(df.head()) print(df.info()) print(df.describe())你会看到类似如下的输出结构Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome 0 6 148 72 35 0 33.6 0.627 50 1 1 1 85 66 29 0 26.6 0.351 31 0 ...这8个特征分别是怀孕次数、血糖、血压、皮褶厚度、胰岛素、体重指数、糖尿病谱系功能、年龄。Outcome是目标变量0代表阴性未患病1代表阳性患病。数据初探的发现与思考 通过df.describe()你可能会立刻发现一些问题。例如像“胰岛素”、“皮褶厚度”这样的特征最小值是0。这在医学上是不可能的人不可能没有胰岛素或皮肤厚度为0这些0值实际上是缺失值的占位符。这是现实数据集的典型特点——不完美、有噪声。如何处理这些“脏数据”将直接决定模型性能的下限。我们稍后在特征工程环节会专门处理。3. 数据预处理与特征工程模型性能的基石原始数据直接喂给模型效果通常很差。预处理的目标是让数据更“干净”、更“规整”便于模型学习。3.1 处理缺失值与异常值如前所述数据中的0值在某些列中代表缺失。我们需要区分哪些列的0是合理的如怀孕次数哪些是不合理的。通常“血糖”、“血压”、“皮褶厚度”、“胰岛素”、“BMI”这几个生理指标0值视为缺失。# 将特定特征中的0值替换为NaN缺失值 zero_fields [Glucose, BloodPressure, SkinThickness, Insulin, BMI] df[zero_fields] df[zero_fields].replace(0, pd.NA) # 检查缺失情况 print(df.isnull().sum())处理缺失值有多种策略删除、用均值/中位数填充、用模型预测填充。对于这个数据集样本量不大768条删除缺失行损失信息太多。一个稳健的做法是使用中位数填充因为中位数对异常值不敏感。from sklearn.impute import SimpleImputer import numpy as np # 使用中位数填充缺失值 imputer SimpleImputer(strategymedian) # 只填充我们标记为缺失的那些特征 df_filled pd.DataFrame(imputer.fit_transform(df[zero_fields]), columnszero_fields) df[zero_fields] df_filled3.2 特征标准化与数据集划分神经网络对输入数据的尺度非常敏感。如果“年龄”范围是20-80而“胰岛素”范围是0-900模型会花很多精力去适应尺度大的特征导致训练缓慢甚至不收敛。标准化Standardization将数据缩放为均值为0、标准差为1的分布。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 分离特征(X)和目标(y) X df.drop(Outcome, axis1) y df[Outcome] # 划分训练集和测试集通常用80%/20%或70%/30%。设置random_state保证结果可复现。 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42, stratifyy) # 注意stratifyy这保证了训练集和测试集中正负样本的比例与原数据集一致非常重要 # 初始化标准化器并用训练集“拟合”它计算均值和标准差 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 用训练集得到的参数去转换测试集避免数据泄露 X_test_scaled scaler.transform(X_test)这里有一个关键经验StandardScaler的fit方法只能在训练集上调用。fit_transform训练集然后只transform测试集。如果用在测试集上也调用fit_transform就等于让模型在训练时“偷看”了测试集的信息会严重高估模型在真实未知数据上的性能这是初学者常犯的严重错误。4. 构建Keras多层感知器模型从蓝图到实现数据准备就绪现在进入核心环节——搭建神经网络。多层感知器MLP是一种前馈神经网络包含输入层、一个或多个隐藏层和输出层。4.1 模型架构设计与层详解我们将构建一个相对简单的MLP输入层8个神经元对应8个特征、两个隐藏层、一个输出层。from tensorflow import keras from tensorflow.keras import layers model keras.Sequential([ # 输入层需要指定input_shape即每个样本的特征数量。 layers.Dense(64, activationrelu, input_shape(X_train_scaled.shape[1],)), # 第一个隐藏层64个神经元使用ReLU激活函数。 # ReLURectified Linear Unit是目前最常用的激活函数能有效缓解梯度消失问题计算简单。 layers.Dropout(0.3), # Dropout层随机“丢弃”30%的神经元输出强制网络学习更鲁棒的特征是防止过拟合的利器。 layers.Dense(32, activationrelu), # 第二个隐藏层32个神经元。 layers.Dropout(0.3), layers.Dense(1, activationsigmoid) # 输出层1个神经元二分类使用sigmoid激活函数将输出压缩到(0,1)之间表示概率。 ])为什么这样设计神经元数量第一个隐藏层神经元数64通常大于输入特征数8以学习更复杂的特征组合。第二个隐藏层减少到32是一种常见的“金字塔”式结构逐步压缩信息。这些数字没有绝对标准需要通过实验调整。激活函数隐藏层用ReLU因为它能产生稀疏激活加速训练。输出层用Sigmoid因为它天然适合二分类的概率输出。Dropout这是模型正则化的关键。在训练时随机断开一部分神经元的连接可以看作是在训练多个“子网络”的集成能显著提升模型的泛化能力。0.3的丢弃率是一个常用的起点。4.2 模型编译配置学习过程模型架构定义好后需要指定它如何学习。model.compile( optimizeradam, # 优化器Adam是当前最流行的自适应学习率优化算法它结合了Momentum和RMSProp的优点通常能获得又快又好的收敛效果。 lossbinary_crossentropy, # 损失函数二分类交叉熵。它衡量模型预测概率分布与真实标签分布的差异是二分类任务的标准选择。 metrics[accuracy, keras.metrics.Precision(), keras.metrics.Recall()] # 评估指标除了准确率我还强烈建议监控精确率Precision和召回率Recall。 # 在医疗诊断场景中我们往往更关心“查得准不准”精确率预测为患者中真正患病的比例和“查得全不全”召回率真正患者中被预测出来的比例。 )5. 模型训练、验证与调优在迭代中寻找最佳有了数据和模型训练似乎只是一行代码的事但其中的细节决定了模型的最终成败。5.1 训练循环与验证集监控我们使用fit方法进行训练。这里引入一个非常重要的概念——验证集。history model.fit( X_train_scaled, y_train, epochs150, # 整个训练集遍历150次 batch_size32, # 每次梯度更新使用32个样本 validation_split0.2, # 从训练集中再划分20%作为验证集 verbose1 # 显示训练进度条 )关键点解析验证集Validation Set它不同于最终的测试集。验证集用于在训练过程中实时评估模型在未见过的数据上的表现帮助我们判断模型是否过拟合以及何时应该停止训练早停Early Stopping。validation_split0.2意味着我们从X_train_scaled中拿出20%不参与参数更新只用于验证。批次大小Batch Size一次迭代一个Batch使用的样本数。较小的Batch如32能带来更频繁的权重更新和可能更好的泛化能力但训练更慢、噪声更大。较大的Batch训练更稳定、更快但可能陷入局部最优。32是一个常用的折中选择。训练轮数Epochs设置一个较大的值如150但我们并不一定真的需要训练这么多轮。我们需要观察验证集损失的变化来决定何时停止。5.2 可视化训练过程与诊断过拟合训练返回的history对象包含了每个epoch的训练和验证指标。绘制它们是最直观的诊断工具。import matplotlib.pyplot as plt def plot_training_history(history): fig, axes plt.subplots(1, 2, figsize(12, 4)) # 绘制损失曲线 axes[0].plot(history.history[loss], label训练损失) axes[0].plot(history.history[val_loss], label验证损失) axes[0].set_title(模型损失) axes[0].set_xlabel(Epoch) axes[0].set_ylabel(Loss) axes[0].legend() axes[0].grid(True) # 绘制准确率曲线 axes[1].plot(history.history[accuracy], label训练准确率) axes[1].plot(history.history[val_accuracy], label验证准确率) axes[1].set_title(模型准确率) axes[1].set_xlabel(Epoch) axes[1].set_ylabel(Accuracy) axes[1].legend() axes[1].grid(True) plt.tight_layout() plt.show() plot_training_history(history)如何解读图表理想情况训练和验证损失同步下降准确率同步上升最终趋于平稳。过拟合Overfitting训练损失持续下降但验证损失在某个点后开始上升。这意味着模型过度记忆了训练数据的噪声而无法泛化到新数据。我们的图中如果验证损失在Epoch 50后开始反弹而训练损失还在降就是典型的过拟合。欠拟合Underfitting训练损失和验证损失都很高且很早就停滞不前。这说明模型能力不足无法捕捉数据中的模式。5.3 使用回调函数实施早停与模型保存为了避免过拟合我们不应该机械地训练完所有150个epoch。Keras的回调函数Callbacks可以在训练过程中介入。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping( monitorval_loss, # 监控验证集损失 patience15, # 容忍验证损失连续15个epoch不下降 restore_best_weightsTrue # 停止训练时恢复验证损失最低时的模型权重 ), ModelCheckpoint( filepathbest_diabetes_model.keras, # 模型保存路径 monitorval_accuracy, # 监控验证集准确率 save_best_onlyTrue, # 只保存最好的模型 verbose1 ) ] # 重新编译并训练加入回调函数 model.fit(X_train_scaled, y_train, epochs150, batch_size32, validation_split0.2, callbackscallbacks, verbose1)早停EarlyStopping的实战心得patience参数需要根据你的训练曲线来调整。如果损失曲线波动很大patience可以设大一点如20避免在局部波动时过早停止。restore_best_weightsTrue这个选项极其有用它能确保你最终得到的是整个训练过程中泛化能力最强的模型而不是停止时的那个可能已经过拟合的模型。6. 模型评估与结果分析超越准确率训练完成后我们需要在真正的、从未参与过任何训练过程的测试集上对模型进行最终评估。6.1 综合性能评估# 加载保存的最佳模型如果使用了ModelCheckpoint # best_model keras.models.load_model(best_diabetes_model.keras) # 或者直接使用训练好的model如果用了restore_best_weightsTrue test_loss, test_accuracy, test_precision, test_recall model.evaluate(X_test_scaled, y_test, verbose0) print(f测试集损失: {test_loss:.4f}) print(f测试集准确率: {test_accuracy:.4f}) print(f测试集精确率: {test_precision:.4f}) print(f测试集召回率: {test_recall:.4f})假设你得到准确率78%精确率75%召回率70%。这说明了什么仅看准确率可能还不错但结合精确率和召回率我们发现模型在“查全”和“查准”之间做了权衡。在这个糖尿病诊断场景中召回率可能更重要因为漏诊一个糖尿病患者假阴性的后果可能比误诊一个健康人假阳性更严重。后者可以通过二次检查排除而前者可能延误治疗。6.2 深入分析混淆矩阵与分类报告为了更细致地了解模型在哪里犯错我们需要混淆矩阵。from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns # 获得模型在测试集上的预测类别概率0.5则为1 y_pred_prob model.predict(X_test_scaled) y_pred (y_pred_prob 0.5).astype(int32) # 计算混淆矩阵 cm confusion_matrix(y_test, y_pred) print(混淆矩阵:) print(cm) # 可视化混淆矩阵 plt.figure(figsize(6,5)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.title(混淆矩阵) plt.ylabel(真实标签) plt.xlabel(预测标签) plt.show() # 打印详细的分类报告 print(\n分类报告:) print(classification_report(y_test, y_pred, target_names[未患病, 患病]))分类报告会给出每个类别的精确率、召回率、F1-score精确率和召回率的调和平均和支持数样本数。通过它你能清晰地看到模型对“患病”类通常样本较少的识别能力如何。6.3 调整决策阈值以优化业务指标默认情况下我们使用0.5作为将概率转换为类别的阈值。但在实际业务中这个阈值是可以调整的以偏向精确率或召回率。from sklearn.metrics import precision_recall_curve # 计算不同阈值下的精确率和召回率 precisions, recalls, thresholds precision_recall_curve(y_test, y_pred_prob) # 绘制精确率-召回率曲线 plt.figure(figsize(8,6)) plt.plot(thresholds, precisions[:-1], b--, label精确率) plt.plot(thresholds, recalls[:-1], g-, label召回率) plt.xlabel(决策阈值) plt.ylabel(分数) plt.legend(loccenter left) plt.grid(True) plt.title(精确率-召回率 vs 决策阈值) plt.show()观察曲线如果你希望召回率提高到80%可能需要将阈值降低到0.3左右但这会牺牲一部分精确率。这个权衡需要结合具体的医疗诊断成本和收益来决定。7. 模型优化与进阶探索从能用变好用一个基础的MLP模型跑通只是起点。要让模型性能再上一个台阶可以从以下几个方面进行探索和优化。7.1 网络架构的超参数调优我们之前手动选择了层数、神经元数、Dropout率。这些都可以系统性地进行优化。虽然手动尝试可行但更高效的方法是使用KerasTuner或scikit-learn的GridSearchCV需配合KerasClassifier包装器进行自动化超参数搜索。# 这是一个简化的思路示例 def build_model(hp): model keras.Sequential() model.add(layers.Dense(unitshp.Int(units_input, min_value32, max_value128, step32), activationrelu, input_shape(8,))) model.add(layers.Dropout(ratehp.Float(dropout_1, 0.2, 0.5, step0.1))) # 可以搜索隐藏层的数量 for i in range(hp.Int(num_layers, 1, 3)): model.add(layers.Dense(unitshp.Int(funits_{i}, min_value16, max_value64, step16), activationrelu)) model.add(layers.Dropout(ratehp.Float(fdropout_{i2}, 0.2, 0.5, step0.1))) model.add(layers.Dense(1, activationsigmoid)) model.compile(optimizerkeras.optimizers.Adam(hp.Float(learning_rate, 1e-4, 1e-2, samplinglog)), lossbinary_crossentropy, metrics[accuracy]) return model7.2 应对类别不平衡问题医疗数据集中患病样本正例往往远少于健康样本负例。我们的数据中Outcome1的样本约占35%存在一定的不平衡。这会导致模型倾向于预测多数类对少数类的识别能力差。解决方法包括类别权重Class Weight在训练时给少数类的样本损失赋予更高的权重。from sklearn.utils.class_weight import compute_class_weight import numpy as np class_weights compute_class_weight(balanced, classesnp.unique(y_train), yy_train) class_weight_dict dict(enumerate(class_weights)) # 在model.fit()中加入参数class_weightclass_weight_dict过采样如SMOTE人工合成少数类样本。可以使用imbalanced-learn库。使用更适合的评估指标如前所述关注精确率-召回率曲线和AUC-PR精确率-召回率曲线下面积它们比单纯准确率更能反映不平衡数据下的模型性能。7.3 特征工程的再深入我们目前只做了基本的清洗和标准化。还可以尝试特征交叉创建新的特征如“BMI与年龄的比值”可能揭示更复杂的风险关系。特征选择使用统计方法如卡方检验、互信息或基于模型的方法如查看特征重要性来筛选出最相关的特征可能提升模型性能并降低过拟合风险。分箱Binning将连续特征如年龄转换为离散区间有时能让线性模型捕捉到非线性关系。8. 项目总结与经验沉淀走完这个完整的流程你应该已经得到了一个能够根据8项生理指标预测糖尿病风险的初步诊断模型。回顾整个过程有几个点是我在多次实践中深刻体会到的第一数据质量决定上限模型调优只是逼近这个上限。在这个项目里对缺失值0的处理方式、是否进行标准化对最终结果的影响可能比把隐藏层从2层调到3层更大。花在理解数据和清洗数据上的时间回报率往往最高。第二验证集和早停是防止过拟合的“守门员”。没有它们你很容易在训练集上得到漂亮的数字却在测试集上遭遇滑铁卢。务必养成在训练开始前就划分好验证集并利用回调函数监控训练过程的习惯。第三评估指标要服务于业务目标。对于糖尿病诊断盲目追求高准确率可能走入歧途。结合混淆矩阵和精确率-召回率曲线理解模型在“假阳性”和“假阴性”上的具体表现才能判断这个模型是否真的具有临床参考价值。一个召回率极高的模型即使准确率稍低也可能更适合作为初筛工具。最后这个项目是一个完美的起点。你可以基于此尝试更复杂的网络如加入批归一化层BatchNormalization集成学习如用多个MLP做投票甚至探索时序模型如果数据有序列关系。每一次调整、每一次实验无论成功与否都会让你对“如何教机器从数据中学习”这件事有更接地气的理解。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻