泰坦尼克号生存预测实战:从数据探索到模型优化的完整Python指南

当历史数据遇上现代机器学习技术,泰坦尼克号乘客生存预测项目成为了无数数据科学入门者的第一个实战演练场。这个经典案例不仅包含了丰富的数据类型和现实场景中的典型问题,更是一个绝佳的机会,让我们能够完整地体验从原始数据到预测模型的全流程。本文将带你深入这个项目的每个关键环节,从数据清洗的实用技巧到特征工程的创造性思维,再到模型调优的系统方法,最终构建出一个高精度的生存预测系统。

1. 数据探索:发现隐藏在数字背后的故事

数据探索是任何机器学习项目的起点,它决定了我们对问题的理解深度和后续处理的方向。让我们首先加载并观察这份跨越百年的乘客数据:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 加载数据
train_df = pd.read_csv('train.csv')

# 初步查看数据
print(f"数据集形状: {train_df.shape}")
print(train_df.info())

关键发现

  • 数据集包含891名乘客的12个特征
  • Age(年龄)、Cabin(舱位)和Embarked(登船港口)存在缺失值
  • 特征类型多样,包括数值型、类别型和文本型

1.1 生存率的基础分析

通过简单的分组统计,我们可以发现一些明显的生存模式:

# 按性别分析生存率
gender_survival = train_df.groupby('Sex')['Survived'].mean()
print(f"女性生存率: {gender_survival['female']:.2%}")
print(f"男性生存率: {gender_survival['male']:.2%}")

# 可视化展示
plt.figure(figsize=(10,6))
sns.barplot(x='Sex', y='Survived', data=train_df, errorbar=None)
plt.title('不同性别的生存率对比')
plt.show()

分析结果

  • 女性生存率高达74.2%,远高于男性的18.9%
  • 这一发现与历史记载的"妇女儿童优先"救援原则一致

1.2 多维度交叉分析

单一维度的分析往往不够全面,我们需要考察多个特征的组合影响:

特征组合高生存率群体低生存率群体
性别+舱位头等舱女性(97%)三等舱男性(13%)
年龄+舱位头等舱儿童(75%)三等舱中年男性(9%)
家庭规模独自旅行者(30%)大家庭成员(15%)
# 创建交互分析函数
def analyze_interaction(feature1, feature2):
    return train_df.groupby([feature1, feature2])['Survived'].mean().unstack()

# 分析舱位与性别的交互影响
print(analyze_interaction('Pclass', 'Sex'))

2. 数据清洗与预处理:打造高质量数据集

原始数据往往存在各种问题,高质量的数据清洗能显著提升模型性能。以下是关键处理步骤:

2.1 缺失值处理策略

不同特征的缺失值需要采用不同的处理方式:

  1. 年龄缺失处理
from sklearn.impute import KNNImputer

# 使用KNN算法填充年龄
imputer = KNNImputer(n_neighbors=5)
train_df['Age'] = imputer.fit_transform(train_df[['Age']])
  1. 舱位缺失处理
# 用'N'标记缺失的舱位,并提取首字母作为新特征
train_df['Cabin'] = train_df['Cabin'].fillna('N').str[0]
  1. 登船港口缺失处理
# 使用众数填充
train_df['Embarked'] = train_df['Embarked'].fillna(train_df['Embarked'].mode()[0])

2.2 异常值检测与处理

票价特征中的极端值可能影响模型表现:

# 检测票价异常值
plt.figure(figsize=(10,4))
sns.boxplot(x=train_df['Fare'])
plt.title('票价分布箱线图')
plt.show()

# 使用对数变换处理右偏分布
train_df['Fare'] = np.log1p(train_df['Fare'])

3. 特征工程:从原始数据中挖掘黄金

优秀的特征工程能让模型性能产生质的飞跃。以下是几个创造性的特征构建方法:

3.1 从姓名中提取社会地位

乘客姓名中包含的头衔往往反映了其社会地位:

# 提取姓名中的头衔
train_df['Title'] = train_df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)

# 头衔映射与归并
title_mapping = {
    'Mr': 'Mr', 'Miss': 'Miss', 'Mrs': 'Mrs', 
    'Master': 'Master', 'Dr': 'Rare', 'Rev': 'Rare',
    'Col': 'Rare', 'Major': 'Rare', 'Mlle': 'Miss',
    'Countess': 'Rare', 'Ms': 'Miss', 'Lady': 'Rare',
    'Jonkheer': 'Rare', 'Don': 'Rare', 'Dona': 'Rare',
    'Mme': 'Mrs', 'Capt': 'Rare', 'Sir': 'Rare'
}
train_df['Title'] = train_df['Title'].map(title_mapping)

3.2 构建家庭特征

家庭关系对生存可能产生复杂影响:

# 家庭总人数
train_df['FamilySize'] = train_df['SibSp'] + train_df['Parch'] + 1

# 家庭规模分类
train_df['FamilyType'] = pd.cut(train_df['FamilySize'], 
                               bins=[0,1,4,20],
                               labels=['Alone','Small','Large'])

3.3 舱位与票价分段

将连续变量转换为分类变量有时能捕捉非线性关系:

# 舱位分段
train_df['Deck'] = train_df['Cabin'].map(lambda x: x[0] if pd.notnull(x) else 'Unknown')

# 票价分段
train_df['FareGroup'] = pd.qcut(train_df['Fare'], 5, labels=False)

4. 模型构建与优化:从基础到卓越

有了高质量的特征,我们可以开始构建和优化预测模型了。

