【机器学习】保姆级教程:7步带你从0到1完成泰坦尼克号生还预测项目

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘

PyTorch系列文章目录

Python系列文章目录

机器学习系列文章目录

01-什么是机器学习?从零基础到自动驾驶案例全解析
02-从过拟合到强化学习:机器学习核心知识全解析
03-从零精通机器学习:线性回归入门
04-逻辑回归 vs. 线性回归:一文搞懂两者的区别与应用
05-决策树算法全解析:从零基础到Titanic实战,一文搞定机器学习经典模型
06-集成学习与随机森林:从理论到实践的全面解析
07-支持向量机(SVM):从入门到精通的机器学习利器
08-【机器学习】KNN算法入门:从零到电影推荐实战
09-【机器学习】朴素贝叶斯入门:从零到垃圾邮件过滤实战
10-【机器学习】聚类算法全解析:K-Means、层次聚类、DBSCAN在市场细分的应用
11-【机器学习】降维与特征选择全攻略:PCA、LDA与特征选择方法详解
12-【机器学习】手把手教你构建神经网络:从零到手写数字识别实战
13-【机器学习】从零开始学习卷积神经网络(CNN):原理、架构与应用
14-【机器学习】RNN与LSTM全攻略:解锁序列数据的秘密
15-【机器学习】GAN从入门到实战:手把手教你实现生成对抗网络
16-【机器学习】强化学习入门:从零掌握 Agent 到 DQN 核心概念与 Gym 实战
17-【机器学习】AUC、F1分数不再迷茫:图解Scikit-Learn模型评估与选择核心技巧
18-【机器学习】Day 18: 告别盲猜!网格/随机/贝叶斯搜索带你精通超参数调优
19-【机器学习】从零精通特征工程:Kaggle金牌选手都在用的核心技术
20-【机器学习】模型性能差?90%是因为数据没洗干净!(缺失值/异常值/不平衡处理)
21-【机器学习】保姆级教程:7步带你从0到1完成泰坦尼克号生还预测项目



前言

欢迎来到我们机器学习系列之旅的第 21 天!经过前面几天的学习,我们已经掌握了诸如模型评估(Day 17)、超参数调优(Day 18)、特征工程(Day 19)和数据预处理(Day 20)等关键技能。今天,我们将把这些知识串联起来,模拟一个真实世界中的机器学习项目,从最初的问题定义到最终的模型评估与结果解释,完整地走一遍端到端的机器学习项目实战流程

构建一个成功的机器学习项目,并不仅仅是调用几个库函数那么简单。它更像是一项系统工程,需要清晰的流程、严谨的态度和持续的迭代。本篇博客将以经典的“泰坦尼克号生还预测”数据集为例,手把手带你实践一个完整的机器学习项目。无论你是刚刚入门的新手,还是希望梳理和规范项目流程的进阶者,相信都能从中获益。

一、问题定义与目标设定

在启动任何机器学习项目之前,首要任务是清晰地理解并定义问题。没有明确的目标,后续的所有努力都可能偏离方向。

1.1 理解业务需求与背景

泰坦尼克号的沉没是历史上著名的海难事件。我们拥有部分乘客的信息(如年龄、性别、船舱等级等)以及他们是否生还的记录。
业务需求/研究目标:我们希望通过分析这些数据,找出哪些因素显著影响了乘客的生还概率,并构建一个模型来预测给定特征的乘客是否能够生还。这有助于我们理解灾难事件中生存模式的规律。

1.2 明确机器学习任务

根据目标(预测乘客是否生还),这是一个典型的二分类问题 (Binary Classification)。我们需要将乘客分为两类:生还 (Survived=1) 或未生还 (Survived=0)。

1.3 设定评估指标

