集成学习

集成学习(Ensemble Learning)是一种机器学习方法,通过结合多个基学习器的预测结果来提高整体模型的性能。其核心思想是“群策群力”,即多个弱学习器可以组合成一个强学习器。集成学习通常能显著降低过拟合风险,提高泛化能力。

集成学习的核心原理

偏差-方差分解:集成学习通过减少模型的偏差或方差来提升性能。单个模型可能因高偏差(欠拟合)或高方差(过拟合)表现不佳,而集成方法通过结合多个模型的预测来平衡两者。

多样性:基学习器之间需要具备一定的差异性(如不同算法、数据子集或参数设置),否则集成效果有限。常见的多样性来源包括数据扰动(如Bagging)、特征扰动或算法扰动。

主要集成方法

Bagging(Bootstrap Aggregating)

  • 通过自助采样(Bootstrap Sampling)生成多个训练子集,每个子集训练一个基学习器。
  • 最终结果通过投票(分类)或平均(回归)产生。
  • 典型算法:随机森林(Random Forest),进一步引入特征随机性。

Boosting

  • 基学习器按顺序训练,后续模型关注前序模型的错误样本。
  • 通过加权投票或加权求和组合预测结果。
  • 典型算法:AdaBoost(Adaptive Boosting)、梯度提升树(GBDT)、XGBoost、LightGBM。

Stacking

  • 使用初级学习器(基模型)生成预测结果,再将结果作为输入训练次级学习器(元模型)。
  • 需注意避免数据泄露,通常通过交叉验证生成元特征。

实际应用中的优势

  1. 鲁棒性:对噪声数据和异常值更具容忍度。
  2. 灵活性:支持异构模型(如Stacking中混合决策树和SVM)。
  3. 可解释性:部分方法(如随机森林)能提供特征重要性分析。

挑战与注意事项

  • 计算开销:训练多个模型可能增加时间和资源消耗。
  • 模型选择:基学习器的性能需均衡,过强的基学习器可能导致集成无效。
  • 过拟合风险:Boosting方法需控制迭代次数,Stacking需谨慎设计次级模型。

XGBoost

XGBoost(eXtreme Gradient Boosting)是一种基于梯度提升框架的机器学习算法,由陈天奇于2016年提出。它通过集成多个弱学习器(通常是决策树)来构建强学习器,广泛应用于分类、回归和排序任务。其核心优势在于高效性、可扩展性和对缺失值的鲁棒性。

核心特点

并行计算:通过特征预排序和块结构优化,支持并行化处理,大幅提升训练速度。
正则化:在目标函数中加入L1/L2正则项,防止过拟合。
缺失值处理:自动学习缺失值的分裂方向,无需人工填充。
灵活性:支持自定义损失函数和评估指标。

关键优化技术

加权分位数草图:近似寻找最优分裂点,减少计算量。
稀疏感知算法:高效处理稀疏数据(如One-Hot编码后的特征)。
缓存访问优化:通过缓存命中率提升数据读取效率。

应用场景

  • 金融风控(信用评分、欺诈检测)
  • 推荐系统(点击率预测)
  • 结构化数据竞赛(如Kaggle比赛中常见)

与同类算法对比

  • LightGBM:更快的训练速度,但可能牺牲部分精度。
  • CatBoost:擅长处理类别特征,无需显式编码。
  • 随机森林:基于Bagging,抗噪声能力更强,但解释性较差。

代码:

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, GridSearchCV
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, roc_auc_score


# ---------------------- 1. 数据加载 ----------------------
def load_data(train_path, test_path):
    train_df = pd.read_csv(train_path)
    test_df = pd.read_csv(test_path)
    # 保存测试集ID用于最终提交
    test_passenger_ids = test_df["PassengerId"]
    print(f"✅ 数据加载完成:训练集{train_df.shape} | 测试集{test_df.shape}")
    return train_df, test_df, test_passenger_ids


