【问题标题】:Ensemble of different kinds of regressors using scikit-learn (or any other python framework)使用 scikit-learn(或任何其他 python 框架)的不同类型回归器的集合
【发布时间】:2015-04-27 23:02:04
【问题描述】:

我正在尝试解决回归任务。我发现 3 种模型对不同的数据子集效果很好:LassoLARS、SVR 和 Gradient Tree Boosting。我注意到,当我使用所有这 3 个模型进行预测,然后制作一个“真实输出”表和我的 3 个模型的输出时,我发现每次至少有一个模型非常接近真实输出,尽管还有 2 个模型可能比较远。

当我计算最小可能误差时(如果我从每个测试示例的“最佳”预测器中进行预测),我得到的误差远小于任何模型单独的误差。所以我考虑尝试将这 3 个不同模型的预测组合成某种集合。问题是,如何正确地做到这一点?我所有的 3 个模型都是使用 scikit-learn 构建和调整的,它是否提供了某种可用于将模型打包成集成的方法?这里的问题是我不想只对所有三个模型的预测进行平均,我想通过加权来做到这一点,其中应根据具体示例的属性确定加权。

即使 scikit-learn 不提供这样的功能,如果有人知道如何解决这个任务,那就太好了 - 为数据中的每个示例计算每个模型的权重。我认为这可以通过在所有这 3 个模型之上构建的单独回归器来完成,它将尝试为 3 个模型中的每一个输出最佳权重,但我不确定这是否是最好的方法。