选择合适的评估指标至关重要,它将指导我们的模型优化方向。对于分类问题,常用指标包括:

  • 准确率 (Accuracy): Accuracy = 预测正确的样本数 总样本数 \text{Accuracy} = \frac{\text{预测正确的样本数}}{\text{总样本数}} Accuracy=总样本数预测正确的样本数。虽然直观,但在数据不平衡时可能具有误导性。
  • 精确率 (Precision): Precision = 真正例 (TP) 真正例 (TP) + 假正例 (FP) \text{Precision} = \frac{\text{真正例 (TP)}}{\text{真正例 (TP) + 假正例 (FP)}} Precision=真正例 (TP) + 假正例 (FP)真正例 (TP)。预测为正例的样本中,实际为正例的比例。
  • 召回率 (Recall): Recall = 真正例 (TP) 真正例 (TP) + 假反例 (FN) \text{Recall} = \frac{\text{真正例 (TP)}}{\text{真正例 (TP) + 假反例 (FN)}} Recall=真正例 (TP) + 假反例 (FN)真正例 (TP)。实际为正例的样本中,被成功预测为正例的比例。
  • F1 分数 (F1-Score): F 1 = 2 × Precision × Recall Precision + Recall F1 = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}} F1=2×Precision+RecallPrecision×Recall。精确率和召回率的调和平均值。
  • AUC (Area Under the ROC Curve): ROC曲线下的面积,衡量模型整体区分正负样本的能力。

考虑到我们希望全面评估模型的表现,我们将综合考察 Accuracy, F1-Score 和 AUC。我们在 Day 17:模型评估与选择 中详细讨论过这些指标的选择与解读。

二、数据获取与探索性数据分析 (EDA)

明确目标后,下一步是获取数据并进行探索性数据分析(Exploratory Data Analysis, EDA)。EDA 的目的是深入理解数据,发现模式、异常值、变量间关系,为后续的数据准备和模型构建打下基础。

2.1 数据来源与加载

泰坦尼克号数据集是公开的,可以通过多种途径获取,例如 Kaggle 竞赛平台。我们也可以直接使用 seaborn 库内置的数据集。

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

# 设置显示风格
plt.style.use('ggplot')
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

# 加载数据 (假设已下载 train.csv 和 test.csv 到当前目录)
# 为了演示完整流程,我们主要使用 train.csv,并在最后划分训练/测试集
# 如果使用 seaborn 加载: titanic_df = sns.load_dataset('titanic')
try:
    train_df = pd.read_csv('train.csv')
    test_df = pd.read_csv('test.csv') # test_df 用于最终提交或模拟预测,没有 'Survived' 列
    print("数据加载成功!")
    # 合并训练集和测试集以便进行一致的预处理(可选,但常见)
    # 注意:合并前保留测试集的 PassengerId,并区分来源
    test_passenger_ids = test_df['PassengerId']
    combined_df = pd.concat([train_df.drop('Survived', axis=1), test_df], ignore_index=True)
    # 添加一个标签区分数据来源
    combined_df['Source'] = 'train'
    combined_df.loc[combined_df.index >= len(train_df), 'Source'] = 'test'

except FileNotFoundError:
    print("错误:请确保 train.csv 和 test.csv 文件在当前目录下!")
    # 如果文件不存在,可以尝试用 seaborn 加载示例数据
    print("尝试从 seaborn 加载泰坦尼克数据集...")
    titanic_df = sns.load_dataset('titanic')
    if titanic_df is not None:
        print("Seaborn 数据加载成功!将使用此数据进行演示。")
        # 为了与文件加载流程一致,模拟划分
        from sklearn.model_selection import train_test_split
        train_df, test_like_df = train_test_split(titanic_df, test_size=0.2, random_state=42)
        # 注意:seaborn 加载的数据可能与Kaggle版本略有差异
        # 这里仅为演示,实际项目中应确保数据源一致
    else:
        print("Seaborn 数据也加载失败,请检查网络或环境。")
        # 退出或采取其他措施
        exit()

print("\n训练集 (train.csv) 前 5 行:")
print(train_df.head())

2.2 初步了解数据

使用 Pandas 的基础函数快速概览数据。

print("\n训练集基本信息:")
train_df.info()

print("\n训练集描述性统计:")
print(train_df.describe())

print("\n训练集数值型特征的描述性统计:")
print(train_df.describe(include=[np.number]))

print("\n训练集类别型特征的描述性统计:")
print(train_df.describe(include=['object', 'category'])) # 'category' 如果有的话

# 检查缺失值比例
print("\n训练集各列缺失值比例:")
print((train_df.isnull().sum() / len(train_df)).sort_values(ascending=False))