# ---------------------- 2. 特征工程(针对XGBoost优化) ----------------------
def feature_engineering(train_df, test_df):
    # 合并数据便于统一处理
    combined_df = pd.concat([train_df, test_df], ignore_index=True)
    original_test_size = test_df.shape[0]

    # 1. 提取头衔特征(对XGBoost重要)
    combined_df["Title"] = combined_df["Name"].str.extract(r"([A-Za-z]+)\.")
    rare_titles = ["Dona", "Lady", "Countess", "Capt", "Col", "Don", "Dr", "Major", "Rev", "Sir", "Jonkheer"]
    combined_df["Title"] = combined_df["Title"].replace(rare_titles, "Rare")
    combined_df["Title"] = combined_df["Title"].replace({"Mlle": "Miss", "Ms": "Miss", "Mme": "Mrs"})

    # 2. 填充缺失值(XGBoost对缺失值有一定容忍度,但填充能提升性能)
    ## 先处理Embarked(分类特征)
    combined_df["Embarked"] = combined_df["Embarked"].fillna(combined_df["Embarked"].mode()[0])
    
    ## 年龄:按Title+Pclass分组填充(更精准)
    age_median = combined_df.groupby(["Title", "Pclass"])["Age"].median()
    for (title, pclass) in age_median.index:
        mask = (combined_df["Age"].isnull()) & (combined_df["Title"] == title) & (combined_df["Pclass"] == pclass)
        combined_df.loc[mask, "Age"] = age_median[(title, pclass)]
    if combined_df["Age"].isnull().any():
        combined_df["Age"] = combined_df["Age"].fillna(combined_df["Age"].median())
    
    ## 票价:按Pclass+Embarked分组填充
    fare_median = combined_df.groupby(["Pclass", "Embarked"])["Fare"].median()
    def fill_fare(x):
        try:
            return fare_median[x["Pclass"], x["Embarked"]]
        except KeyError:
            return combined_df.groupby("Pclass")["Fare"].median()[x["Pclass"]]
    combined_df["Fare"] = combined_df["Fare"].fillna(combined_df.apply(fill_fare, axis=1))

    # 3. 衍生高价值特征(XGBoost能有效利用交互特征)
    combined_df["FamilySize"] = combined_df["SibSp"] + combined_df["Parch"] + 1
    combined_df["IsAlone"] = 1  # 1表示独自旅行
    combined_df.loc[combined_df["FamilySize"] > 1, "IsAlone"] = 0
    combined_df["Age_Pclass"] = combined_df["Age"] * combined_df["Pclass"]  # 年龄与舱位交互
    combined_df["Fare_Per_Person"] = combined_df["Fare"] / combined_df["FamilySize"]  # 人均票价

    # 4. 分箱处理(减少噪声,增强稳定性)
    combined_df["AgeBin"] = pd.cut(combined_df["Age"], bins=[0, 12, 18, 30, 50, 100], labels=["Child", "Teen", "Young", "Mid", "Elder"])
    combined_df["FareBin"] = pd.qcut(combined_df["Fare"], 5, labels=["Fare1", "Fare2", "Fare3", "Fare4", "Fare5"])

    # 5. 移除无用特征
    drop_cols = ["Name", "Ticket", "Cabin", "PassengerId", "Age", "Fare"]  # 移除原始连续特征,保留分箱和交互特征
    combined_df = combined_df.drop(columns=drop_cols)

    # 拆分回训练集和测试集
    train_processed = combined_df.iloc[:-original_test_size, :].copy()
    test_processed = combined_df.iloc[-original_test_size:, :].copy()

    print(f"✅ 特征工程完成:训练集特征{train_processed.columns.tolist()}")
    return train_processed, test_processed


# ---------------------- 3. 数据拆分 ----------------------
def split_data(train_processed):
    # 分离特征和标签
    X = train_processed.drop(columns=["Survived"])
    y = train_processed["Survived"].astype(int)

    # 拆分训练集和验证集(分层抽样)
    X_train, X_val, y_train, y_val = train_test_split(
        X, y, test_size=0.2, random_state=42, stratify=y
    )
    print(f"✅ 数据拆分完成:训练集{X_train.shape} | 验证集{X_val.shape}")
    return X, y, X_train, X_val, y_train, y_val


