学习记录:机器学习案例——泰坦尼克号生存预测(二):逻辑回归、单棵决策树、随机森林

2026.4.30

分析上述代码,采用了逻辑回归模型和随机森林模型,随机森林模型就是多个决策树的集成,是一种集成机器学习。下面修改代码,采用逻辑回归、单棵决策树和随机森林三种模型,对比结果,学习这3种模型机的基本概念和特点。

代码如下:

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import seaborn as sns

from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV

from sklearn.preprocessing import StandardScaler

from sklearn.linear_model import LogisticRegression

from sklearn.tree import DecisionTreeClassifier

from sklearn.ensemble import RandomForestClassifier

from sklearn.metrics import accuracy_score, confusion_matrix, classification_report

# 设置中文显示

plt.rcParams['font.sans-serif'] = ['SimHei']

plt.rcParams['axes.unicode_minus'] = False

# 忽略警告

import warnings

warnings.filterwarnings('ignore')

print("=" * 60)

print("🚢 泰坦尼克号生存预测 - 多模型对比")

print("=" * 60)

# ============================================================

# 第一步:加载数据

# ============================================================

print("\n【第一步】加载数据...")

train_df = pd.read_csv('titanic/train.csv')

test_df = pd.read_csv('titanic/test.csv')

print(f"训练集形状: {train_df.shape}")

print(f"测试集形状: {test_df.shape}")

print("\n训练集前5行:")

print(train_df.head())

# ============================================================

# 第二步:数据探索

# ============================================================

print("\n【第二步】数据探索...")

print("\n训练集缺失值情况:")

missing_train = train_df.isnull().sum()

missing_train_pct = (missing_train / len(train_df)) * 100

missing_df = pd.DataFrame({'缺失数量': missing_train, '缺失比例(%)': missing_train_pct})

print(missing_df[missing_df['缺失数量'] > 0])

print(f"\n幸存者比例: {train_df['Survived'].mean():.2%}")

# ============================================================

# 第三步:可视化分析

# ============================================================

print("\n【第三步】可视化分析...")

plt.figure(figsize=(14, 10))

plt.subplot(2, 3, 1)

sns.countplot(data=train_df, x='Survived')

plt.title('幸存者分布 (0=遇难, 1=幸存)')

plt.subplot(2, 3, 2)

sns.barplot(data=train_df, x='Sex', y='Survived')

plt.title('性别与幸存率')

plt.subplot(2, 3, 3)

sns.barplot(data=train_df, x='Pclass', y='Survived')

plt.title('舱位等级与幸存率')

plt.subplot(2, 3, 4)

sns.histplot(data=train_df, x='Age', hue='Survived', bins=30, alpha=0.6)

plt.title('年龄分布与幸存关系')

plt.subplot(2, 3, 5)

sns.boxplot(data=train_df, x='Survived', y='Fare')

plt.title('票价与幸存关系')

plt.subplot(2, 3, 6)

sns.barplot(data=train_df, x='Embarked', y='Survived')

plt.title('登船港口与幸存率')

plt.tight_layout()

plt.show()

# ============================================================

# 第四步:特征工程

# ============================================================

print("\n【第四步】特征工程...")

# 合并训练集和测试集

train_df['Dataset'] = 'train'

test_df['Dataset'] = 'test'

passenger_ids = test_df['PassengerId']

combined = pd.concat([train_df, test_df], sort=False)

print(f"合并后数据形状: {combined.shape}")

# 处理缺失值

print("\n处理缺失值...")

age_median = combined['Age'].median()

combined['Age'] = combined['Age'].fillna(age_median)

print(f"  Age用中位数 {age_median:.1f} 填充")

fare_median = combined['Fare'].median()

combined['Fare'] = combined['Fare'].fillna(fare_median)

print(f"  Fare用中位数 {fare_median:.2f} 填充")

embarked_mode = combined['Embarked'].mode()[0]

combined['Embarked'] = combined['Embarked'].fillna(embarked_mode)

