ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

泰坦尼克号Kaggle实战:数据清洗、特征工程到模型调参全流程

泰坦尼克号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], axis=0, sort=False).reset_index(drop=True) print(full.shape) # (1309, 12) print(full.isnull().sum())

这段代码先分别读入两个csv,再用pd.concat沿行方向合并,得到一个1309行的完整表。合并的目的是让后续所有填充和编码操作对训练、测试数据一视同仁,避免出现“训练集填了均值、测试集忘记填”的尴尬。sort=False参数在Pandas新版本里是官方推荐设置,防止列顺序被打乱。reset_index(drop=True)则保证合并后索引连续,后面按位置切片时不会错位。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提取舱室区域字母,没有的填'U'(Unknown) 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, columns=features, drop_first=False) # 数值特征做标准化 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_first=False保留全部列而不是去掉一列,牺牲一点特征维度换回可解释性。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'], axis=1) y = train_processed['Survived'].astype(int) X_test = test_processed.drop(['Survived', 'PassengerId', 'Name', 'Ticket'], axis=1) 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_iter=1000, C=0.1, random_state=42) scores = cross_val_score(lr, X, y, cv=5, scoring='accuracy') print(f"LR CV Accuracy: {scores.mean():.4f} (+/- {scores.std():.4f})")

先说一下为什么第一站选逻辑回归:它不是效果最好的模型,但它是最容易理解“特征如何影响预测”的模型,系数直接对应每个特征的权重方向。max_iter=1000是防止默认值(100)在特征维度较高时迭代不收敛;C=0.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_estimators=200, max_depth=5, random_state=42), 'SVM': SVC(kernel='rbf', C=1.0, gamma='scale', random_state=42), 'KNN': KNeighborsClassifier(n_neighbors=5) } for name, model in models.items(): cv_scores = cross_val_score(model, X, y, cv=5, scoring='accuracy') print(f"{name}: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})")

三个模型的核心参数意图:随机森林n_estimators=200保证树的数量足够,max_depth=5限制单棵树深度防止过拟合——泰坦尼克号特征维度小,深度大了纯属浪费还容易记住噪声;SVM用rbf核函数处理非线性边界,gamma='scale'让Sklearn根据特征维度自动推算gamma值,这是新版Sklearn的推荐写法,手动指定一个数字反而容易翻车;KNN的n_neighbors=5是经验值,取太大(比如20)会把不同类别的样本混为一谈,取太小(比如1)则完全没有泛化能力。

跑完这组对比,通常会出现一个很有意思的结果:随机森林和SVM分数接近(约0.81-0.82),KNN明显偏低(约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_state=42) grid = GridSearchCV(rf, param_grid, cv=5, scoring='accuracy', n_jobs=-1) grid.fit(X, y) print(f"Best params: {grid.best_params_}") print(f"Best CV score: {grid.best_score_:.4f}")

GridSearchCV会遍历3×3×3=27组参数组合,每组做5折交叉验证,共135次训练。n_jobs=-1让所有CPU核心并行跑,27组大概耗时几十秒。min_samples_split是新增的考察维度——它控制一个节点要继续分裂需要的最少样本数,数值越大模型越保守、越不容易过拟合。跑完看best_params_,如果最优的max_depth是7或更大,说明当前特征量下模型还没到过拟合边界;如果最优是3,说明特征里的噪声已经不少了。泰坦尼克号最优参数通常集中在max_depth=5、min_samples_split=5附近,比默认参数能提升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', axis=1),然后发现分数从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, cv=5, train_sizes=[0.3, 0.5, 0.7, 0.9, 1.0] ) train_mean = train_scores.mean(axis=1) test_mean = test_scores.mean(axis=1) for size, tr, te in zip(train_sizes, train_mean, test_mean): print(f"Train: {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', index=False)

写入文件前做四件事:第一,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以上,优先检查是否数据泄漏或测试集与训练集分布差异大,这时去调整特征比盲调参更有意义。如果公共分数反而高于交叉验证,别高兴太早——很可能是在小测试集上的随机波动,后续几天的私有榜才会见真章。

从那以后我每次跑泰坦尼克号这个项目,都会逼自己走一遍“加载→清洗→特征→训练→提交文件自检→交叉验证对比公共分”的完整闭环,一步都不省。这个流程看起来繁琐,但它能帮你过滤掉绝大多数低级错误,把精力真正留给特征和模型。希望你在这个项目上少走我走过的弯路,一次跑通全链路。

本文还有配套的精品资源,点击获取

返回列表