资讯详情

资讯详情

病历数据挖掘实战:SPSS+Python混合建模,聚类关联与神经网络

数据挖掘这事儿最怕的不是算法不会调而是数据拿到手之后不知道怎么下手。尤其是病历数据这种典型的非结构化、多噪声、多缺失的字段集合光清洗就得占掉大半时间。我最近把手头一批慢性胃炎门诊病历完整跑了一遍从统计描述到聚类、关联规则、再到神经网络预测走的是SPSS加Python的混合路线。传统统计工具负责任基础分析Python负责深度建模两个工具串起来用整个过程比单靠某一边顺手得多。这篇就把整个流程、关键参数、还有踩过的坑都整理出来供同行参考。1. 慢性胃炎病历数据的特殊性先想清楚再动手1.1 为什么用SPSS做统计底子让Python专注深度挖掘很多人一看到神经网络、聚类就习惯性打开Jupyter Notebook一把梭但病历数据的前期探索其实更适合用SPSS这类传统统计工具来完成。原因很简单SPSS在描述统计、交叉表、卡方检验这些常规操作上效率极高点几下就能输出一张干净的表而用Python写同样的分析往往要十几行代码甚至更多。这批数据来自一次临床数据整理合作项目原始记录约几千条字段涵盖年龄、性别、病程时长、胃镜诊断分型、幽门螺杆菌检测结果、主要症状、饮食习惯、复发次数、用药情况等。第一步我用SPSS做了全变量的频率分析和描述统计几个关键结果直接决定了后面的建模方向男性患者比例约占总体的55%年龄集中在35到55岁区间幽门螺杆菌阳性率约四成阳性人群的复发率明显高于阴性人群症状字段里最常见的是上腹部隐痛、饱胀感、反酸和嗳气。这些统计结果看起来简单但价值很大。比如我发现复发次数这个字段分布极不均衡大部分患者是0到2次少数超过5次这种偏态分布直接影响到后面预测模型的目标变量构造。没有SPSS这层快速探索直接在Python里画分布图也能看但效率和交互性差不少。1.2 病历字段的构成与分析路径设计数据字段大致可以分为三类结构化数值字段、分类字段、自由文本字段。年龄和病程时长是数值型性别和胃镜分型是分类型而症状、饮食习惯这类往往是医生手写的短文本或者带标点的描述字符串。我的整体分析路径分四步走先做数据清洗再跑聚类看患者分群接着用关联规则挖字段之间的关系最后构造预测模型判断患者的复发风险。聚类的目的是找到自然形成的患者群体比如是否存在年轻轻度组和中老年反复发作组这样的结构关联规则侧重发现症状与诊断、生活习惯之间的共现关系预测模型则用BP神经网络同时引入Resblock结构来缓解深度训练中的梯度问题。这三条分析路径不是各自独立的后面的预测模型要用到前面聚类和关联规则的结果。比如患者所属的群体标签会作为一个特征输入神经网络关联规则挖掘出的强相关症状组也会加工成组合特征。先有整体规划后续每步才不会跑偏。2. 数据清洗与特征工程建模前必须跨过的三道坎2.1 缺失值与重复病历的处理策略病历数据缺失太常见了。有的患者没有做幽门螺杆菌检测这项就整行缺失有的症状描述完全空白可能因为患者自己说不清或者医生记录习惯不同。我统计下来幽门螺杆菌字段缺失率约为12%症状文本缺失率约为8%。对缺失值的处理不能一把梭。幽门螺杆菌缺失并不意味着完全无信息很多情况下是患者没有规范检测我选择单独设置一个未检测类别而不是简单用众数填充。症状文本缺失则参考其他字段做了条件填充比如胃镜诊断为反流性食管炎的患者反酸症状缺失时按同诊断组的众数补入这种条件填充比全局众数合理得多。重复病历也是个隐藏问题。同一患者在不同的就诊时间会被记录多次直接按行去重会丢失时间维度上的变化信息。我的做法是以患者ID为粒度保留最后一次就诊记录的同时提取复发次数和历次诊断变化作为独立字段。2.2 文本字段的编码症状、饮食习惯怎么转数值这是整个预处理中最耗时的部分。症状字段往往写成上腹隐痛伴反酸嗳气或者空腹时上腹部不适饭后缓解直接当分类变量处理不现实。我手工整理了一份症状词典覆盖上腹疼痛、饱胀、反酸、嗳气、恶心、食欲减退、烧心等高频症状然后对每条文本做词频映射每个症状生成一个0/1字段。这条规则简单粗暴但胜在稳定可控效果比直接用词嵌入好解释。饮食习惯字段更麻烦。记录里包括喜食辛辣三餐不规律经常饮酒爱吃腌制食品等散乱的表述。我同样做了关键词匹配抽出辛辣偏好、腌制食品、饮酒、吸烟、饮食不规律几个维度每个维度仍然是一个0/1标志。这一步做完以后我得到了一批结构化的特征列。文本转数值的过程里务必保留原始字段到新字段的映射表不然等到后续做特征解释时面对一堆0/1列会完全不知道每一项代表什么含义。2.3 样本均衡与归一化各项数据要在一个尺度上对话慢性胃炎病历里浅表性胃炎远多于萎缩性胃炎前者占比接近七成。如果直接用原始样本训练分类模型模型几乎不需要学习就能达到70%以上的准确率但这种假准确率没有实用价值。针对这个问题我对萎缩性胃炎样本做了SMOTE过采样合成少数类样本再参与训练。过采样必须放在训练集内部做如果把测试集也一起合成了验证结果就完全失真。数值型特征归一化也在这里完成。年龄、病程时长取值跨度很大直接输入神经网络会让梯度更新被大数值特征主导。我做了Z-score标准化把所有数值特征拉到均值为0、方差为1的分布上。分类变量全部做独热编码后最终特征维度到了40多个为后面的聚类和神经网络都做好了输入准备。3. K-means与AGNES聚类慢性胃炎患者分群实战对比3.1 K-means聚类先用SSE拐点和轮廓系数双重定簇数K-means是最直观的聚类方法它的目标是把样本分成K个簇使得每个样本到所属簇中心的距离平方和最小。核心问题就是K值怎么选。我同时看了SSE曲线和轮廓系数两个指标配合着判断避免单一指标带来的偏差。SSE簇内误差平方和随着K增加单调递减但下降速度会在某个点之后明显放缓这个拐点通常就是合理的K。同时轮廓系数反映簇内紧密度和簇间分离度取值越接近1表示聚类效果越好接近0或负值说明样本可能被分错了簇。在这份数据上K取3时SSE曲线出现明显拐点轮廓系数达到0.52再增加K值虽然SSE继续下降但轮廓系数反而走低。最终确定K3三个簇的规模比例大约是45%、32%和23%。查看各簇中心后三个群体特征很清晰簇1是年龄偏轻、症状轻微、复发少的轻症组簇2以幽门螺杆菌阳性为主、反酸烧心突出是反流相关组簇3是年龄偏大、病程长、复发次数多的顽固反复组。3.2 AGNES层次聚类从树状图解释患者群体的层次关系AGNES属于自底向上的层次聚类先把每个样本当作一个簇然后不断合并距离最近的两个簇直到达到指定簇数。它的优势在于不需要预先指定聚类数量可以在聚类过程中观察树状图来决定从哪里切分。我选用了Ward连接法合并簇这个方法在合并时最小化簇内离差平方和的增量倾向于形成大小相对均衡的球形簇。与K-means的欧氏距离逻辑接近便于结果对比。同时在AGNES中绘图时使用了树状图放大局部区域以后明显能看到两个大分支左侧分支在更细粒度上还能分出三个小的亚组。当聚类数量设为3时AGNES和K-means的总体划分比较接近但一个重要的差异是AGNES抓到了大约8%的样本这些患者在症状表现上群体特征并不明显更像是多个亚组之间的过渡样本。K-means会强制把这些过渡样本塞进最近的簇而层次聚类能够通过树状图的层次关系反映出这种模糊边界这在临床解读上更有参考价值。3.3 两种聚类结果怎么对照解读聚类分析的价值不在于把算法跑出几个簇就结束而在于解释为什么这些人被分到一起。我将K-means和AGNES的结果做了交叉分析匹配度约为78%不一致的样本主要集中在症状组合复杂、多种诊断并存的患者身上。聚类结果的稳定性方面用不同随机种子跑了多遍K-means簇中心的浮动情况显示整体稳定个别敏感样本会在簇2和簇3之间来回跳动。这种不稳定样本恰恰是有价值的它们往往意味着症状介于两种亚型之间临床上更需要关注。聚类打出来的标签也别浪费后面转化为患者分群编号作为BP神经网络的输入特征之一。模型实验表明加入分群特征后预测准确率从71%提升到76%说明聚类提取的结构信息确实对预测有帮助。聚类不该和预测脱节两者组合起来的价值更大。4. 关联规则挖掘从病历组合中找出反复出现的规律4.1 事务数据的构造把每条病历变成购物篮关联规则挖掘最经典的场景是购物篮分析核心逻辑是把每条记录看成一个事务集合里面装着若干项。用在病历数据上就是把每个患者的多项特征映射成集合中的元素比如一个患者的事务集合可能是{中年男性, 幽门螺杆菌阳性, 反酸, 上腹隐痛, 不规律饮食}。构造事务的时候我需要把手头的特征全部转成二元项。数值型字段比如年龄我先离散化成年龄段病程时长也切分成短期、中期、长期三档。分类字段本来就是离散值直接作为项使用。生成的稀疏矩阵里每一行就是一位患者的全部特征集合。这里有一个容易被忽略的细节不要让同一字段的不同取值有逻辑冲突。年龄字段如果既出现了中年又因为编码错误出现了青年就会生成荒谬的规则比如同时包含年龄青年和年龄中年的事务。清洗阶段做一致性校验把这部分错误提前过滤掉关联规则结果才可信。4.2 Apriori算法的参数选择与规则筛选Apriori算法的三个核心阈值支持度、置信度、提升度。支持度衡量规则覆盖多少比例的事务置信度衡量规则前件出现时后件同时出现的概率提升度则衡量规则相对于随机共现的提升多少倍。我的参数选择思路是先宽松起步再逐步收紧。初始支持度设为0.05置信度设为0.5这意味着至少5%的患者样本中同时出现规则前件和后件并且后件在前件出现时的概率至少五成。跑出来数千条规则太多且大量规则都是低价值共现。随后把支持度提到0.08置信度提到0.6最终保留的规则数量在百余条左右。整个过程中提升度保持在大于1的水平大于1说明规则前后件确实存在相关性小于等于1的规则直接剔除因为那只反映随机的共现频率。其中一条比较有代表性的规则是幽门螺杆菌阳性且空腹上腹不适指向反复发作置信度接近80%提升度超过2。这类规则从数据中浮现出来的共现结构比单看统计相关性更直观也更容易向临床人员解释。4.3 挖掘结果在临床随访中的落地参考关联规则不是跑完就完了需要进行解释和转化。我按前件特征组合后件指向提升度三列整理了一张规则表重点筛选了两类规则预计对随访管理有指导意义的以及疑似存在潜在关联后续待验证的。例如规则显示饮食不规律 常年饮酒与胃镜分型为糜烂性胃炎之间有显著关联这虽然没有超出医学常识但数据的价值在于给出了量化的置信度能够让随访方案设计时定向关注这类高风险习惯组合。另有一部分规则的前件同时包含反酸和嗳气后件指向睡眠质量差其中的关联机制目前尚不明确需要后续分析验证。把规则结果和聚类标签进一步叠加可以看到反流相关组中的患者其事务规则里高提升度的组合主要围绕幽门螺杆菌和反酸顽固反复组则围绕饮食因素和复发次数。聚类给了人群划分关联规则给的是人群内部的特征共现规律两层信息合起来整体分析报告的内容密度高了很多。5. Resblock-BP神经网络用残差结构改进传统BP的短板5.1 传统BP在病历数据上的两个典型问题经典的BP神经网络在层数加深后最常遇见的两个问题是梯度消失和过拟合。病历数据本身特征维度不算高但如果只做两三层网络表达复杂非线性关系的能力有限要做到六七层以上常规逐一传播的BP结构又会出现深层权重的梯度更新幅度远小于浅层的问题。另一个问题是医疗数据里很多标签本身有噪声同一个症状组合可能对应不同的诊断结果。传统BP的每一层都在强行拟合目标函数网络加深之后会把训练集中的噪声也学进来导致验证集上表现反而变差。我在实验中先试了一个五层的全连接BP隐藏层结构为128-64-32-16激活函数使用ReLU优化器用Adam初始学习率取0.001。训练集准确率很快逼近90%但验证集准确率只到了71%左右差距明显。这就是典型的过拟合也是后面要引入残差结构的直接原因。5.2 残差块的引入原理给梯度留一条高速公路Resblock原本是在图像分类网络中大规模使用的结构核心思想是在层与层之间增加一条恒等映射路径让前面的输入可以直接跳过若干层送到后面。数学表达上残差块输出不是直接学习目标映射H(x)而是学习残差F(x)H(x)-x最后的输出是F(x)与x相加。引入这个结构之后梯度更新时除了经过每个卷积或者全连接层的权重路径还能通过跳连接直接回流到前面的层短的路径大大缓解了梯度问题。换句话说网络加深不再带来训练困难每一条跳过的连接都相当于给梯度开了一条高速通道。在病历数据上我在全连接BP的基础上做了如下重构把原来的5层直连BP改为包含两个残差块的网络。每个残差块内部包括两个全连接层块内两侧各接跳跃连接整体结构变为[输入层残差块1残差块2输出层]。每块内隐藏维度选用64激活函数仍用ReLU。结构并不复杂但它改变了信息流动的路径。5.3 模型训练、调参与评估训练配置上损失函数用交叉熵优化器Adam学习率从0.003开始并加入学习率衰减。批量大小取64训练轮次上限200轮同时加入了早停策略当验证集损失连续10轮不再下降时就停止训练。早停机制直接抑制了过拟合状态最终训练约在80轮附近达到最优。评估维度不只看了准确率。对于医疗预测场景我更关注召回率和F1值。萎缩性胃炎这一类样本虽然过采样处理过但召回率偏低是常见问题。最终结果如下指标传统BP五层Resblock-BP验证集准确率71%78%召回率0.630.72F1值0.680.75从表格可以直观看到残差结构带来的提升超过了7个百分点。在测试集上萎缩性胃炎少数类的预测召回率更是从0.58提高到0.70这样的提升对临床应用而言意味着更少遗漏潜在的风险患者。整个过程中我也对比了加入分群特征带来的增益特征向量中加入聚类标签后的准确率还能再提升约1到2个百分点。6. 完整流程复盘排错记录与真正起作用的几个细节6.1 排查反复出现的过拟合问题第一次跑Resblock-BP时我一度遇到训练损失降得很顺滑、验证集却波动很大的问题。很多人第一反应是加大Dropout或加L2正则但这治标不治本。真正的根源在于预处理时的特征泄漏最早版本的特征矩阵里包含了目标变量的类别编码字段模型等于提前看到了答案训练时损失自然降得飞快。排查方式足够土但有效我把每条特征列与目标变量的相关系数做了扫描发现有一个字段与目标变量的相关系数高达0.6往回一查正是我在特征工程早期错误保留的原始诊断编码。删除之后模型回归正常。这也是整个项目里最想提醒大家的一点特征泄漏在医疗数据里防不胜防特征列越多越要在建模前做相关性扫描。另外还有一个细节是过采样时机的控制。SMOTE必须先对训练集处理再允许验证集保持原始分布。我最初对整个数据集过采样后切分训练和测试训练的准确率很高但测试时面对真实类别分布性能就明显打折这种偏差会误导模型部署后的真实性能预估。6.2 预测结果与聚类、关联规则如何互相验证一个容易被忽视的闭环操作是把预测结果往回映射到聚类标签和关联规则上看看模型预测的高风险复发患者是不是和聚类结果中的顽固反复组高度重合。我做了一次交叉统计模型预测为高风险复发的患者中约八成属于K-means分出来的簇3顽固反复组同时关联规则中出现幽门螺杆菌阳性空腹上腹不适组合的患者在模型预测高风险组中的占比也远高于整体水平。这说明聚类分群、关联规则、神经网络预测三条线在描述同一批人群互相印证分析结论的说服力明显增强。这种不同模型互相印证的思路在处理病历这类信息冗余度高但又互相矛盾的数据时特别有用。单一模型的结果容易被数据噪声干扰多个模型得到一致信号的时候结论的可靠性才真正值得相信。6.3 踩过的几个低层次坑文本编码不一致的问题值得单独说。症状文本里上腹隐痛上腹部隐痛上腹疼痛三种写法在原始数据里竟然是作为三个类别存在的。最初做词频映射时我把它们当成不同症状导致一位患者在一个特征上重复标记多个字段特征膨胀严重。整理方法很朴素扩充同义词映射表把上腹部上腹腹部中上区归一到同一个标准词隐痛疼痛不适也根据上下文归并。光这一步就削减了约15%的特征维度聚类的簇边界清晰了不少。归一化处理的目标变量也遇到过问题。用药频率字段里存在单位不统一的情况有的记录是按月服药次数有的是按年直接标准化以后完全失真。做归一化之前先统一口径这个逻辑不仅适用于数值列也适用于所有文本衍生特征。还有一点要提的是SPSS导出的数据到了Python里中文编码偶尔会乱比如某些字段首尾出现空格。在导入时统一指定UTF-8编码并且对全表做strip清洗省去了后面调用时排查奇怪Bug的时间。从这套流程里沉淀下来的几点体会整套流程走下来我最想强调的第一点就是不要过度信仰某一个算法或者工具。SPSS在数据探索和统计检验阶段的高效率是Python短期内替代不了的而Python在数据清洗和深度学习上的灵活性也是SPSS比不了的。混合使用不是炫技而是根据每个环节的痛点选合适的工具。Resblock结构这个选择也是同样的逻辑。它不是万灵药但在处理中低维度医疗特征、网络深度要求适中的场景下确实有效解决了BP训练不稳定和过拟合的问题。修改原理是残差连接带来了更稳定的梯度路径但最终的效果要靠和传统BP的对比实验验证而不是拍了脑袋就认定它有效。病历数据的分析工作每一环都需要留下可追溯的记录。从映射表、清洗规则到参数选择只要中间有一个环节说明丢失整个分析结果就失去了复现性。数据本身会有很多缺陷但分析过程的规范性能把缺陷的影响压到最低。这套流程往后还能继续扩展的方向比如引入时间序列建模观察患者复发趋势变化或者把不同医院的病历数据做多中心验证。不过当下这批数据的结果已经足够回答最初的问题用SPSS做底层统计支撑Python做深度挖掘中间细节严格把控病历数据完全能够挖掘出对临床随访有参考价值的结构化信息。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →