拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Python机器学习实战:肺癌数据分析可视化与预测系统全流程解析

Python机器学习实战:肺癌数据分析可视化与预测系统全流程解析

做一个肺癌数据预测系统,我一开始以为核心工作量在模型调参上。真正动手之后才恍然大悟:数据清洗、特征理解和可视化呈现,每一块都比跑模型更耗精力。如果你也准备拿 Python 和机器学习做类似的数据分析与预测项目,这篇内容应该能帮你少走不少弯路。

这篇文章会完整复盘我用 Python 一整套工具链(Pandas、Scikit-learn、Flask、ECharts 等)实现“肺癌数据分析可视化与预测系统”的完整过程,覆盖数据选型、特征工程、模型训练、Web 可视化以及部署实测这几大块。不管你是准备拿它作为入门实战项目,还是想了解医疗健康类机器学习项目怎么做才靠谱,都可以参考里面的思路和代码。

1. 项目背景与数据来源:先搞清楚手里有什么

1.1 医疗类机器学习项目的核心挑战

很多初学者对机器学习项目的理解是“找到数据集 -> 训练模型 -> 输出准确率”,实际上在肺癌这类医疗场景里,这条路走不通。医疗数据有三个显著特点:样本量通常不大、字段质量参差不齐、正负样本往往不均衡。直接拿默认参数跑逻辑回归或随机森林,很容易得到虚高的准确率,但在真实场景中毫无用处。

我最初设想得比较简单:收集一批肺癌患者的临床数据,比如年龄、吸烟史、胸痛程度、气短情况、咳嗽类型、家族病史等字段,加上是否患病的标签,训练二分类模型,最后在 Web 页面上展示数据和预测结果。结果第一次跑完模型,准确率 0.94,我很兴奋,但拿混淆矩阵仔细一看,发现模型几乎把所有样本都预测成了患病,纯属“聪明但偷懒”的做法。

这里需要强调一个认知:在医疗辅助分析项目中,模型评价的核心不是准确率,而是敏感度(召回率)和特异度(查准率)的平衡。漏掉一个真实患者是严重问题,把健康人误判为高风险人群同样麻烦。这也是为什么整个项目花在业务理解上的时间远超调参时间。

1.2 数据集的选型思路

我选用的数据来自公开科研数据集(如 UCI 的肺癌数据),这类数据通常以 CSV 形式提供,字段包括人口统计学信息和临床指标。选它的原因很简单:字段完整、有明确的二分类标签、无需额外授权,适合做技术验证和教学案例。

拿到手的数据大致长这样:

字段名含义说明数据类型
age患者年龄整数
gender性别(1男/0女)分类型
smoking_history吸烟年数或包年数连续型/分类型
yellow_fingers指端发黄0/1
anxiety焦虑状态0/1
chronic_disease慢性病0/1
fatigue疲劳感0/1
allergy过敏0/1
wheezing喘息0/1
alcohol饮酒0/1
coughing咳嗽0/1
shortness_of_breath气短0/1
swallowing_difficulty吞咽困难0/1
chest_pain胸痛0/1
outcome是否患病0/1

第一件事就是把每个字段的含义和分布摸清楚。比如 age 字段是否存在超出合理区间的异常值,coughing、chest_pain 这类二值字段的正例占比是多少。这些看起来零碎的信息,会直接影响后续缺失值填充策略和特征选择方向。

2. 数据清洗与特征工程:模型 80% 的功劳在这里

2.1 缺失值处理:不是所有空缺都该用中位数填

处理缺失值时,我遇到过不少盲目用均值或中位数填充的情况。但这要分情况讨论。

年龄、肿瘤大小这类连续数值字段,如果缺失比例不高(少于 5%),可以考虑用中位数填充,因为中位数对极端值不敏感。像吸烟年数这种偏态分布明显的字段,平均值会被老烟民拉高,用它填充反而不合理,这时候中位数更稳。而胸痛、咳嗽这类二分类字段,我会先看业务含义:如果“咳嗽有痰”这类字段缺失,而数据集中大部分患者都有这个症状,用众数填合理;如果缺失比例超过 20%,我会考虑增加一个“未知”类别而不是强行填充。

