Python+scikit-learn实现癌症预测模型:随机森林二分类实战

发布时间:2026/10/21 8:48:50
Python+scikit-learn实现癌症预测模型:随机森林二分类实战 简介基于Python实现的癌症预测机器学习项目适合机器学习初学者、医疗数据分析人员及人工智能相关课程设计使用演示了从数据准备、特征工程到模型评估的完整流程。压缩包共18个文件约100KB包含6个源代码脚本、3个数据表格、2个说明文档以及工程配置文件结构清晰便于二次开发。目前已有993人学习是同类入门项目中较受关注的一份资料。项目内容覆盖数据清洗、缺失值处理、独热编码、特征选择与标准化等关键步骤并给出了逻辑回归、随机森林等分类模型的训练与评估代码采用交叉验证与网格搜索调参可输出准确率、召回率、F1分数等评价指标帮助理解模型优化方向。通过复现整个项目读者可掌握一套可迁移的医疗数据分类建模方法为后续真实场景应用打下基础。1. 从一份肿瘤数据开始这个项目在预测什么这份 python实现预测癌症模型 的资源核心是一个用 pandas 和 scikit-learn 完成的二分类机器学习项目。输入是一份带诊断标签的肿瘤检测表格数据记录着细胞核半径、纹理、面积等特征模型要学的是区分样本属于良性还是恶性最终输出“新样本有多大概率是恶性”的概率值。它不依赖 GPU也不牵扯深度学习框架几百行 Python 就能跑通。适合刚学完机器学习基础、想找完整模板跑通流程的新手也适合手里有表格数据、想快速落地分类模型的业务开发。资源把数据清洗、标准化、训练、交叉验证、评估、预测拆成独立模块跑完一遍对“分类模型怎么被制造出来”会有直观认识。2. 技术栈与目录结构机器学习项目的骨架2.1 技术选型为什么选 scikit-learn 而不是深度学习癌症预测这个场景在机器学习落地里有一个典型特征数据是表格型的特征列几十个样本量从几百到几千。面对这种数据scikit-learn 里的逻辑回归、随机森林、梯度提升树往往比深度学习更实用。原因很直白表格数据没有图像、文本那样的空间或时序结构树模型天然能处理数值型和类别型混合的特征还能自动捕捉特征之间的非线性关系而神经网络在样本量不足时容易把训练集的噪声也学进去调参一遍下来要花的时间够随机森林网格搜索跑好几轮了。选型时我一般会把逻辑回归当成第一棒跑个基线看数据本身有没有区分度再上随机森林这类集成模型。这个项目默认用的是随机森林配合网格搜索属于表格分类场景里最稳妥的组合之一。树模型本身不要求特征缩放但项目里还是加了标准化目的很简单等你想换成逻辑回归或 SVM 时不需要回头再改数据处理代码属于给后续留后路的做法。2.2 目录结构每个文件管什么事cancer-predict/ ├── data/ │ ├── tumor_data.csv # 原始肿瘤诊断数据只读 │ └── processed/ │ └── train_test.parquet # 清洗和切分后的训练测试数据 ├── src/ │ ├── preprocess.py # 数据清洗、标签编码、标准化 │ ├── train.py # 模型训练 网格搜索交叉验证 │ ├── evaluate.py # 在测试集上输出分类报告和指标 │ └── predict.py # 加载模型对新样本输出概率 ├── models/ │ └── best_model.joblib # 训练产物joblib 持久化 ├── notebooks/ │ └── eda.ipynb # 探索性数据分析 ├── requirements.txt └── README.md这个目录结构有三个设计要点。第一数据目录分原始区和处理区tumor_data.csv 始终是只读的清洗逻辑全部收敛在 preprocess.py 里你不小心把数据改坏时随时可以重跑恢复。第二训练和预测拆成两个脚本train.py 负责训练保存模型predict.py 只负责加载模型和计算概率线上使用时不需要重新导入训练代码依赖面更小。第三notebooks 单独放探索分析避免把手动分析过程写进正式训练脚本保证数据管道的可重复性。四个脚本的交互关系是以文件为界的preprocess.py 读入 data/tumor_data.csv输出 parquet 到 data/processed/train.py 读 parquet训练后把模型写到 models/best_model.joblibevaluate.py 读模型和测试集生成指标报告predict.py 加载模型对命令行传入的一条新样本输出概率。每个脚本可以单独运行改数据管道时不会牵动训练逻辑这是小型机器学习项目里比较清爽的组织方式。2.3 数据流水线从 CSV 到概率值的完整链路一条原始记录变成预测概率在项目里走五步。第一步pandas 读入 CSV先不急着建模用 df.info() 和 df.isna().sum() 看数据类型和缺失值分布确认目标列、特征列和无关列。第二步把诊断列从字符串映射成 0/1同时丢弃 id 列因为 id 和诊断结果没有逻辑关系留着只会让模型去拟合一个无意义的编号。第三步把数值特征全部过一遍 StandardScaler标准化公式是(x - mean) / std让所有特征落在同一量纲上。第四步按 8:2 切分训练集和测试集切分时加stratifyy保证两个集合里良恶性比例一致。第五步训练随机森林用五折交叉验证搜索参数把最优模型落盘。为什么 stratify 在这个项目里几乎是必须的肿瘤数据的正负样本通常不平衡恶性样本可能只占 30% 左右如果随机切分运气差的时候测试集里恶性比例会偏离原始分布评估出的准确率就失真了。stratify 的作用就是让切分后的类别比例与原始数据集保持一致这是表格分类里容易忽略但很关键的一步。特征标准化同样重要肿瘤特征里面积均值的量纲可能到几千而纹理标准差可能只有个位数不标准化的话基于距离和梯度的模型会不自觉地把权重压在大数值特征上。随机森林本身不受影响但流水线里提前做好标准化后面换模型就不用再动数据处理代码。2.4 依赖管理几个关键的第三方库requirements.txt 里核心依赖是 pandas、scikit-learn、matplotlib、joblib、pyarrow深度学习相关的包一个都没有。pandas 处理表格读写scikit-learn 提供模型、交叉验证和评估指标matplotlib 用来画 ROC 曲线和混淆矩阵热力图joblib 负责模型持久化pyarrow 是 pandas 写 parquet 格式所需的引擎。版本选择上我一般建议直接装当前稳定版但有一个坑要提前说scikit-learn 的模型持久化存在版本兼容问题用 1.2 版本训练并保存的 joblib 文件在 1.4 版本里加载时可能报错或者更隐蔽地给出不一致的预测结果。如果这个项目只是你自己训练自己用问题不大但如果你打算把训练好的模型交给别人复用最好在 requirements.txt 里锁住 scikit-learn 和 joblib 的主版本号比如scikit-learn1.3.2这种写法避免两端版本错位。这类问题在第五章会再展开一次。3. 核心模块拆解从数据清洗到随机森林训练的关键代码3.1 数据加载与标签编码先看预处理模块的核心逻辑。这一步放在 preprocess.py 里控制台执行python src/preprocess.py就会得到一份干净的 parquet 文件import pandas as pd def load_and_clean(raw_path: str) - pd.DataFrame: df pd.read_csv(raw_path) print(df.shape, df.isna().sum().sum()) # 目标列从字符串映射成 0/1良性 B 记为 0恶性 M 记为 1 df[diagnosis] df[diagnosis].map({B: 0, M: 1}) # id 与诊断结果无关丢掉的目的是防止模型学习无意义编号 if id in df.columns: df df.drop(columns[id]) # 用返回布尔值的 mask 过滤掉映射失败的脏数据 df df[df[diagnosis].isin([0, 1])] return df if __name__ __main__: df load_and_clean(data/tumor_data.csv) df.to_parquet(data/processed/clean.parquet)这段代码里值得注意的是 map 操作。map 是 pandas 在 Series 上的逐元素映射比 for 循环快得多但如果字典里没有对应键结果会变成 NaN所以我在 map 之后又做了一次过滤用isin([0, 1])把非法标签行直接剔除。这样即使原始数据里出现拼写错误的标签模型训练前也会被拦下来而不是带着脏数据一路跑进训练流程。放弃 id 列你可能会舍不得总觉得“多一列特征总比少一列好”。但在表格分类里编号类特征没有任何泛化意义模型的表达能力会被浪费在一个随机变量上偶尔还会因为某条样本 id 数值极端而扰动树的切分。这类特征的删除越早越好清洗阶段就要下手。3.2 特征标准化与训练测试集切分from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler def prepare_features(df: pd.DataFrame): X df.drop(columns[diagnosis]) y df[diagnosis] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) # 测试集只 transform不 fit return X_train, X_test, y_train, y_test, scaler这段代码是整个项目里最容易写错的地方。fit_transform和transform的关系是scaler 在训练集上 fit算出每个特征的均值和标准差然后对训练集做变换测试集只用同一套参数做 transform。如果有人顺手对 X_test 也调了 fit_transform就会发生数据泄漏测试集的信息提前进入预处理参数评估出来的准确率虚高模型上线后马上打回原形。提示训练集 fit_transform、测试集 transform 是一条铁律不只是 StandardScalerPCA、归一化等所有无监督预处理都遵守同一个原则。random_state42 的作用是固定随机种子让每次切分结果一致。做实验时如果切分不固定模型指标忽高忽低你很难判断改动到底是让模型变好了还是只是运气变好了。固定种子之后前后两次训练的唯一变量就是代码本身对比才有意义。3.3 网格搜索交叉验证训练随机森林from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV def train_model(X_train, y_train): param_grid { n_estimators: [50, 100, 200], max_depth: [3, 5, None], min_samples_leaf: [1, 2, 4], } grid GridSearchCV( RandomForestClassifier(random_state42), param_grid, cv5, scoringroc_auc, n_jobs-1, ) grid.fit(X_train, y_train) print(best_auc:, round(grid.best_score_, 4)) print(best_params:, grid.best_params_) return grid.best_estimator_这里的 param_grid 定义了 3×3×3 27 种参数组合每种组合跑 5 折交叉验证总共 135 次训练。单棵随机森林在千行数据上的训练耗时通常不到一秒n_jobs-1 表示用满所有 CPU 核心整体跑完也就一两分钟。第一次跑完你会看到控制台输出 best_auc 和 best_params这两个值对应交叉验证里的平均得分和最优参数组合。scoring 用 roc_auc 而不是 accuracy原因是面对不平衡数据准确率有很强的迷惑性就算模型把所有样本都预测成良性这个多数类准确率也能有七成以上但模型实际上完全没有区分能力。AUC 衡量的是模型把正样本排在负样本前面的能力对类别不平衡更鲁棒这是医疗预测项目里最基本的一条评估原则。max_depth 和 min_samples_leaf 是控制树复杂度、抑制过拟合的两个关键参数。max_depth 限制树生长层数None 表示不限制min_samples_leaf 要求叶子节点至少包含指定数量样本设太大容易欠拟合太小又容易把噪声学进去。网格搜索结束后best_estimator_ 就是 27 组里交叉验证得分最高的那个模型后续评估和预测都使用它。3.4 测试集评估与新样本预测from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix import joblib def evaluate(model, X_test, y_test): y_pred model.predict(X_test) y_prob model.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) print(roc_auc:, round(roc_auc_score(y_test, y_prob), 4)) print(confusion_matrix(y_test, y_pred)) joblib.dump(model, models/best_model.joblib) def predict_single(model_path, sample_array): model joblib.load(model_path) proba model.predict_proba(sample_array)[:, 1] return proba[0]predict_proba 返回的是二维数组第一列是良性概率第二列是恶性概率代码里取[:, 1]拿第二列。项目对外输出的核心其实是这个概率值而不是 predict 的 0/1 硬标签因为临床场景里决策更关注置信度概率 0.7 和 0.3 的处理策略完全不同只输出一个类别会丢掉大量信息。这类医疗辅助决策项目里保留概率输出而不是只打印分类结果是刻意设计的选择。evaluate 函数里同时打印了分类报告和混淆矩阵。混淆矩阵能告诉你具体错在哪里假阴性是真正的恶性样本被预测成了良性在癌症筛查场景里这是最不可接受的错误假阳性带来的代价是让患者做多余的复查虽然浪费资源但至少不会漏诊。看懂这四个数字比盯着一个准确率数字有用得多。4. 训练与评估实操参数调优与效果验证4.1 环境准备与运行命令拿到资源第一步是创建虚拟环境这部分我习惯用 condaconda create -n cancer-predict python3.10 -y conda activate cancer-predict pip install -r requirements.txtrequirements.txt 里没有 GPU 相关依赖装起来几分钟。数据文件放到 data/tumor_data.csv 之后按三条命令就能跑完整流程python src/preprocess.py # 清洗数据生成干净的 parquet python src/train.py # 网格搜索训练保存 best_model.joblib python src/evaluate.py # 加载模型输出分类报告和指标如果你是第一次跑建议先用小参数集验证流程通不通比如把 param_grid 临时改成{n_estimators: [50]}快速跑一遍确认没有报错再放开完整参数。train.py 跑起来会有一段时间的静默期如果发现控制台长时间没有输出优先检查是不是 n_jobs-1 把 CPU 占满导致其他进程响应变慢可以临时把 n_jobs 调成 2 再对比速度。4.2 评估指标怎么读训练结束后的 classification_report 里重点看三个指标。精确率表示预测为恶性的样本里真正恶性的比例召回率表示真实恶性样本里被正确找出来的比例。在癌症筛查场景召回率优先级高于精确率宁可多安排一些不必要的复查也不要漏掉一个真恶性样本。如果报告里召回率偏低一个常用的做法是把分类阈值从 0.5 下调到 0.3让模型更容易把样本判为恶性代价是误报增多但漏诊率会降下来。AUC 是另一个维度衡量模型把正样本排在负样本前面的能力。0.95 以上的 AUC 说明模型的排序能力很强但单独看 AUC 并不够还是要结合混淆矩阵确认错在哪。有个经验AUC 很高但召回率很差的情况完全可能同时出现因为漏掉的那几个恶性样本如果排名在 0.4 到 0.5 之间并没有破坏整体的排序结构AUC 依然很高。所以评估报告、AUC、混淆矩阵三者必须一起看缺一不可。4.3 网格搜索参数说明与调整方向整理一份常用的参数调整表照着这个方向改基本不会跑偏参数默认值作用调整方向n_estimators100树的数量越大越稳定样本量大时加到 300~500效果边际递减max_depthNone树的深度控制复杂度数据量少时限制在 3~5 防过拟合min_samples_leaf1叶子节点最小样本数过拟合时调大到 4~8max_featuressqrt每次分裂考虑的特征数特征多时试试 log2 或固定值class_weightNone类别权重缓解不平衡不平衡明显时设 balancedclass_weight 是容易被忽略但很有用的参数。恶性样本占比明显偏低时设成 balanced 会让模型在训练时给少数类更高的惩罚权重相当于强制模型更关注恶性样本召回率往往会有明显提升代价是精确率可能下降。业务的取舍要看场景筛查场景更看重召回率诊断确认场景更看重精确率。这个参数在随机森林里直接传换成逻辑回归也一样生效。4.4 训练过程监控与异常判断我习惯在训练时把关键位置的状态打印出来而不是让脚本安安静静跑完。标准化之后打印 scaler.mean_能直观看到每个特征的均值是否合理网格搜索每跑完一组就打印当前参数和得分方便中途判断哪个方向更值得继续搜。如果交叉验证得分比测试集得分高出 0.1 以上大概率存在数据泄漏重点检查预处理是否在切分前就做了全量数据的 fit。另外要留意标准差为 0 的特征。如果一个特征的方差是 0标准化后整列全是 0模型在这列上得不到任何信息还白白浪费一次分裂尝试。出现这种情况时应该回到 preprocess.py 把该列过滤掉。这种问题在原始数据里很常见但经常被埋没在“反正模型也能跑”的沉默里打印中间状态是性价比最高的排除方式。5. 避坑与常见问题排查五个真实踩坑记录跑这类医疗预测项目最容易出问题的不是模型选型而是数据流里那些你以为理所当然的地方。这一章把我在这个项目上踩过的五个坑按“现象、原因、解决”整理出来每一个都在真实运行里出现过也都有对应的排查路径。5.1 模型把所有样本预测成良性第一次跑完分类报告显示精确率 0.98但恶性样本的召回率是 0。原因是训练集里恶性样本占比只有两成左右随机森林学到的策略是“全部预测良性”成本最低准确率依然有八成以上但模型完全没区分能力。解决方式有三步同时做scoring 换成 roc_auc避免网格搜索按准确率挑模型模型参数里加class_weightbalanced给少数类更高的权重确认 train_test_split 用了 stratifyy。三条都做完测试集上的召回率通常会有明显改善。这类项目里指标选错比模型选错更致命。5.2 测试集评估指标虚高把 X_test 也拿去 fit_transform 之后测试集上的 AUC 高得离谱0.98 看起来惊艳。原因就是数据泄漏测试集的统计信息提前进入了标准化参数模型在评估时已经“见过”测试集的分布。解决方式scaler 只在训练集上 fit测试集一律 transform。这个坑对逻辑回归和 SVM 的伤害尤其明显随机森林因为是树模型所以影响相对小但流水线既然已经做了标准化就要从头到尾遵守“训练集 fit、测试集 transform”这条原则。如果你发现测试集指标比交叉验证高出一截先怀疑这个位置。5.3 joblib 加载时模型不兼容在一台机器上训练保存的模型换到另一台机器加载时报错或者不报错但预测结果对不上。原因是 scikit-learn 版本不一致不同版本之间模型的内部表示可能变化joblib 只负责把对象序列化和反序列化不负责跨版本兼容。解决方式requirements.txt 里锁住 scikit-learn 和 joblib 的版本两端用完全相同的虚拟环境。加载模型时打印model.__class__和sklearn.__version__做一次自检确认对象类型和版本符合预期再进入预测流程。模型交付给别人时把 requirements.txt 一起给是最基本的礼貌。5.4 网格搜索一跑就是半小时参数组合多、交叉验证折数多、树也深三者叠一起计算量就爆了。比如 27 组参数配 10 折交叉验证就是 270 次训练每次训练里还有几百棵树跑起来容易让人误以为脚本卡死了。解决方式先在小数据子集上用少参数组合验证流程比如df.sample(500)配合单参数跑一遍n_jobs-1 把并行度拉满实验阶段用 cv3最终确定参数后再用 cv5 做正式训练。网格搜索的价值在于缩小参数范围不在一次跑完所有可能性。5.5 不同机器上结果不可复现同一份代码在两台机器上训练出的模型指标略有差异。原因是浮点运算顺序、线程并行时的随机数使用都会引入不确定性树模型尤其明显。解决方式给模型和切分都固定 random_state并设置 numpy 的随机种子。如果项目对可复现性要求很高做一次规范化在 train.py 入口处统一random.seed(42)、np.random.seed(42)同时给 RandomForestClassifier 传入 random_state。这样至少保证同一环境下实验可复现换机器时差异也能控制在很小的范围内。6. 二次开发思路把模型换成你的数据集和业务场景6.1 替换数据集时最少要改的几个地方换成自己的 CSV 数据时核心改动集中在三处。第一处是 preprocess.py 的列名适配把 diagnosis 换成任务目标列把 id 删除逻辑换成自己的无关列。第二处是标签映射如果是多分类map 字典要扩展成多个类别如果是回归任务整个分类评估部分都要换成回归指标比如 MSE 和 R2。第三处是 param_grid 搜索范围数据量变大时 n_estimators 可以往上调特征数量变多时 max_features 可以试试 log2。数据替换之后先做一次快速验证用df.describe()对比新旧数据集的特征范围确认没有极端异常值进入训练。最怕的是新数据集里混入了字符串列StandardScaler 会直接报错所以替换后先跑 preprocess.py不要跳过清洗直接训练。6.2 切换模型与概率校准from sklearn.ensemble import GradientBoostingClassifier from sklearn.calibration import CalibratedClassifierCV base GradientBoostingClassifier( max_depth3, learning_rate0.1, n_estimators120, random_state42, ) calibrated CalibratedClassifierCV(base, cv3, methodisotonic) calibrated.fit(X_train, y_train)把随机森林换成梯度提升树只改一次模型实例化流水线其他代码完全不用动。替换之后我建议多做一步用 CalibratedClassifierCV 对概率做校准让模型输出的概率更接近真实频率。随机森林的概率输出有时会偏激进比如把 0.6 的样本直接给到 0.9概率校准会在交叉验证里学习一个修正映射让输出概率和实际比例对齐。医疗场景里概率值本身就会被当作决策依据这一步值得加。6.3 落地部署的最小方案脚本能跑通只是第一步真正要交付给别人用我一般会再补三件事。第一件把 predict.py 改成一个接收 JSON、返回概率的 HTTP 接口用你熟悉的轻量 Web 框架包一层路由里完成标准化和模型预测。第二件把 scaler 和模型一起打包预测请求进来时先标准化再喂给模型避免接口侧重复实现数据变换逻辑。第三件写一个接口自测脚本模拟一条新样本请求验证输入输出格式和概率范围是否符合预期。从那以后我每次做表格分类项目都强制走一遍“先看类别分布、再切分、再评估、再看混淆矩阵”的固定顺序这套流程跑熟了踩坑的概率低很多。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询