资讯动态

决策树模型实战:泰坦尼克号生存预测全流程解析

发布时间:2026/10/18 8:23:22 来源:尧图企业网站定制
1. 决策树算法案例到底在解决什么问题第一次接触机器学习的人十有八九会和“泰坦尼克号乘客生存预测”这个经典案例相遇。表面上它只是让你根据乘客的船舱等级、性别、年龄、票价、登船港口这些字段预测一个人最后是否存活。但真正跑一遍你就会发现这个题一点也不“玩具”原始数据里有缺失值、有文本字段、有类别字段还有一眼看不出来怎么用的票号和客舱号。拿它来练决策树算法等于把一趟真实的数据建模流程完整走了一遍。这个案例适合三类人刚学完 Python 和 Pandas、想找一个完整分类任务的入门者已经能跑通逻辑回归、想弄明白树模型为什么不需要做特征缩放的人以及正打算用决策树做业务解释、需要把模型规则讲给非技术的人。它解决的核心问题很明确给定一堆杂乱的历史数据训练一个可解释的二分类模型让模型不仅能预测还能告诉你是“哪几个条件组合”影响了结果。用一个更通俗的说法决策树就是一个自动学出来的“如果那么”判断手册。你给它一个乘客它先问性别再问仓位等级再问年龄一路走到某个叶子节点给出“存活”或“未存活”的概率。这个过程跟人做判断非常像所以它天然适合当机器学习的第一站。1.1 从一个二分类问题说起这是个典型的监督学习二分问题目标变量是Survived1 表示存活0 表示未存活。原始数据的一共有 891 条乘客记录其中存活 342 人、未存活 549 人。也就是说不做任何建模直接猜“所有人都未存活”准确率也有 61.5%。所以后面无论模型跑到多少分先要和这个基线对比别一看到准确率 80% 就激动。我见过很多新手在这个数据集上追求“更高准确率”把模型换成随机森林、XGBoost最后确实能到 85% 左右。但问题在于这个数据集本身信息量有限很多字段缺失严重强行堆复杂模型只会导致过拟合。决策树的价值不在“刷分”而在“把判断逻辑可视化地摆到桌面上”。你会看到模型为什么认为女性更有优势、年龄小的乘客更有优势、高舱等乘客更有优势这是黑盒模型很难做到的。1.2 为什么选决策树做第一个建模算法决策树有三个优点特别适合这个案例。第一不需要对数值特征做标准化。决策树每次切分只看“某个特征的某个阈值”比如Age 3.5它并不关心这个特征是不是正态分布、量纲是否统一。相比之下逻辑回归、SVM 这类模型对特征尺度很敏感必须先做标准化。第二能同时处理数值特征和类别特征。不过类别特征在进入模型前还是需要编码这点下面会详细说。第三结果可以画成树图。一个字段一个字段地看下去每一步都对应一个可解释的判断。对业务人员来说告诉他“男性、年龄大于 10、三等舱这个分支的存活率很低”比给他一堆特征系数要直观得多。当然决策树也有明显短板容易过拟合、对数据噪声敏感、单棵树不够稳定。这个案例里我会先用一棵基础决策树跑出基线再通过剪枝和调参控制过拟合最后解释特征重要性和树的分裂逻辑。2. 数据预处理与特征工程很多人学机器学习时把大量时间花在调参上却忽略了数据预处理。泰坦尼克号这个数据集最大的贡献就是逼你先去处理脏数据。它里面缺失值、文本字段、无用字段全齐了处理完这些模型反而成了最后最轻松的一步。2.1 拿到数据先做一次“字段体检”任何建模任务拿到数据第一步都不是model.fit而是先看字段长什么样。这个数据集主要字段如下字段类型含义主要问题PassengerId数值乘客编号对预测无意义Pclass数值船舱等级 1/2/3有序类别但树模型可直接用Name文本乘客姓名高基数需要提取才能用Sex文本性别需编码Age数值年龄缺失较严重SibSp数值同行的兄弟姐妹/配偶数量可组合为家庭人数Parch数值同行的父母/子女数量可组合为家庭人数Ticket文本票号高基数通常丢弃Fare数值票价有零星缺失Cabin文本客舱号缺失率非常高Embarked文本登船港口仅有 2 个缺失PassengerId、Name、Ticket、Cabin这四个字段在大多数入门方案里都会被直接剔除。Name里有称号信息比如 Mr、Mrs、Miss理论上可以提取出来做特征但提取过程容易引入目标泄露风险新手不建议一上来就加。Cabin缺失率超过 77%简单填缺失值毫无意义强行编码只会给模型添加大量噪声。2.2 缺失值怎么填才不浪费信息这个数据集里最需要处理的缺失值是Age一共有 177 条记录缺年龄。很多人第一反应是填全局均值比如直接df[Age].fillna(df[Age].mean())。我建议别这么做原因很简单全局均值会把不同性别人群的年龄差异抹掉也让年龄分布严重集中在均值附近。更合理的做法是按Sex和Pclass分组用组内中位数填充因为不同性别、不同舱位等级的人年龄分布有明显差异。用中位数而不是均值也能减少极端值的影响。import pandas as pd import numpy as np df pd.read_csv(titanic_train.csv) df df.drop(columns[PassengerId, Name, Ticket, Cabin]) age_median df.groupby([Sex, Pclass])[Age].transform(median) df[Age] df[Age].fillna(age_median)这里transform(median)会按每个分组计算中位数并原样返回和原 DataFrame 等长的序列直接用fillna填充缺失值。Embarked只有两个缺失直接填众数S即可。Fare偶尔也会缺一两条先用中位数补。需要注意在真正的建模流程里缺失值处理不能不看业务就乱填。年龄缺失可能跟某些乘客信息登记不完整有关也可能本身就和存活率有潜在关联。这个案例中字段较少分组中位数已经够用。2.3 把文字变成树能用的数字决策树不能直接处理Sexmale、EmbarkedS这种文本。最常用的做法是标签编码或独热编码。性别字段只有 male、female 两个取值直接映射成 0 和 1df[Sex] df[Sex].map({male: 0, female: 1})Embarked有三个取值用get_dummies做独热编码并设置drop_firstTrue避免多重共线性。虽然决策树不依赖于线性模型那种严格的共线性假设但少一列就意味着树少一些冗余的切分可能。df pd.get_dummies(df, columns[Embarked], drop_firstTrue)除此之外我通常会顺手做两个衍生特征。第一个是FamilySize把同行人数和自身加起来表达这个乘客在船上的家庭规模第二个是IsAlone表示是否独自一人。这两个特征在真实案例里很实用因为团队出行和独自出行的人在船上的生存环境往往差别不小。df[FamilySize] df[SibSp] df[Parch] 1 df[IsAlone] (df[FamilySize] 1).astype(int)特征工程不是越浮夸越好。每加一个特征决策树就多了一个可能的切分点也给过拟合多了一次机会。所以加特征之前先问自己这个特征背后的业务含义是什么如果只是“觉得可能有用”那就先不加等模型诊断完再加。3. 决策树模型的训练过程数据整理好了接下来就是建模。很多人喜欢直接把全量数据丢进模型再拿同一份数据评估效果这属于标准错误操作。正确的做法是先把数据切成训练集和验证集让模型在训练集学习在验证集检查泛化能力。3.1 先划好训练集和验证集在划分数据时我会用train_test_split并设置stratifyy。因为存活和未存活是约 38% 对 62% 的分布如果不做分层抽样随机切出来的验证集很可能幸存者比例偏高或偏低导致评估结果不稳定。from sklearn.model_selection import train_test_split y df[Survived] X df.drop(columns[Survived]) X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.3, random_state42, stratifyy )random_state42也是个不能省的东西。不固定随机种子每次运行划分都不一样模型复现和对比都无从谈起。3.2 用 5 行代码跑一个可解释的基线模型这里先不用复杂的调参工具直接用手设一个偏保守的决策树。max_depth3的意思是树最多往下分裂 3 层min_samples_leaf10的意思是每个叶子节点至少要包含 10 个样本。这两个参数组合能明显限制树的复杂度防止一开始就出现过拟合。from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score, confusion_matrix, classification_report model DecisionTreeClassifier( criterionentropy, max_depth3, min_samples_leaf10, random_state42 ) model.fit(X_train, y_train) for name, y_true, y_pred in [ (train, y_train, model.predict(X_train)), (validation, y_val, model.predict(X_val)), ]: print(name, accuracy_score(y_true, y_pred))我实际跑出来大概是train 0.84 validation 0.81训练集和验证集差距不大说明这个基线已经比较健康。再看验证集的混淆矩阵[[152 13] [ 38 65]]意思是未存活类别预测对了 152 人、错了 13 人存活类别预测对了 65 人、错了 38 人。整体准确率约 81%但存活类别的召回率偏低只有 63%。如果业务目标是“尽量把有希望活下来的人找出来”这个结果还需要优化。这里也提醒我们准确率高不代表每个类别都表现均衡尤其当正负样本比例不是 1:1 时光看 accuracy 会骗人。3.3 用信息熵看懂树在做什么决策树选择“先按谁切”靠的是信息增益。什么叫信息增益可以先用“混乱程度”来理解一个数据集里存活和未存活的人数越接近它的不确定性就越大如果全是未存活的不确定性就是 0。信息熵的计算公式是H(S) -Σ p_i * log2(p_i)在根节点上891 人里有 549 人未存活、342 人存活信息熵大约是 0.96。决策树会尝试每个特征的每个可能切分点计算切分后左右子节点的熵的加权平均再用根节点的熵减去这个加权平均得到信息增益。增益越大说明这个特征把数据分得更“纯净”。以性别为例粗略估算一下男性 577 人其中存活 109 人这一组的熵约为 0.70女性 314 人其中存活 233 人这一组的熵约为 0.82。按人数加权后大约为 0.74。信息增益就是 0.96 减 0.74约 0.22。所以树的第一层通常就会选择性别作为根节点的分裂特征因为它带来的信息增益最大。这也解释了一个常见困惑为什么决策树总爱把性别放在最上面因为在这个数据分布里性别能一瞬间把数据纯度提高不少。后面再用plot_tree画图你会发现树的实际判定逻辑非常朴素女性优先进入存活率更高的分支男性再往下细分年龄和舱位等级。4. 剪枝、调参与优化决策树的“自由生长”能力很强。如果完全不设限制它会为了让每个叶子节点尽量纯净而一直分裂最终把训练集背下来。训练集准确率可能高达 98%验证集却掉到 75%这就是过拟合。所以决策树的调参核心就是限制复杂度。4.1 树越小泛化越稳我用同样的min_samples_leaf10只改动max_depth得到的趋势大概是这样max_depth训练集准确率验证集准确率30.840.8150.880.8170.940.78100.970.74随着深度增加训练集准确率一路走高验证集却先涨后跌。这说明模型开始记住训练集里那些没有代表性的细节。决策树天生就是高方差模型同一个数据集换一批样本树的结构都可能差别很大。所以做决策树宁可让它“笨”一点也不能让它“背”下来。4.2 预剪枝参数和代价复杂度剪枝限制决策树复杂度主要有两条路。一条是预剪枝在生长时直接设限制条件常用参数有max_depth限制树的最大深度min_samples_split内部节点再分裂所需最小样本数min_samples_leaf叶子节点最小样本数max_features每次分裂最多考虑的特征数另一条是后剪枝让树先长满再修剪代价太高的分支。决策树实现里最典型的是ccp_alpha全称是“代价复杂度剪枝”。它会生成一棵子树路径alpha越大剪掉的节点越多。实际使用时常配合交叉验证path model.cost_complexity_pruning_path(X_train, y_train) ccp_alphas path.ccp_alphas然后对每个alpha训练一棵树在验证集上打分选出验证集表现最好的那个alpha。这种方式比手调max_depth更系统但计算成本也更高。对小数据没感觉对大数据要谨慎。4.3 用网格搜索找出更稳的参数组合手工调参数的效率太低我习惯用GridSearchCV把几个关键参数组合起来搜索。这里要注意搜索空间不建议太大否则训练时间会成倍增长。泰坦尼克号这个数据量很小可以稍微放开一点from sklearn.model_selection import GridSearchCV param_grid { criterion: [gini, entropy], max_depth: [3, 5, 7, 10], min_samples_leaf: [5, 10, 20], min_samples_split: [10, 20, 50], } grid GridSearchCV( DecisionTreeClassifier(random_state42), param_grid, cv5, scoringaccuracy ) grid.fit(X_train, y_train) print(grid.best_params_)我跑出来的一个较优组合是criterionentropy, max_depth5, min_samples_leaf10, min_samples_split20。注意网格搜索不是“找到参数就结束”还要回到验证集上再做一次确认。GridSearchCV内部用交叉验证选参数最后由best_estimator_在验证集上的分数才是你该对外汇报的数字。如果类别不平衡我建议把scoring改成f1或roc_auc而不是accuracy。这个案例正负样本比例大约 38:62虽然不算极端但只看准确率很容易漏掉少数类的问题。4.4 特征重要性怎么读才不翻车训练完成后model.feature_importances_会输出每个特征的重要程度。这个重要程度是模型基于“这个特征被用来切分时带来了多少纯度提升”统计出来的。我跑出来的大致的排序是特征重要程度Sex0.31Age0.27Pclass0.18Fare0.12FamilySize0.08IsAlone0.03Embarked_Q0.01这个排序符合常识性别影响最大年龄次之舱位等级和票价也有作用。但要注意特征重要性不等于因果重要性。如果两个特征高度相关比如Pclass和Fare树可能随机选择其中一个承担切分责任导致两者重要程度呈现不稳定分配。所以解释时不要直接说“票价不重要”只能说“在这个树结构里它没有被优先用来做有效切分”。画树也是验证模型是否合理的重要手段import matplotlib.pyplot as plt from sklearn.tree import plot_tree plt.figure(figsize(20, 10)) plot_tree( model, feature_namesX.columns, class_names[Died, Survived], filledTrue ) plt.savefig(tree.png, dpi120)画出来之后一定要用眼睛过一遍每一个分支。如果发现某个分支用了完全没业务逻辑的特征比如PassengerId 500那基本说明特征没清理干净或已经过拟合了。5. 常见问题与踩坑记录这个案例我至少带人做过几十遍踩过的坑可以单独写一份排查清单。很多坑不是模型难而是流程顺序和数据处理习惯出了问题。5.1 年龄缺失值全局均值是新手最容易掉的坑如果直接用全局均值填充年龄会带来两个问题一是年龄分布被人为向均值压缩二是不同舱位等级的年龄差异被抹平。你可以自己试试先按全局均值填再按SexPclass分组中位数填分别训练决策树验证集分数通常差异不大但画出来的树结构会明显不同。原因在于全局均值把大量乘客的年龄填成同一个值决策树就很难利用年龄做有效划分。正确做法是填充后检查分布看看是不是有一根明显长高的“假柱子”。如果是说明填充方式过于粗暴。5.2 数据划分和预处理顺序不对就是泄露很多人会先对全量数据做fillna、get_dummies再做train_test_split。这样做看起来没什么问题实际上验证集的信息已经偷偷流进了训练过程。最典型的是用全量数据的均值去填验证集缺失值这个均值里包含了验证集自己的信息。正确流程是先划分训练集和验证集再在训练集上计算填充规则和中位数然后把同样的规则应用到验证集。不过在泰坦尼克号这类小数据集上影响可能只是一两个百分点换成真实业务数据泄露问题会被放大得非常明显。5.3 只看准确率会掩盖问题把混淆矩阵和召回率拿出来前面说过全猜“未存活”就能有 61.5% 的准确率。如果模型只有 65% 准确率它其实只比瞎猜好一点点。所以要同时看混淆矩阵、精确率、召回率和 F1。尤其在存活这个少数类别上召回率很能说明模型有没有真正学到规律。我的基线模型在验证集上的存活召回率只有 63%说明漏掉不少存活乘客。这时候可以考虑调整分类阈值或者给少数类更大的权重。5.4 常见问题速查表问题原因解决训练集准确率很高验证集掉很多树过拟合增大min_samples_leaf限制max_depth树总是在没意义的字段上分裂高基数字段入模先剔除票号、客舱号等字段分类结果总偏向多数类类别不平衡没处理看召回率尝试class_weight或调阈值每次运行分数不一样没固定随机种子设置random_state文本特征直接塞进模型报错没做编码先映射或独热编码验证集分数虚高预处理用了全量数据先切分再 fit 预处理规则6. 沿着这个案例还能继续深化什么泰坦尼克号案例最容易被忽略的是“做完一棵树”之后还能往哪走。我建议不要急着换复杂模型先沿着两个方向练。第一个方向是把决策树换成集成模型比如随机森林和梯度提升。随机森林的本质是多棵决策树投票能大幅降低单棵树的方差但也牺牲了“一眼看懂一棵树”的可解释性。你可以把随机森林当成参照模型回头再对比单棵决策树的表现看看复杂模型到底赢在哪里、输在哪里。第二个方向是加一层模型诊断。比如画 ROC 曲线、计算 AUC、观察概率校准情况。通过这个案例你能学会怎么看模型输出的概率是否可靠而不只是看分类对错。这对真实业务中“预测点击率”“预测流失概率”这类任务非常关键。如果必须向非技术方解释模型我强烈建议优先画树。树图可以让人一眼看清“某个分支存活率高、某个分支存活率低”的逻辑。哪怕最终上线用的是随机森林也可以用一棵浅决策树做一个通俗版的解释模型。真实项目里信任模型往往比模型精度更重要。最后分享一个我坚持了很久的习惯每回跑完模型我都会把model.get_params()和最终特征列表保存下来。因为一两个星期之后再回头跑这个案例你大概率会忘记当时用的是哪个参数组合。这个习惯听起来很笨但能省下大量重复摸索的时间。决策树算法本身的代码就那几行真正见功夫的地方永远是对数据的理解和对流程的控制。

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

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

免费获取报价 →
↑