# ---------------------- 4. XGBoost模型超参数调优 ----------------------
def tune_xgboost(X_train, y_train):
    # 定义特征类型
    numeric_features = ["SibSp", "Parch", "FamilySize", "Age_Pclass", "Fare_Per_Person"]
    categorical_features = ["Sex", "Pclass", "Embarked", "Title", "IsAlone", "AgeBin", "FareBin"]

    # 预处理管道(XGBoost需要将分类特征转为数值)
    preprocessor = ColumnTransformer(
        transformers=[
            ("num", "passthrough", numeric_features),  # 数值特征直接使用
            ("cat", OneHotEncoder(drop="first", sparse_output=False, handle_unknown="ignore"), categorical_features)  # 分类特征独热编码
        ]
    )

    # XGBoost管道
    xgb_pipeline = Pipeline(steps=[
        ("preprocessor", preprocessor),
        ("classifier", XGBClassifier(
            random_state=42,
            use_label_encoder=False,
            eval_metric='logloss',  # 二分类评估指标
            objective='binary:logistic'  # 二分类任务
        ))
    ])

    # 超参数搜索空间(针对泰坦尼克号数据优化)
    param_grid = {
        "classifier__n_estimators": [100, 200, 300],  # 树的数量
        "classifier__max_depth": [3, 4, 5],  # 树深度(控制复杂度)
        "classifier__learning_rate": [0.01, 0.05, 0.1],  # 学习率
        "classifier__subsample": [0.8, 0.9, 1.0],  # 样本采样比例
        "classifier__colsample_bytree": [0.8, 0.9, 1.0],  # 特征采样比例
        "classifier__gamma": [0, 0.1, 0.2],  # 分裂所需的最小损失减少量
        "classifier__reg_alpha": [0, 0.1, 1],  # L1正则化
        "classifier__reg_lambda": [1, 10, 100]  # L2正则化
    }

    # 网格搜索
    grid_search = GridSearchCV(
        estimator=xgb_pipeline,
        param_grid=param_grid,
        cv=5,  # 5折交叉验证
        scoring="accuracy",
        n_jobs=-1,  # 并行计算
        verbose=1
    )
    grid_search.fit(X_train, y_train)

    print(f"✅ XGBoost调优完成:最优准确率{grid_search.best_score_:.4f},最优参数:{grid_search.best_params_}")
    return grid_search.best_estimator_


# ---------------------- 5. 模型评估 ----------------------
def evaluate_model(model, X_val, y_val):
    # 预测
    y_pred = model.predict(X_val)
    y_proba = model.predict_proba(X_val)[:, 1]

    # 评估指标
    acc = accuracy_score(y_val, y_pred)
    roc = roc_auc_score(y_val, y_proba)

    print("\n=== 模型评估结果 ===")
    print(f"验证集准确率:{acc:.4f}")
    print(f"ROC-AUC:{roc:.4f}")
    print("\n混淆矩阵:")
    print(confusion_matrix(y_val, y_pred))
    print("\n分类报告:")
    print(classification_report(y_val, y_pred, digits=4))
    return acc


# ---------------------- 6. 生成提交文件 ----------------------
def generate_submission(model, X, y, test_processed, test_passenger_ids, output_path):
    # 用全量训练数据重新训练模型
    model.fit(X, y)
    # 预测测试集
    test_pred = model.predict(test_processed)
    # 生成提交文件
    submission = pd.DataFrame({
        "PassengerId": test_passenger_ids.astype(int),
        "Survived": test_pred.astype(int)
    })
    submission.to_csv(output_path, index=False)
    print(f"\n✅ 提交文件生成完成:{output_path}")
    print("提交文件前5行:\n", submission.head())
    return submission


# ---------------------- 7. 主函数 ----------------------
def main():
    # 文件路径
    TRAIN_PATH = "/kaggle/input/titanic/train.csv"
    TEST_PATH = "/kaggle/input/titanic/test.csv"
    OUTPUT_PATH = "titanic_xgboost_submission.csv"

    # 流程串联
    train_df, test_df, test_passenger_ids = load_data(TRAIN_PATH, TEST_PATH)
    train_processed, test_processed = feature_engineering(train_df, test_df)
    X, y, X_train, X_val, y_train, y_val = split_data(train_processed)
    best_xgb = tune_xgboost(X_train, y_train)
    evaluate_model(best_xgb, X_val, y_val)
    generate_submission(best_xgb, X, y, test_processed, test_passenger_ids, OUTPUT_PATH)


if __name__ == "__main__":
    main()

训练结果:

✅ 数据加载完成:训练集(891, 12) | 测试集(418, 11)
✅ 特征工程完成:训练集特征['Survived', 'Pclass', 'Sex', 'SibSp', 'Parch', 'Embarked', 'Title', 'FamilySize', 'IsAlone', 'Age_Pclass', 'Fare_Per_Person', 'AgeBin', 'FareBin']
✅ 数据拆分完成:训练集(712, 12) | 验证集(179, 12)
Fitting 5 folds for each of 6561 candidates, totalling 32805 fits
✅ XGBoost调优完成:最优准确率0.8371,最优参数:{'classifier__colsample_bytree': 0.9, 'classifier__gamma': 0.1, 'classifier__learning_rate': 0.1, 'classifier__max_depth': 4, 'classifier__n_estimators': 100, 'classifier__reg_alpha': 0.1, 'classifier__reg_lambda': 10, 'classifier__subsample': 0.9}

=== 模型评估结果 ===
验证集准确率:0.8101
ROC-AUC:0.8577

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