从0.75到0.784:Kaggle Titanic生存预测中的特征工程与模型优化实践
(文章为我的探索学习过程,有不足的地方还请批评改正,感谢!)
1.背景和目标:
本项目基于Kaggle Titanic数据集,目标是预测乘客在灾难中的生存情况。
项目的核心在于:
- 构建完整的机器学习流程(EDA → 特征工程 → 模型训练)
- 通过实验验证特征设计的有效性
- 分析特征之间的关系
成果:最终达到 0.78468 分。
2.数据集
2.1数据总览
比赛提供了两个数据集,一个训练集和一个测试集,训练集train.csv包含了891名乘客的特征及其存活情况,测试集test.csv包含了418名乘客的特征,没有存活标签,用于最终预测和提交。
数据集中共有12个字段,其中
PassengerId:乘客编号
Survived:是否存活(0=遇难, 1=生还)
Pclass:客舱等级(1=头等舱, 2=二等舱, 3=三等舱)
Name:姓名
Sex:性别
Age:年龄
SibSp:同行的兄弟姐妹/配偶人数
Parch:同行的父母/子女人数
Ticket:船票编号
Fare:票价
Cabin:船舱号
Embarked:登船港口(S=南安普顿, C=瑟堡, Q=皇后镇)
使用train.head(10)预览前10行数据,可以看到数据中存在缺失值,并且Sex、Name字段是文本,需要转换为数值才能让模型处理。
紧接着再用train.isnull().sum()和test.isnull().sum()来分别查看训练集和测试集哪些字段有缺失值及其缺失值的数量。
训练集缺失值情况:
测试集缺失值情况:
2.2可视化分析
接下来对数据进行可视化分析,首先是存活人数统计,0代表遇难,1代表存活,在此次灾害中数据集中一共有342人存活,549人遇难。
下面分析各特征与存活率之间的关系,Sex与Survived之间的柱状图如下:
从2-2中可以看出来女性存活率为74.2%,男性存活率为18.9%,性别间的存活率差距高达55%,在救生艇数量有限的情况下,女性拥有较高的救援优先权。Sex是预测存活的重要特征之一,需要将其作为模型的判断依据。
泰坦尼克号共有三个客舱等级,其中1代表头等舱,2代表二等舱,3代表三等舱。客舱等级与存活率关系如下:
从上图可以看出,头等舱存活率为63%,二等舱存活率为47.3%,三等舱存活率为24.2%。客舱等级与存活率呈现明显的负相关,等级越低,存活越难。这反映了当时社会阶层的残酷现实,头等舱位于上层甲板,离救生艇最近,三等舱乘客住在底层,逃生路径遥远,可能还会因为混乱局面而被限制进入头等舱。Pclass是一个极强的特征,需要将其纳入模型的判断依据中。
泰坦尼克号上各年龄段人群数量及年龄与存活率之间的关系如下图所示:
从上面两个图中可以看出,泰坦尼克号上20-40岁的人群占比居多,其次是低龄儿童,从存活率看出,儿童具有绝对的生存优势,生还者的年龄下限很低。同时可以看出有一些离群的圆圈分布在年龄较高的地方,说明年长者逃生难度更大,一旦遇到危险,很容易成为语言群体中的极端案例。无论存活与否,大部分乘客年龄都集中分布在20-40岁之间,这部分人能够存活,更大程度上取决于他们手中的船票和性别,而非年龄本身。对于Age这个特征,后续数据清理应处理好缺失值,考虑将年龄段分段加工。
泰坦尼克号总共有三个登船港口,S=南安普顿, C=瑟堡, Q=皇后镇。登船港口与存活率之间的关系如下图所示:
可以看出从瑟堡港口登船的乘客存活率最高,皇后镇和南安普顿其次,这其实是和乘客的舱位分布有关:瑟堡港口登船的的乘客中头等舱比例较高,而皇后镇港口登船的乘客多为三等舱,所以这个特征后隐藏的还是社会差异,Embarked是一个辅助性特征,能够提供额外信息,但是不如Pclass直接,处理好缺失值后仍可以加入到模型中。
最后是特征相关性热力图,如下图所示:
从上面可以看出,与存活率最相关的特征有Pclass与Fare,Pclass与Survived的相关系数为-0.34呈现中度负相关,这意味着客舱等级越低,存活率越低,其次是Fare与Survived的相关系数为0.26,呈现中度正相关,票价越高,存活概率越大。Sex虽然经过上述分析确认是强特征,但是尚未对原始数据进行编码,未纳入相关性计算。
特征之间的内部关联,Pclass与Fare之间相关系数高达-0.55,这符合常识:头等舱票价远高于三等舱。SibSp与Parch之间相关系数为+0.41,说明携带配偶的乘客往往也带有子女。这两个特征可以组合成FamilySize以减少冗余。
弱相关特征中,Age与Survived的相关系数仅为-0.08,说明年龄与存活率间不存在明显的线性关系。但这并不意味着年龄不重要,从前面的箱线图分析可知,年龄的影响是“两极化”的,这种非线性模式是无法被相关系数捕捉的。
总结来说,Pclass和Fare是核心预测特征,但它们之间存在较强的共线性,在建模时需要留意,或者通过特征组合来缓解。SibSp和Parch单独与存活的相关性很弱,但将两者相加并 +1 构造出FamilySize后,可能挖掘出家庭规模的 U 型或倒 U 型影响。
基于上述观察后,确定后续策略,对Age进行合理填充,Cabin不作为核心特征,构造新特征,探索家庭关系对生存的影响,构造与社会身份相关的特征。
3.数据预处理
3.1缺失值处理
对于Age这一特征,缺失了177条,占总数近20%,直接删除的话可能导致大量样本浪费,所以选择中位数,而不采取平均数填充,因为年龄分布存在极端值,如80岁以上的老人,这些离群值会导致均值受到离群点影响,中位数对离群值不敏感,可以更稳健的反映乘客的年龄。
注意:测试集的年龄填充必须使用训练集的中位数,而非测试集本身的中位数。
Cabin的缺失值高达687,占比77.1%,对于这类缺失值很严重的特征,不管采用哪种填充方式都相当于编造了大部分信息,填充的话反而会引入大量噪声,拉低模型的性能,所以我选择将这一列直接删除。
Embarked特征仅缺失2条占比较低,对数据整体影响不大,因为Embarked是分类变量,所以我选择直接用众数填充。
测试集的缺失值处理同训练集,但注意需要用训练集的数据。
3.2特征编码
观察数据集可以看到,Sex和Embarked这两个特征为文本数据,接下来分别对这两个特征进行编码处理。
Sex特征编码:
Embarked特征编码:
数据集总览:
4.特征工程(此部分具体分数迭代情况请查看5.5分数迭代记录)
4.1Baseline模型
初始模型使用以下特征:
Pclass, Sex, Age, SibSp, Parch, Fare, Embarked
使用RandomForest,默认参数。
Baseline分数:0.75837
4.2加入FamilySize
将SibSp和Parch这两个特征合成一个更直观的特征FamilySize,这样可以更好的反应乘客的家庭关系,FamilySize=1,独行乘客,可能更容易在混乱中逃生,也可能因为无人协助而遇难;FamilySize=2-4人,小家庭,可能有部分人生还;FamilySize>4,大家庭,紧急情况下互相寻找等待,可能会降低整体生还率。
做了上述代码进行提交后,发现从第一版的0.75837降低为了0.74641,模型性能出现下降。后续排查后发现我的特征冗余了,已经有的特征SibSp和Parch和新增特征FamilySize存在强相关性,模型可能会混淆或者过拟合,另一个方面就是FamilySize中1的生存率是低,2-4生存率高,5+生存率低。关系是中间高两边低,呈非线性关系。模型难以直接学习家庭规模与生存率之间的非线性关系,导致学习效果差,所以我设计了一个分箱函数,把家庭人数切成三个区间并且删除了冗余特征。修改代码如下:
修改后再次提交分数变成了0.76794,较上次分数提升了。
4.3Title 提取
观察数据集发现,每个乘客前都有一个头衔Name,头衔背后同时隐含了性别、年龄、婚姻状态以及社会地位这些信息。例如Mr指成年男性,Mrs已婚女性,通常有丈夫同行,Miss婚女性,可能较年轻,Master未成年男孩,年龄小。把这些信息提取出来,模型就会多一个高密度的复合特征。我使用正则表达式从’Name’中提取头衔。
提取完成后,我用 value_counts() 检查了头衔分布。除了常见的 Mr、Mrs、Miss、Master 外,还有一些稀有头衔,如 Dr、Rev、Col等,每种只有个位数样本。对于这些稀有头衔,我采用合并策略——将它们统一归为“其他”类,编码为0。
进行完上述两种操作提交后,分数下降了,从0.76794降到了0.76076。这让我发现可能把剩下的稀有头衔全部编码为0会忽略重要的信息,因为部分稀有头衔可能隐含更高的社会地位,因此简单归为同一类别可能导致信息损失,高社会地位往往与高存活率相关。所以下面我尝试对Title进行分组,而不是直接填0。
因为Title涵盖有Sex和Age的信息,所以接下来我还做了一组对照实验,看看Title是否能替代Sex。直接进行Title分组后,我首先保持特征Sex,发现分数从0.76076下降到了0.75837。然后去掉Sex之后,发现分数又回到了0.76076。实验结果表明,Title特征在一定程度上能够替代Sex特征中的部分信息,并且可以让模型学习的更好。接着我尝试了更细致的社会身份划分,比如区分专业人士和贵族,但发现分数没有任何变化,但是我也暂且保留了这个Title分组。
除了改变Title分组之外,我对Title的提取方式进行了优化,刚开始的时候我使用正则表达式提取Title,下面我用split方法对Title重新提取,提交后,分数上升到0.76287,说明优化Title提取方式对模型学习有提升作用。
5.模型训练和迭代
5.1模型选择
在模型选择上,我选择随机森林,有以下两点原因,一是随机森林对异常值和缺失值鲁棒,不会因为个别样本的异常而剧烈波动;二是存活与否并非和年龄、票价等特征呈简单线性关系,树模型天然能捕捉这种非线性模式。
5.2模型调参与优化
初始基线模型
我使用默认参数训练了一个基线模型:
n_estimators=100:100 棵决策树,在速度和稳定性之间取得平衡。
其他参数均使用 sklearn 默认值。
调参优化
在基线模型的基础上,我进行了两个方向的调参:
首先是将树的数量从100增加到200,增加树的数量可以降低方差,使投票效果更稳定。第二个是限制每棵树的最大深度为5,防止模型记忆训练数据中的噪声,提升泛化能力;第三个是固定随机种子为42,确保每次运行结果完全一致,便于对比实验结果。
调参后的效果:交叉验证平均得分0.816,Public Score:0.78468
5.3交叉验证
为了更客观的评估模型的性能,我引入了5折交叉验证:
平均 81.6% 的准确率,说明上述两个特征工程是有效的。五折得分波动极小,最低 0.804,最高 0.826,差距仅 2.2%,证明模型对数据的不同划分方式不敏感,泛化能力良好。
5.4模型局限性
虽然随机森林能够较好的处理非线性关系,但当前模型仍然存在一些局限性,特征工程主要依赖人工经验,缺少自动化特征搜索,当前实验主要是基于单模型,没有尝试模型融合,Titanic数据集规模小,模型结果容易受到数据划分影响。
5.5分数迭代记录
版本 | 改动内容 | Public Score | 变化 |
V1 | Baseline | 0.75837 | 基础分数 |
V2 | 加入 FamilySize | 0.74641 | 分数降低 |
V3 | 修改FamilySize | 0.76794 | 分数升高 |
V4 | 加入Title | 0.76076 | 分数降低 |
V5 | 修改Title(Title分组保留Sex) | 0.75837 | 分数降低 |
V6 | 不保留Sex | 0.76076 | 分数升高 |
V7 | 再次修改Title(细致分组) | 0.76076 | 分数不变 |
V8 | 改变Title提取方式 | 0.76287 | 分数升高 |
V9 | 改变模型 | 0.78468 | 分数升高 |
6.总结与反思
以下是我收获的几个点,第一新增特征并不一定能提升性能,第二是特征之间可能存在共线性,某些关系可能是非线性的;交叉验证能够更稳定的评估模型的泛化性能。