print(f"  Embarked用众数 '{embarked_mode}' 填充")

combined['HasCabin'] = combined['Cabin'].notna().astype(int)

combined = combined.drop('Cabin', axis=1)

# 提取称谓

print("\n从姓名提取称谓...")

def extract_title(name):

    import re

    title_search = re.search(r' ([A-Za-z]+)\.', name)

    if title_search:

        return title_search.group(1)

    return 'Unknown'

combined['Title'] = combined['Name'].apply(extract_title)

title_mapping = {

    'Mr': 'Mr', 'Miss': 'Miss', 'Mrs': 'Mrs', 'Master': 'Master',

    'Dr': 'Rare', 'Rev': 'Rare', 'Col': 'Rare', 'Major': 'Rare',

    'Mlle': 'Miss', 'Mme': 'Mrs', 'Ms': 'Miss', 'Lady': 'Rare',

    'Countess': 'Rare', 'Sir': 'Rare', 'Jonkheer': 'Rare',

    'Don': 'Rare', 'Capt': 'Rare'

}

combined['Title'] = combined['Title'].map(title_mapping)

# 家庭规模特征

combined['FamilySize'] = combined['SibSp'] + combined['Parch'] + 1

combined['IsAlone'] = (combined['FamilySize'] == 1).astype(int)

combined['FamilySizeGroup'] = pd.cut(

    combined['FamilySize'], bins=[0, 1, 4, 11], labels=['Alone', 'Small', 'Large']

)

# 年龄分组

bins = [0, 12, 18, 35, 60, 100]

labels = ['Child', 'Teenager', 'YoungAdult', 'Adult', 'Elderly']

combined['AgeGroup'] = pd.cut(combined['Age'], bins=bins, labels=labels)

# 票价分组

combined['FareGroup'] = pd.qcut(combined['Fare'], 4, labels=['Low', 'Medium', 'High', 'VeryHigh'])

# 删除不需要的列

columns_to_drop = ['PassengerId', 'Name', 'Ticket', 'Dataset']

combined = combined.drop(columns_to_drop, axis=1)

# 编码类别变量

combined['Sex'] = combined['Sex'].map({'male': 0, 'female': 1})

categorical_cols = ['Embarked', 'Title', 'AgeGroup', 'FareGroup', 'FamilySizeGroup']

combined = pd.get_dummies(combined, columns=categorical_cols, drop_first=True)

# 特征标准化(决策树和随机森林不需要,但逻辑回归需要)

numeric_cols = ['Age', 'Fare', 'FamilySize']

scaler = StandardScaler()

combined_scaled = combined.copy()

combined_scaled[numeric_cols] = scaler.fit_transform(combined[numeric_cols])

print(f"特征工程完成,当前特征数: {combined.shape[1]}")

# ============================================================

# 第五步:分离训练集和测试集

# ============================================================

print("\n【第五步】分离训练集和测试集...")

# 原始数据(决策树、随机森林用)

train_processed = combined[combined['Survived'].notna()].copy()

test_processed = combined[combined['Survived'].isna()].copy()

X = train_processed.drop('Survived', axis=1)

y = train_processed['Survived']

# 标准化数据(逻辑回归用)

train_scaled = combined_scaled[combined_scaled['Survived'].notna()].copy()

test_scaled = combined_scaled[combined_scaled['Survived'].isna()].copy()

X_scaled = train_scaled.drop('Survived', axis=1)

# 划分验证集

X_train, X_val, y_train, y_val = train_test_split(

    X, y, test_size=0.2, random_state=42, stratify=y

)

# 逻辑回归用标准化后的数据

X_train_scaled, X_val_scaled, _, _ = train_test_split(

    X_scaled, y, test_size=0.2, random_state=42, stratify=y

)

print(f"训练集: {X_train.shape[0]} 样本")

print(f"验证集: {X_val.shape[0]} 样本")

# ============================================================

# 第六步:训练三个模型并对比

# ============================================================

