本次分享的内容基于我的Spaceship Titanic 文章
在该文章采用的是Lightgbm模型进行的分类预测,本次分享一个在竞赛中常用的策略,堆叠。
在机器学习中,常常需要使用多种回归或分类模型来解决不同的任务。然而,单个模型的表现往往受到其算法特性的限制,可能无法达到最佳性能。那么,如何将多个模型的优点结合起来,进一步提高预测效果呢?Stacking(堆叠)方法应运而生,它通过集成多个模型的预测结果,优化整体性能。本文将通过一个具体的案例,详细阐述如何运用 Stacking 技术进行模型构建。
Stacking
Stacking(堆叠)是一种集成学习方法,其核心思想是通过结合多个基学习器(基模型)的预测结果,来构建一个更强大的最终模型。与其他集成方法(如 Bagging 和 Boosting)不同,Stacking 通过训练一个 “元学习器”(Meta-model),以便学习如何最佳地组合多个基模型的输出,最终获得更高的预测准确性。
Stacking 的原理
- 多个基学习器(Base Learners):
在 Stacking 中,首先使用多个不同类型的基学习器(如决策树、支持向量机、神经网络等)对训练数据进行训练。这些基学习器可能是同质的(即相同的算法),也可以是异质的(即不同算法组合)。
每个基学习器都会生成一个预测值。
- 训练元学习器(Meta-Model):
基学习器的输出会被作为新的特征输入给一个 元学习器。元学习器通常是一个较简单的模型(如线性回归、逻辑回归等),它的任务是学习如何将这些基学习器的预测值有效地结合起来,生成最终的预测结果。
例如,如果基学习器分别输出预测的分类概率或回归值,元学习器就会根据这些输出数据来进行二次预测。
两层结构:
第一层:由多个基学习器组成,独立地对训练数据进行预测。
第二层:将第一层的预测结果作为输入,训练一个元学习器来综合这些预测结果。
训练过程:
训练基学习器:首先,在原始训练数据上训练多个基学习器,得到每个基学习器的预测。
生成新的训练数据:用基学习器对训练数据的预测结果作为新的特征,构建一个新的数据集。这些新的特征就是基学习器的预测值。
训练元学习器:在新的数据集上训练一个元学习器,这个元学习器会学习如何将基学习器的预测结果结合起来,从而得到最终的输出。
预测过程:
在预测阶段,首先用所有基学习器对测试数据进行预测,得到基学习器的预测输出。
然后,将这些预测输出作为输入传递给训练好的元学习器,最终得到模型的最终预测结果。
例子
假设我们要构建一个分类模型,采用三种基学习器:决策树、随机森林和支持向量机(SVM)。Stacking 的过程如下:
第一层(基学习器):
使用决策树、随机森林和SVM模型分别对训练数据进行训练,并预测每个模型在测试集上的结果。
第二层(元学习器):
将决策树、随机森林和SVM的预测结果作为新的特征,构建一个新的数据集。
然后,训练一个简单的元学习器(例如逻辑回归),用这个元学习器来学习如何将三个模型的输出结合起来,从而做出最终的预测。
最终预测:
在实际预测时,首先使用基学习器(决策树、随机森林和SVM)对新数据进行预测,并将这些预测结果输入到元学习器中,得到最终的预测结果。
Stacking 的优点
- 提升性能:
Stacking 能够充分利用不同模型的优点,尤其是在基学习器之间存在互补性时。通过元学习器的组合,通常可以获得比单一模型更好的预测效果。
- 避免过拟合:
与某些单一模型相比,Stacking 更能减少过拟合的风险,因为它结合了多个模型的优势,并通过元学习器来调节输出。
- 灵活性高:
Stacking 支持使用不同类型的基学习器(如决策树、SVM、神经网络等),而且元学习器的选择也可以灵活调整(如逻辑回归、线性回归等)。
- 适应复杂问题:
对于那些很难通过单一算法解决的复杂问题,Stacking 提供了一种有效的集成策略,可以从多个角度对数据进行建模。
Stacking 的缺点
- 训练时间长:
由于需要训练多个基学习器和元学习器,Stacking 的训练时间通常较长,尤其是在数据量很大时。
- 计算开销大:
训练多个模型和生成额外的预测数据集需要大量的计算资源,这在资源有限的情况下可能成为问题。
- 元学习器的选择依赖性:
元学习器的性能对最终结果有较大影响,如果选择不当,可能无法充分发挥基学习器的优势。
总结
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
可以看到模型在几乎都是默认参数的情况下训练集表现良好,但是在测试集就有点差强人意,说明该模型是有略微的过拟合,总体来说效果还不错。如果将每种模型的最优参数都找出来进行堆叠,效果是优于单个模型的。
以上就是训练和评估的全部内容,代码都是源码,可以自行拿去运行。由于本次分享主要讲解堆叠策略,因此并没有对每个模型的参数进行调整,几乎使用的都是默认参数。主要是为了让读者更清晰的看到搭建操作,一些参数的优化这里就不过多赘述。
本次分享到这里就结束了,感谢大家的观看。