我正在尝试解决回归任务。我发现有三个模型对数据的不同子集表现良好:LassoLARS、SVR和梯度提升树。我注意到,当我使用这三个模型进行预测并制作一个包含“真实输出”和三个模型输出的表格时,每次至少有一个模型的预测非常接近真实输出,尽管其他两个模型可能相对较远。
当我计算最小可能误差(如果我为每个测试样本选择“最佳”预测器的预测)时,我得到的误差远小于任何单个模型的误差。因此,我考虑尝试将这三个不同模型的预测组合成某种形式的集成。问题是,如何正确地做到这一点?我的三个模型都是使用scikit-learn构建和调整的,它是否提供了某种方法可以将模型打包成集成?这里的问题是我不想简单地平均所有三个模型的预测,我希望进行加权,而加权应该基于特定样本的属性来确定。
即使scikit-learn不提供这种功能,如果有人知道如何正确处理这个任务——即为数据中的每个样本确定每个模型的权重,那将是非常好的。我认为这可以通过在所有这三个模型之上构建一个单独的回归器来实现,该回归器将尝试为三个模型中的每一个输出最佳权重,但我并不确定这是最佳方法。
回答:
这是已知的有趣(且常常痛苦!)的层次预测问题。训练多个预测器在训练数据上,然后在它们之上训练一个更高层次的预测器,再次使用训练数据——这与偏差-方差分解有关。
假设你有两个预测器,其中一个基本上是另一个的过拟合版本,那么前者在训练集上看起来会比后者更好。组合预测器会无故偏向前者,仅仅因为它无法区分过拟合和真正的高质量预测。
处理这种情况的已知方法是,为训练数据中的每一行,为每个预测器,基于未针对此行拟合的模型,预测该行的值。对于过拟合版本,例如,这平均不会为该行产生好的结果。组合预测器然后能够更好地评估一个公平的模型来组合低层次的预测器。
Shahar Azulay和我编写了一个变换阶段来处理这个问题:
class Stacker(object): """ 一个变换器,应用于将预测器`pred`拟合到数据上,以允许更高层次的预测器构建一个模型,利用这个和其他预测器正确地进行。 这个类的fit_transform(self, x, y)将创建一个列矩阵,其每一行包含`pred`在除此行之外的其他行上拟合的预测。 这允许更高层次的预测器正确地拟合一个模型在这个和其他从其他低层次预测器获得的列矩阵上。 fit(self, x, y)和transform(self, x_)方法,将`pred`拟合到所有`x`上,并使用拟合的`pred`变换`x_`的输出(可能是`x`或不是)。 参数: pred: 要堆叠的低层次预测器。 cv_fn: 接受`x`并返回一个交叉验证对象的函数。在`fit_transform`中, 对象的训练和测试索引将被迭代。对于每次迭代,`pred`将 被拟合到与训练索引对应的`x`和`y`上,测试索引的输出将通过预测相应索引的`x`获得。 """ def __init__(self, pred, cv_fn=lambda x: sklearn.cross_validation.LeaveOneOut(x.shape[0])): self._pred, self._cv_fn = pred, cv_fn def fit_transform(self, x, y): x_trans = self._train_transform(x, y) self.fit(x, y) return x_trans def fit(self, x, y): """ 与任何sklearn变换器相同的签名。 """ self._pred.fit(x, y) return self def transform(self, x): """ 与任何sklearn变换器相同的签名。 """ return self._test_transform(x) def _train_transform(self, x, y): x_trans = np.nan * np.ones((x.shape[0], 1)) all_te = set() for tr, te in self._cv_fn(x): all_te = all_te | set(te) x_trans[te, 0] = self._pred.fit(x[tr, :], y[tr]).predict(x[te, :]) if all_te != set(range(x.shape[0])): warnings.warn('Not all indices covered by Stacker', sklearn.exceptions.FitFailedWarning) return x_trans def _test_transform(self, x): return self._pred.predict(x)
这是对@人名回答中描述的设置改进的一个示例。
首先,一些设置:
from sklearn import linear_model from sklearn import cross_validation from sklearn import ensemble from sklearn import metrics y = np.random.randn(100) x0 = (y + 0.1 * np.random.randn(100)).reshape((100, 1)) x1 = (y + 0.1 * np.random.randn(100)).reshape((100, 1)) x = np.zeros((100, 2))
请注意,x0
和x1
只是y
的噪声版本。我们将使用前80行作为训练数据,最后20行作为测试数据。
这是两个预测器:一个高方差的梯度提升器和一个线性预测器:
g = ensemble.GradientBoostingRegressor() l = linear_model.LinearRegression()
这是回答中建议的方法:
g.fit(x0[: 80, :], y[: 80]) l.fit(x1[: 80, :], y[: 80]) x[:, 0] = g.predict(x0) x[:, 1] = l.predict(x1) >>> metrics.r2_score( y[80: ], linear_model.LinearRegression().fit(x[: 80, :], y[: 80]).predict(x[80: , :])) 0.940017788444
现在,使用堆叠:
x[: 80, 0] = Stacker(g).fit_transform(x0[: 80, :], y[: 80])[:, 0] x[: 80, 1] = Stacker(l).fit_transform(x1[: 80, :], y[: 80])[:, 0] u = linear_model.LinearRegression().fit(x[: 80, :], y[: 80]) x[80: , 0] = Stacker(g).fit(x0[: 80, :], y[: 80]).transform(x0[80:, :]) x[80: , 1] = Stacker(l).fit(x1[: 80, :], y[: 80]).transform(x1[80:, :]) >>> metrics.r2_score( y[80: ], u.predict(x[80:, :])) 0.992196564279
堆叠预测表现得更好。它意识到梯度提升器并不是那么好。