提示:处理缺失值要在训练集和测试集划分之后进行,并且只能在训练集上计算填充值,再应用到测试集或新样本上。否则会引入数据泄露,模型评估结果会比真实表现虚高。

2.2 编码与标准化:让算法真正“听懂”数据

机器学习算法本质上只懂数字。性别这类二分类字段,0/1 编码就可以了。但像吸烟史如果包含“从不吸烟”“以前吸烟”“现在吸烟”等多个类别,不能简单编码成 1、2、3,因为 3 不是比 2 更大的“量”,只是不同类别。这时候要么做 One-Hot 独热编码,要么用有序编码并说明顺序含义。

数值字段里,年龄和吸烟年数的量纲差异很悬殊,如果不做标准化,距离类算法和梯度类算法会天然偏向数值大的特征。我用的是 StandardScaler(标准化),把数据转换到均值为 0、方差为 1 的分布,而不是 MinMaxScaler 归一化。原因在于:标准化在数据存在某些极端值时,比归一化有更强的鲁棒性,而且 Scikit-learn 对标准化处理后的数据做逻辑回归或 SVM 时收敛更稳定。

2.3 相关性分析与特征去重

第一次做完基础清洗后,我画了一张相关性热力图,发现 smoking_history 和 outcome(是否患病)之间的相关性高达 0.5 左右,而 fatigue(疲劳感)和 anxiety(焦虑)存在 0.6 以上的正相关。这说明两个问题:一个是吸烟史确实是强特征,模型应该充分利用它;另一个是疲劳感和焦虑这两列信息重叠度高,同时放进去会带来多重共线性,对树模型影响稍小,但对逻辑回归影响很大。

我的处理方式:先保留全部特征训练第一版模型,然后查看随机森林的特征重要性排序,把重要性低于 0.01 且与业务先验明显不符的字段逐步删除。同时手动丢弃相关系数超过 0.8 的冗余特征对中的一个,避免信息重复。

这里补充一个经验:对于医疗数据,特征选择不要只看统计指标,还要听“临床常识”。比如“吞咽困难”和“胸痛”在统计上可能相关性不强,但两者都是肺癌患者常见症状,就不能因为分数低直接砍掉。统计特征重要性只能作为参考,不能完全替代业务判断。

3. 模型选型与训练:别一上来就堆集成方法

3.1 从简单模型起步,建立基准线

模型选型阶段,我没有直接上随机森林和 XGBoost,而是先用逻辑回归做了基准模型。为什么?因为逻辑回归足够简单,可解释性好,而且能给出样本属于某一类别的概率值,非常适合医疗辅助预测场景——医生不关心“这人是阳性”,更关心“这个人患病概率是 78%”。

第一版逻辑回归在验证集上得到大约 0.85 的准确率,但召回率偏低,这说明有部分真实患病样本被漏掉了。随后我用随机森林,准确率提升到 0.90 左右,召回率也上来了。再之后试了 XGBoost,训练速度更快,调参空间更大,最终在测试集上的 AUC 达到 0.95 附近。

我用一个表格汇总当时三个模型的横向对比,方便大家直接看差异:

模型准确率精确率召回率F1AUC
逻辑回归0.870.890.840.860.91
随机森林0.910.920.900.910.94
XGBoost0.920.910.930.920.95

3.2 评价指标的取舍逻辑

很多人拿到测试结果只看准确率。但对于肺癌预测这种正负样本比例接近 1:1 的数据集,准确率虽然不骗人,却无法告诉你“漏诊率”和“误报率”。换句话说,100 个真实患者里你抓到了几个?100 个被标记为高风险的人里,有多少其实没事?

医疗辅助系统里,我优先看召回率和 F1。如果为了安全宁可多安排复查,那模型可以把阈值调低,让更多疑似样本被标记出来;如果体检资源紧张,需要尽量精准,就把阈值调高一点。这个概念我在可视化系统里也做了对应设计:预测结果不仅输出“患病/不患病”,还会展示一个 0 到 100 的风险概率条,让使用者自己把握尺度。

注意:模型输出的“概率”并不等于真实医学意义上的“患病概率”,它只是在当前训练数据分布下的一种风险评分。这类项目输出的结果只能作为医生决策的辅助参考,不能替代专业医学诊断。

3.3 交叉验证与阈值调优