print("\n【第六步】训练模型并对比...")

results = []

# 6.1 逻辑回归

print("\n" + "-" * 40)

print("1️⃣ 逻辑回归模型")

print("-" * 40)

lr_model = LogisticRegression(max_iter=1000, random_state=42)

lr_model.fit(X_train_scaled, y_train)

y_pred_lr = lr_model.predict(X_val_scaled)

accuracy_lr = accuracy_score(y_val, y_pred_lr)

print(f"验证集准确率: {accuracy_lr:.4f}")

# 交叉验证

cv_scores_lr = cross_val_score(lr_model, X_scaled, y, cv=5)

print(f"5折交叉验证准确率: {cv_scores_lr.mean():.4f} (+/- {cv_scores_lr.std():.4f})")

# 逻辑回归系数

lr_coef = pd.DataFrame({

    '特征': X.columns,

    '系数': lr_model.coef_[0]

}).sort_values('系数', key=lambda x: abs(x), ascending=False)

print("\n特征影响 Top 5(正=促进幸存,负=促进遇难):")

for _, row in lr_coef.head(5).iterrows():

    direction = "✅ 促进幸存" if row['系数'] > 0 else "❌ 促进遇难"

    print(f"  {row['特征']}: {row['系数']:.4f} ({direction})")

results.append({

    '模型': '逻辑回归',

    '验证集准确率': accuracy_lr,

    '交叉验证平均准确率': cv_scores_lr.mean(),

    '交叉验证标准差': cv_scores_lr.std()

})

# 6.2 决策树

print("\n" + "-" * 40)

print("2️⃣ 单棵决策树模型")

print("-" * 40)

dt_model = DecisionTreeClassifier(random_state=42)

dt_model.fit(X_train, y_train)

y_pred_dt = dt_model.predict(X_val)

accuracy_dt = accuracy_score(y_val, y_pred_dt)

print(f"验证集准确率: {accuracy_dt:.4f}")

cv_scores_dt = cross_val_score(dt_model, X, y, cv=5)

print(f"5折交叉验证准确率: {cv_scores_dt.mean():.4f} (+/- {cv_scores_dt.std():.4f})")

# 决策树特征重要性

dt_importance = pd.DataFrame({

    '特征': X.columns,

    '重要性': dt_model.feature_importances_

}).sort_values('重要性', ascending=False)

print("\n特征重要性 Top 5:")

for _, row in dt_importance.head(5).iterrows():

    print(f"  {row['特征']}: {row['重要性']:.4f}")

results.append({

    '模型': '决策树',

    '验证集准确率': accuracy_dt,

    '交叉验证平均准确率': cv_scores_dt.mean(),

    '交叉验证标准差': cv_scores_dt.std()

})

# 6.3 随机森林

print("\n" + "-" * 40)

print("3️⃣ 随机森林模型")

print("-" * 40)

rf_model = RandomForestClassifier(random_state=42, n_jobs=-1)

rf_model.fit(X_train, y_train)

y_pred_rf = rf_model.predict(X_val)

accuracy_rf = accuracy_score(y_val, y_pred_rf)

print(f"验证集准确率: {accuracy_rf:.4f}")

cv_scores_rf = cross_val_score(rf_model, X, y, cv=5)

print(f"5折交叉验证准确率: {cv_scores_rf.mean():.4f} (+/- {cv_scores_rf.std():.4f})")

# 随机森林特征重要性

rf_importance = pd.DataFrame({

    '特征': X.columns,

    '重要性': rf_model.feature_importances_

}).sort_values('重要性', ascending=False)

print("\n特征重要性 Top 5:")

for _, row in rf_importance.head(5).iterrows():

    print(f"  {row['特征']}: {row['重要性']:.4f}")

results.append({

    '模型': '随机森林',

    '验证集准确率': accuracy_rf,

    '交叉验证平均准确率': cv_scores_rf.mean(),

    '交叉验证标准差': cv_scores_rf.std()

})

# 6.4 随机森林超参数调优