初步发现:

  • Age, Cabin 列存在较多缺失值,Embarked 也有少量缺失。
  • 数据包含数值型(Age, Fare, SibSp, Parch)和类别型(Sex, Embarked, Pclass, Cabin, Name, Ticket)特征。
  • Survived 是我们的目标变量 (0 或 1)。
  • PassengerId 是唯一标识符,通常不用于建模。
  • TicketCabin 的值看起来比较杂乱,可能需要特殊处理或舍弃。

2.3 探索性数据分析 (EDA)

通过可视化手段深入探索数据。

2.3.1 单变量分析

分析单个特征的分布及其与目标变量的关系。

# 分析目标变量分布
plt.figure(figsize=(6, 4))
sns.countplot(x='Survived', data=train_df)
plt.title('生还情况分布 (0: 未生还, 1: 生还)')
plt.show()
print(f"生还比例: {train_df['Survived'].mean():.2%}")

# 分析性别与生还关系
plt.figure(figsize=(6, 4))
sns.countplot(x='Sex', hue='Survived', data=train_df)
plt.title('性别与生还关系')
plt.show()
print(train_df[['Sex', 'Survived']].groupby(['Sex'], as_index=False).mean().sort_values(by='Survived', ascending=False))

# 分析船舱等级与生还关系
plt.figure(figsize=(6, 4))
sns.countplot(x='Pclass', hue='Survived', data=train_df)
plt.title('船舱等级与生还关系')
plt.show()
print(train_df[['Pclass', 'Survived']].groupby(['Pclass'], as_index=False).mean().sort_values(by='Survived', ascending=False))

# 分析年龄分布
plt.figure(figsize=(10, 6))
sns.histplot(data=train_df, x='Age', kde=True, bins=30)
plt.title('乘客年龄分布')
plt.show()

# 分析年龄与生还关系 (考虑缺失值)
plt.figure(figsize=(10, 6))
sns.histplot(data=train_df, x='Age', hue='Survived', kde=True, bins=30)
plt.title('年龄与生还关系')
plt.show()

2.3.2 双变量/多变量分析

探索特征之间的相互关系以及它们共同对生还率的影响。

# 登船港口与生还关系
plt.figure(figsize=(6, 4))
sns.countplot(x='Embarked', hue='Survived', data=train_df)
plt.title('登船港口与生还关系')
plt.show()
print(train_df[['Embarked', 'Survived']].groupby(['Embarked'], as_index=False).mean().sort_values(by='Survived', ascending=False))


# 带家属数量 (SibSp + Parch) 与生还关系
train_df['FamilySize'] = train_df['SibSp'] + train_df['Parch'] + 1
plt.figure(figsize=(10, 6))
sns.countplot(x='FamilySize', hue='Survived', data=train_df)
plt.title('家庭规模与生还关系')
plt.show()
print(train_df[['FamilySize', 'Survived']].groupby(['FamilySize'], as_index=False).mean().sort_values(by='Survived', ascending=False))


# 不同等级船舱乘客的年龄分布与生还关系
plt.figure(figsize=(12, 7))
sns.violinplot(x='Pclass', y='Age', hue='Survived', data=train_df, split=True, palette='muted')
plt.title('不同船舱等级乘客的年龄分布与生还关系')
plt.show()

# 数值特征之间的相关性
plt.figure(figsize=(10, 8))
numerical_features = train_df.select_dtypes(include=np.number)
# 排除 PassengerId 和可能添加的 FamilySize (如果前面添加过)
if 'PassengerId' in numerical_features.columns:
    numerical_features = numerical_features.drop('PassengerId', axis=1)

sns.heatmap(numerical_features.corr(), annot=True, cmap='coolwarm', fmt=".2f")
plt.title('数值特征相关性热力图')
plt.show()

2.3.3 发现洞察与假设

