web-dev-qa-db-ja.com

scikit-learn(またはその他のpythonフレームワーク)を使用したさまざまな種類のリグレッサーのアンサンブル

回帰課題を解決しようとしています。 LassoLARS、SVR、Gradient Tree Boostingの3つのモデルが、データのさまざまなサブセットに対して適切に機能していることがわかりました。これら3つのモデルすべてを使用して予測を行い、「真の出力」と3つのモデルの出力のテーブルを作成すると、モデルの少なくとも1つが実際に真の出力に近いことがわかりますが、他の2つは比較的遠くにある可能性があります。

考えられる最小限のエラーを計算すると(各テスト例の「最良の」予測子から予測を取得した場合)、モデルのみのエラーよりもはるかに小さいエラーが発生します。そこで、これら3つの異なるモデルからの予測をある種のアンサンブルに結合しようと考えました。問題は、これを適切に行う方法ですか?私の3つのモデルはすべて、scikit-learnを使用して構築および調整されていますが、モデルをアンサンブルにパックするために使用できる何らかの方法を提供しますか?ここでの問題は、3つのモデルすべてからの予測を平均化するだけではなく、特定の例のプロパティに基づいて重み付けを決定する必要がある重み付けを使用してこれを実行することです。

Scikit-learnがそのような機能を提供していない場合でも、データ内の各例の各モデルの重み付けを把握するというこのタスクにプロパティで対処する方法を誰かが知っていると便利です。これら3つのモデルすべての上に構築された個別のリグレッサによって実行される可能性があると思います。これにより、3つのモデルのそれぞれに最適な重みが出力されますが、これが最善の方法かどうかはわかりません。

17

これは、階層的予測に関する既知の興味深い(そしてしばしば苦痛な!)問題です。トレインデータに対して多数の予測子をトレーニングし、次にそれらに対してより高い予測子をトレーニングし、再びトレインデータを使用する場合の問題は、偏りと分散の分解に関係しています。

2つの予測子があり、一方は本質的に他方の過剰適合バージョンであるとすると、前者は列車セット上で後者よりも優れているように見えます。結合予測子は、過剰適合と真の高品質予測を区別できないという理由だけで、真の理由なしに前者を支持します。

これに対処する既知の方法は、列車データの各行について、各予測子について、この行に適合するモデルnotに基づいて行の予測を準備することです。過剰適合バージョンの場合、たとえば、これは平均して行に対して良い結果を生成しません。結合予測子は、低レベルの予測子を結合するための公正なモデルをより適切に評価できるようになります。

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およびx1yのノイズの多いバージョンです。最初の80行をトレーニングに使用し、最後の20行をテストに使用します。

これらは2つの予測子です:より高い分散の勾配ブースターと線形予測子:

    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

スタッキング予測の方が優れています。それは、勾配ブースターがそれほど大きくないことを認識しています。

19
Ami Tavory

さて、グーグルで「スタッキング」に時間を費やした後(@andreasで前述したように)、scikit-learnを使用してもpythonで重み付けを行う方法を見つけました。以下を検討してください。

回帰モデルのセットをトレーニングします(前述のように、SVR、LassoLars、GradientBoostingRegressor)。次に、それらすべてをトレーニングデータ(これら3つのリグレッサーのそれぞれのトレーニングに使用されたものと同じデータ)で実行します。各アルゴリズムの例の予測を取得し、これら3つの結果をpandas dataframe with column'predictedSVR '、' predictedLASSO 'and'predictedGBR'に保存します。最後の列をこのデータフランに追加しますこれを「予測済み」と呼びます。これは実際の予測値です。

次に、この新しいデータフレームで線形回帰をトレーニングします。

#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つのリグレッサーの出力について。そして結果を得る。

ここでの問題は、平均してリグレッサーの最適な重みを見つけていることです。重みは、予測を行う各例で同じになります。

10

応答は遅れましたが、この種のスタック回帰アプローチ(これは私の仕事で頻繁に使用します)に1つの実用的なポイントを追加したいと思いました。

スタッカーにpositive = Trueを許可するアルゴリズム(ElasticNetなど)を選択することをお勧めします。比較的強力なモデルが1つある場合、制約のないLinearRegression()モデルは、より大きな正の係数をより強いモデルに、負の係数をより弱いモデルに適合させることがよくあります。

弱いモデルに負の予測力があると実際に信じていない限り、これは有益な結果ではありません。通常の回帰モデルの特徴間に高い多重共線性があるのと非常に似ています。あらゆる種類のエッジ効果を引き起こします。

このコメントは、ノイズの多いデータの状況に最も重要に当てはまります。 0.9-0.95-0.99のRSQを取得することを目的としている場合は、負の重みを取得していたモデルを破棄することをお勧めします。

5
sf631

あなたが説明するのは「スタッキング」と呼ばれ、scikit-learnにはまだ実装されていませんが、貢献を歓迎すると思います。ちょうど平均化するアンサンブルはすぐに登場します: https://github.com/scikit-learn/scikit-learn/pull/4161

5
Andreas Mueller