print("\n" + "-" * 40)

print("4️⃣ 随机森林(超参数调优)")

print("-" * 40)

param_grid = {

    'n_estimators': [100, 200],

    'max_depth': [10, 15, None],

    'min_samples_split': [2, 5]

}

grid_search = GridSearchCV(

    RandomForestClassifier(random_state=42, n_jobs=-1),

    param_grid, cv=5, scoring='accuracy', n_jobs=-1

)

grid_search.fit(X_train, y_train)

print(f"最佳参数: {grid_search.best_params_}")

print(f"最佳交叉验证分数: {grid_search.best_score_:.4f}")

best_rf = grid_search.best_estimator_

y_pred_best = best_rf.predict(X_val)

accuracy_best = accuracy_score(y_val, y_pred_best)

print(f"调优后验证集准确率: {accuracy_best:.4f}")

# ============================================================

# 第七步:模型对比可视化

# ============================================================

print("\n【第七步】模型对比可视化...")

# 创建对比表格

results_df = pd.DataFrame(results)

print("\n📊 模型性能对比表:")

print(results_df.to_string(index=False))

# 柱状图对比

plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)

bars = plt.bar(results_df['模型'], results_df['验证集准确率'], color=['#1f77b4', '#ff7f0e', '#2ca02c'])

plt.ylim(0.7, 0.9)

plt.ylabel('准确率')

plt.title('三个模型验证集准确率对比')

for bar, acc in zip(bars, results_df['验证集准确率']):

    plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.005,

             f'{acc:.3f}', ha='center', va='bottom')

# 混淆矩阵(用调优后的随机森林)

plt.subplot(1, 2, 2)

cm = confusion_matrix(y_val, y_pred_best)

sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',

            xticklabels=['遇难', '幸存'], yticklabels=['遇难', '幸存'])

plt.title('随机森林(调优后)混淆矩阵')

plt.ylabel('实际值')

plt.xlabel('预测值')

plt.tight_layout()

plt.show()

# 特征重要性对比

plt.figure(figsize=(14, 6))

plt.subplot(1, 2, 1)

top_features = rf_importance.head(8)

sns.barplot(data=top_features, y='特征', x='重要性', palette='viridis')

plt.title('随机森林特征重要性 Top 8')

plt.subplot(1, 2, 2)

top_features_dt = dt_importance.head(8)

sns.barplot(data=top_features_dt, y='特征', x='重要性', palette='viridis')

plt.title('决策树特征重要性 Top 8')

plt.tight_layout()

plt.show()

# ============================================================

# 第八步:生成提交文件(用最好的模型)

# ============================================================

print("\n【第八步】生成提交文件...")

X_test = test_processed.drop('Survived', axis=1)

predictions = best_rf.predict(X_test)

submission = pd.DataFrame({

    'PassengerId': passenger_ids,

    'Survived': predictions

})

print(submission.head())

print(f"\n预测结果分布:")

print(submission['Survived'].value_counts())

submission.to_csv('titanic_submission.csv', index=False)

print("\n✅ 预测结果已保存为 titanic_submission.csv")

# ============================================================

# 总结

# ============================================================

print("\n" + "=" * 60)

print("📊 模型对比总结")

print("=" * 60)

print(f"""

┌─────────────────────────────────────────────────────────────┐

│  模型                     验证集准确率    5折交叉验证        │

├─────────────────────────────────────────────────────────────┤

│  逻辑回归                 {accuracy_lr:.4f}            {cv_scores_lr.mean():.4f} (±{cv_scores_lr.std():.4f})│

│  决策树                   {accuracy_dt:.4f}            {cv_scores_dt.mean():.4f} (±{cv_scores_dt.std():.4f})│

│  随机森林                 {accuracy_rf:.4f}            {cv_scores_rf.mean():.4f} (±{cv_scores_rf.std():.4f})│

│  随机森林(调优)           {accuracy_best:.4f}                   -│

└─────────────────────────────────────────────────────────────┘

🏆 最佳模型: {'随机森林(调优)'}

💡 模型特点:

   • 逻辑回归: 线性模型,可解释系数,需要特征标准化

   • 决策树: 树形模型,可可视化,容易过拟合

   • 随机森林: 集成学习,最稳定,准确率最高

""")