基于 EDA,我们可以得出一些初步的结论和假设:

  • 性别: 女性的生还率远高于男性。这是一个非常强的预测因子。
  • 船舱等级 (Pclass): 船舱等级越高(数字越小,如 1 等舱),生还率越高。
  • 年龄 (Age): 年幼的乘客(儿童)生还率相对较高,老年乘客生还率较低。年龄数据有缺失,需要处理。
  • 家庭规模 (FamilySize): 独自一人或家庭规模过大(>4)的乘客生还率较低,适中规模(2-4人)的家庭生还率较高。
  • 票价 (Fare): 票价与生还率可能存在正相关,这与 Pclass 相关。
  • 登船港口 (Embarked): 来自 C 港(Cherbourg)的乘客生还率似乎更高。缺失值需要处理。
  • Cabin: 缺失严重,直接使用可能困难,但也许可以提取首字母作为特征。
  • Ticket, Name: 包含复杂信息,直接使用难度大,可能需要高级特征工程或舍弃。

三、数据准备

数据准备阶段的目标是将原始数据转换为适合机器学习模型输入的格式。这通常涉及数据清洗、特征工程、特征选择、数据集划分和特征缩放等步骤。这一步综合运用了我们在 Day 19 (特征工程)Day 20 (数据预处理) 中学到的知识。

3.1 数据清洗

处理 EDA 阶段发现的缺失值和潜在的异常值。

# 为了演示,我们重新加载并合并数据(如果在EDA阶段未合并)
try:
    train_df = pd.read_csv('train.csv')
    test_df = pd.read_csv('test.csv')
    test_passenger_ids = test_df['PassengerId']
    # 保存训练集的目标变量
    y_train = train_df['Survived']
    # 合并前删除训练集的目标变量和ID,测试集删除ID
    train_ids = train_df['PassengerId']
    train_df_processed = train_df.drop(['Survived', 'PassengerId'], axis=1)
    test_df_processed = test_df.drop('PassengerId', axis=1)
    # 合并用于统一处理
    combined_df = pd.concat([train_df_processed, test_df_processed], ignore_index=True)
    print("数据已重新加载并准备合并处理。")

except FileNotFoundError:
    print("错误:train.csv 或 test.csv 未找到。请确保文件存在。")
    exit()


# --- 处理缺失值 ---

# 1. Age: 缺失较多,用中位数填充可能是比较稳妥的选择,或者考虑基于 Pclass/Sex 的分组中位数
# 为了简化,我们先用全局中位数填充
combined_df['Age'] = combined_df['Age'].fillna(combined_df['Age'].median())
print(f"使用中位数填充 Age 后,缺失值数量: {combined_df['Age'].isnull().sum()}")

# 2. Embarked: 缺失很少,可以用众数填充
most_frequent_embarked = combined_df['Embarked'].mode()[0]
combined_df['Embarked'] = combined_df['Embarked'].fillna(most_frequent_embarked)
print(f"使用众数 '{most_frequent_embarked}' 填充 Embarked 后,缺失值数量: {combined_df['Embarked'].isnull().sum()}")

# 3. Fare: 测试集中有一个缺失值,可以用中位数填充
combined_df['Fare'] = combined_df['Fare'].fillna(combined_df['Fare'].median())
print(f"使用中位数填充 Fare 后,缺失值数量: {combined_df['Fare'].isnull().sum()}")

# 4. Cabin: 缺失太多,直接填充意义不大。可以考虑创建一个新特征表示是否有 Cabin 信息,
# 或者提取 Cabin 的首字母作为类别特征。这里我们先创建一个 'HasCabin' 特征。
combined_df['HasCabin'] = combined_df['Cabin'].notnull().astype(int)
print(f"创建 HasCabin 特征,表示是否有船舱号信息。")

# (可选) 处理异常值:例如 Fare 中可能有极端高值,可以根据业务理解或统计方法(如IQR)处理,
# 但在此示例中我们暂时不处理,留作后续优化的可能方向。

3.2 特征工程

基于对业务和数据的理解,创建新特征,转换现有特征。

# --- 创建新特征 ---

# 1. FamilySize: 在 EDA 中已创建,这里确保它存在
combined_df['FamilySize'] = combined_df['SibSp'] + combined_df['Parch'] + 1

# 2. IsAlone: 表示是否独自一人
combined_df['IsAlone'] = (combined_df['FamilySize'] == 1).astype(int)

