一文搞懂:什么是Stacking堆叠?手把手带你搭建堆叠模型,附有python源码和数据集。

本次分享的内容基于我的Spaceship Titanic 文章

在该文章采用的是Lightgbm模型进行的分类预测,本次分享一个在竞赛中常用的策略,堆叠。

在机器学习中,常常需要使用多种回归或分类模型来解决不同的任务。然而,单个模型的表现往往受到其算法特性的限制,可能无法达到最佳性能。那么,如何将多个模型的优点结合起来,进一步提高预测效果呢?Stacking(堆叠)方法应运而生,它通过集成多个模型的预测结果,优化整体性能。本文将通过一个具体的案例,详细阐述如何运用 Stacking 技术进行模型构建。

Stacking

Stacking(堆叠)是一种集成学习方法,其核心思想是通过结合多个基学习器(基模型)的预测结果,来构建一个更强大的最终模型。与其他集成方法(如 Bagging 和 Boosting)不同,Stacking 通过训练一个 “元学习器”(Meta-model),以便学习如何最佳地组合多个基模型的输出,最终获得更高的预测准确性。

Stacking 的原理

  • 多个基学习器(Base Learners):

在 Stacking 中,首先使用多个不同类型的基学习器(如决策树、支持向量机、神经网络等)对训练数据进行训练。这些基学习器可能是同质的(即相同的算法),也可以是异质的(即不同算法组合)。
每个基学习器都会生成一个预测值。

  • 训练元学习器(Meta-Model):

基学习器的输出会被作为新的特征输入给一个 元学习器。元学习器通常是一个较简单的模型(如线性回归、逻辑回归等),它的任务是学习如何将这些基学习器的预测值有效地结合起来,生成最终的预测结果。
例如,如果基学习器分别输出预测的分类概率或回归值,元学习器就会根据这些输出数据来进行二次预测。
两层结构:

第一层:由多个基学习器组成,独立地对训练数据进行预测。
第二层:将第一层的预测结果作为输入,训练一个元学习器来综合这些预测结果。

训练过程:

训练基学习器:首先,在原始训练数据上训练多个基学习器,得到每个基学习器的预测。
生成新的训练数据:用基学习器对训练数据的预测结果作为新的特征,构建一个新的数据集。这些新的特征就是基学习器的预测值。
训练元学习器:在新的数据集上训练一个元学习器,这个元学习器会学习如何将基学习器的预测结果结合起来,从而得到最终的输出。

预测过程:

在预测阶段,首先用所有基学习器对测试数据进行预测,得到基学习器的预测输出。
然后,将这些预测输出作为输入传递给训练好的元学习器,最终得到模型的最终预测结果。
例子
假设我们要构建一个分类模型,采用三种基学习器:决策树、随机森林和支持向量机(SVM)。Stacking 的过程如下:

第一层(基学习器):

使用决策树、随机森林和SVM模型分别对训练数据进行训练,并预测每个模型在测试集上的结果。
第二层(元学习器):

将决策树、随机森林和SVM的预测结果作为新的特征,构建一个新的数据集。
然后,训练一个简单的元学习器(例如逻辑回归),用这个元学习器来学习如何将三个模型的输出结合起来,从而做出最终的预测。
最终预测:

在实际预测时,首先使用基学习器(决策树、随机森林和SVM)对新数据进行预测,并将这些预测结果输入到元学习器中,得到最终的预测结果。

Stacking 的优点
  1. 提升性能:

Stacking 能够充分利用不同模型的优点,尤其是在基学习器之间存在互补性时。通过元学习器的组合,通常可以获得比单一模型更好的预测效果。

  1. 避免过拟合:

与某些单一模型相比,Stacking 更能减少过拟合的风险,因为它结合了多个模型的优势,并通过元学习器来调节输出。

  1. 灵活性高:

Stacking 支持使用不同类型的基学习器(如决策树、SVM、神经网络等),而且元学习器的选择也可以灵活调整(如逻辑回归、线性回归等)。

  1. 适应复杂问题:

对于那些很难通过单一算法解决的复杂问题,Stacking 提供了一种有效的集成策略,可以从多个角度对数据进行建模。

Stacking 的缺点
  1. 训练时间长:

由于需要训练多个基学习器和元学习器,Stacking 的训练时间通常较长,尤其是在数据量很大时。

  1. 计算开销大:

