これは、階層的予測に関する既知の興味深い (そしてしばしば苦痛な!) 問題です。トレーニング データに対して多数の予測子をトレーニングし、次にトレーニング データを使用してより高い予測子をトレーニングする際の問題は、バイアス分散分解に関係しています。
2 つの予測子があり、一方が他方の過剰適合バージョンであると仮定すると、前者は後者よりも優れているようにトレイン セットに表示されます。結合予測子は、オーバーフィッティングと真の高品質予測を区別できないという理由だけで、真の理由なく前者を優先します。
これに対処する既知の方法は、トレーニング データの各行、各予測子に対して、この行に適合しないモデルに基づいて行の予測を準備することです。過剰適合バージョンの場合、たとえば、これは行に対して平均して良い結果を生成しません。結合予測子は、下位レベルの予測子を結合するための公平なモデルをより適切に評価できるようになります。
Shahar Azulay と私は、これに対処するための Transformer ステージを作成しました。
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 行をテストに使用します。
高分散勾配ブースターと線形予測の 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
スタッキング予測の方が優れています。勾配ブースターがそれほど優れていないことがわかります。