# (可选) 3. Title: 从姓名中提取称谓 (Mr, Mrs, Miss, Master等)
combined_df['Title'] = combined_df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
# 合并稀有称谓
common_titles = combined_df['Title'].value_counts().index[:4] # 例如取前4个
combined_df['Title'] = combined_df['Title'].apply(lambda x: x if x in common_titles else 'Rare')
print("\n提取并简化后的 Title 分布:")
print(combined_df['Title'].value_counts())


# --- 特征转换 ---

# 1. 类别特征编码: Sex, Embarked, Title, Pclass (虽然是数字,但代表类别)
# 使用 One-Hot Encoding
combined_df = pd.get_dummies(combined_df, columns=['Sex', 'Embarked', 'Title', 'Pclass'], drop_first=True)
# drop_first=True 可以避免多重共线性,减少特征维度

print("\n进行 One-Hot Encoding 后的部分列名:")
print(combined_df.columns[-10:]) # 打印最后几列看效果

3.3 特征选择

移除对模型预测贡献不大或冗余的特征。

# --- 删除不再需要的列 ---
# Name, Ticket, Cabin (原始列), SibSp, Parch (已被FamilySize替代)
# HasCabin 已经创建,可以删掉 Cabin
# PassengerId 已在加载时处理
columns_to_drop = ['Name', 'Ticket', 'Cabin', 'SibSp', 'Parch']
combined_df = combined_df.drop(columns=columns_to_drop)

print(f"\n删除列 {columns_to_drop} 后的数据维度: {combined_df.shape}")
print("\n最终特征列表:")
print(combined_df.columns)

3.4 数据集划分

将处理好的 combined_df 重新拆分为训练集和测试集。

# 找到合并时训练集和测试集的边界
train_processed_df = combined_df.iloc[:len(train_df)]
test_processed_df = combined_df.iloc[len(train_df):]

# 确保拆分正确
assert len(train_processed_df) == len(train_df)
assert len(test_processed_df) == len(test_df)

X = train_processed_df # 特征
y = y_train           # 目标变量 (之前已保存)
X_final_test = test_processed_df # 最终用于预测的测试集特征

# 将训练数据 X 进一步划分为训练集和验证集 (用于模型评估和调优)
from sklearn.model_selection import train_test_split
X_train, X_val, y_train_split, y_val_split = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) # 使用 stratify 保证类别比例

print(f"\n拆分后:")
print(f"训练集 X_train 形状: {X_train.shape}, y_train_split 形状: {y_train_split.shape}")
print(f"验证集 X_val 形状: {X_val.shape}, y_val_split 形状: {y_val_split.shape}")
print(f"最终测试集 X_final_test 形状: {X_final_test.shape}")

3.5 特征缩放

对数值型特征进行缩放,使其具有相似的尺度,这对于很多依赖距离或梯度的算法(如 SVM、逻辑回归、神经网络)很重要。

from sklearn.preprocessing import StandardScaler

# 找出需要缩放的数值列 (Age, Fare, FamilySize)
# 注意:One-Hot 编码产生的0/1列通常不需要缩放
numerical_cols = ['Age', 'Fare', 'FamilySize'] # 可能还有其他,取决于具体特征工程

scaler = StandardScaler()

# 在训练集上拟合 scaler,然后转换训练集和验证集、最终测试集
X_train[numerical_cols] = scaler.fit_transform(X_train[numerical_cols])
X_val[numerical_cols] = scaler.transform(X_val[numerical_cols])
X_final_test[numerical_cols] = scaler.transform(X_final_test[numerical_cols])

print("\n特征缩放完成。查看缩放后训练集 Age 的均值和标准差:")
print(f"均值: {X_train['Age'].mean():.4f}, 标准差: {X_train['Age'].std():.4f}") # 应接近 0 和 1
print("\n处理后的训练集前 5 行:")
print(X_train.head())

四、基线模型建立与评估

在进行复杂的模型优化之前,先建立一个简单的基线模型(Baseline Model)。这有助于我们了解问题的基准难度,并为后续优化提供一个比较的起点。

4.1 选择基线模型

逻辑回归 (Logistic Regression) 是一个很好的二分类基线模型选择,它简单、快速且易于解释。

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, roc_auc_score

# 初始化逻辑回归模型
baseline_model = LogisticRegression(random_state=42, max_iter=1000) # 增加 max_iter 防止不收敛警告

4.2 模型训练

