Ансамбль различных видов регрессоров с использованием scikit-learn (или любой другой структуры python)

Я пытаюсь решить задачу регрессии. Я узнал, что 3 модели работают хорошо для разных подмножеств данных: LassoLARS, SVR и Gradient Tree Boosting. Я заметил, что когда я делаю прогнозы с использованием всех этих трех моделей, а затем создаю таблицу "истинного вывода" и выходов своих трех моделей, я вижу, что каждый раз хотя бы одна из моделей действительно близка к истинному результату, хотя 2 других может быть относительно далеко.

Когда я вычисляю минимальную возможную ошибку (если я беру предсказание из "лучшего" предиктора для каждого тестового примера), я получаю ошибку, которая намного меньше ошибки любой модели. Поэтому я подумал о том, чтобы попытаться объединить прогнозы из этих трех разных моделей в какой-то ансамбль. Вопрос в том, как это сделать правильно? Все мои 3 модели построены и настроены с использованием scikit-learn, предоставляет ли он какой-то метод, который можно использовать для упаковки моделей в ансамбль? Проблема здесь в том, что я не хочу просто средние прогнозы из всех трех моделей, я хочу сделать это с помощью взвешивания, где взвешивание должно определяться на основе свойств конкретного примера.

Даже если scikit-learn не предоставляет такую ​​функциональность, было бы неплохо, если бы кто-то знал, как свойство адресует эту задачу - выяснить вес каждой модели для каждого примера в данных. Я думаю, что это может быть сделано отдельным регрессором, построенным поверх всех этих трех моделей, которые будут пытаться выводить оптимальные веса для каждой из трех моделей, но я не уверен, что это лучший способ сделать это.

Ответ 1

Хорошо, потратив некоторое время на "стекирование" в Google (как уже упоминалось ранее @andreas), я узнал, как я могу делать взвешивание на питоне даже при помощи scikit-learn. Рассмотрим ниже:

Я тренирую набор моих моделей регрессии (как упоминалось SVR, LassoLars и GradientBoostingRegressor). Затем я запускаю всех из них по данным обучения (те же данные, которые использовались для обучения каждого из этих 3 регрессоров). Я получаю предсказания для примеров с каждым из моих алгоритмов и сохраняю эти 3 результата в pandas фреймворке данных с предсказанными столбцамиSVR ',' предсказаноLASSO 'и' prededGBR '. И я добавляю последний столбец в этот datafrane, который я называю "предсказанным", который является реальным значением предсказания.

Затем я просто тренирую линейную регрессию на этом новом фрейме данных:

#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 регрессоров. И получить результат.

Проблема здесь в том, что я нахожу оптимальные веса для средних регрессоров, веса будут одинаковыми для каждого примера, на котором я попытаюсь сделать прогноз.

Ответ 2

Это известная интересная (и часто болезненная!) проблема с иерархическими предсказаниями. Проблема с подготовкой ряда предикторов по данным поезда, а затем обучение более высокого предиктора над ними, снова используя данные поезда - связано с разложением дисперсии смещения.

Предположим, у вас есть два предиктора, один из которых по сути является версией для перепрограммирования другой, тогда первая появится над набором поездов, который лучше, чем последний. Комбинирующий предиктор будет способствовать первому из-за истинной причины, просто потому, что он не может отличить переобучение от истинного качественного предсказания.

Известный способ борьбы с этим состоит в том, чтобы подготовить для каждой строки данных поезда для каждого из предсказателей предсказание для строки на основе модели, не подходящей для этой строки. В случае версии для переопределения, например, это не приведет к хорошему результату для строки, в среднем. Затем предиктор объединения сможет лучше оценить справедливую модель для объединения предикторов более низкого уровня.

Шахар Азулай и я создали трансформаторный этап для решения этой проблемы:

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

Теперь, используя stacking:

    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

Прогнозирование стеков делает лучше. Он понимает, что ускоритель градиента не так уж хорош.

Ответ 3

То, что вы описываете, называется "укладкой", которое еще не реализовано в scikit-learn, но, я думаю, вклады будут приветствоваться. Ансамбль, который просто средние, будет в ближайшее время: https://github.com/scikit-learn/scikit-learn/pull/4161

Ответ 4

Поздний отклик, но я хотел добавить одну практическую точку для такого рода сложного регрессионного подхода (который я часто использую в своей работе).

Вы можете выбрать алгоритм для укладчика, который позволяет положительный = True (например, ElasticNet). Я обнаружил, что, когда у вас есть одна относительно более сильная модель, неограниченная модель LinearRegression() часто будет соответствовать большему положительному коэффициенту для более сильного и отрицательного коэффициента для более слабой модели.

Если вы не верите, что ваша более слабая модель имеет отрицательную интеллектуальную силу, это не является полезным результатом. Очень похоже на высокую мультиколинеарность между особенностями регулярной регрессионной модели. Вызывает всевозможные краевые эффекты.

Этот комментарий наиболее применим к ситуации с шумными данными. Если вы хотите получить RSQ 0,9-0,95-0,99, вы, вероятно, захотите выбросить модель, которая получает отрицательный вес.