scikit-learn中融合分组模型与全局模型的集成学习实现方案
group feature_1 feature_2 year dependent_variable group_a 12 19 2010 0.4 group_a 11 13 2011 0.9 group_a 10 5 2012 1.2 group_a 16 9 2013 3.2 group_b 8 29 2010 0.6 group_b 9 33 2011 0.1 group_b 111 15 2012 2.1 group_b 16 19 2013 12.2
使用特征feature_1、feature_2预测因变量dependent_variable,构建两类基础模型:
- 分组独立模型:每个分组单独训练独立子模型
- 全局模型:使用全量可用数据训练统一模型
两类模型均以2010-2012年数据为训练集、2013年数据为测试集,需要构建兼容所有符合scikit-learn规范模型的集成方案,适配包含更多分组、年份、特征的真实数据集。
整个方案完全遵循scikit-learn的Estimator接口规范,不需要修改基模型的底层逻辑,线性模型、树模型、集成模型等所有符合sklearn API的模型都可以直接接入。
1. 基础模型标准化封装
首先把分组独立模型封装成符合sklearn规范的估计器,全局模型可以直接用原生sklearn模型不需要额外处理。封装参考代码:
from sklearn.base import BaseEstimator, RegressorMixin, clone import pandas as pd import numpy as np class GroupWiseRegressor(BaseEstimator, RegressorMixin): def __init__(self, base_model, group_col="group", fallback_model=None): self.base_model = base_model self.group_col = group_col self.fallback_model = fallback_model self.group_models_ = {} def fit(self, X, y): X = X.reset_index(drop=True) y = y.reset_index(drop=True) # 训练各分组子模型 for group_name, sub_idx in X.groupby(self.group_col).groups.items(): sub_X = X.loc[sub_idx].drop(columns=[self.group_col]) sub_y = y.loc[sub_idx] self.group_models_[group_name] = clone(self.base_model).fit(sub_X, sub_y) # 训练兜底模型(传入全局模型即可处理未见过的新分组) if self.fallback_model is not None: self.fallback_model_ = clone(self.fallback_model).fit( X.drop(columns=[self.group_col]), y ) return self def predict(self, X): X = X.reset_index(drop=True) preds = np.zeros(len(X)) for group_name, sub_idx in X.groupby(self.group_col).groups.items(): sub_X = X.loc[sub_idx].drop(columns=[self.group_col]) if group_name in self.group_models_: preds[sub_idx] = self.group_models_[group_name].predict(sub_X) else: # 新分组用兜底模型输出 preds[sub_idx] = self.fallback_model_.predict(sub_X) return preds
如果是分类任务,把继承的RegressorMixin换成ClassifierMixin,对应实现predict_proba接口即可。
2. 三种集成策略(全兼容sklearn生态)
策略1:加权平均(实现成本最低,鲁棒性最强)
不需要额外训练元模型,从2010-2012年的训练集里按时间切分出验证集(比如用2012年数据做验证,绝对不能碰2013年测试集),网格搜索权重系数w(取值范围0~1),使得验证集上w * 分组模型预测值 + (1-w) * 全局模型预测值的误差最小即可。如果不同分组数据分布差异大,可以给每个分组单独搜索最优权重,进一步提升效果。
策略2:Stacking堆叠(效果上限更高)
直接用sklearn原生的StackingRegressor/StackingClassifier实现:第一层基学习器放封装好的分组独立模型、全局模型,第二层元学习器选简单的线性模型(比如线性回归、岭回归,不要选太复杂的模型防止过拟合)。注意生成第一层交叉验证预测值时,要严格按时间+分组维度切分,避免同一年同组的数据同时出现在训练和验证折里造成数据泄露。
策略3:动态路由(适配分组异质性极强的场景)
从训练集里生成两个基础模型在验证集上的预测误差标签:对于每个样本,如果分组模型预测误差更小标记为0,全局模型预测误差更小标记为1。用这个标签训练一个轻量的sklearn分类模型(比如浅决策树、逻辑回归)作为路由模型,预测时先由路由模型判断当前样本应该采信哪个基础模型的输出,直接返回对应模型的预测值即可。
3. 工作流统一封装
把最终选择的集成逻辑封装成独立的sklearn估计器,实现fit、predict、get_params、set_params标准接口,后续可以直接接入Pipeline、GridSearchCV、cross_validate等sklearn原生工具,不管后续新增多少分组、特征,或者更换什么基模型,都不需要修改核心代码。
注意事项:所有调参、权重搜索、元模型训练环节,只能使用2010-2012年的训练集数据,严格按时间维度切分验证集,绝对不能泄露2013年测试集的任何信息,否则离线评估指标会严重虚高。
内容的提问来源于stack exchange,提问作者user308827