使用准备好的训练集 (X_train, y_train_split) 训练基线模型。

# 训练模型
baseline_model.fit(X_train, y_train_split)
print("基线模型 (逻辑回归) 训练完成。")

4.3 模型预测

使用训练好的模型在验证集 (X_val) 上进行预测。

# 在验证集上预测
y_pred_baseline = baseline_model.predict(X_val)
y_pred_proba_baseline = baseline_model.predict_proba(X_val)[:, 1] # 获取正类的概率,用于 AUC 计算
print("已在验证集上生成预测结果。")

4.4 模型评估

使用之前选定的评估指标评估基线模型的性能。这部分内容与 Day 17:模型评估与选择 紧密相关。

# 计算评估指标
accuracy_baseline = accuracy_score(y_val_split, y_pred_baseline)
conf_matrix_baseline = confusion_matrix(y_val_split, y_pred_baseline)
class_report_baseline = classification_report(y_val_split, y_pred_baseline)
auc_baseline = roc_auc_score(y_val_split, y_pred_proba_baseline)

print("\n--- 基线模型 (逻辑回归) 评估结果 (验证集) ---")
print(f"准确率 (Accuracy): {accuracy_baseline:.4f}")
print(f"AUC: {auc_baseline:.4f}")
print("\n混淆矩阵:")
print(conf_matrix_baseline)
# 可视化混淆矩阵
plt.figure(figsize=(6, 4))
sns.heatmap(conf_matrix_baseline, annot=True, fmt='d', cmap='Blues', xticklabels=['未生还', '生还'], yticklabels=['未生还', '生还'])
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.title('基线模型混淆矩阵')
plt.show()

print("\n分类报告:")
print(class_report_baseline)

基线结果分析: 记录下基线模型的各项指标。例如,我们可能得到 80% 左右的准确率和 AUC。这就是我们后续模型优化的目标——超越这个基准。

五、模型迭代优化

基线模型提供了一个起点,现在我们尝试更复杂的模型和优化技术来提升性能。这部分结合了 Day 17 (交叉验证)Day 18 (超参数调优) 的知识。

5.1 尝试更复杂的模型

训练一些在分类任务中表现通常更好的模型,例如决策树、随机森林、梯度提升树(如 XGBoost, LightGBM)或支持向量机。

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import xgboost as xgb # 需要先安装 xgboost 库: pip install xgboost

# 初始化随机森林模型
rf_model = RandomForestClassifier(random_state=42, n_estimators=100) # 先用默认参数

# 初始化 XGBoost 模型
xgb_model = xgb.XGBClassifier(random_state=42, use_label_encoder=False, eval_metric='logloss') # 常用参数设置

# 使用交叉验证评估模型性能 (更可靠)
# 注意:这里在完整的训练数据 X, y 上进行交叉验证,而不是在 train/val split 上
print("\n--- 使用 5 折交叉验证评估不同模型 ---")

models = {
    "Logistic Regression": baseline_model, # 用之前的基线模型作对比
    "Random Forest": rf_model,
    "XGBoost": xgb_model
}

for name, model in models.items():
    # 使用 'accuracy' 作为评分标准,也可以换成 'roc_auc', 'f1' 等
    cv_scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
    print(f"{name}: 平均准确率 = {cv_scores.mean():.4f} (+/- {cv_scores.std() * 2:.4f})") # +/- 2倍标准差表示95%置信区间大致范围

初步比较: 根据交叉验证的结果,选择表现较好的模型进行下一步的超参数调优,例如随机森林或 XGBoost。

5.2 超参数调优

使用网格搜索 (Grid Search) 或随机搜索 (Random Search) 为选定的模型寻找最佳超参数组合。我们在 Day 18 详细学习了这些技术。

from sklearn.model_selection import GridSearchCV

# 以随机森林为例进行超参数调优
param_grid_rf = {
    'n_estimators': [100, 200, 300], # 树的数量
    'max_depth': [None, 10, 20, 30], # 树的最大深度
    'min_samples_split': [2, 5, 10], # 内部节点再划分所需最小样本数
    'min_samples_leaf': [1, 2, 4]  # 叶子节点最少样本数
    # 可以添加更多参数如 'max_features', 'criterion' 等
}

