从Kaggle竞赛入门:用随机森林搞定泰坦尼克号预测的完整避坑指南(含特征工程与调参)
从Kaggle竞赛入门:用随机森林搞定泰坦尼克号预测的完整避坑指南(含特征工程与调参)
1. 为什么选择泰坦尼克号数据集作为机器学习入门项目?
泰坦尼克号幸存者预测是Kaggle平台上最经典的入门竞赛之一,被称为"机器学习界的Hello World"。这个数据集之所以成为绝佳的学习案例,是因为它完美融合了现实世界数据的复杂性和教学友好性。数据集包含891名乘客的12个特征字段,既有结构化数据(如年龄、票价),也有非结构化数据(如姓名),让你能全面练习数据清洗、特征工程和模型调优的全流程。
我在第一次接触这个项目时,曾天真地以为直接扔进随机森林就能得到不错的结果。现实给了我一记响亮的耳光——未经处理的原始数据得到的预测准确率甚至不如"全部预测死亡"的基准线。这促使我深入理解每个特征背后的意义,也让我意识到特征工程的重要性远超模型选择。
2. 数据探索:超越简单的统计描述
2.1 结构化数据的基础分析
首先加载数据并查看基本信息:
import pandas as pd train_df = pd.read_csv('train.csv') test_df = pd.read_csv('test.csv') print(f"训练集形状: {train_df.shape}") print(f"测试集形状: {test_df.shape}") print(train_df.info())关键发现:
- 年龄(Age)有约20%缺失值
- 船舱号(Cabin)有大量缺失(77%)
- 票价(Fare)在测试集中有1个缺失值
- 登船港口(Embarked)在训练集中有2个缺失值
2.2 可视化分析的进阶技巧
不要满足于简单的直方图和箱线图,试试这些更有洞察力的可视化:
import seaborn as sns import matplotlib.pyplot as plt # 年龄与生存率的核密度估计 plt.figure(figsize=(10,6)) sns.kdeplot(data=train_df, x='Age', hue='Survived', fill=True, alpha=0.5, palette='Set2') plt.title('Age Distribution by Survival Status') plt.show()这个可视化揭示了几个关键点:
- 儿童(0-10岁)生存率明显更高
- 20-30岁年龄段的死亡率显著
- 老年人(>60岁)生存率较低
3. 特征工程:从原始数据中挖掘黄金
3.1 从姓名中提取社会地位信息
原始数据中的姓名字段看似无用,实则包含宝贵信息:
# 提取称呼(Title) train_df['Title'] = train_df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False) # 查看称呼分布 print(train_df['Title'].value_counts()) # 将稀有称呼归类 rare_titles = ['Lady', 'Countess','Capt', 'Col', 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'] train_df['Title'] = train_df['Title'].replace(rare_titles, 'Rare') train_df['Title'] = train_df['Title'].replace('Mlle', 'Miss') train_df['Title'] = train_df['Title'].replace('Ms', 'Miss') train_df['Title'] = train_df['Title'].replace('Mme', 'Mrs')称呼与生存率的关系:
| Title | Survival Rate |
|---|---|
| Mrs | 79.4% |
| Miss | 70.7% |
| Master | 57.5% |
| Mr | 15.7% |
| Rare | 35.7% |
3.2 处理缺失值的智能策略
对于年龄缺失值,不要简单使用整体中位数填充:
# 按性别、船舱等级和称呼分组填充年龄 train_df['Age'] = train_df.groupby(['Sex', 'Pclass', 'Title'])['Age'].apply( lambda x: x.fillna(x.median()))3.3 创建有意义的组合特征
尝试创建这些能提升模型表现的新特征:
# 家庭规模 = 兄弟姐妹数 + 父母子女数 + 1(自己) train_df['FamilySize'] = train_df['SibSp'] + train_df['Parch'] + 1 # 是否独自旅行 train_df['IsAlone'] = 0 train_df.loc[train_df['FamilySize'] == 1, 'IsAlone'] = 1 # 票价每人(考虑家庭规模) train_df['FarePerPerson'] = train_df['Fare'] / train_df['FamilySize']4. 模型构建与调优:超越默认参数
4.1 基础随机森林模型
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score # 选择特征和目标变量 features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Title', 'FamilySize', 'IsAlone'] X = pd.get_dummies(train_df[features]) y = train_df['Survived'] # 基础模型 rfc = RandomForestClassifier(random_state=42) scores = cross_val_score(rfc, X, y, cv=5) print(f"基础模型准确率: {scores.mean():.4f} (±{scores.std():.4f})")4.2 网格搜索调参实战
不要盲目搜索所有参数,先理解每个参数的影响:
from sklearn.model_selection import GridSearchCV param_grid = { 'n_estimators': [100, 200, 300], 'max_depth': [5, 8, 10, None], 'min_samples_split': [2, 5, 10], 'min_samples_leaf': [1, 2, 4], 'max_features': ['sqrt', 'log2'] } grid_search = GridSearchCV( estimator=RandomForestClassifier(random_state=42), param_grid=param_grid, cv=5, n_jobs=-1, verbose=1 ) grid_search.fit(X, y) print(f"最佳参数: {grid_search.best_params_}") print(f"最佳得分: {grid_search.best_score_:.4f}")调参前后对比:
| 指标 | 默认参数 | 调优后 |
|---|---|---|
| 准确率 | 0.812 | 0.835 |
| 过拟合程度 | 较高 | 较低 |
4.3 特征重要性分析
训练后查看特征重要性,指导后续特征工程:
best_rfc = grid_search.best_estimator_ feature_importance = pd.DataFrame({ 'Feature': X.columns, 'Importance': best_rfc.feature_importances_ }).sort_values('Importance', ascending=False) plt.figure(figsize=(10,6)) sns.barplot(x='Importance', y='Feature', data=feature_importance) plt.title('Feature Importance') plt.show()关键发现:
- 性别是最重要的预测因素
- 票价和年龄紧随其后
- 称呼(Title)的重要性高于原始预期
- 登船港口(Embarked)贡献较小
5. 提交结果前的最后检查清单
在生成最终提交文件前,确保完成以下步骤:
数据一致性检查:
- 训练集和测试集的特征工程处理是否一致?
- 所有分类变量是否都进行了相同的编码?
模型验证:
- 是否在保留的验证集上测试过?
- 交叉验证结果是否稳定?
提交文件格式:
- 确保预测结果与乘客ID正确对应
- 检查文件格式是否符合Kaggle要求
# 最终预测与提交 test_df = pd.read_csv('test.csv') # 在测试集上重复所有特征工程步骤... predictions = best_rfc.predict(X_test) output = pd.DataFrame({ 'PassengerId': test_df.PassengerId, 'Survived': predictions }) output.to_csv('submission.csv', index=False)在第一次参加这个比赛时,我犯了一个低级错误——忘记对测试集进行相同的特征工程处理,导致提交结果异常糟糕。现在每次提交前,我都会专门检查这个清单。
