泰坦尼克号Kaggle实战:数据清洗、特征工程到模型调参全流程 简介面向数据科学初学者和机器学习爱好者的Kaggle泰坦尼克号经典实战资料包完整包含train.csv、test.csv等核心数据集以及带详细注释的Python源代码适合用来快速入门数据预处理、特征工程与模型构建的完整流程。压缩包共6个文件包括4个csv数据文件训练集、测试集、提交示例等和2个py脚本整体仅40KB轻量易用下载后即可直接运行。已有1362人学习下载是广受验证的入门练习资源。代码注释逐段解释关键操作可跟随学习如何用Pandas清洗缺失值、编码分类变量、基于姓名与舱位构造新特征并尝试逻辑回归、随机森林等模型进行训练与交叉验证同时理解准确率、F1分数等评估指标。完成本项目后不仅能掌握Kaggle竞赛的标准提交流程还能积累一套可复用于其他表格型任务的建模与调参经验为进阶数据科学实践打下扎实基础。1. 泰坦尼克号Kaggle实战一份能带你走完全流程的源码包第一次上Kaggle的人八成会被泰坦尼克号这个入门赛“教育”一顿。你以为难点在模型结果卡在数据清洗你以为调参能提分结果发现提交格式错了直接零分。我拆过不少这类资源真正能让人从零跑到提交的并不多这份源码包算是把路铺平了——train.csv、test.csv、gender_submission.csv三个标准数据文件齐全还带两个带注释的Python脚本example1.py和example2.py以及一份已经做好的one-hot编码文件。它解决的就是“经典二分类任务怎么做完整流程”这件事从读数据、清洗、特征工程到建模评估再到生成可提交的预测文件。适合刚学完Pandas和Sklearn基础、想跑第一个完整项目的人也适合想看看别人怎么写特征工程来对比思路的在职数据分析师。注意它不教深度学习就是经典机器学习路线。2. 拆开压缩包五个文件各自扮演什么角色拿到压缩包别急着跑代码先把五个文件的作用理清。这个项目的数据结构和Kaggle原版比赛一致文件之间是严格对应的少一个都跑不通。2.1 train.csv与test.csv训练和预测的边界train.csv有891行每行是一名乘客包含12个字段PassengerId、Survived、Pclass、Name、Sex、Age、SibSp、Parch、Ticket、Fare、Cabin、Embarked。其中Survived是标签列1表示幸存0表示遇难这是模型要学习的目标。test.csv有418行字段和train.csv完全一致唯独没有Survived列——你的任务就是基于训练集学到的规律预测这418人的生存概率。两个文件的行数差异要留意训练集891人、测试集418人合起来1309人并不是泰坦尼克号全部乘客实际约2224人这是Kaggle有意裁剪的子集目的是保证训练和测试分布尽量接近。实际操作中我习惯先df.info()看一下每列的非空计数train.csv里Age缺了177个、Cabin缺了687个test.csv同样有缺失这种分布一致性是后续填充策略能直接复用的前提。2.2 gender_submission.csv你知道但别直接用的基线这个文件是Kaggle官方给的基准答案只依据Sex列做预测女性全设为1男性全设为0。418行两列——PassengerId和Survived。它存在的意义不是让你直接提交而是告诉你一个残酷事实单靠性别这个特征就能拿到约0.765的准确率Kaggle评分用accuracy。很多新手费半天劲调模型最后分还不如这个无脑规则所以它是个很好的“下限参考线”。我的建议是拿它做三件事第一提交一次确认自己的提交通道和数据格式没问题第二作为模型对比的baseline任何模型效果低于它都说明特征工程白做了第三用它校验你最终生成的submission.csv的PassengerId顺序是否一致。这个文件的列顺序是PassengerId在前、Survived在后生成提交文件时别搞反。2.3 Taitan_onehot.csv提前做好的特征工程样板这个文件是已经把类别变量Sex、Embarked、Pclass转成one-hot编码后的训练集版本1236行14列PassengerId Survived 12个编码后特征。它最大的价值是省掉你重复敲pd.get_dummies()的时间可以直接当输入跑模型。但我更建议把它当“对比样本”用——你手动做完预处理后用pd.read_csv()分别读入手工版和自己处理版df.equals()对比一下差异能立刻发现自己漏了哪一步。2.4 example1.py与example2.py两套思路的完整代码两个脚本注释都非常全适合逐行跟读。example1.py是“快速出结果版”用Pandas清洗数据、填充缺失值、直接训练逻辑回归并输出提交文件整个流程紧凑适合先跑通全链路。example2.py是“调参探索版”加了特征工程细节比如从Cabin提取首位字母、构造FamilySize并用交叉验证评估多个模型逻辑回归、随机森林、SVM、KNN最后选出最优模型预测。从学习角度先跑example1理解流程再对比example2看特征工程对分数的影响效果最好。提示两个脚本的预测结果文件会覆盖同一个submission.csv跑完第二个记得回头看一眼别提交错版本。3. 数据预处理与特征工程从原始表到模型输入的全流程代码预处理决定模型分数的上限这句话在泰坦尼克号上体现得尤其明显。原始数据直接丢给模型准确率很难过0.8但做好特征工程随机森林就能跑到0.8以上。这一章给出完整可跑的代码对应example1.py的主干逻辑并做扩展说明。3.1 数据加载与初步探查先看再动手import pandas as pd import numpy as np train pd.read_csv(train.csv) test pd.read_csv(test.csv) full pd.concat([train, test], axis0, sortFalse).reset_index(dropTrue) print(full.shape) # (1309, 12) print(full.isnull().sum())这段代码先分别读入两个csv再用pd.concat沿行方向合并得到一个1309行的完整表。合并的目的是让后续所有填充和编码操作对训练、测试数据一视同仁避免出现“训练集填了均值、测试集忘记填”的尴尬。sortFalse参数在Pandas新版本里是官方推荐设置防止列顺序被打乱。reset_index(dropTrue)则保证合并后索引连续后面按位置切片时不会错位。isnull().sum()是必看的体检报告这里会看到Age缺263个、Cabin缺1014个、Embarked缺2个。注意合并后Cabin缺失比单独看train.csv更多因为test.csv的Cabin缺得厉害。3.2 缺失值填充Age、Fare、Embarked三种不同策略# 年龄按Pclass分组填充中位数 full[Age] full.groupby(Pclass)[Age].transform(lambda x: x.fillna(x.median())) # Fare用整列中位数填充唯一缺失 full[Fare] full[Fare].fillna(full[Fare].median()) # Embarked用众数填充 full[Embarked] full[Embarked].fillna(full[Embarked].mode()[0])三个缺失字段的填充逻辑完全不同这是有实际考量的。Age与Pclass相关性高——一等舱乘客平均年龄更大所以按舱位分组取中位数比全局中位数更贴近真实分布。Fare只有一个缺失值直接全局中位数就行没必要复杂化。Embarked缺失2个用众数S港口填充是通行做法。这里的参数值得记一下groupby(Pclass)[Age].transform(lambda x: x.fillna(x.median()))这段如果用full[Age].fillna(full[Age].median())替代分数会掉约0.01差别不大但积少成多。填充完建议再跑一遍isnull().sum()确认全为0再继续。3.3 特征构造Cabin首位字母、家庭规模、名字头衔# 从Cabin提取舱室区域字母没有的填UUnknown full[Cabin_Letter] full[Cabin].apply(lambda x: x[0] if pd.notnull(x) else U) # 家庭成员总数自己 兄弟姐妹/配偶 父母/子女 full[FamilySize] full[SibSp] full[Parch] 1 # 从姓名提取头衔Mr、Mrs、Miss、Master等 full[Title] full[Name].apply(lambda x: x.split(,)[1].split(.)[0].strip()) # 独身标记FamilySize 1 full[IsAlone] (full[FamilySize] 1).astype(int)这四个新特征的价值依次是Cabin首位字母分出A-G七个区域加上未知U一等舱乘客Cabin值更完整这个特征间接携带了舱位层级信息FamilySize捕捉“一家人同生共死”的强规律实测FamilySize为2和4的生存率高为1和大于5的生存率低这是非线性关系Title能把女性里的Mrs、Miss、Master区分开因为她们生存率差异很大IsAlone是FamilySize的二值化简化版有些模型对0/1特征更友好。这里的split(,)[1].split(.)[0].strip()是典型的字符串清洗三板斧先按逗号切出名字段再按句号切出头衔最后去掉首尾空格。3.4 分类变量编码与特征矩阵拼接# 转成category类型XGBoost和Sklearn能直接处理 full[Sex] full[Sex].map({male: 0, female: 1}) full[Embarked] full[Embarked].map({S: 0, C: 1, Q: 2}) # 类别型特征做one-hot features [Pclass, Sex, Cabin_Letter, Embarked, Title] full pd.get_dummies(full, columnsfeatures, drop_firstFalse) # 数值特征做标准化 from sklearn.preprocessing import StandardScaler scaler StandardScaler() for col in [Age, Fare, FamilySize]: full[col] scaler.fit_transform(full[col].values.reshape(-1, 1))Sex和Embarked两个二分/三分变量直接用映射编码成整数逻辑回归能理解大小关系但你要知道这是有损的——把男/女映射成0/1没问题把C/Q/S映射成1/2/3就隐含了“Q(2) C(1)”的错误方向。所以Embarked和Cabin_Letter、Title一起走one-hot彻底消除大小关系。drop_firstFalse保留全部列而不是去掉一列牺牲一点特征维度换回可解释性。StandardScaler这步在逻辑回归里是非做不可的因为梯度下降对特征尺度敏感在随机森林里做不做都行但统一做没坏处。注意reshape(-1, 1)是Sklearn的老要求新版本也跳不过这一下。处理好后把合并表重新切回训练集和测试集train_processed full[full[Survived].notnull()] test_processed full[full[Survived].isnull()] X train_processed.drop([Survived, PassengerId, Name, Ticket], axis1) y train_processed[Survived].astype(int) X_test test_processed.drop([Survived, PassengerId, Name, Ticket], axis1) print(X.shape) # (891, 47) print(X_test.shape) # (418, 47)drop掉PassengerId是防止模型把它当连续特征学习——实际上它是纯编号和生存没有任何关系Name和Ticket在构造完Title后也完成了历史使命。到这里X是47维特征矩阵y是0/1标签模型可以进场了。4. 模型训练、调参与评估从默认参数到交叉验证选优数据准备好了模型选择就是最后一道坎。泰坦尼克号这个任务样本量只有891不适合深度学习经典机器学习足够。这一章对应example2.py的核心逻辑。4.1 逻辑回归先打底理解模型行为再谈优化from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score lr LogisticRegression(max_iter1000, C0.1, random_state42) scores cross_val_score(lr, X, y, cv5, scoringaccuracy) print(fLR CV Accuracy: {scores.mean():.4f} (/- {scores.std():.4f}))先说一下为什么第一站选逻辑回归它不是效果最好的模型但它是最容易理解“特征如何影响预测”的模型系数直接对应每个特征的权重方向。max_iter1000是防止默认值100在特征维度较高时迭代不收敛C0.1是正则化强度的倒数C越小正则越强在47维特征里适度正则能压制噪声。cross_val_score默认返回5折准确率这里考察的是平均分和标准差——平均分看模型真实水平标准差看稳定性如果标准差超过0.03说明模型对数据划分敏感需要检查是否有异常特征。这段代码跑完通常能得到约0.81左右的平均准确率。此时建议打印系数看看哪些特征权重绝对值大和常识对照验证模型没学歪。4.2 多模型对比随机森林、SVM、KNN在同一基准下比拼from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC from sklearn.neighbors import KNeighborsClassifier models { RandomForest: RandomForestClassifier(n_estimators200, max_depth5, random_state42), SVM: SVC(kernelrbf, C1.0, gammascale, random_state42), KNN: KNeighborsClassifier(n_neighbors5) } for name, model in models.items(): cv_scores cross_val_score(model, X, y, cv5, scoringaccuracy) print(f{name}: {cv_scores.mean():.4f} (/- {cv_scores.std():.4f}))三个模型的核心参数意图随机森林n_estimators200保证树的数量足够max_depth5限制单棵树深度防止过拟合——泰坦尼克号特征维度小深度大了纯属浪费还容易记住噪声SVM用rbf核函数处理非线性边界gammascale让Sklearn根据特征维度自动推算gamma值这是新版Sklearn的推荐写法手动指定一个数字反而容易翻车KNN的n_neighbors5是经验值取太大比如20会把不同类别的样本混为一谈取太小比如1则完全没有泛化能力。跑完这组对比通常会出现一个很有意思的结果随机森林和SVM分数接近约0.81-0.82KNN明显偏低约0.78。这不是KNN不适合而是它对人造的高维one-hot特征不敏感距离计算被大量0值稀释。4.3 网格搜索调参把交叉验证分数再往上顶一点from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200, 300], max_depth: [3, 5, 7], min_samples_split: [2, 5, 10] } rf RandomForestClassifier(random_state42) grid GridSearchCV(rf, param_grid, cv5, scoringaccuracy, n_jobs-1) grid.fit(X, y) print(fBest params: {grid.best_params_}) print(fBest CV score: {grid.best_score_:.4f})GridSearchCV会遍历3×3×327组参数组合每组做5折交叉验证共135次训练。n_jobs-1让所有CPU核心并行跑27组大概耗时几十秒。min_samples_split是新增的考察维度——它控制一个节点要继续分裂需要的最少样本数数值越大模型越保守、越不容易过拟合。跑完看best_params_如果最优的max_depth是7或更大说明当前特征量下模型还没到过拟合边界如果最优是3说明特征里的噪声已经不少了。泰坦尼克号最优参数通常集中在max_depth5、min_samples_split5附近比默认参数能提升0.005-0.01个点。4.4 行业通用习惯把最终分数理解为置信区间调参结束后我习惯把5折交叉验证的每一折分数单独打印出来看分布。如果5个分数分别是0.81、0.82、0.80、0.83、0.81说明稳定如果出现0.78、0.85、0.79、0.83、0.80说明某一折的数据划分导致模型表现波动大这时回查是否有极端离群样本在干扰。0.81这个交叉验证分数到了Kaggle公共榜大概会落在0.78左右这个折扣是因为训练集和测试集分布有细微信号差异属正常现象别慌。5. 避坑指南泰坦尼克号最常见的五个翻车点这个项目我见人踩过太多坑而且几乎全是低级错误。每一条都按“现象 → 原因 → 解决”写清楚你遇到的时候直接对号入座。5.1 提交后得零分PassengerId对不上现象本地交叉验证0.82提交到Kaggle得0.0分。原因生成提交文件时预测结果的行顺序和test.csv的原始顺序不一致或者PassengerId列没有从test.csv原样拷贝。解决提交前先用test[PassengerId]当作提交文件的第一列然后用.reset_index()确认行索引没有乱序。我习惯在生成submission前跑一行断言代码assert len(pred) len(test) and test[PassengerId].is_unique快速校验后再写文件。5.2 训练和测试一起处理时泄漏了Survived标签现象交叉验证0.86提交只有0.75差价大到离谱。原因用pd.concat([train, test])合表后对全表做填充时不小心用了Survived信息——比如按Survived分组填充Age或者后续切分时没有把Survived列干净分离。解决严格按“先合并处理、再按Survived是否为空切回”的顺序操作切分后立刻检查X里不得存在Survived列assert Survived not in X.columns。这条断言值得写进每个项目的预处理脚本里。5.3 男性乘客预测全错性别映射写反了现象训练集准确率很高但提交文件里男性全预测为1、女性全预测为0。原因{male: 0, female: 1}手滑写成{male: 1, female: 0}或者直接用LabelEncoder编码Sex列编码结果完全随机。解决映射字典只有两种写法务必跑一行print(train[[Sex, Survived]].groupby(Sex).mean())确认女性平均生存率高于男性再回头检查映射表。这一步不花时间但能省一次没必要的提交次数。5.4 Cabin缺失值过多直接删列导致分数暴跌现象看到Cabin缺失1014个直接drop(Cabin, axis1)然后发现分数从0.81掉到0.78。原因Cabin虽然有大量缺失但“缺失本身”就是信息——Cabin有值的乘客多为高舱位生存率相对更高。直接删列等于把这个信号扔了。解决提取Cabin_Letter并保留U作为独立类别让模型自己学习缺失和非缺失的差别。这也是我坚持不简单drop列的原因宁可多一个分类也别少一条线索。5.5 Age用全局均值填充导致性别与年龄交互失效现象特征工程做完后模型始终在0.79-0.80之间上不去。原因全局均值填充会让大量乘客年龄集中在同一个值附近削弱了“儿童生存率高”这个关键信号尤其把真正的儿童样本稀释了。解决改用groupby(Pclass)[Age].transform(lambda x: x.fillna(x.median()))保留舱位与年龄的相关性。另外年龄可以再试一下pd.cut分桶成儿童/青年/中年/老年有时比连续值效果更好这个可以自己对比验证。6. 验证与提交用交叉验证、学习曲线和提交反馈循环调优到这里模型已经能跑了但在按下提交按钮之前有三件收尾工作值得认真做。第一件是“模型分数是否可信”的验证第二件是“提交文件格式是否正确”的自检第三件是拿官方baseline文件做对照实验。6.1 学习曲线判断模型状态欠拟合还是过拟合from sklearn.model_selection import learning_curve train_sizes, train_scores, test_scores learning_curve( grid.best_estimator_, X, y, cv5, train_sizes[0.3, 0.5, 0.7, 0.9, 1.0] ) train_mean train_scores.mean(axis1) test_mean test_scores.mean(axis1) for size, tr, te in zip(train_sizes, train_mean, test_mean): print(fTrain: {size:.0f}, Train Acc: {tr:.3f}, CV Acc: {te:.3f})learning_curve通过逐步增加训练集大小观察两条曲线。如果训练集分数高但交叉验证分数低中间差距大说明过拟合考虑加强正则化或补充数据如果两者都低说明欠拟合需要加特征或换复杂模型。泰坦尼克号891条样本跑这个分析能看到训练集分数约0.84、验证分数约0.81差距小于0.05就属于健康状态。每行输出对应一个训练集规模从小到大看趋势是最有效的判断方式。6.2 提交文件生成与格式自检pred grid.best_estimator_.predict(X_test) submission pd.DataFrame({ PassengerId: test_processed[PassengerId].astype(int), Survived: pred.astype(int) }) submission.to_csv(submission.csv, indexFalse)写入文件前做四件事第一submission.shape应为(418, 2)第二PassengerId顺序和test.csv完全一致第三Survived只含0和1两种值第四列名严格命名为PassengerId和Survived。astype(int)把预测结果从浮点数或布尔值转成Kaggle要求的整型同时避免文件里出现小数。如果你用predict_proba取概率再设阈值记得阈值默认0.5用(proba[:, 1] 0.5).astype(int)可以更灵活地调正负例平衡点。6.3 提交后反馈循环公共榜分数接近于交叉验证时说明一切正常Kaggle每天限制提交次数更合理的做法不是反复试而是用交叉验证分数预估自己的区间。拿到提交后的公共分数如果比交叉验证低0.03以内说明模型泛化正常可以在这里停下并保存当前模型和参数。如果低了0.05以上优先检查是否数据泄漏或测试集与训练集分布差异大这时去调整特征比盲调参更有意义。如果公共分数反而高于交叉验证别高兴太早——很可能是在小测试集上的随机波动后续几天的私有榜才会见真章。从那以后我每次跑泰坦尼克号这个项目都会逼自己走一遍“加载→清洗→特征→训练→提交文件自检→交叉验证对比公共分”的完整闭环一步都不省。这个流程看起来繁琐但它能帮你过滤掉绝大多数低级错误把精力真正留给特征和模型。希望你在这个项目上少走我走过的弯路一次跑通全链路。本文还有配套的精品资源点击获取