# 注意:GridSearchCV 会自动进行交叉验证
# 使用 n_jobs=-1 利用所有 CPU 核心加速搜索
grid_search_rf = GridSearchCV(RandomForestClassifier(random_state=42),
                              param_grid=param_grid_rf,
                              cv=5, # 5 折交叉验证
                              scoring='accuracy', # 评估指标
                              n_jobs=-1,
                              verbose=1) # verbose=1 会打印搜索过程信息

print("\n--- 开始对随机森林进行网格搜索调优 (可能需要几分钟) ---")
# 在完整的训练数据 X, y 上进行搜索
grid_search_rf.fit(X, y)

print("\n--- 网格搜索完成 ---")
print(f"最佳参数组合: {grid_search_rf.best_params_}")
print(f"对应的最佳交叉验证准确率: {grid_search_rf.best_score_:.4f}")

# 获取最佳模型
best_rf_model = grid_search_rf.best_estimator_

5.3 交叉验证

交叉验证(Cross-Validation)是模型评估和选择中非常重要的一环,它能提供比单次划分验证集更可靠的模型性能估计。GridSearchCV 内部已经使用了交叉验证。我们在评估不同模型类型和进行超参数调优时都应用了它。

5.4 模型比较与选择

在调优后,我们得到了性能更优的模型 (best_rf_model)。可以再次在预留的验证集 (X_val, y_val_split) 上评估这个最终模型的性能,并与基线模型对比。

# 使用调优后的最佳模型在验证集上评估
y_pred_best = best_rf_model.predict(X_val)
y_pred_proba_best = best_rf_model.predict_proba(X_val)[:, 1]

accuracy_best = accuracy_score(y_val_split, y_pred_best)
conf_matrix_best = confusion_matrix(y_val_split, y_pred_best)
class_report_best = classification_report(y_val_split, y_pred_best)
auc_best = roc_auc_score(y_val_split, y_pred_proba_best)

print("\n--- 调优后最佳模型 (随机森林) 评估结果 (验证集) ---")
print(f"准确率 (Accuracy): {accuracy_best:.4f}")
print(f"AUC: {auc_best:.4f}")
print("\n混淆矩阵:")
# 可视化混淆矩阵
plt.figure(figsize=(6, 4))
sns.heatmap(conf_matrix_best, annot=True, fmt='d', cmap='Greens', xticklabels=['未生还', '生还'], yticklabels=['未生还', '生还'])
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.title('优化后模型混淆矩阵')
plt.show()
print(class_report_best)

print(f"\n对比:基线模型准确率 {accuracy_baseline:.4f}, AUC {auc_baseline:.4f}")
print(f"优化后模型准确率 {accuracy_best:.4f}, AUC {auc_best:.4f}")

选择: 优化后的模型在验证集上的性能通常会优于基线模型。确认后,best_rf_model 就是我们选定的最终模型。

六、结果解释与报告

模型训练完成并不意味着项目的结束。理解模型如何做出决策,并将结果清晰地传达给相关方同样重要。

6.1 解释最佳模型

对于像随机森林这样的模型,我们可以查看特征重要性 (Feature Importance),了解哪些特征对预测结果的贡献最大。

# 获取特征重要性
importances = best_rf_model.feature_importances_
feature_names = X_train.columns # 获取特征名称列表
feature_importance_df = pd.DataFrame({'Feature': feature_names, 'Importance': importances})
feature_importance_df = feature_importance_df.sort_values(by='Importance', ascending=False)

print("\n--- 最佳模型 (随机森林) 特征重要性 ---")
print(feature_importance_df.head(10)) # 显示最重要的 10 个特征

# 可视化特征重要性
plt.figure(figsize=(10, 6))
sns.barplot(x='Importance', y='Feature', data=feature_importance_df.head(10), palette='viridis')
plt.title('Top 10 Feature Importances (Random Forest)')
plt.tight_layout()
plt.show()

解释: 从特征重要性中,我们可能会发现 Sex_male, Fare, Age, Title_Mr, Pclass_3 等特征排名靠前,这与我们 EDA 的发现基本一致,验证了模型的合理性。更深入的模型解释技术(如 LIME, SHAP)将在后续 Day 29 讨论。