训练多个模型和生成额外的预测数据集需要大量的计算资源,这在资源有限的情况下可能成为问题。

  1. 元学习器的选择依赖性:

元学习器的性能对最终结果有较大影响,如果选择不当,可能无法充分发挥基学习器的优势。

总结

Stacking 通过整合多个基学习器的预测结果,结合元学习器的学习能力,能够显著提升模型的预测性能。它不仅能提高预测的准确性,还能减少过拟合风险,适用于多种复杂的机器学习任务。然而,Stacking 也存在训练时间长、计算开销大的问题,因此在使用时需要根据具体情况权衡。

import pandas as pd
import numpy as np
import warnings

# 忽略所有警告
warnings.filterwarnings("ignore")
train_data = pd.read_csv("rain.csv")
test_data = pd.read_csv("test.csv")

train_data2 = train_data.dropna()
test_data2 = test_data

# 对需要转换的列进行对数转换
columns_to_transform = ['RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', 'VRDeck']

# 为了避免对数运算中的负值或零,加上常数1,确保数据不为零
for col in columns_to_transform:
    train_data2[col] = np.log(train_data2[col] + 1)  # 对每一列进行对数转换,避免 log(0)

# 为了避免对数运算中的负值或零,加上常数1,确保数据不为零
for col in columns_to_transform:
    test_data2[col] = np.log(test_data2[col] + 1)  # 对每一列进行对数转换,避免 log(0)

train_data2[['team', 'team_encoder']] = train_data2['PassengerId'].str.split('_', expand=True)

test_data2[['team', 'team_encoder']] = test_data2['PassengerId'].str.split('_', expand=True)
# 将该列转换为整数类型
train_data2['team'] = train_data2['team'].astype(int)
train_data2['team_encoder'] = train_data2['team'].astype(int)
test_data2['team'] = test_data2['team'].astype(int)
test_data2['team_encoder'] = test_data2['team'].astype(int)

train_data2.loc[:, 'total_fee'] = train_data2['RoomService'] + train_data2['FoodCourt'] + train_data2['ShoppingMall'] + train_data2['Spa'] + train_data2['VRDeck']
test_data2.loc[:, 'total_fee'] = test_data2['RoomService'] + test_data2['FoodCourt'] + test_data2['ShoppingMall'] + test_data2['Spa'] + test_data2['VRDeck']

# 使用 str.split() 拆分
train_data2[['deck', 'num', 'side']] = train_data2['Cabin'].str.split('/', expand=True)
test_data2[['deck', 'num', 'side']] = test_data2['Cabin'].str.split('/', expand=True)

# 删除多个列 
train_data3 = train_data2.drop(['Cabin', 'Name','PassengerId'], axis=1)
test_data3 = test_data2.drop(['Cabin', 'Name','PassengerId'], axis=1)
train_data3.drop_duplicates(inplace = True)
PassengerId=test_data2['PassengerId']

# 使用 map() 方法进行编码
train_data3['Transported'] = train_data3['Transported'].map({True: 1, False: 0})

train_data3['Transported']

train_data4=train_data3.drop(['Transported'], axis=1)

label=train_data3['Transported']

from sklearn.preprocessing import OneHotEncoder

def encode_categorical_features(train_df, test_df, categorical_columns):
    """
    对指定的类别型特征进行 One-Hot 编码,并对训练集和测试集进行特征对齐。
    
    参数:
    - train_df: 训练集 DataFrame
    - test_df: 测试集 DataFrame
    - categorical_columns: 需要编码的类别型特征列名列表
    
    返回:
    - train_encoded: 编码后的训练集 DataFrame
    - test_encoded: 编码后的测试集 DataFrame
    """
    encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore')  # 创建 OneHotEncoder
    train_encoded = train_df.copy()  # 使用传入的训练集数据
    test_encoded = test_df.copy()  # 使用传入的测试集数据
    
    for column in categorical_columns:
        # 对训练集进行 fit_transform
        train_encoded_array = encoder.fit_transform(train_df[[column]])
        # 对测试集使用训练集的规则进行 transform
        test_encoded_array = encoder.transform(test_df[[column]])
        
        # 将编码后的数据转换为 DataFrame
        train_encoded_df = pd.DataFrame(train_encoded_array, 
                                        columns=encoder.get_feature_names_out([column]), 
                                        index=train_df.index)
        test_encoded_df = pd.DataFrame(test_encoded_array, 
                                       columns=encoder.get_feature_names_out([column]), 
                                       index=test_df.index)
        
        # 合并编码后的数据到原始 DataFrame 中
        train_encoded = pd.concat([train_encoded, train_encoded_df], axis=1)
        test_encoded = pd.concat([test_encoded, test_encoded_df], axis=1)
        
        # 删除原始的类别型列
        train_encoded.drop(column, axis=1, inplace=True)
        test_encoded.drop(column, axis=1, inplace=True)
    
    # 确保训练集和测试集的列顺序一致
    test_encoded = test_encoded.reindex(columns=train_encoded.columns, fill_value=0)
    
    return train_encoded, test_encoded

