资讯动态

【深度学习实验】—— 基于 LSTM 实现糖尿病探索与预测

发布时间:2026/9/24 10:58:08 来源:尧图企业网站定制
本文为365天深度学习训练营 中的学习记录博客 原作者K同学啊文章目录1. 简介2. 环境3. 代码实现3.1 前期准备3.1.1 导入库与配置运行环境3.1.2 读取数据3.1.3 数据质量检查3.2 探索性数据分析3.2.1 不同类别的特征分布3.3 数据预处理3.3.1 特征与标签划分3.3.2 构建 DataLoader3.4 模型建立与训练3.4.1 定义双层 LSTM3.4.2 定义训练与评估函数3.4.3 训练模型4. 模型评估4.1 训练过程可视化4.2 结果解读5. 总结1. 简介本项目使用包含 1006 条体检记录的糖尿病数据集以年龄、血脂、血压、尿酸等临床指标作为输入使用 PyTorch 搭建双层 LSTM 完成糖尿病二分类实验。修正测试加载器后Notebook 重新运行了 30 个 Epoch。训练准确率从 54.0% 提升到 79.7%测试准确率在第 18、20、21 个 Epoch 达到最高值 74.3%第 30 个 Epoch 回落到 72.3%。这说明模型已经学到超过多数类基线的分类规律但训练后期出现了逐渐扩大的泛化差距。项目内容模型双层 LSTM13 → 200 → 200 → 2任务基于体检指标的糖尿病二分类数据集dia.xls1006 条样本、16 个原始字段建模特征删除卡号、标签和高密度脂蛋白胆固醇后保留 13 个特征数据划分804 条训练样本、202 条测试样本test_size0.2训练配置Adam学习率1e-4批量大小 64训练 30 个 Epoch模型参数量494,002 个可训练参数Notebook 最佳结果测试准确率 74.3%Epoch 18、20、21Notebook 最终结果训练准确率 79.7%测试准确率 72.3%测试损失 0.511数据标签中无糖尿病样本 559 条占 55.57%糖尿病样本 447 条占 44.43%。类别比例较为接近但多数类基线仍有 55.57%因此模型结果需要和该基线共同理解。2. 环境语言环境Python 3.12.13编译器Jupyter Notebook深度学习框架PyTorch 2.14.0、torchvision 0.29.0数据处理pandas 3.0.5、NumPy 2.5.2、scikit-learn 1.9.0数据可视化Matplotlib 3.11.1、Seaborn 0.13.2本次运行设备CPU3. 代码实现3.1 前期准备3.1.1 导入库与配置运行环境首先导入 PyTorch、pandas、Seaborn 等依赖。绘图部分不再将中文字体固定为当前系统未安装的SimHei而是从候选列表中自动选择已安装字体以避免findfont报错和中文方框。importtorch.nnasnnimporttorch.nn.functionalasFimporttorchvision,torchimportnumpyasnpimportpandasaspdimportseabornassnsimportmatplotlib.pyplotaspltfrommatplotlibimportfont_managerfromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportStandardScalerfromtorch.utils.dataimportTensorDataset,DataLoaderimportwarningsfromdatetimeimportdatetime warnings.filterwarnings(ignore)plt.rcParams[savefig.dpi]500plt.rcParams[figure.dpi]100chinese_font_candidates[PingFang SC,PingFang HK,Hiragino Sans GB,STHeiti,Heiti SC,Songti SC,Arial Unicode MS,Microsoft YaHei,SimHei,Noto Sans CJK SC,WenQuanYi Micro Hei,]installed_fonts{font.nameforfontinfont_manager.fontManager.ttflist}chinese_fontnext((fontforfontinchinese_font_candidatesiffontininstalled_fonts),None,)ifchinese_fontisNone:raiseRuntimeError(未找到可用的中文字体请安装 SimHei 或 Noto Sans CJK SC 后重启内核。)plt.rcParams[font.family]sans-serifplt.rcParams[font.sans-serif][chinese_font,DejaVu Sans]plt.rcParams[axes.unicode_minus]Falsedevicetorch.device(cudaiftorch.cuda.is_available()elsecpu)devicedevice(typecpu)3.1.2 读取数据使用 pandas 从 Excel 文件中读取体检数据。数据共有 1006 行、16 列其中是否糖尿病为二分类标签卡号为样本标识其余字段为人口统计或体检指标。DataFramepd.read_excel(./Data/dia.xls)DataFrame.head()DataFrame.shape(1006,16)数据字段包括类型字段样本标识卡号基础信息性别、年龄血脂指标高密度脂蛋白胆固醇、低密度脂蛋白胆固醇、极低密度脂蛋白胆固醇、甘油三酯、总胆固醇生命体征与病史脉搏、舒张压、高血压史生化与检查指标尿素氮、尿酸、肌酐、体重检查结果预测标签是否糖尿病3.1.3 数据质量检查对每一列统计缺失值并检查整行重复记录。运行结果显示所有字段均无缺失值重复样本数也为 0因此当前数据不需要执行缺失值填补或重复值删除。print(数据缺失值-----------)print(DataFrame.isnull().sum())print(数据重复值为f{DataFrame.duplicated().sum()})数据缺失值----------- 卡号0性别0年龄0高密度脂蛋白胆固醇0低密度脂蛋白胆固醇0极低密度脂蛋白胆固醇0甘油三酯0总胆固醇0脉搏0舒张压0高血压史0尿素氮0尿酸0肌酐0体重检查结果0是否糖尿病0dtype: int64 数据重复值为03.2 探索性数据分析3.2.1 不同类别的特征分布使用箱线图比较无糖尿病组标签 0和糖尿病组标签 1的特征分布。图中可以观察到糖尿病组的年龄、甘油三酯、脉搏、舒张压、尿素氮、尿酸和肌酐整体偏高而高密度脂蛋白胆固醇整体偏低。feature_map{年龄:年龄,高密度脂蛋白胆固醇:高密度脂蛋白胆固醇,低密度脂蛋白胆固醇:低密度脂蛋白胆固醇,极低密度脂蛋白胆固醇:极低密度脂蛋白胆固醇,甘油三酯:甘油三酯,总胆固醇:总胆固醇,脉搏:脉搏,舒张压:舒张压,尿素氮:尿素氮,尿酸:尿酸,肌酐:肌酐,体重检查结果:体重检查结果,}plt.figure(figsize(15,10))fori,(col,col_name)inenumerate(feature_map.items(),1):plt.subplot(3,5,i)sns.boxplot(xDataFrame[是否糖尿病],yDataFrame[col])plt.title(f{col_name}的箱线图,fontsize14)plt.ylabel(数值,fontsize12)plt.grid(axisy,linestyle--,alpha0.7)plt.tight_layout()plt.show()两类样本的部分特征中位数如下特征无糖尿病0糖尿病1年龄43.0054.00高密度脂蛋白胆固醇1.230.98低密度脂蛋白胆固醇2.562.85甘油三酯1.031.87脉搏77.0083.00舒张压71.0083.00尿素氮4.965.80尿酸307.10363.00肌酐56.0065.00体重检查结果1.002.00箱线图也显示甘油三酯、尿素氮和肌酐等指标存在明显离群点。标准化可以减小量纲差异但不会消除离群点的影响后续可结合稳健缩放、截尾处理或医学规则进一步检查。3.3 数据预处理3.3.1 特征与标签划分删除仅用于标识样本的卡号、预测标签是否糖尿病以及当前实验没有纳入模型的高密度脂蛋白胆固醇最终得到 13 个输入特征。StandardScaler将每一列转换为近似零均值、单位方差使不同量纲的指标能够在相近数值范围内参与梯度更新。随后将 NumPy 数组转换为 PyTorch 张量并按照 80%/20% 随机划分数据。XDataFrame.drop([卡号,是否糖尿病,高密度脂蛋白胆固醇],axis1)yDataFrame[是否糖尿病]sc_XStandardScaler()Xsc_X.fit_transform(X)Xtorch.tensor(np.array(X),dtypetorch.float32)ytorch.tensor(np.array(y),dtypetorch.int64)train_X,test_X,train_y,test_ytrain_test_split(X,y,test_size0.2,random_state1)train_X.shape,train_y.shape(torch.Size([804,13]), torch.Size([804]))本次划分得到 804 条训练样本和 202 条测试样本。训练集中标签 0/1 的数量分别为 452/352测试集中分别为 107/95。当前 Notebook 在划分前对全部样本执行了fit_transform测试集统计量因此参与了标准化参数计算存在数据泄漏。更严谨的流程应当先划分数据再只对train_X调用fit_transform对test_X仅调用transform。3.3.2 构建 DataLoader训练集和测试集分别以 64 为批量大小构建DataLoader。新版 Notebook 已将测试加载器修正为test_X/test_y因此每轮输出的Test_acc和Test_loss来自划分出的 202 条测试样本。train_dlDataLoader(TensorDataset(train_X,train_y),batch_size64,shuffleFalse,)test_dlDataLoader(TensorDataset(test_X,test_y),batch_size64,shuffleFalse,)3.4 模型建立与训练3.4.1 定义双层 LSTM模型由两个隐藏维度为 200 的 LSTM 层和一个二分类全连接层组成。第一层接收 13 维输入第二层继续处理 200 维隐藏表示最后通过线性层输出两个类别的 logits。模型共包含 494,002 个可训练参数。层输入维度输出维度作用lstm013200将临床特征映射到隐藏空间lstm1200200进一步提取隐藏表示fc02002输出无糖尿病/糖尿病两个类别的分数classmodel_lstm(nn.Module):def__init__(self):super(model_lstm,self).__init__()self.lstm0nn.LSTM(input_size13,hidden_size200,num_layers1,batch_firstTrue,)self.lstm1nn.LSTM(input_size200,hidden_size200,num_layers1,batch_firstTrue,)self.fc0nn.Linear(200,2)defforward(self,x):out,hidden1self.lstm0(x)out,_self.lstm1(out,hidden1)outself.fc0(out)returnout modelmodel_lstm().to(device)modelmodel_lstm((lstm0): LSTM(13,200,batch_firstTrue)(lstm1): LSTM(200,200,batch_firstTrue)(fc0): Linear(in_features200,out_features2,biasTrue))这里还需要注意输入维度DataLoader当前输出(batch_size, 13)的二维张量。PyTorch 会把它视为“不含 batch 维度的单条序列”即将同一批次中的不同患者当作连续时间步处理batch_firstTrue对二维输入不起作用。因此当前模型结果会依赖患者排列和批次边界并不是标准的逐患者表格分类。如果只是希望每位患者以一个时间步进入 LSTM至少应在送入网络前增加序列维度XX.unsqueeze(1)# (样本数, 1, 13)此时模型输出为(batch_size, 1, 2)还需要选择最后一个时间步outself.fc0(out[:,-1,:])# (batch_size, 2)不过该数据没有真实时间序列结构实践中使用多层感知机、逻辑回归、随机森林或梯度提升树通常更自然也更便于解释。3.4.2 定义训练与评估函数训练阶段执行前向传播、交叉熵损失计算、梯度清零、反向传播和参数更新评估阶段通过torch.no_grad()关闭梯度计算只累计损失与准确率。deftrain(dataloader,model,loss_fn,optimizer):sizelen(dataloader.dataset)num_batcheslen(dataloader)train_loss,train_acc0,0forX,yindataloader:X,yX.to(device),y.to(device)predmodel(X)lossloss_fn(pred,y)optimizer.zero_grad()loss.backward()optimizer.step()train_acc(pred.argmax(1)y).type(torch.float).sum().item()train_lossloss.item()train_acc/size train_loss/num_batchesreturntrain_acc,train_lossdeftest(dataloader,model,loss_fn):sizelen(dataloader.dataset)num_batcheslen(dataloader)test_loss,test_acc0,0withtorch.no_grad():forX,yindataloader:X,yX.to(device),y.to(device)y_predmodel(X)lossloss_fn(y_pred,y)test_lossloss.item()test_acc(y_pred.argmax(1)y).type(torch.float).sum().item()test_acc/size test_loss/num_batchesreturntest_acc,test_loss3.4.3 训练模型本实验使用交叉熵损失和 Adam 优化器学习率为1e-4共训练 30 个 Epoch。每轮结束后分别记录训练集和测试集的准确率与平均损失。loss_fnnn.CrossEntropyLoss()learn_rate1e-4opttorch.optim.Adam(model.parameters(),lrlearn_rate)epochs30train_loss,train_acc[],[]test_loss,test_acc[],[]forepochinrange(epochs):model.train()epoch_train_acc,epoch_train_losstrain(train_dl,model,loss_fn,opt)model.eval()epoch_test_acc,epoch_test_losstest(test_dl,model,loss_fn)train_acc.append(epoch_train_acc)train_loss.append(epoch_train_loss)test_acc.append(epoch_test_acc)test_loss.append(epoch_test_loss)lropt.state_dict()[param_groups][0][lr]template(Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E})print(template.format(epoch1,epoch_train_acc*100,epoch_train_loss,epoch_test_acc*100,epoch_test_loss,lr,))print(*20,Done,*20)部分关键 Epoch 的运行结果如下Epoch:1, Train_acc:54.0%, Train_loss:0.692, Test_acc:53.0%, Test_loss:0.694, Lr:1.00E-04 Epoch:5, Train_acc:56.2%, Train_loss:0.674, Test_acc:53.5%, Test_loss:0.689, Lr:1.00E-04 Epoch:10, Train_acc:65.4%, Train_loss:0.629, Test_acc:60.9%, Test_loss:0.648, Lr:1.00E-04 Epoch:15, Train_acc:76.0%, Train_loss:0.482, Test_acc:72.3%, Test_loss:0.551, Lr:1.00E-04 Epoch:18, Train_acc:77.5%, Train_loss:0.455, Test_acc:74.3%, Test_loss:0.533, Lr:1.00E-04 Epoch:20, Train_acc:77.6%, Train_loss:0.444, Test_acc:74.3%, Test_loss:0.525, Lr:1.00E-04 Epoch:21, Train_acc:77.6%, Train_loss:0.439, Test_acc:74.3%, Test_loss:0.522, Lr:1.00E-04 Epoch:25, Train_acc:78.4%, Train_loss:0.424, Test_acc:73.8%, Test_loss:0.514, Lr:1.00E-04 Epoch:29, Train_acc:79.6%, Train_loss:0.412, Test_acc:73.3%, Test_loss:0.510, Lr:1.00E-04 Epoch:30, Train_acc:79.7%, Train_loss:0.408, Test_acc:72.3%, Test_loss:0.511, Lr:1.00E-04Done4. 模型评估4.1 训练过程可视化根据 Notebook 保存的 30 轮运行数值绘制准确率和损失曲线。原绘图代码中的plt.legend(lower right)会把字符串拆成图例标签因此展示时改为plt.legend(loclower right)横轴也改为 Epoch使图像含义更清楚。epochs_rangerange(1,epochs1)plt.figure(figsize(12,4))plt.subplot(1,2,1)plt.plot(epochs_range,train_acc,labelTraining Accuracy)plt.plot(epochs_range,test_acc,labelTest Accuracy)plt.axvline(18,colorgray,linestyle--,alpha0.6,labelFirst Best Test Acc.)plt.legend(loclower right)plt.title(Accuracy over 30 Epochs)plt.xlabel(Epoch)plt.ylabel(Accuracy)plt.subplot(1,2,2)plt.plot(epochs_range,train_loss,labelTraining Loss)plt.plot(epochs_range,test_loss,labelTest Loss)plt.legend(locupper right)plt.title(Loss over 30 Epochs)plt.xlabel(Epoch)plt.ylabel(Cross-Entropy Loss)plt.tight_layout()plt.show()从曲线可以看到第 1 个 Epoch 的测试准确率为 53.0%恰好等于测试集多数类基线说明模型初期基本只会预测无糖尿病类别。第 715 个 Epoch 是性能提升最快的阶段测试准确率从 55.0% 提升到 72.3%。测试准确率在第 18、20、21 个 Epoch 三次达到最高值 74.3%比测试集多数类基线高 21.3 个百分点。第 21 个 Epoch 后训练准确率继续从 77.6% 上升到 79.7%但测试准确率最终回落到 72.3%泛化差距扩大到 7.4 个百分点表现出轻微过拟合。测试损失在第 29 个 Epoch 降至最低值 0.510第 30 个 Epoch 略升到 0.511最低损失与最高准确率不在同一轮说明准确率和概率置信度反映的是不同方面。4.2 结果解读Notebook 的最佳测试结果和第 30 个 Epoch 最终结果如下指标数值解释最佳测试准确率74.3%出现在 Epoch 18、20、21最低测试损失0.510出现在 Epoch 29最终训练准确率79.7%Epoch 30最终训练损失0.408Epoch 30最终测试准确率72.3%Epoch 30比最佳值低 2.0 个百分点最终测试损失0.511Epoch 30测试集多数类基线53.0%测试集中标签 0 占 107/202测试加载器已经使用 202 条留出样本因此新结果比上一轮更能反映训练集之外的表现。最佳测试准确率比多数类基线高 21.3 个百分点说明模型确实捕捉到了一部分有效模式。不过标准化过程仍提前看到了全部数据而且 LSTM 的二维输入会在不同患者之间传递循环状态因此 74.3% 仍不能视为严格无泄漏、样本独立的最终性能。5. 总结数据质量较好1006 条记录没有缺失值和重复值糖尿病与非糖尿病样本比例分别为 44.43% 和 55.57%类别失衡程度不高。探索性分析呈现出可区分信号糖尿病组的年龄、甘油三酯、舒张压、尿酸等指标中位数较高高密度脂蛋白胆固醇中位数较低说明体检指标中存在可用于分类的统计差异。模型取得了高于基线的测试结果双层 LSTM 共包含 494,002 个参数最佳测试准确率为 74.3%比测试集 53.0% 的多数类基线高 21.3 个百分点。后期出现轻微过拟合第 21 个 Epoch 后训练准确率继续上升测试准确率却从最佳 74.3% 回落到 72.3%。若以准确率选模可保留第 18、20 或 21 轮权重并加入 Early Stopping。评估流程仍未完全严谨缩放器在划分前拟合全部数据带来了测试集统计信息泄漏二维张量还会使 LSTM 把不同患者当作连续时间步结果受到样本顺序和批次边界影响。后续实验应优先尝试适用于表格数据的 MLP、逻辑回归、随机森林或梯度提升树并报告完整分类指标。

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

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

免费获取报价