print("\n🎉 泰坦尼克号生存预测完成!")

print("=" * 60)

运行结果如下:

============================================================
🚢 泰坦尼克号生存预测 - 多模型对比
============================================================

【第一步】加载数据...
训练集形状: (891, 12)
测试集形状: (418, 11)

训练集前5行:
   PassengerId  Survived  Pclass                                               Name     Sex   Age  SibSp  Parch            Ticket     Fare Cabin Embarked
0            1         0       3                            Braund, Mr. Owen Harris    male  22.0      1      0         A/5 21171   7.2500   NaN        S
1            2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1      0          PC 17599  71.2833   C85        C
2            3         1       3                             Heikkinen, Miss. Laina  female  26.0      0      0  STON/O2. 3101282   7.9250   NaN        S
3            4         1       1       Futrelle, Mrs. Jacques Heath (Lily May Peel)  female  35.0      1      0            113803  53.1000  C123        S
4            5         0       3                           Allen, Mr. William Henry    male  35.0      0      0            373450   8.0500   NaN        S

【第二步】数据探索...

训练集缺失值情况:
          缺失数量    缺失比例(%)
Age        177  19.865320
Cabin      687  77.104377
Embarked     2   0.224467

幸存者比例: 38.38%

【第三步】可视化分析...

【第四步】特征工程...
合并后数据形状: (1309, 13)

处理缺失值...
  Age用中位数 28.0 填充
  Fare用中位数 14.45 填充
  Embarked用众数 'S' 填充

从姓名提取称谓...
特征工程完成,当前特征数: 25

【第五步】分离训练集和测试集...
训练集: 712 样本
验证集: 179 样本

【第六步】训练模型并对比...

----------------------------------------
1️⃣ 逻辑回归模型
----------------------------------------
验证集准确率: 0.8324
5折交叉验证准确率: 0.8249 (+/- 0.0200)

特征影响 Top 5(正=促进幸存,负=促进遇难):
  Title_Mr: -1.9304 (❌ 促进遇难)
  FamilySizeGroup_Large: -1.2616 (❌ 促进遇难)
  Title_Rare: -0.9406 (❌ 促进遇难)
  Sex: 0.8775 (✅ 促进幸存)
  HasCabin: 0.8646 (✅ 促进幸存)

----------------------------------------
2️⃣ 单棵决策树模型
----------------------------------------
验证集准确率: 0.7821
5折交叉验证准确率: 0.7778 (+/- 0.0169)

特征重要性 Top 5:
  Title_Mr: 0.3214
  Fare: 0.1867
  Age: 0.1402
  Pclass: 0.0886
  HasCabin: 0.0507

----------------------------------------
3️⃣ 随机森林模型
----------------------------------------
验证集准确率: 0.7709
5折交叉验证准确率: 0.7958 (+/- 0.0355)

特征重要性 Top 5:
  Fare: 0.1832
  Age: 0.1704
  Title_Mr: 0.1285
  Sex: 0.0952
  Pclass: 0.0576

----------------------------------------
4️⃣ 随机森林(超参数调优)
----------------------------------------
最佳参数: {'max_depth': 10, 'min_samples_split': 5, 'n_estimators': 100}
最佳交叉验证分数: 0.8217
调优后验证集准确率: 0.8101

【第七步】模型对比可视化...

📊 模型性能对比表:
  模型   验证集准确率  交叉验证平均准确率  交叉验证标准差
逻辑回归 0.832402   0.824901 0.020026
 决策树 0.782123   0.777779 0.016865
随机森林 0.770950   0.795750 0.035541

【第八步】生成提交文件...
   PassengerId  Survived