6.2 生成预测结果

使用最终选定的模型 (best_rf_model) 对原始的测试集 (X_final_test) 进行预测。这通常是为了提交给 Kaggle 平台或在实际应用中使用。

# 使用最佳模型对最终测试集进行预测
final_predictions = best_rf_model.predict(X_final_test)

# 创建提交文件 (Kaggle 格式)
submission_df = pd.DataFrame({'PassengerId': test_passenger_ids, 'Survived': final_predictions})
# 保存提交文件
# submission_df.to_csv('titanic_submission.csv', index=False)

print("\n已生成对最终测试集的预测结果。")
print("预测结果前 5 行:")
print(submission_df.head())

七、模型部署与监控考量

虽然本次实战侧重于建模流程,但实际项目中,模型需要部署到生产环境才能真正发挥价值。这部分内容将在 Day 35: MLOps 中详细展开,这里仅做简要介绍。

7.1 模型序列化

将训练好的最佳模型保存到文件中,以便后续加载和使用。

import joblib # joblib 在处理包含大型 numpy 数组的对象时可能更高效

# 保存模型
model_filename = 'titanic_rf_model.joblib'
joblib.dump(best_rf_model, model_filename)

# 保存特征缩放器也很重要!
scaler_filename = 'titanic_scaler.joblib'
joblib.dump(scaler, scaler_filename)

print(f"\n最佳模型已保存到 {model_filename}")
print(f"特征缩放器已保存到 {scaler_filename}")

# 加载模型和缩放器 (示例)
# loaded_model = joblib.load(model_filename)
# loaded_scaler = joblib.load(scaler_filename)
# print("\n模型和缩放器加载成功 (示例)。")

7.2 部署选项简介

  • API 服务: 将模型封装成 REST API(使用 Flask, FastAPI 等框架),供其他应用程序调用进行实时预测。
  • 批处理预测: 定期运行脚本,对一批新数据进行预测,并将结果存储起来。
  • 流处理预测: 与流处理系统(如 Kafka, Spark Streaming)集成,对实时数据流进行预测。
  • 边缘计算: 将模型部署到设备端(如手机、传感器),在本地进行预测。

7.3 监控要点

模型部署后并非一劳永逸,需要持续监控:

  • 性能监控: 跟踪模型在线上的实际预测效果(如果能获得真实标签)。
  • 数据漂移 (Data Drift): 监控输入数据的分布是否随时间发生变化,这可能导致模型性能下降。
  • 概念漂移 (Concept Drift): 监控特征与目标变量之间的关系是否发生变化。
  • 运行监控: 确保模型服务稳定运行,响应时间和资源消耗在预期范围内。

八、总结

恭喜你!通过本篇文章,我们一起完整地实践了从 0 到 1 构建一个机器学习项目的标准流程。回顾一下我们走过的关键步骤:

  1. 问题定义与目标设定: 清晰地理解业务需求,确定任务类型(分类/回归等)和评估指标。这是项目成功的基石。
  2. 数据获取与 EDA: 加载数据,通过统计和可视化深入理解数据特性、发现模式和潜在问题。
  3. 数据准备: 进行数据清洗(缺失值、异常值处理)、特征工程(创造、转换特征)、特征选择、数据集划分和特征缩放,将原始数据转化为模型可用的格式。这是决定模型性能上限的关键步骤。
  4. 基线模型建立与评估: 快速建立一个简单模型,设定性能基准。
  5. 模型迭代优化: 尝试更复杂的模型,利用交叉验证进行可靠评估,并通过超参数调优(如网格搜索)提升选定模型的性能。这是一个反复试验和改进的过程。
  6. 结果解释与报告: 分析模型(如特征重要性),理解其决策依据,并将整个项目过程和结果清晰地记录和传达。
  7. (简介)部署与监控: 将训练好的模型进行序列化保存,并了解后续部署上线和持续监控的基本概念。

这个端到端的流程不仅适用于泰坦尼克号预测,也适用于绝大多数监督学习项目。掌握并熟练运用这个流程,将使你的机器学习实践更加规范、高效和成功。

希望今天的实战演练对你有所帮助!在接下来的学习中,我们将继续深入探索机器学习的更多领域。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴师兄大模型

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值