1. 项目背景与核心目标
1912年泰坦尼克号沉船事件是人类历史上最著名的海难之一,这个数据集记录了船上乘客的个人信息和生存状态。作为机器学习领域的经典入门项目,它完美涵盖了数据清洗、特征工程、模型训练等完整流程。我选择这个案例进行讲解,是因为它具备三个独特优势:
首先,数据集规模适中(约1300条记录),包含数值型、类别型、文本型等多种特征类型,非常适合练习不同类型的数据预处理技巧。其次,生存预测本身是一个二分类问题(生还/遇难),但特征间的复杂关系又能体现真实业务场景的挑战性。最后,Kaggle平台上超过5万份公开提交为我们的方法优化提供了丰富参考。
提示:虽然数据集已存在百余年,但直到今天它仍在机器学习教学中占据重要地位。2023年更新的Titanic Extended数据集甚至加入了乘客亲属关系网络等新特征,展现出这个项目的持久生命力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据探索与清洗实战
2.1 原始数据初探
从Kaggle获取的原始数据包含以下关键字段:
- 乘客ID(PassengerId)
- 生存状态(Survived:0=遇难,1=生还)
- 舱位等级(Pclass:1/2/3等舱)
- 姓名(Name)
- 性别(Sex)
- 年龄(Age)
- 同船兄弟姐妹/配偶数量(SibSp)
- 同船父母/子女数量(Parch)
- 船票编号(Ticket)
- 票价(Fare)
- 船舱号(Cabin)
- 登船港口(Embarked)
使用Python的Pandas库加载数据后,我习惯先用df.info()快速查看数据概况。实际分析时会发现几个典型问题:
- Age字段约20%缺失
- Cabin字段缺失率高达77%
- Embarked有2条记录缺失
- 票价分布极不均匀(最低0英镑,最高512英镑)
2.2 缺失值处理技巧
对于年龄缺失,常规均值填充并不理想。我采用了一种基于称呼(Mr/Mrs/Miss等)的分组填充法:
python复制# 从姓名中提取称呼
df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
# 按称呼分组计算年龄中位数
title_median = df.groupby('Title')['Age'].median()
# 用对应称呼的中位数填充
def fill_age(row):
if pd.isnull(row['Age']):
return title_median[row['Title']]
return row['Age']
df['Age'] = df.apply(fill_age, axis=1)
对于Cabin字段,直接删除会损失过多信息。我的处理方案是提取船舱首字母(代表甲板区域)作为新特征:
python复制df['Deck'] = df['Cabin'].str[0]
df['Deck'] = df['Deck'].fillna('Unknown')
2.3 异常值与数据分布修正
票价字段存在明显右偏分布,我进行了对数变换:
python复制df['Fare'] = np.log1p(df['Fare'])
年龄字段则采用分箱处理,将连续值转换为年龄段:
python复制df['AgeGroup'] = pd.cut(df['Age'],
bins=[0,12,18,30,50,100],
labels=['Child','Teen','Young','Middle','Senior'])
3. 特征工程深度解析
3.1 姓名字段的隐藏价值
原始姓名看起来像无用信息,但通过正则表达式可以提取出:
- 称呼(反映社会地位)
- 姓氏(用于家族关联分析)
- 是否有括号内的别名(可能暗示身份伪装)
我创建了以下衍生特征:
python复制# 是否贵族
df['IsNoble'] = df['Name'].str.contains('Countess|Jonkheer|Sir|Lady|Don', regex=True).astype(int)
# 姓氏频率
df['Surname'] = df['Name'].str.split(',').str[0]
surname_count = df['Surname'].value_counts()
df['FamilySize'] = df['Surname'].map(surname_count)
3.2 家庭关系网络构建
通过组合SibSp和Parch字段,可以重建乘客的家庭关系:
python复制df['FamilySize'] = df['SibSp'] + df['Parch'] + 1
df['IsAlone'] = (df['FamilySize'] == 1).astype(int)
# 家庭生存率特征
family_survival = df.groupby('Surname')['Survived'].mean()
df['FamilySurvivalRate'] = df['Surname'].map(family_survival).fillna(0)
3.3 舱位与票价的关系挖掘
头等舱票价差异极大,可能反映舱内位置优劣。我创建了"票价与舱位均值比"特征:
python复制class_mean_fare = df.groupby('Pclass')['Fare'].mean()
df['FareRatio'] = df['Fare'] / df['Pclass'].map(class_mean_fare)
4. 模型构建与优化
4.1 基线模型建立
先构建一个包含基础特征的逻辑回归模型作为基准:
python复制base_features = ['Pclass','Sex','Age','SibSp','Parch','Fare','Embarked']
X = pd.get_dummies(df[base_features])
y = df['Survived']
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
lr = LogisticRegression(max_iter=1000)
scores = cross_val_score(lr, X, y, cv=5)
print(f"Baseline Accuracy: {scores.mean():.3f}") # 约0.78
4.2 特征重要性分析
使用随机森林评估特征重要性:
python复制from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100)
rf.fit(X, y)
plt.figure(figsize=(10,6))
pd.Series(rf.feature_importances_, index=X.columns).sort_values().plot.barh()
结果显示性别(Sex_female)是最强预测因子,其次是票价和舱位等级,这与历史记载"妇女儿童优先"的救援原则一致。
4.3 模型集成策略
我测试了多种模型组合,最终Stacking方法表现最佳:
python复制from sklearn.ensemble import StackingClassifier
from xgboost import XGBClassifier
from sklearn.svm import SVC
estimators = [
('rf', RandomForestClassifier(n_estimators=100)),
('xgb', XGBClassifier()),
('svc', SVC(probability=True))
]
stack = StackingClassifier(estimators=estimators,
final_estimator=LogisticRegression())
stack.fit(X_train, y_train)
通过网格搜索优化超参数后,模型在测试集上的准确率提升到0.83。
5. 业务解读与模型可解释性
5.1 SHAP值分析
使用SHAP库解释模型决策逻辑:
python复制import shap
explainer = shap.TreeExplainer(rf)
shap_values = explainer.shap_values(X)
shap.summary_plot(shap_values, X)
分析显示:
- 女性身份对生存预测有最大正向影响
- 三等舱乘客的预测生存率显著降低
- 高票价乘客(可能住在更靠近救生艇的上层甲板)生存优势明显
5.2 典型个案分析
查看预测概率最高的生还者和最可能遇难者:
python复制df['PredProba'] = stack.predict_proba(X)[:,1]
print(df.sort_values('PredProba', ascending=False)[['Name','Sex','Pclass','PredProba']].head(3))
print(df.sort_values('PredProba')[['Name','Sex','Pclass','PredProba']].head(3))
结果与历史记录高度吻合:生还概率最高的是头等舱年轻女性,而预测遇难的是三等舱中年男性。
6. 项目进阶方向
6.1 社交网络分析
利用姓氏和家庭关系构建乘客社交网络:
python复制import networkx as nx
G = nx.Graph()
families = df.groupby('Surname')
for name, group in families:
if len(group) > 1:
G.add_edges_from(itertools.combinations(group['PassengerId'], 2))
网络中心性指标(如度中心性)可作为新特征加入模型。
6.2 文本特征挖掘
从姓名中提取更多语义信息:
python复制from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(analyzer='char', ngram_range=(2,3))
name_features = tfidf.fit_transform(df['Name'])
6.3 迁移学习应用
将预训练的语言模型(如BERT)用于姓名文本编码:
python复制from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
inputs = tokenizer(df['Name'].tolist(), return_tensors='pt', padding=True, truncation=True)
outputs = model(**inputs)
name_embeddings = outputs.last_hidden_state.mean(dim=1)
7. 工程化部署考量
7.1 特征处理管道封装
将预处理流程封装为可复用的Pipeline:
python复制from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())])
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))])
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)])
7.2 模型监控指标设计
除了准确率外,还需监控:
- 性别预测公平性(避免对男性乘客的预测偏差)
- 舱位间的预测差异
- 特征稳定性指数(PSI)
7.3 在线预测API示例
使用FastAPI构建预测服务:
python复制from fastapi import FastAPI
import joblib
app = FastAPI()
model = joblib.load('titanic_model.pkl')
@app.post("/predict")
async def predict(passenger: dict):
df = pd.DataFrame([passenger])
# 执行相同的特征工程流程
processed = preprocessor.transform(df)
proba = model.predict_proba(processed)[0][1]
return {"survival_probability": float(proba)}
这个项目最让我惊讶的是,即使用最现代的机器学习方法,模型准确率也很难突破85%——这说明数据中存在着无法通过现有特征捕捉的随机因素。在实际业务中,我们需要学会区分模型可解释的规律和真正的随机性,避免过度拟合数据噪声。