0          892       0.0
1          893       0.0
2          894       0.0
3          895       0.0
4          896       1.0

预测结果分布:
Survived
0.0    263
1.0    155
Name: count, dtype: int64

✅ 预测结果已保存为 titanic_submission.csv

============================================================
📊 模型对比总结
============================================================

┌─────────────────────────────────────────────────────────────┐
│  模型                     验证集准确率    5折交叉验证        │
├─────────────────────────────────────────────────────────────┤
│  逻辑回归                 0.8324            0.8249 (±0.0200)│
│  决策树                   0.7821            0.7778 (±0.0169)│
│  随机森林                 0.7709            0.7958 (±0.0355)│
│  随机森林(调优)           0.8101                   -│
└─────────────────────────────────────────────────────────────┘

🏆 最佳模型: 随机森林(调优)

💡 模型特点:
   • 逻辑回归: 线性模型,可解释系数,需要特征标准化
   • 决策树: 树形模型,可可视化,容易过拟合
   • 随机森林: 集成学习,最稳定,准确率最高


🎉 泰坦尼克号生存预测完成!
============================================================

逻辑回归为什么需要标准化?

举个例子(未标准化)
特征取值范围对z的贡献(假设w=1)
年龄0-100贡献 0~100
票价0-500贡献 0~500
家庭人数1-10贡献 1~10

问题: 票价的数值天然就大,模型会错误地认为票价更重要,即使它的真实预测能力可能并不强。

标准化后
特征取值范围对z的贡献(假设w=1)
年龄-2~+3贡献 -2~+3
票价-2~+3贡献 -2~+3
家庭人数-2~+3贡献 -2~+3

效果: 所有特征在同一量纲上公平比较,权重w₁、w₂、w₃可以直接反映特征的重要性。

用泰坦尼克号数据测试:

数据逻辑回归准确率决策树准确率随机森林准确率
未标准化0.68(很差!)0.77(不变)0.81(不变)
标准化后0.80(正常)0.77(不变)0.81(不变)

结论:

  • 逻辑回归:标准化后准确率大幅提升

  • 树模型:标准化完全没有影响

逻辑回归 vs 线性回归

模型预测什么输出范围问题类型
线性回归连续数值负无穷 ~ 正无穷回归问题
逻辑回归属于某一类的概率0 ~ 1分类问题

对比维度线性回归逻辑回归
问题类型回归(预测数值)分类(预测类别)
输出连续值(如:35.8万)概率(如:0.85),转成类别(1)
输出范围(-∞, +∞)[0, 1]
公式y = wx + bp = 1/(1+e^-(wx+b))
损失函数均方误差(MSE)交叉熵(Cross-Entropy)
决策边界一条回归线一条分类线
典型应用房价预测、温度预测垃圾邮件识别、疾病诊断

线性回归算数值,逻辑回归判类别。逻辑回归 = 线性回归 + Sigmoid函数。

损失函数 = 衡量模型预测得"有多离谱"的数学公式

常用损失函数对比

问题类型常用损失函数对应模型
回归(预测数值)均方误差(MSE)线性回归
分类(预测类别)交叉熵(Cross-Entropy)逻辑回归、神经网络

均方误差(MSE):
误差 ↑
  │                    ╭──╮
  │                 ╭──╯  ╰──╮
  │              ╭──╯        ╰──╮
  │           ╭──╯              ╰──╮
  │        ╭──╯                    ╰──╮
  │     ╭──╯                          ╰──╮
  │  ╭──╯                                ╰──╮
  └────────────────────────────────────────→ 预测值
   抛物线形,误差越大惩罚越大(平方关系)


交叉熵损失:
损失 ↑
  │                                    ╭──
  │                                ╭───╯
  │                            ╭───╯
  │                        ╭───╯
  │                    ╭───╯
  │                ╭───╯
  │            ╭───╯
  │        ╭───╯
  │    ╭───╯
  │╭───╯
  └────────────────────────────────────────→ 预测概率
  当预测严重错误时,损失急剧增加

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值