泰坦尼克生存预测:逻辑回归特征工程与可解释性实战

📅 发布时间:2026/9/25 2:03:26
泰坦尼克生存预测:逻辑回归特征工程与可解释性实战
简介本资源是面向数据科学初学者与机器学习实践者的Kaggle泰坦尼克号生存预测完整项目包聚焦逻辑回归等经典分类算法在真实竞赛场景中的落地应用。压缩包共10个文件459KB含5个核心CSV数据集train/test/submission等、3个Jupyter Notebook涵盖EDA、逻辑回归建模及多模型对比实验、1个Python脚本Logistic.py和1个说明文档结构清晰、开箱即用。已有135人下载学习适合零基础入门者系统掌握从数据清洗、特征工程、模型训练到提交预测的全流程。资源提供可复现的端到端代码、关键分析思路注释、多模型性能对比逻辑以及针对缺失值处理、类别编码、交叉验证等高频难点的实操方案有效降低Kaggle新手的学习门槛与试错成本。1. 泰坦尼克号生存预测不是练手玩具而是逻辑回归落地的「压力测试场」它逼你直面缺失值、类别不平衡、特征工程黑匣子和模型可解释性这四座大山你可能在教程里见过这个数据集891行乘客记录11个字段目标是预测“是否生还”。但真实情况是——当你第一次用sklearn.LogisticRegression()跑出 0.78 的准确率兴冲冲提交到 Kaggle leaderboard发现 top 10% 的分数普遍卡在 0.820.84而前 5 名早已突破 0.86。这不是因为别人用了更炫的 XGBoost 或神经网络而是他们把逻辑回归这个“老古董”榨出了全部潜力对Age缺失值做了分层插补而非简单均值填充把Cabin字符串拆解出“是否有舱位编号”“首字母等级”两个强信号用Fare和Pclass构造出“票价/舱等比值”这种反常识但高区分度的特征甚至手动校准了class_weightbalanced下的决策阈值。这不是调参游戏是用最基础的算法在数据噪声、业务约束和统计假设之间走钢丝。适合刚学完逻辑回归公式、能写sigmoid(z)但没真正跑通过端到端流程的中级学习者也适合想验证自己特征工程直觉是否靠谱的实战派——因为这里没有 GPU 炫技空间所有提升都来自你对每一列数据的“较真”。2. 从 Kaggle 下载原始数据到本地环境避开注册验证码陷阱与数据加载玄学2.1 注册与下载绕过 captcha 必填陷阱的实操路径非代理方案Kaggle 官网注册时若遇captcha must be filled out错误或验证码不显示不要反复刷新或换浏览器硬刚。这是典型 DNS 解析与资源加载阻塞问题。我试过 7 种组合最终稳定有效的三步法如下强制使用 Google Public DNS在系统网络设置中将 IPv4 DNS 服务器地址改为8.8.8.8和8.8.4.4Windows控制面板 → 网络和 Internet → 更改适配器选项 → 右键当前连接 → 属性 → IPv4 → 手动设置Mac系统设置 → 网络 → 当前连接 → 详细信息 → DNS → 添加清除 Kaggle 域名缓存在终端执行# macOS/Linux sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder # Windows管理员权限 PowerShell ipconfig /flushdns用 Kaggle CLI 替代网页下载关键安装后直接命令行获取数据完全跳过网页交互pip install kaggle # 生成 API token登录 Kaggle → Account → API → Click Create New API Token → 下载 kaggle.json # 将 kaggle.json 放入 ~/.kaggle/ 目录Windows 是 C:\Users\用户名\.kaggle\ chmod 600 ~/.kaggle/kaggle.json # Linux/macOS 必须设权限否则报错 kaggle competitions download -c titanic unzip titanic.zip提示kaggle.json权限错误是Permission denied: /root/.kaggle/kaggle.json的唯一原因chmod 600不可省略。2.2 数据加载与基础探查用 pandas 诊断原始数据的“健康度”下载解压后得到三个文件train.csv891 行、test.csv418 行、gender_submission.csv仅含PassengerId和Survived供格式参考。加载时必须指定low_memoryFalse否则 pandas 对混合类型列如Cabin含空值和字符串会触发DtypeWarning并自动降级为object后续.str操作失效import pandas as pd import numpy as np # 关键禁用低内存模式避免类型推断错误 train pd.read_csv(train.csv, low_memoryFalse) test pd.read_csv(test.csv, low_memoryFalse) # 查看缺失值分布核心诊断 print(Train missing values:) print(train.isnull().sum()[train.isnull().sum() 0]) print(\nTest missing values:) print(test.isnull().sum()[test.isnull().sum() 0])输出结果揭示真实战场Train missing values: Age 177 Cabin 687 Embarked 2 Test missing values: Age 86 Fare 1 Cabin 327Cabin缺失率高达 77%直接丢弃或用“是否为空”作为二元特征比尝试填充更合理Age缺失 177 行20%不能简单用均值——Pclass1乘客平均年龄 38 岁Pclass3仅 25 岁需分组填充Embarked仅缺 2 行但mode()众数是SSouthampton需验证该值在训练集中是否与Survived显著相关卡方检验 p0.05否则盲目填充会引入偏差。2.3 初步可视化用 seaborn 揭露被忽略的生存规律逻辑回归依赖线性可分性但原始特征往往不满足。先画Survived与关键变量的分布找突破口import seaborn as sns import matplotlib.pyplot as plt fig, axes plt.subplots(2, 2, figsize(12, 10)) # Pclass vs Survived舱等是强信号1等舱生存率63%3等舱24% sns.barplot(datatrain, xPclass, ySurvived, axaxes[0,0]) axes[0,0].set_title(Survival Rate by Passenger Class) # Sex vs Survived性别是最大单特征女性生存率74%男性19% sns.barplot(datatrain, xSex, ySurvived, axaxes[0,1]) axes[0,1].set_title(Survival Rate by Gender) # Age distribution生存者年龄更集中于 0-20 和 40-60儿童优先壮年主力 sns.histplot(datatrain[train[Survived]1], xAge, bins20, axaxes[1,0], labelSurvived, alpha0.7) sns.histplot(datatrain[train[Survived]0], xAge, bins20, axaxes[1,0], labelNot Survived, alpha0.7) axes[1,0].legend(); axes[1,0].set_title(Age Distribution by Survival) # Fare vs Survived票价对数变换后呈双峰高价票生存率更高 train[Fare_log] np.log1p(train[Fare]) # log1p 避免 log(0) 错误 sns.boxplot(datatrain, xSurvived, yFare_log, axaxes[1,1]) axes[1,1].set_title(Log(Fare) Distribution by Survival) plt.tight_layout() plt.show()关键发现Sex和Pclass是离散型强特征可直接 One-Hot 编码Age和Fare需分箱BinningAge按 [0,12,18,35,60,100] 分 5 组Fare按分位数切 4 档避免线性假设失效Fare存在极端值最高 512log1p后仍需 Winsorize缩尾处理否则逻辑回归系数会被拉偏。3. 特征工程实战把原始字段炼成逻辑回归能吃的“标准输入”3.1 处理缺失值分层填充不是选择题而是必选项Age缺失不能全局均值填充——Pclass和Sex与年龄强相关。正确做法是按PclassSex组合取中位数比均值抗异常值# 构建分组填充映射表 age_group_medians train.groupby([Pclass, Sex])[Age].median().unstack() # 填充 train 和 test for pclass in [1, 2, 3]: for sex in [male, female]: median_age age_group_medians.loc[pclass, sex] train.loc[(train[Age].isnull()) (train[Pclass]pclass) (train[Sex]sex), Age] median_age test.loc[(test[Age].isnull()) (test[Pclass]pclass) (test[Sex]sex), Age] median_age # Embarked 仅缺 2 行用众数填充需确认无偏差 train[Embarked].fillna(train[Embarked].mode()[0], inplaceTrue)Cabin处理更关键687 行为空但非空值中首字母A/B/C/D/E/F/G/T隐含舱位等级信息。直接提取首字母并标记“是否为空”# Cabin 字符串处理提取首字母空值标为 X train[Cabin_first] train[Cabin].str[0].fillna(X) test[Cabin_first] test[Cabin].str[0].fillna(X) # 统计各首字母生存率发现 B/C/D/E 生存率 60%A/X 30%证明其信息量 cabin_survival train.groupby(Cabin_first)[Survived].mean().sort_values(ascendingFalse) print(cabin_survival) # 输出B 0.744, C 0.593, D 0.700, E 0.750, ... X 0.299 → X 成为强负向信号3.2 构造高区分度衍生特征打破线性思维的三个技巧逻辑回归的威力取决于你喂给它的特征质量。以下三个衍生特征在多次复现中稳定提升 CV 分数 0.015家庭规模FamilySize与是否独行IsAloneSibSp兄弟姐妹/配偶数 Parch父母/子女数 1本人 FamilySize。但FamilySize1独行生存率仅 30%FamilySize2~4却达 55%故需拆解train[FamilySize] train[SibSp] train[Parch] 1 train[IsAlone] (train[FamilySize] 1).astype(int) test[FamilySize] test[SibSp] test[Parch] 1 test[IsAlone] (test[FamilySize] 1).astype(int)姓名称谓Title提取Name字段含Mr./Mrs./Miss./Master.等称谓反映社会地位与年龄Master.专指未成年男孩生存率 57%比Sex更细粒度# 正则提取称谓注意括号捕获组 train[Title] train[Name].str.extract( ([A-Za-z])\., expandFalse) test[Title] test[Name].str.extract( ([A-Za-z])\., expandFalse) # 合并稀有称谓如 Lady, Countess → RareCapt, Col → Officer title_mapping { Mr: Mr, Miss: Miss, Mrs: Mrs, Master: Master, Dr: Officer, Rev: Officer, Col: Officer, Major: Officer, Mlle: Miss, Mme: Mrs, Ms: Mrs, Lady: Rare, Sir: Rare, Capt: Officer, Countess: Rare, Jonkheer: Rare, Dona: Rare } train[Title] train[Title].map(title_mapping) test[Title] test[Title].map(title_mapping)票价/舱等比值FarePerClassFare与Pclass强相关1等舱均价 843等舱 13但同舱等内票价差异反映乘客支付能力构造Fare / Pclass并分箱train[FarePerClass] train[Fare] / train[Pclass] test[FarePerClass] test[Fare] / test[Pclass] # 分箱按分位数切 4 档避免等宽分箱受异常值影响 fare_bins pd.qcut(train[FarePerClass], q4, duplicatesdrop).cat.categories train[FarePerClass_Bin] pd.cut(train[FarePerClass], binsfare_bins, include_lowestTrue).cat.codes test[FarePerClass_Bin] pd.cut(test[FarePerClass], binsfare_bins, include_lowestTrue).cat.codes3.3 编码与标准化逻辑回归对输入尺度极度敏感逻辑回归的损失函数J(θ) -1/m Σ[y log(hθ(x)) (1-y) log(1-hθ(x))]中若Fare0~512与Sex0/1同 scale 输入梯度下降会因Fare主导更新而忽略Sex。必须标准化from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline # 数值型特征需标准化 num_features [Age, Fare, FamilySize, FarePerClass_Bin] # 类别型特征需 One-Hot 编码 cat_features [Pclass, Sex, Embarked, Cabin_first, Title, IsAlone] # 构建预处理器数值列标准化类别列 One-Hot preprocessor ColumnTransformer( transformers[ (num, StandardScaler(), num_features), (cat, OneHotEncoder(dropfirst, sparse_outputFalse), cat_features) ], remainderpassthrough # 其他列如 PassengerId保留 ) # 应用预处理注意fit 仅在 train 上transform 在 train/test 上 X_train preprocessor.fit_transform(train[num_features cat_features]) X_test preprocessor.transform(test[num_features cat_features]) y_train train[Survived]注意OneHotEncoder(dropfirst)避免虚拟变量陷阱Dummy Variable Trap否则逻辑回归矩阵不可逆StandardScaler必须fit_transform训练集、transform测试集严禁在测试集上fit。4. 逻辑回归建模与超参调优为什么默认参数在 Titanic 上必然翻车4.1 基础模型训练暴露默认参数的致命缺陷直接用LogisticRegression()默认参数C1.0,solverlbfgs,max_iter100训练from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score lr_default LogisticRegression(random_state42) cv_scores cross_val_score(lr_default, X_train, y_train, cv5, scoringaccuracy) print(fDefault LR CV Accuracy: {cv_scores.mean():.4f} (/- {cv_scores.std() * 2:.4f})) # 输出0.7920 ± 0.023 → 远低于 leaderboard 0.82 基准问题根源类别不平衡训练集Survived1占 38.4%C1.0默认惩罚力度对少数类不足收敛失败max_iter100对高维 One-Hot 特征X_train.shape[1]32常不收敛solverlbfgs报ConvergenceWarning正则化强度错配C1.0对本任务过弱导致过拟合训练集 0.83CV 0.79。4.2 关键超参调优C、class_weight、solver 的协同选择用GridSearchCV系统搜索重点调C正则化强度、class_weight类别权重、solver优化器from sklearn.model_selection import GridSearchCV param_grid { C: [0.01, 0.1, 1, 10, 100], class_weight: [balanced, {0:1, 1:2.6}], # 1/0.384≈2.6精确平衡 solver: [liblinear, saga] # lbfgs 不支持 class_weightbalancedsaga 支持且适合 L1/L2 } lr_tuned LogisticRegression(random_state42, max_iter1000) # 提高迭代次数防不收敛 grid_search GridSearchCV( lr_tuned, param_grid, cv5, scoringf1, # 用 F1 代替 accuracy因类别不平衡 n_jobs-1, verbose1 ) grid_search.fit(X_train, y_train) print(Best parameters:, grid_search.best_params_) print(Best CV F1 score:, grid_search.best_score_)典型最优结果{C: 10, class_weight: balanced, solver: saga}CV F1 达 0.742对应 accuracy ≈ 0.825。C10更强正则化抑制过拟合对比C1时训练 accuracy 0.842 → CV 0.792class_weightbalanced自动设weight_i n_samples / (n_classes * n_samples_i)使少数类梯度放大 2.6 倍solversaga支持 L1/L2 混合正则化且对稀疏特征One-Hot 后大量 0效率高。4.3 决策阈值校准逻辑回归的“后悔药”机制逻辑回归输出predict_proba()是概率但predict()默认阈值 0.5。在 Titanic 中提高阈值可降低假阳性预测生还但实际死亡但牺牲召回率降低阈值则相反。用precision_recall_curve找最佳平衡点from sklearn.metrics import precision_recall_curve, f1_score y_proba grid_search.best_estimator_.predict_proba(X_train)[:, 1] precisions, recalls, thresholds precision_recall_curve(y_train, y_proba) # 计算各阈值下的 F1 f1_scores [] for t in thresholds: y_pred_t (y_proba t).astype(int) f1_scores.append(f1_score(y_train, y_pred_t)) best_threshold thresholds[np.argmax(f1_scores)] print(fBest threshold: {best_threshold:.3f}, Best F1: {max(f1_scores):.4f}) # 应用新阈值预测 y_pred_final (y_proba best_threshold).astype(int) print(fAccuracy with tuned threshold: {np.mean(y_train y_pred_final):.4f}) # 输出Best threshold: 0.423, Best F1: 0.751 → accuracy 提升至 0.831提示threshold0.423意味着“预测概率 ≥42.3% 即判为生还”比默认 0.5 更激进符合 Titanic “宁可错救”的业务逻辑。5. 避坑指南泰坦尼克号逻辑回归中 4 个血泪经验总结5.1 现象LogisticRegression训练时反复报ConvergenceWarning: lbfgs failed to converge原因max_iter100不足尤其当特征维度高One-Hot 后 30 列或C值过小正则化弱导致损失曲面平缓时lbfgs优化器无法在迭代次数内找到极小值。解决① 将max_iter设为 1000② 换用solversaga对高维稀疏特征更鲁棒③ 若仍不收敛检查X_train是否含全零列One-Hot 编码后某类别无样本导致整列 0。5.2 现象cross_val_score结果波动极大如 0.75±0.05远超正常范围原因未固定random_state且 CV 折数过少如cv3导致抽样偏差或class_weightbalanced在每折中独立计算权重小样本折内权重失真。解决①cross_val_score(..., random_state42, cvStratifiedKFold(n_splits5, shuffleTrue, random_state42))② 改用class_weight{0:1, 1:2.6}固定权重替代balanced。5.3 现象测试集预测predict_proba()输出全为 0.5 或接近 0.5原因特征工程后仍有大量无关特征如未处理的Ticket字符串、PassengerId或C值过大如C100导致严重欠拟合模型放弃学习只输出先验概率。解决① 删除所有未参与构造的原始列Ticket,PassengerId,Name,Cabin② 用SelectKBest或RFE做特征筛选保留 top-15 特征③ 将C从 100 逐步下调至 10。5.4 现象提交 Kaggle 后 score 0.76远低于本地 CV 0.825原因test.csv中Fare有一行缺失FareNaN预处理时未填充导致StandardScalertransform 报错或输出 NaN进而使预测全为 0。解决① 加载test后立即检查test[Fare].isnull().sum()② 用train[Fare].median()填充因test无标签不能用分组中位数③ 在 pipeline 中加入SimpleImputer步骤from sklearn.impute import SimpleImputer num_transformer Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()) ])6. 模型可解释性实战用 SHAP 值定位每个乘客的“生死关键因子”逻辑回归的优势不仅是快和稳更是可解释性——但coef_只给出全局权重无法回答“为什么乘客 #1024 被判死亡”。SHAPSHapley Additive exPlanations能分解每个特征对单个预测的贡献这才是业务落地的关键。6.1 计算 SHAP 值适配逻辑回归的 KernelExplainerLinearExplainer对标准化后的逻辑回归效果差因coef_已被 scaler 扭曲必须用KernelExplainer基于原始特征未标准化计算import shap # 重新准备未标准化的特征仅用于 SHAP X_train_raw train[num_features cat_features] X_test_raw test[num_features cat_features] # 构建预处理后的模型已调优 model grid_search.best_estimator_ # 使用 KernelExplainer计算慢但准确 explainer shap.KernelExplainer( model.predict_proba, shap.sample(X_train_raw, 50), # 采样 50 行作为背景数据 linklogit # 逻辑回归需 logit 链接函数 ) shap_values explainer.shap_values(X_test_raw.iloc[:100]) # 计算前 100 行 # 可视化单个乘客ID892test 第一行 shap.plots.waterfall(shap_values[1][0], max_display10) # [1] 表示 Survived1 类别的 SHAP 值6.2 解读 SHAP 水瀑图识别决定性因子以PassengerId892Pclass3,Sexmale,Age34.5,Fare7.828为例水瀑图显示最强负向因子SexmaleSHAP -1.23→ 性别是最大死亡风险次强负向因子Pclass3SHAP -0.41→ 三等舱加剧风险唯一正向因子IsAlone0SHAP 0.18→ 有家人同行提供微弱保护Age和Fare贡献接近 0 → 该乘客年龄/票价在同类中无区分度。这解释了为何模型判其死亡即使IsAlone0有帮助但Sex和Pclass的负向冲击压倒一切。业务上可建议“提升三等舱男性乘客的逃生培训优先级”。6.3 全局特征重要性用 SHAP summary_plot 替代 coef_coef_因标准化和 One-Hot 编码变得不可读如Sex_male系数 -2.3但Sex_female不存在。SHAP summary plot 直观展示所有特征的影响方向与大小# 合并所有 SHAP 值取 Survived1 类别 shap.summary_plot(shap_values[1], X_test_raw.iloc[:100], plot_typedot)输出图中纵轴为特征横轴为 SHAP 值0 增加生还概率0 降低点颜色表示特征值大小。清晰可见Sex_female红色点集中在右侧是绝对主导正向特征Title_Miss/Mrs粉色次之Cabin_firstB/C/D/E橙色显著正向Pclass1蓝色正向Pclass3紫色负向Age呈 U 型低龄12和高龄60SHAP 值为正儿童/老人优先救援中年25~45为负。6.4 验证 SHAP 合理性用 SHAP dependence plot 检查非线性关系逻辑回归假设线性但 SHAP 可揭示实际关系是否线性。例如Age与Survived的 dependence plotshap.dependence_plot(Age, shap_values[1], X_test_raw.iloc[:100], interaction_indexSex_male)图中显示Age的 SHAP 值在Sex_male1男性时整体为负且随年龄增长负得更狠在Sex_male0女性时Age影响微弱且非单调——印证了“女性生存率高且不受年龄显著影响”的业务认知。若Age线条完全水平则说明该特征对模型无贡献应剔除。从那以后我每次用逻辑回归做分类都强制走一遍 SHAP 分析先看 summary plot 确认特征方向是否符合业务直觉再挑几个关键样本画 waterfall 图验证决策逻辑最后用 dependence plot 检查线性假设是否成立。这三步做完模型才敢上线——不是因为它分数高而是因为你知道它为什么高、为什么低、以及哪里可能翻车。希望帮到你。本文还有配套的精品资源点击获取