# 需要编码的列名
encoder_columns = ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'deck', 'num', 'side']

# 调用函数进行编码,传入指定的类别列
train_data_encoded, test_data_encoded = encode_categorical_features(train_data4, test_data3, encoder_columns)

以上都是一些建模之前的准备工作,我直接全部放上来了,看不懂的大家可以去我之前的文章(上面的链接)看这一部分,会有一些解释。

from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score
from sklearn.preprocessing import StandardScaler
from lightgbm import LGBMClassifier
from sklearn.ensemble import RandomForestClassifier,GradientBoostingClassifier,AdaBoostClassifier,StackingClassifier
from xgboost import XGBClassifier
from sklearn.linear_model import LogisticRegression


x = train_data_encoded
y = label

# 切分数据集
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)

# 标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 对训练集进行标准化
X_test_scaled = scaler.transform(X_test)        # 对测试集使用相同的缩放器进行标准化

# 定义一级学习器
base_learners=[
    ('RF', RandomForestClassifier(n_estimators=100,random_state=1)),
    ('GBM', GradientBoostingClassifier(n_estimators=100,random_state=1)),
    ('Adaboost', AdaBoostClassifier(n_estimators=100,random_state=1)),
    ('XGB', XGBClassifier(n_estimators=100,random_state=1)),
    ('LGB', LGBMClassifier(n_estimators=100,random_state=1))
]

# 定义二级学习器
meta_model = LogisticRegression()

# 创建堆叠分类器
stacking_classifier = StackingClassifier(estimators=base_learners,final_estimator=meta_model)

stacking_classifier

在这里插入图片描述
这就是构建出来的堆叠分类器。主要是基学习器和元学习器构成,这里基学习器用到了RF,XGB,LGB等,元学习器主要是为了结合基学习器,由于使用的是分类模型,因为这里使用逻辑回归来结合基学习器。如果是回归任务,只需将基学习器改为回归模型,元学习器可以使用线性回归将其结合。

stacking_classifier.fit(X_train_scaled, y_train)
# 进行预测
y_pred_test = stacking_classifier.predict(X_test_scaled)
y_pred_train = stacking_classifier.predict(X_train_scaled)

# 计算训练集指标
precision_train = precision_score(y_train, y_pred_train)
recall_train = recall_score(y_train, y_pred_train)
f1_train = f1_score(y_train, y_pred_train)

# 测试集指标
precision_test = precision_score(y_test, y_pred_test)
recall_test = recall_score(y_test, y_pred_test)
f1_test= f1_score(y_test, y_pred_test)

# 输出评估结果
print('训练集评估指标')
print("查准率: {:.5f}".format(precision_train))
print("查全率: {:.5f}".format(recall_train))
print("F1分数: {:.5f}".format(f1_train))


# 输出评估结果
print('测试集评估指标')
print("查准率: {:.5f}".format(precision_test))
print("查全率: {:.5f}".format(recall_test))
print("F1分数: {:.5f}".format(f1_test))
训练集评估指标
查准率: 0.91359
查全率: 0.93819
F1分数: 0.92572
测试集评估指标
查准率: 0.79458
查全率: 0.81791
F1分数: 0.80608

可以看到模型在几乎都是默认参数的情况下训练集表现良好,但是在测试集就有点差强人意,说明该模型是有略微的过拟合,总体来说效果还不错。如果将每种模型的最优参数都找出来进行堆叠,效果是优于单个模型的。

以上就是训练和评估的全部内容,代码都是源码,可以自行拿去运行。由于本次分享主要讲解堆叠策略,因此并没有对每个模型的参数进行调整,几乎使用的都是默认参数。主要是为了让读者更清晰的看到搭建操作,一些参数的优化这里就不过多赘述。

本次分享到这里就结束了,感谢大家的观看。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

机器学习司猫白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值