训练时我用了 StratifiedKFold 分层交叉验证,保持每一折中正负样本比例一致,这样评估结果更稳。最终模型上线时,我又结合验证集画出 ROC 曲线,找到约登指数(Youden‘s J)最大化的阈值点作为默认判断阈值,而不是死板地用 0.5。

这个细节非常关键。如果分类器的默认决策边界是 0.5,而你的数据分布并不对称,那么 0.5 往往不是最优决策点。通过调阈值,我用随机森林在测试集上把召回率从 0.88 提升到了 0.92,代价只是精确率轻微下降。对肺癌风险筛查来说,这个取舍显然是值得的。

4. 可视化系统:把数据和模型“翻译”给使用者看

4.1 可视化技术栈选型:为什么是 Flask + ECharts

可视化部分的选型,我对比过几套方案:

  • Jupyter Notebook 内嵌 matplotlib:适合自己分析和写报告,但没法做成交互式页面。
  • Streamlit:开发效率极高,内置组件也很漂亮,但定制企业级仪表盘时布局自由度受限。
  • Flask + ECharts:前后端边界清晰,ECharts 生态成熟,图表交互能力强,部署也轻量。

我最终选了 Flask + ECharts,原因很实际:Flask 只有几百行代码就能把数据和模型接口暴露出去,前端页面可以完整自由控制,适合做成“像样”的 Web 可视化系统。ECharts 的图表交互效果、缩放联动、数据钻取能力都远超静态图,而且纯前端运行,对服务器压力小。

4.2 页面模块设计清单

整个系统我拆成了四个功能区域,分别承担不同的“翻译”职责:

页面模块核心展示内容交互方式
数据总览样本总数、患病占比、年龄分布直方图、性别占比切换统计维度
特征分析相关性热力图、吸烟史与患病关系箱线图鼠标悬停查看数值
模型评估ROC 曲线、混淆矩阵、特征重要性条形图切换模型查看对比
风险预测输入患者特征,输出风险评分与预测标签表单提交实时预测

这四个模块不是炫技,而是分别对应一个核心问题:数据整体长什么样?哪些特征和风险相关?模型到底靠不靠谱?一个新样本进来,风险有多高?

4.3 核心代码实现:从接口到图表

后端接口部分,我设计了一个简单的 Flask 路由,接收前端表单传来的 JSON,调用训练好的模型返回预测概率和标签:

import joblib import numpy as np from flask import Flask, request, jsonify app = Flask(__name__) model = joblib.load("model/lung_cancer_model.pkl") feature_names = [ "age", "smoking_history", "yellow_fingers", "anxiety", "chronic_disease", "fatigue", "allergy", "wheezing", "alcohol", "coughing", "shortness_of_breath", "swallowing_difficulty", "chest_pain" ] @app.route("/predict", methods=["POST"]) def predict(): data = request.get_json() features = np.array([data.get(name, 0) for name in feature_names]).reshape(1, -1) prob = model.predict_proba(features)[0][1] label = int(prob >= 0.45) return jsonify({ "probability": round(prob, 4), "label": label, "risk_level": "高风险" if label == 1 else "低风险" }) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)

前端图表部分,ECharts 的配置不算复杂。比如年龄分布直方图,核心就是配置 xAxis 数据、series 数据和样式:

const chart = echarts.init(document.getElementById('ageChart')); const option = { tooltip: { trigger: 'axis' }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', data: ['20-29', '30-39', '40-49', '50-59', '60-69', '70+'], axisLabel: { fontSize: 13 } }, yAxis: { type: 'value', name: '人数' }, series: [{ name: '肺癌患者', type: 'bar', data: [5, 12, 28, 45, 38, 20], itemStyle: { color: '#3f7fd0' } }] }; chart.setOption(option);

风险预测页用了仪表盘式的 gauge 图表来展示风险概率,视觉上更直观:

const gaugeOption = { series: [{ type: 'gauge', min: 0, max: 100, progress: { show: true, width: 18 }, pointer: { length: '60%' }, axisLine: { lineStyle: { width: 18 } }, detail: { formatter: '{value}%', fontSize: 24 }, data: [{ value: 78, name: '患病风险' }] }] };

前端与后端通过 axios 或 fetch 发请求,拿到 JSON 后更新 gauge 的 value 和预测标签文本。整个交互链路非常短,部署在云服务器或本地局域网跑都很流畅。

5. 运行实测与部署心得:看到系统跑起来,才算完成一半

5.1 本地完整跑通全流程的实测记录

我在本地运行系统时,完整的操作流程是:先启动 Flask 服务,然后在浏览器打开http://localhost:5000。首页数据总览会在一两秒内渲染出所有图表,因为 ECharts 是纯前端渲染,后端只是返回静态数据 JSON,不存在大并发压力,体感很流畅。

风险预测模块,我特意挑了训练集里的真实样本和不存在的虚构样本分别测试。真实样本运行结果很稳定,输出概率和训练时的预期一致;虚构样本比如一个 60 岁重度吸烟且多项症状齐全的人,预测结果为高风险,概率 91%,符合常识预期。这其实是很好的“冒烟测试”思路:拿业务上明显是极端情况的样本去验证模型输出,能很快发现特征对齐问题。

我遇到的一个坑是特征顺序不一致。前端提交的数据字段顺序如果和后端feature_names列表不一致,模型输出会完全错乱,而且不会报错。所以我在后端固定使用字段名取值再拼接数组,前端字段名和后端键名严格对齐,彻底规避这个问题。

5.2 部署模式与使用边界

如果需要给同事或客户演示,Flask 默认的app.run()只能本机访问,设置host="0.0.0.0"后,同一局域网内的人就能通过你的局域网 IP 访问系统了。但如果你只是想临时演示,这个模式够用;真要放到公网,Flask 自带的 Werkzeug 开发服务器并不适合生产环境,需要换成 Gunicorn 或 uWSGI,外面再套一层 Nginx 反向代理。

安全提醒:涉及医疗类数据的项目,对外部署前必须做字段脱敏,真实姓名、身份证号这类个人信息绝不允许出现在页面、日志或 URL 参数里。我做的系统里用的都是公开数据,但如果你把代码移植到真实场景,这是底线要求。

5.3 可视化系统还能怎么扩展

这个系统的架构其实没有绑定“肺癌”这个主题。把后端模型换成其他二分类模型,前端特征表单字段对应改一下,一套“数据总览 + 特征分析 + 模型评估 + 风险预测”的模板就能复用到其他疾病筛查、金融风控、设备故障预测等场景。

另外,有两个我想继续优化的方向:

  • 给预测结果加 SHAP 解释图,直观展示“为什么这个人被判为高风险”,是吸烟史权重最大还是胸痛症状贡献最多,这在医疗场景里价值很高。
  • 把前端图表数据改成定时从数据库读取,让系统不再只依赖训练时那份静态 CSV,这样后续接入新数据,预测效果也能持续迭代。

6. 整体项目复盘与实操经验:代码之外的三件事

6.1 数据质量决定模型天花板

这个项目最深刻的体会就是:特征工程和业务理解占整个项目时间的七成以上。模型从逻辑回归换成 XGBoost,准确率提升不过三四个百分点;但把缺失值策略、类别编码方式、特征选择逻辑做对,提升是跨维度的。如果你做了一个模型,准确率一直上不去,先别急着换算法,回去看数据分布和特征清洗,大概率能找到原因。

6.2 可视化不是装饰,是模型价值的放大镜

刚开始我把可视化当成“给页面润色”,做到一半才意识到:一个枯燥的混淆矩阵数字,在业务方面前远没有一张 ROC 曲线图和一个风险仪表盘有说服力。可视化系统的真正作用是让不懂机器学习的人也能理解模型的脆弱点和优势点,“高风险”这类输出必须让使用者理解它的概率含义,而不是当成最终判决。这也是我坚持用 ECharts 做交互式图表的原因所在。

6.3 医疗项目的表达边界

最后说一点最重要的:这类项目做技术验证没问题,但产出结果不能被解读为权威诊断。我在系统页面上专门留了一行说明文字——“本系统输出结果仅为科研与教学场景下的辅助参考,不构成医疗建议”。这个不是免责套话,而是我们做技术的人必须时刻记得的边界。

如果你也想亲手跑一遍这个流程,建议直接上线把代码逻辑敲一遍。你收获的不仅是一个能展示的项目,更是对数据分析和机器学习实战链路的一次完整体检。

返回列表