从Kaggle Titanic竞赛出发:构建你的第一个机器学习生存预测项目
1. 为什么选择Kaggle Titanic竞赛作为第一个机器学习项目第一次接触机器学习时最让人头疼的就是不知道从哪里开始。Kaggle的Titanic生存预测竞赛完美解决了这个问题它就像游戏里的新手村难度适中但又能让你体验完整的机器学习流程。这个项目最大的优势在于数据集足够简单。训练集只有891条记录测试集418条用普通笔记本电脑就能轻松处理。字段数量也很友好总共12个特征列加1个目标列Survived不会让初学者望而生畏。我刚开始学机器学习时试过直接上手图像识别项目结果被各种矩阵变换和卷积操作劝退。相比之下Titanic项目中的年龄、性别、票价等特征都非常直观理解起来毫无压力。数据质量也恰到好处——有缺失值需要处理比如Age字段约20%缺失有异常值需要检测比如Fare字段的极端值但又不至于复杂到让人无从下手。这种半成品状态特别适合练习数据清洗和特征工程。我记得第一次看到Cabin字段时发现它既有字母又有数字还夹杂着缺失值当时就懵了。后来才明白这正是项目设计的精妙之处逼着你去思考怎么把杂乱的数据转化为模型能理解的格式。另一个不可忽视的优势是丰富的学习资源。因为太经典网上有无数教程和解决方案。当你在某个步骤卡住时随便一搜就能找到参考。不过要提醒的是初期最好不要直接看TOP选手的方案他们的特征工程和模型融合技巧对新手来说太复杂。我建议先自己尝试完整流程遇到瓶颈再去参考别人的思路。2. 数据获取与初步观察2.1 获取数据集的正规姿势Kaggle官网是获取数据的最佳途径。注册账号后进入Titanic竞赛页面https://www.kaggle.com/c/titanic在Data标签页就能下载三个关键文件train.csv、test.csv和gender_submission.csv。前两个分别是训练集和测试集最后一个示例文件展示了提交结果的格式要求。对于国内用户可能会遇到下载速度慢的问题。这时可以考虑使用Kaggle官方API需要配置kaggle.json凭证文件通过国内镜像站获取但要注意数据完整性直接从我提供的网盘链接下载文末会给出下载后建议立即检查数据完整性。我遇到过文件损坏的情况解压时没报错但pd.read_csv()时各种诡异错误。一个简单的验证方法是查看文件行数import pandas as pd train pd.read_csv(train.csv) print(f训练集行数{len(train)}) # 应该输出8912.2 用Python打开数据的第一眼导入数据后千万别急着开始建模。先花10分钟做个快速探索你会少走很多弯路。这是我的标准操作流程import pandas as pd import seaborn as sns import matplotlib.pyplot as plt # 设置可视化风格 sns.set(stylewhitegrid, palettepastel) # 导入数据 train pd.read_csv(train.csv) test pd.read_csv(test.csv) # 合并数据集方便后续处理 full train.append(test, ignore_indexTrue) # 快速查看数据概况 print(train.info()) # 查看各字段类型和缺失情况 print(train.describe()) # 数值型字段的统计摘要输出结果会告诉你很多关键信息Age字段有缺失特别是测试集缺失更多Cabin字段缺失严重训练集只有204条记录Embarked字段有少量缺失Fare在测试集有1个缺失值2.3 生存率的可视化探索用seaborn画几个基础图表能快速建立对数据的直觉。下面这段代码展示了如何分析不同特征的生存率# 设置画布 plt.figure(figsize(12,8)) # 性别与生存率 plt.subplot(2,2,1) sns.barplot(xSex, ySurvived, datatrain) plt.title(Survival Rate by Gender) # 舱位等级与生存率 plt.subplot(2,2,2) sns.barplot(xPclass, ySurvived, datatrain) plt.title(Survival Rate by Pclass) # 登船港口与生存率 plt.subplot(2,2,3) sns.barplot(xEmbarked, ySurvived, datatrain) plt.title(Survival Rate by Embarked) # 同辈/配偶数量与生存率 plt.subplot(2,2,4) sns.barplot(xSibSp, ySurvived, datatrain) plt.title(Survival Rate by SibSp) plt.tight_layout() plt.show()这些图表会揭示一些明显规律女性生存率远高于男性约74% vs 19%头等舱乘客生存率最高约63%三等舱最低约24%从Cherbourg登船的乘客生存率最高有1-2个同辈/配偶的乘客生存率较高3. 数据清洗与特征工程3.1 处理缺失值的三种策略面对缺失值我一般按这个优先级处理直接填充固定值适用于意义不大的字段。比如Cabin字段缺失太多我直接用UUnknown填充full[Cabin] full[Cabin].fillna(U)填充统计值适用于有明确业务含义的数值字段。比如Fare缺失值用同舱位乘客的中位数填充full[Fare] full[Fare].fillna( full[(full[Pclass]3) (full[Embarked]S)][Fare].median())预测填充适用于关键特征且缺失比例不高的情况。Age字段最适合这种方法from sklearn.ensemble import RandomForestRegressor # 准备预测用的特征 age_features full[[Pclass,Sex,SibSp,Parch,Fare,Embarked,Title]] age_features pd.get_dummies(age_features) # 拆分已知和未知年龄的数据 known_age age_features[full[Age].notnull()] unknown_age age_features[full[Age].isnull()] y full[Age][full[Age].notnull()] # 训练随机森林模型预测年龄 rfr RandomForestRegressor(n_estimators200) rfr.fit(known_age, y) predicted_age rfr.predict(unknown_age) # 填充预测结果 full.loc[full[Age].isnull(), Age] predicted_age3.2 从姓名中提取头衔原始数据中的Name字段包含隐藏信息——乘客头衔。提取这些头衔能显著提升模型表现# 提取头衔 full[Title] full[Name].str.extract( ([A-Za-z])\., expandFalse) # 头衔映射与合并 title_mapping { Mr: Mr, Miss: Miss, Mrs: Mrs, Master: Master, Dr: Officer, Rev: Officer, Col: Officer, Major: Officer, Mlle: Miss, Don: Royalty, Dona: Royalty, Lady: Royalty, Countess: Royalty, Sir: Royalty, Jonkheer: Royalty, Mme: Mrs, Ms: Mrs, Capt: Officer } full[Title] full[Title].map(title_mapping)这个操作把30多种头衔合并为5类既保留了信息又避免了稀疏特征。实际测试中这个特征对模型准确率的提升能达到3-5个百分点。3.3 创建家庭规模特征原始数据中有SibSp同辈/配偶数量和Parch父母/子女数量但单独使用效果有限。将它们组合成家庭规模特征效果更好# 计算家庭成员总数 full[FamilySize] full[SibSp] full[Parch] 1 # 将家庭规模分组 full[FamilyGroup] full[FamilySize].apply( lambda x: single if x1 else small if x4 else large) # 可视化验证 sns.barplot(xFamilyGroup, ySurvived, datafull[full[Survived].notnull()])分组后的特征呈现明显规律单独出行的乘客生存率最低约30%小型家庭生存率最高约55%大型家庭生存率又有所下降。4. 模型构建与优化4.1 基础模型对比在特征工程完成后先用多种算法做初步比较from sklearn.model_selection import cross_val_score from sklearn.ensemble import (RandomForestClassifier, GradientBoostingClassifier, ExtraTreesClassifier) from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from sklearn.neighbors import KNeighborsClassifier # 准备数据 X full[full[Survived].notnull()].drop(Survived, axis1) y full[Survived][full[Survived].notnull()] X pd.get_dummies(X) # 处理分类变量 # 模型列表 models [ (LR, LogisticRegression(max_iter1000)), (KNN, KNeighborsClassifier()), (SVC, SVC(probabilityTrue)), (RF, RandomForestClassifier()), (GBDT, GradientBoostingClassifier()), (ET, ExtraTreesClassifier()) ] # 交叉验证比较 results [] for name, model in models: cv_results cross_val_score(model, X, y, cv5, scoringaccuracy) results.append((name, cv_results.mean(), cv_results.std())) # 输出结果 for name, mean, std in sorted(results, keylambda x: x[1], reverseTrue): print(f{name}: 准确率{mean:.4f} (±{std:.4f}))典型输出结果可能是GBDT: 准确率0.8328 (±0.0263) RF: 准确率0.8272 (±0.0247) ET: 准确率0.8216 (±0.0251) SVC: 准确率0.8204 (±0.0226) LR: 准确率0.7980 (±0.0249) KNN: 准确率0.7834 (±0.0258)4.2 梯度提升树(GBDT)调优从基础对比可以看出梯度提升树表现最好。下面演示如何进行参数调优from sklearn.model_selection import GridSearchCV # 参数网格 param_grid { n_estimators: [100, 200], learning_rate: [0.05, 0.1], max_depth: [3, 4], min_samples_split: [2, 5], subsample: [0.8, 1.0] } # 网格搜索 gbdt GradientBoostingClassifier(random_state42) grid_search GridSearchCV(gbdt, param_grid, cv5, scoringaccuracy, n_jobs-1) grid_search.fit(X, y) # 输出最佳参数 print(f最佳参数{grid_search.best_params_}) print(f最佳得分{grid_search.best_score_:.4f})调优后的模型准确率通常能提升1-3个百分点。要注意的是参数搜索范围不宜过大否则计算时间会成倍增加。我建议先用大范围粗调再在小范围内微调。4.3 模型融合技巧单一模型的表现总有上限这时可以尝试模型融合。最简单的投票法就能带来提升from sklearn.ensemble import VotingClassifier # 定义子模型 estimators [ (gbdt, GradientBoostingClassifier(n_estimators200, learning_rate0.1, max_depth3)), (rf, RandomForestClassifier(n_estimators200, max_depth5)), (svc, SVC(probabilityTrue, C0.1)) ] # 软投票 voting VotingClassifier(estimators, votingsoft) cross_val_score(voting, X, y, cv5, scoringaccuracy).mean()在我的测试中投票融合后的模型准确率能达到0.84-0.85比单一模型提升约1%。如果想进一步优化可以尝试堆叠(Stacking)等更高级的融合方法。5. 结果提交与改进思路5.1 生成提交文件模型训练完成后用测试集生成预测结果# 准备测试集 X_test full[full[Survived].isnull()].drop(Survived, axis1) X_test pd.get_dummies(X_test) # 确保特征顺序一致 X_test X_test.reindex(columnsX.columns, fill_value0) # 预测 best_model grid_search.best_estimator_ predictions best_model.predict(X_test) # 生成提交文件 submission pd.DataFrame({ PassengerId: full[full[Survived].isnull()][PassengerId], Survived: predictions.astype(int) }) submission.to_csv(submission.csv, indexFalse)生成的CSV文件可以直接在Kaggle上提交。第一次提交通常能拿到0.77-0.82的准确率这在初学者中已经不错了。5.2 持续改进的方向如果想让成绩进入前10%还需要更多优化特征工程从Cabin字段提取甲板信息首字母将Ticket号码分类数字票vs字母票创建家庭ID特征识别同一家庭模型层面尝试XGBoost、LightGBM等更先进的算法使用分层交叉验证确保评估稳定性调整分类阈值默认0.5不一定最优后处理技巧根据姓氏识别家庭组统一预测结果结合历史知识调整预测如已知某些乘客的生还情况我自己的最佳成绩是0.85167通过以下组合实现精细化的特征工程共28个特征LightGBM模型带贝叶斯优化调参家庭组后处理规则这个项目最有趣的地方在于你永远能找到改进空间。即使现在排行榜上仍有新方法不断涌现这正是机器学习的魅力所在。