4.1 基础模型对比

首先评估几种常见分类器的基线表现:

from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score

# 准备特征和目标变量
X = pd.get_dummies(train_df.drop(['PassengerId','Survived','Name','Ticket'], axis=1))
y = train_df['Survived']

# 模型列表
models = {
    'Logistic Regression': LogisticRegression(max_iter=1000),
    'Random Forest': RandomForestClassifier(),
    'Gradient Boosting': GradientBoostingClassifier(),
    'SVM': SVC(probability=True)
}

# 交叉验证评估
results = {}
for name, model in models.items():
    scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
    results[name] = scores.mean()

print(pd.DataFrame.from_dict(results, orient='index', columns=['Accuracy']))

4.2 随机森林深度优化

随机森林通常在这个问题上表现良好,让我们深入优化它:

from sklearn.model_selection import GridSearchCV

# 参数网格
param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [None, 5, 10],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

# 网格搜索
rf = RandomForestClassifier(random_state=42)
grid_search = GridSearchCV(rf, param_grid, cv=5, scoring='accuracy', n_jobs=-1)
grid_search.fit(X, y)

# 最佳参数
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳分数: {grid_search.best_score_:.4f}")

4.3 特征重要性分析

了解哪些特征对预测最有帮助:

# 训练最佳模型
best_rf = grid_search.best_estimator_
best_rf.fit(X, y)

# 获取特征重要性
feature_importance = pd.DataFrame({
    'Feature': X.columns,
    'Importance': best_rf.feature_importances_
}).sort_values('Importance', ascending=False)

# 可视化
plt.figure(figsize=(12,8))
sns.barplot(x='Importance', y='Feature', data=feature_importance.head(15))
plt.title('Top 15 Important Features')
plt.show()

5. 模型集成与性能提升

单一模型可能达到性能瓶颈,集成方法可以进一步提升预测准确率。

5.1 投票分类器

结合多个模型的优势:

from sklearn.ensemble import VotingClassifier

# 定义基分类器
estimators = [
    ('rf', RandomForestClassifier(n_estimators=200, max_depth=10, random_state=42)),
    ('gb', GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, random_state=42)),
    ('svm', SVC(probability=True, C=0.1, gamma='scale', random_state=42))
]

# 创建投票分类器
voting = VotingClassifier(estimators=estimators, voting='soft')
voting_score = cross_val_score(voting, X, y, cv=5, scoring='accuracy').mean()
print(f"投票分类器准确率: {voting_score:.4f}")

5.2 堆叠集成

更高级的集成方法可以挖掘模型间的互补性:

from sklearn.ensemble import StackingClassifier
from sklearn.model_selection import RepeatedStratifiedKFold

# 定义基模型和元模型
base_models = [
    ('rf', RandomForestClassifier(n_estimators=200, max_depth=10, random_state=42)),
    ('gb', GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, random_state=42))
]
meta_model = LogisticRegression(max_iter=1000)

# 创建堆叠分类器
stacking = StackingClassifier(estimators=base_models, final_estimator=meta_model, cv=5)
stacking_score = cross_val_score(stacking, X, y, cv=5, scoring='accuracy').mean()
print(f"堆叠分类器准确率: {stacking_score:.4f}")

5.3 模型解释性

对于如此重要的预测,理解模型决策过程同样关键:

import shap

# 创建SHAP解释器
explainer = shap.TreeExplainer(best_rf)
shap_values = explainer.shap_values(X)

# 可视化单个预测解释
shap.initjs()
shap.force_plot(explainer.expected_value[1], shap_values[1][0,:], X.iloc[0,:])

6. 部署准备与持续改进

完成模型开发后,我们需要为实际部署做好准备。

6.1 构建预测流水线

将整个处理流程封装为可复用的管道:

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler

# 定义预处理步骤
numeric_features = ['Age', 'Fare', 'SibSp', 'Parch']
numeric_transformer = Pipeline(steps=[
    ('imputer', KNNImputer(n_neighbors=5)),
    ('scaler', StandardScaler())])

categorical_features = ['Pclass', 'Sex', 'Embarked', 'Title', 'Deck', 'FamilyType']
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))])

preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)])

# 完整管道
pipeline = Pipeline(steps=[('preprocessor', preprocessor),
                          ('classifier', best_rf)])

# 保存管道
import joblib
joblib.dump(pipeline, 'titanic_pipeline.pkl')

6.2 监控与迭代

模型部署后需要持续监控其表现:

  1. 数据漂移检测:定期检查输入数据的统计特性变化
  2. 概念漂移检测:监控模型预测分布的变化
  3. 定期重新训练:使用新数据更新模型参数
  4. A/B测试:对比新旧模型的实际表现
# 示例监控代码
def monitor_drift(reference_data, current_data, threshold=0.05):
    drift_report = {}
    for col in reference_data.columns:
        ks_stat, p_value = ks_2samp(reference_data[col], current_data[col])
        drift_report[col] = {'KS Statistic': ks_stat, 'p-value': p_value}
    
    drift_detected = any([v['p-value'] < threshold for v in drift_report.values()])
    return drift_detected, drift_report

在实际项目中,我发现特征工程的质量往往比模型选择对最终结果的影响更大。特别是在处理像泰坦尼克号这样的小型数据集时,精心设计的特征能够显著提升模型捕捉复杂模式的能力。例如,从姓名中提取的社会地位特征和构建的家庭关系特征,在我的多次实验中都被证明是提升预测准确率的关键因素。

更多推荐