【问题讨论】:

    标签: machine-learning scikit-learn ensemble-learning


    【解决方案1】:

    响应较晚,但我想为这种堆叠回归方法(我在工作中经常使用这种方法)添加一个实用点。

    您可能希望为堆栈器选择允许 positive=True 的算法(例如 ElasticNet)。我发现,当你有一个相对更强的模型时,无约束的 LinearRegression() 模型通常会为更强的模型拟合一个较大的正系数,而为较弱的模型拟合一个负系数。

    除非您真的相信您的较弱模型具有负预测能力,否则这不是一个有用的结果。非常类似于在常规回归模型的特征之间具有高度的多重共线性。导致各种边缘效应。

    此评论最适用于嘈杂的数据情况。如果您的目标是获得 0.9-0.95-0.99 的 RSQ,您可能想要丢弃获得负权重的模型。

    【讨论】:

      【解决方案2】:

      这是分层预测的一个已知有趣(而且通常很痛苦!)的问题。在训练数据上训练多个预测器,然后在它们上训练更高的预测器,再次使用训练数据的问题 - 与偏差 - 方差分解有关。

      假设您有两个预测变量,一个本质上是另一个的过拟合版本,那么前者在训练集上的表现会比后者好。组合预测器会无缘无故偏爱前者,只是因为它无法区分过拟合和真正的高质量预测。

      处理这个问题的已知方法是,为训练数据中的每一行,为每个预测变量准备一个预测,基于模型不适合这一行.例如,对于过拟合版本,平均而言,这不会为行产生好的结果。然后,组合预测器将能够更好地评估用于组合较低级别预测器的公平模型。

      Shahar Azulay 和我写了一个变压器阶段来处理这个问题:

      class Stacker(object):
          """
          A transformer applying fitting a predictor `pred` to data in a way
              that will allow a higher-up predictor to build a model utilizing both this 
              and other predictors correctly.
      
          The fit_transform(self, x, y) of this class will create a column matrix, whose 
              each row contains the prediction of `pred` fitted on other rows than this one. 
              This allows a higher-level predictor to correctly fit a model on this, and other
              column matrices obtained from other lower-level predictors.
      
          The fit(self, x, y) and transform(self, x_) methods, will fit `pred` on all 
              of `x`, and transform the output of `x_` (which is either `x` or not) using the fitted 
              `pred`.
      
          Arguments:    
              pred: A lower-level predictor to stack.
      
              cv_fn: Function taking `x`, and returning a cross-validation object. In `fit_transform`
                  th train and test indices of the object will be iterated over. For each iteration, `pred` will
                  be fitted to the `x` and `y` with rows corresponding to the
                  train indices, and the test indices of the output will be obtained
                  by predicting on the corresponding indices of `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):
              """
              Same signature as any sklearn transformer.
              """
              self._pred.fit(x, y)
      
              return self
      
          def transform(self, x):
              """
              Same signature as any sklearn transformer.
              """
              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)
      

      这是@MaximHaytovich 的回答中描述的设置的改进示例。

      首先,一些设置:

          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
      

      堆叠预测效果更好。它意识到梯度增强器并不是那么好。

      【讨论】:

      • 阿米,真的很震撼。但是,stacker 必须适合我使用的每个预测器与我在训练数据中的记录相同的次数,这对吗?因此,不是在 80 行的数据上拟合基础预测器一次,而是我必须实际拟合 80 次?
      • 谢谢,@MaximHaytovich 这就是它需要可选 cv 函数的原因。如果构建 80 个预测变量(对应于 left-one-out)太昂贵,那么您也许可以构建 8 个(对应于 kfold)。不幸的是,正确地进行堆叠本身就更昂贵。
      • 在您的堆叠示例中,并且考虑到问题是如何优化基础学习者的权重而不是平均他们的预测,您可以在哪里提取堆叠器分配给两个基础学习者的权重(例如 80 % 梯度提升和 20% 线性回归)?
      【解决方案3】:

      好的,在花了一些时间在谷歌上搜索“堆叠”(如 @andreas 之前提到的)之后,我发现即使使用 scikit-learn 也可以在 python 中进行加权。考虑以下内容:

      我训练了一组回归模型(如 SVR、LassoLars 和 GradientBoostingRegressor 所述)。然后我在训练数据上运行所有这些数据(用于训练这 3 个回归量中的每一个的相同数据)。我使用我的每个算法得到示例的预测,并将这 3 个结果保存到带有“predictedSVR”、“predictedLASSO”和“predictedGBR”列的 pandas 数据框中。然后我将最后一列添加到这个我称之为“预测”的数据帧中,这是一个真实的预测值。

      然后我只是在这个新数据帧上训练一个线性回归:

      #df - dataframe with results of 3 regressors and true output
      from sklearn linear_model
      stacker= linear_model.LinearRegression()
      stacker.fit(df[['predictedSVR', 'predictedLASSO', 'predictedGBR']], df['predicted'])
      

      因此,当我想对新示例进行预测时,我只需分别运行 3 个回归器中的每一个,然后执行:

      stacker.predict() 
      

      关于我的 3 个回归器的输出。并得到一个结果。

      这里的问题是,我正在平均找到回归变量的最佳权重,对于我将尝试进行预测的每个示例,权重都是相同的。

      【讨论】:

        【解决方案4】:

        您所描述的称为“堆叠”,尚未在 scikit-learn 中实现,但我认为欢迎贡献。一个只有平均值的集合很快就会出现:https://github.com/scikit-learn/scikit-learn/pull/4161

        【讨论】:

        • 谢谢。在谷歌上搜索了一段时间后,我发现了如何“堆叠”,而不是仅仅使用多数投票规则(或平均),而是使用一些更高级的方法。很快就会在答案中发布。
        • 在统计学习的元素中有一个很好的描述。
        • @AndreasMueller 请在下面查看我的回答。关于您对欢迎贡献的评论,如果有兴趣,我很乐意尝试贡献。
        • 不确定我是否遵循您的代码,但我认为这不是堆叠。调用fit 时,您只适合单个基分类器,对吗?这看起来更像是 sklearn 中的 VotingClassifier 实现的。
        • 最后的判决是什么?是不是堆垛机?那这个呢 - rasbt.github.io/mlxtend/user_guide/regressor/StackingRegressor ?
        猜你喜欢
        • 2018-04-03
        • 2014-01-07
        • 2017-08-01
        • 2014-04-21
        • 2021-04-01
        • 2020-01-29
        • 2016-07-31
        • 2014-06-13
        相关资源
        最近更新 更多