Формула победы для каждого чемпиона Kaggle: демистификация ансамблей моделей в Python

Python контрольная работа Kaggle модульный тест

из Датаквест

Автор: Себастьян Фленнерхаг

Сборник "Сердце машины"

Ансамбльный подход, который объединяет прогнозы нескольких моделей машинного обучения для получения точных результатов, не сравнимых с одной моделью, стал обязательным практически для каждого победителя конкурса Kaggle. Итак, как мы можем интегрировать различные модели с помощью Python? Об этом вкратце рассказывает автор данной статьи Себастьян Фленнерхаг, докторант Школы компьютерных наук и социальной статистики Манчестерского университета.


Эффективное стекирование моделей в Python

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

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

Значок интеграции. Входной массив X подается нескольким базовым учащимся f(i) через два конвейера предварительной обработки. Ансамбль объединяет прогнозы всех базовых учеников, чтобы получить окончательный массив прогнозов P. (Источник изображения:ml-ensemble.com/)


В этой статье представлены основы интеграции: что она определяет и как работает, а также представлено практическое руководство по созданию базовой интеграции. Прочитав эту статью, вы:

  • понимать основы интеграции;
  • Научитесь писать интеграции;
  • Понять основные преимущества и недостатки интеграции.


Прогнозирование пожертвований республиканцев и демократов

В этой статье мы будем использовать набор данных о политических взносах США, чтобы объяснить, как работает ансамбль. Набор данных был первоначально подготовлен Беном Видером из FiveThirtyEight, который изучил записи правительства США о политических пожертвованиях и обнаружил, что, когда ученые жертвуют политикам, они часто выбирают демократов.

Утверждение основано на соотношении пожертвований республиканцам и демократам. Но мы также можем увидеть больше вещей: например, какие дисциплины, скорее всего, пожертвуют республиканцам, а какие штаты, скорее всего, пожертвуют демократам. Мы сделаем еще один шаг и предскажем, куда с большей вероятностью пойдут пожертвования.

Используемые здесь данные немного изменены (woohoo.data quest.IO/blog/large_…). Мы удалили пожертвования партиям, отличным от республиканцев и демократов, чтобы сделать процесс более понятным, а также удалили повторяющуюся информацию и менее интересные функции. Адрес скрипта данных:woohoo.data quest.IO/blog/large_…. Данные следующие:

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
%matplotlib inline

### Import data
# Always good to set a seed for reproducibility
SEED = 222
np.random.seed(SEED)

df = pd.read_csv('input.csv')

### Training and test set
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

def get_train_test(test_size=0.95):
"""Split Data into train and test sets."""
   y = 1 * (df.cand_pty_affiliation == "REP")
   X = df.drop(["cand_pty_affiliation"], axis=1)
   X = pd.get_dummies(X, sparse=True)
   X.drop(X.columns[X.std() == 0], axis=1, inplace=True)
return train_test_split(X, y, test_size=test_size, random_state=SEED)

xtrain, xtest, ytrain, ytest = get_train_test()

# A look at the data
print("\nExample data:")
df.head()
df.cand_pty_affiliation.value_counts(normalize=True).plot(
   kind="bar", title="Share of No. donations")
plt.show()

Приведенный выше рисунок является основой данных для утверждения Бена. Действительно, 75% пожертвований идут демократам. Давайте посмотрим на функции, которые можно использовать, у нас есть данные о доноре, деталях транзакции и получателе пожертвования:

Мы используем ROC-AUC для оценки производительности модели. Если вы раньше не использовали эту метрику, случайное угадывание может составлять 0,5 балла, а идеальный отзыв и точность — 1,0.


Что такое интеграция?

Представьте, что вы играете в викторину. Когда вы играете в одиночку, могут возникнуть некоторые вопросы, которые вы вообще не понимаете. Если мы хотим получить высокие оценки, нам нужно сформировать команду, чтобы охватить все актуальные темы. Это основная концепция ансамбля: объединение прогнозов из нескольких моделей и усреднение конкретных ошибок для получения лучшего общего прогноза.

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

Структура базовой интеграции. Входные данные подаются в серию моделей, а мета-обучение объединяет прогнозы нескольких моделей. (Источник изображения:положить молоко и hagen.com/2017-04-18-…)


Понимание интеграции через деревья решений

Мы объясняем ансамбль с помощью простой модели интерпретируемости: деревья решений, использующие правила «если-то». Чем глубже дерево решений, тем сложнее шаблоны, которые можно зафиксировать, но тем выше вероятность его переобучения. Следовательно, нам нужен другой способ построения сложных моделей деревьев решений, и ансамбль различных деревьев решений является одним из таких способов.

Мы используем следующие вспомогательные функции для визуализации деревьев решений:

import pydotplus  # you can install pydotplus with: pip install pydotplus 
from IPython.display import Image
from sklearn.metrics import roc_auc_score
from sklearn.tree import DecisionTreeClassifier, export_graphviz

def print_graph(clf, feature_names):
"""Print decision tree."""
   graph = export_graphviz(
       clf,
       label="root",
       proportion=True,
       impurity=False, 
       out_file=None, 
       feature_names=feature_names,
       class_names={0: "D", 1: "R"},
       filled=True,
       rounded=True
   )
   graph = pydotplus.graph_from_dot_data(graph)  
return Image(graph.create_png())

Сопоставьте узел (правило принятия решения) с деревом решений на обучающих данных и посмотрите, как оно работает на тестовом наборе:

t1 = DecisionTreeClassifier(max_depth=1, random_state=SEED)
t1.fit(xtrain, ytrain)
p = t1.predict_proba(xtest)[:, 1]

print("Decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
print_graph(t1, xtrain.columns)

Оценка ROC-AUC для дерева решений: 0,672


Каждый конечный узел записывает свою долю в обучающих выборках, распределении классов и прогнозах меток классов. Наше дерево решений сделало прогноз на основе того, была ли сумма пожертвования больше 101,5: оно дало тот же прогноз! Такой результат не удивителен, учитывая, что 75% пожертвований идут демократам. Но это не позволяет в полной мере использовать данные, которые у нас уже есть.Давайте воспользуемся трехуровневым правилом принятия решений, чтобы увидеть, что мы получим:

t2 = DecisionTreeClassifier(max_depth=3, random_state=SEED)
t2.fit(xtrain, ytrain)
p = t2.predict_proba(xtest)[:, 1]

print("Decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
print_graph(t2, xtrain.columns)


Оценка ROC-AUC для дерева решений: 0,751

Модель была немногим лучше простого дерева решений: прогнозируемый процент пожертвований республиканцев составлял всего 5%, что значительно ниже 25%. Более пристальный взгляд показывает, что в дереве решений используется множество неопределенных правил разделения. Колоссальные 47,3% наблюдений находятся в крайнем левом конечном узле, а 35,9% — во втором правом конечном узле. Поэтому большое количество листовых узлов не связаны. Углубление этой модели приведет только к переоснащению.

С фиксированной глубиной деревья решений могут увеличивать сложность за счет увеличения «ширины», т. Е. Создавая несколько деревьев решений и соединяя их вместе. То есть интеграция деревьев решений. Чтобы понять, почему эта ансамблевая модель работает, сначала рассмотрим, как мы можем заставить дерево решений исследовать больше шаблонов, чем верхнее дерево. Самое простое решение — удалить объекты, которые появляются в дереве раньше. Если мы удалим признак суммы перевода (transaction_amt), корневой узел дерева, новое дерево решений будет выглядеть следующим образом:

drop = ["transaction_amt"]

xtrain_slim = xtrain.drop(drop, 1)
xtest_slim = xtest.drop(drop, 1)

t3 = DecisionTreeClassifier(max_depth=3, random_state=SEED)
t3.fit(xtrain_slim, ytrain)
p = t3.predict_proba(xtest_slim)[:, 1]


print("Decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))
print_graph(t3, xtrain_slim.columns)

Оценка ROC-AUC для дерева решений: 0,740


Оценка ROC-AUC была аналогична оценке Uptree, но процент пожертвований республиканцев увеличился до 7,3%. По-прежнему низко, но немного выше, чем раньше. Важно отметить, что в отличие от первого дерева (где большая часть правил связана с самим переводом), это дерево больше ориентировано на то, где живет кандидат. Теперь у нас есть две модели с одинаковой прогностической силой, но работающие по разным правилам. Следовательно, они могут иметь разные ошибки предсказания, которые мы можем усреднить, используя метод ансамбля.


Почему усредненные прогнозы работают

Предположим, мы хотим сгенерировать прогнозы на основе двух наблюдений. Настоящим ярлыком для первого наблюдения является республиканец, а для второго — демократ. В этом примере предположим, что Модель 1 предсказывает демократов, а Модель 2 предсказывает республиканцев, как показано в следующей таблице:

Если мы используем стандартное правило отсечки 50% для предсказания класса, каждое дерево решений предсказывает правильное и неправильное. Мы усредняем вероятности классов моделей для создания ансамбля. В этом примере предсказание модели 2 относительно наблюдения 1 является достоверным, в то время как модель 1 относительно менее определенна. Ансамбль измеряет предсказания обоих, а затем поддерживает модель 2, которая правильно предсказывает Республиканскую партию. Что касается второго наблюдения, ситуация изменилась, и ансамбль правильно предсказал демократов:

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

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


Лес — это совокупность деревьев.

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

p1 = t2.predict_proba(xtest)[:, 1]
p2 = t3.predict_proba(xtest_slim)[:, 1]

pd.DataFrame({"full_data": p1,
"red_data": p2}).corr()

Некоторая корреляция есть, но не слишком большая: есть еще много возможностей для использования дисперсии прогноза. Чтобы построить этот ансамбль, мы просто усреднили предсказания двух моделей.

p1 = t2.predict_proba(xtest)[:, 1]
p2 = t3.predict_proba(xtest_slim)[:, 1]
p = np.mean([p1, p2], axis=0)
print("Average of decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))

Средний балл ROC-AUC для дерева решений: 0,783


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

Быстрая и эффективная практика состоит в том, чтобы случайным образом выбрать подмножество функций, подобрать дерево решений для каждого розыгрыша и усреднить его прогнозы. Этот процесс называется усреднением с начальной загрузкой (часто сокращенно «бэггинг») и применяется к деревьям решений для создания модели, называемой случайным лесом. Давайте посмотрим, что случайные леса могут сделать для нас. Мы использовали реализацию Scikit-learn для построения ансамбля из 10 деревьев решений, каждое из которых содержит подмножество из 3 функций.

from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(
   n_estimators=10,
   max_features=3,
   random_state=SEED
)

rf.fit(xtrain, ytrain)
p = rf.predict_proba(xtest)[:, 1]
print("Average of decision tree ROC-AUC score: %.3f" % roc_auc_score(ytest, p))

Средняя оценка ROC-AUC для дерева решений: 0,844

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


Ансамбль как средний прогноз

До сих пор мы видели два важных аспекта интеграции:

1. Чем менее коррелирована ошибка предсказания, тем лучше

2. Чем больше моделей, тем лучше эффект

По этой причине рекомендуется использовать как можно больше разных моделей (при условии, что они хорошо работают). До сих пор мы полагались на простое усреднение, но позже мы увидим, как использовать более сложные комбинации. Для записи прогресса мы формулируем интеграцию следующим образом:

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

Чтобы построить ансамбль различных моделей, мы сначала сравним набор классификаторов Scikit-learn с набором данных. Чтобы избежать дублирования кода, мы используем следующую вспомогательную функцию:

# A host of Scikit-learn models
from sklearn.svm import SVC, LinearSVC
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.kernel_approximation import Nystroem
from sklearn.kernel_approximation import RBFSampler
from sklearn.pipeline import make_pipeline


def get_models():
"""Generate a library of base learners."""
   nb = GaussianNB()
   svc = SVC(C=100, probability=True)
   knn = KNeighborsClassifier(n_neighbors=3)
   lr = LogisticRegression(C=100, random_state=SEED)
   nn = MLPClassifier((80, 10), early_stopping=False, random_state=SEED)
   gb = GradientBoostingClassifier(n_estimators=100, random_state=SEED)
   rf = RandomForestClassifier(n_estimators=10, max_features=3, random_state=SEED)

   models = {'svm': svc,
'knn': knn,
'naive bayes': nb,
'mlp-nn': nn,
'random forest': rf,
'gbm': gb,
'logistic': lr,
             }

return models


def train_predict(model_list):
"""Fit models in list on training set and return preds"""
   P = np.zeros((ytest.shape[0], len(model_list)))
   P = pd.DataFrame(P)

print("Fitting models.")
   cols = list()
for i, (name, m) in enumerate(models.items()):
print("%s..." % name, end=" ", flush=False)
       m.fit(xtrain, ytrain)
       P.iloc[:, i] = m.predict_proba(xtest)[:, 1]
       cols.append(name)
print("done")

   P.columns = cols
print("Done.\n")
return P


def score_models(P, y):
"""Score model in prediction DF"""
print("Scoring models.")
for m in P.columns:
       score = roc_auc_score(y, P.loc[:, m])
print("%-26s: %.3f" % (m, score))
print("Done.\n")

Теперь мы готовимся создать матрицу прогнозирования P, где каждая функция соответствует прогнозу, сделанному данной моделью, и оцениваем каждую модель по тестовому набору:

models = get_models()
P = train_predict(models)
score_models(P, ytest)

Это наш базовый уровень. Лучше всего работает Gradient Boosting Machine (GBM), за которой следует простая логистическая регрессия. Для нашей ансамблевой стратегии ошибки предсказания должны быть относительно некоррелированными.

# You need ML-Ensemble for this figure: you can install it with: pip install mlens
from mlens.visualization import corrmat

corrmat(P.corr(), inflate=False)
plt.show()

Ошибка явно коррелирована, чего и следует ожидать от модели с хорошим поведением, поскольку она обычно является выбросом и его трудно исправить. Однако большинство ассоциаций находятся в диапазоне 50-80%, поэтому есть много возможностей для улучшения. На самом деле все выглядит более многообещающе, если мы посмотрим на корреляции ошибок с точки зрения предсказания классов:

corrmat(P.apply(lambda pred: 1*(pred >= 0.5) - ytest.values).corr(), inflate=False)
plt.show()

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

print("Ensemble ROC-AUC score: %.3f" % roc_auc_score(ytest, P.mean(axis=1)))

Интегрированная оценка ROC-AUC: 0,884


Визуализируйте интегрированный рабочий процесс

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

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

Пример границ решений для трех моделей и их ансамблей


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

С другой стороны, для каждой точки обучения нелинейный мета-обучаемый может настроить модель, от которой он зависит. Это означает, что он может значительно сократить ненужные жертвы и поддерживать высокую точность при увеличении отзыва (и наоборот). На рисунке ниже ансамбль приносит меньшую жертву в точности, чтобы увеличить отзыв.

from sklearn.metrics import roc_curve

def plot_roc_curve(ytest, P_base_learners, P_ensemble, labels, ens_label):
"""Plot the roc curve for base learners and ensemble."""
   plt.figure(figsize=(10, 8))
   plt.plot([0, 1], [0, 1], 'k--')

   cm = [plt.cm.rainbow(i)
for i in np.linspace(0, 1.0, P_base_learners.shape[1] + 1)]

for i in range(P_base_learners.shape[1]):
       p = P_base_learners[:, i]
       fpr, tpr, _ = roc_curve(ytest, p)
       plt.plot(fpr, tpr, label=labels[i], c=cm[i + 1])

   fpr, tpr, _ = roc_curve(ytest, P_ensemble)
   plt.plot(fpr, tpr, label=ens_label, c=cm[0])

   plt.xlabel('False positive rate')
   plt.ylabel('True positive rate')
   plt.title('ROC curve')
   plt.legend(frameon=False)
   plt.show()


plot_roc_curve(ytest, P.values, P.mean(axis=1), list(P.columns), "ensemble")

Интеграция за пределами простых средних значений

Но разве вы не ожидаете большего улучшения, учитывая изменение ошибки прогноза? Некоторые модели работают относительно хуже, чем другие, но их влияние столь же велико. Это разрушительно для несбалансированных наборов данных: если модель делает экстремальные прогнозы (т. е. близкие к 0 или 1), она отзывается мягким голосованием, поскольку оказывает огромное влияние на среднее значение прогноза.

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

p = P.apply(lambda x: 1*(x >= 0.5).value_counts(normalize=True))
p.index = ["DEM", "REP"]
p.loc["REP", :].sort_values().plot(kind="bar")
plt.axhline(0.25, color="k", linewidth=0.5)
plt.text(0., 0.23, "True share republicans")
plt.show() 

Мы пытаемся улучшить ансамбль, удаляя худшие из них, скажем, многослойные персептроны (MLP):

include = [c for c in P.columns if c not in ["mlp-nn"]]
print("Truncated ensemble ROC-AUC score: %.3f" % roc_auc_score(ytest, P.loc[:, include].mean(axis=1)))

Усеченный ансамбль Оценка ROC-AUC: 0,883


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


Учимся комбинировать предсказания

Изучение средневзвешенного значения означает, что для каждой модели f_i существует весовой параметр ω_i ∈ (0,1), который присваивает веса предсказаниям этой модели. Средневзвешенное значение требует, чтобы сумма всех весов равнялась 1. Теперь интеграция определяется следующим образом:

Это небольшое изменение по сравнению с предыдущим определением, но интересный момент: как только модель генерирует прогнозы p_i=f_i(x), изучение весов эквивалентно подбору линейной регрессии на основе прогнозов:

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


Реализовать интеграцию

Для построения такого типа интеграции нам потребуется:

1. Базовая обучающая библиотека для создания прогнозов;

2. Мета-обучение, которое учит, как лучше всего комбинировать прогнозы;

3. Метод разделения обучающих данных между базовыми учащимися и мета-учащимися.

Базовый учащийся получает исходные данные и генерирует серию прогнозов. Если наш исходный набор данных представляет собой матрицу X формы (n_samples, n_features), то библиотека базового учащегося выводит новую матрицу прогнозирования P_base формы (n_samples, n_base_learners), где каждый столбец представляет результат прогноза базового учащегося. Мета-обучаемый обучается на P_base.

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

Нам нужна стратегия для создания матрицы предсказания P, которая отражает ошибку теста. Простейшая стратегия состоит в том, чтобы разделить полный набор данных X на две части: обучить базовых учащихся на одной половине, позволить им предсказать другую половину и передать прогнозы мета-обучаемому. Этот метод прост и быстр, но некоторые данные будут потеряны. Для небольших и средних наборов данных потеря информации может быть серьезной, что приводит к снижению производительности базовых и мета-обучающихся.

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


Шаг 1. Определите базовую библиотеку для учащихся

Это модели, которые обрабатывают входные данные и генерируют прогнозы, будь то линейная регрессия, нейронная сеть или даже другой ансамбль. Как всегда, разнообразие — это сила! Единственное предостережение заключается в том, что чем больше моделей мы добавляем, тем медленнее работает ансамбль. Здесь я буду использовать предыдущую коллекцию моделей:

base_learners = get_models()

Шаг 2: Определите мета-обучающегося

Нет единого мнения о том, какой метод метаобучения следует использовать, но популярным выбором являются линейные модели, модели на основе ядра (машины опорных векторов и алгоритмы KNN) и модели на основе дерева решений. Вы также можете использовать другой ансамбль в качестве «мета-обучающего»: в этом конкретном случае вы получите двухслойный ансамбль, который чем-то похож на нейронную сеть с прямой связью.

Здесь мы будем использовать машину повышения градиента. Чтобы гарантировать, что GBM может исследовать локальные особенности, нам нужно ограничить каждую 1000 деревьев решений для обучения на случайном подмножестве из 4 базовых учеников и 50% входных данных. Таким образом, GBM выражает прогнозы каждого базового ученика в разных соседних входных пространствах.

meta_learner = GradientBoostingClassifier(
   n_estimators=1000,
   loss="exponential",
   max_features=4,
   max_depth=3,
   subsample=0.5,
   learning_rate=0.005, 
   random_state=SEED
)


Шаг 3. Определите шаги для создания обучающих и тестовых наборов

Для простоты мы разделим полный обучающий набор на обучающий набор и набор прогнозов для базового ученика. Этот подход иногда называют «смешиванием». Однако терминология варьируется в зависимости от сообщества, поэтому иногда бывает непросто узнать, какой тип перекрестной проверки использует ансамбль.

xtrain_base, xpred_base, ytrain_base, ypred_base = train_test_split(
   xtrain, ytrain, test_size=0.5, random_state=SEED)

Теперь у нас есть обучающий набор (X_train_base, y_train_base) и набор прогнозов (X_pred_base, y_pred_base) для базового учащегося, готовый для создания матрицы прогноза для мета-обучаемого.


Шаг 4: Обучите базового ученика на тренировочном наборе

Чтобы обучить базового учащегося на обучающих данных, мы запускаем как обычно:

def train_base_learners(base_learners, inp, out, verbose=True):
"""Train all base learners in the library."""
if verbose: print("Fitting models.")
for i, (name, m) in enumerate(base_learners.items()):
if verbose: print("%s..." % name, end=" ", flush=False)
       m.fit(inp, out)
if verbose: print("done")

Чтобы обучить базового ученика, нам нужно выполнить:

train_base_learners(base_learners, xtrain_base, ytrain_base)


Шаг 5: Сгенерируйте базовые прогнозы учащихся

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

В базовом наборе прогнозов учащегося мы генерируем набор базовых прогнозов учащегося:

Если вы реализуете свой собственный ансамбль, обратите особое внимание на то, как вы индексируете строки и столбцы матрицы предсказания — разбить данные на две части несложно, но для последующей перекрестной проверки это может быть затруднительно.

def predict_base_learners(pred_base_learners, inp, verbose=True):
"""Generate a prediction matrix."""
   P = np.zeros((inp.shape[0], len(pred_base_learners)))

if verbose: print("Generating base learner predictions.")
for i, (name, m) in enumerate(pred_base_learners.items()):
if verbose: print("%s..." % name, end=" ", flush=False)
       p = m.predict_proba(inp)
# With two classes, need only predictions for one class
       P[:, i] = p[:, 1]
if verbose: print("done")

return P

Для генерации прогнозов нам нужно выполнить:

P_base = predict_base_learners(base_learners, xpred_base)


Шаг 6: Обучите мета-обучение

Матрица прогнозирования P_base отражает производительность во время тестирования и может использоваться для обучения мета-обучения:

meta_learner.fit(P_base, ypred_base)

Вот и все! Теперь у нас есть полностью обученный ансамбль, который мы можем использовать для прогнозирования новых данных. Чтобы сгенерировать прогноз для наблюдения x(j), мы сначала передаем его базовому учащемуся. они выводят серию прогнозов

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

Теперь, когда у нас есть четкое представление об обучении ансамбля, пришло время посмотреть, как оно предсказывает набор данных политических пожертвований:

def ensemble_predict(base_learners, meta_learner, inp, verbose=True):
"""Generate predictions from the ensemble."""
   P_pred = predict_base_learners(base_learners, inp, verbose=verbose)
return P_pred, meta_learner.predict_proba(P_pred)[:, 1]

Для генерации прогнозов нам нужно выполнить:

P_pred, p = ensemble_predict(base_learners, meta_learner, xtest)
print("\nEnsemble ROC-AUC score: %.3f" % roc_auc_score(ytest, p))

Интегрированная оценка ROC-AUC: 0,881

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


Тренируйтесь с перекрестной проверкой

При обучении базового учащегося с перекрестной проверкой каждая резервная копия базового учащегося снабжается складками K-1 и прогнозируется для остальных сверток. Этот процесс повторяется до тех пор, пока не будет предсказана каждая складка. Чем больше складок мы указываем, тем меньше данных у нас будет на каждом проходе обучения. Это делает перекрестно проверенные прогнозы менее зашумленными во время тестирования и работает лучше. Но это значительно увеличивает время обучения. Подгонка ансамбля с помощью перекрестной проверки часто называется суммированием, а сам ансамбль называется Super Learner.

Чтобы понять, как работает перекрестная проверка, мы можем думать о ней как о внешнем цикле предыдущей интеграции. Внешний цикл выполняет итерацию по различным тестовым сверткам, в то время как остальные данные используются для обучения; внутренний цикл обучает базового учащегося и создает прогнозные данные. Вот простая реализация стека:

from sklearn.base import clone

def stacking(base_learners, meta_learner, X, y, generator):
"""Simple training routine for stacking."""

# Train final base learners for test time
print("Fitting final base learners...", end="")
   train_base_learners(base_learners, X, y, verbose=False)
print("done")

# Generate predictions for training meta learners
# Outer loop:
print("Generating cross-validated predictions...")
   cv_preds, cv_y = [], []
for i, (train_idx, test_idx) in enumerate(generator.split(X)):

       fold_xtrain, fold_ytrain = X[train_idx, :], y[train_idx]
       fold_xtest, fold_ytest = X[test_idx, :], y[test_idx]

# Inner loop: step 4 and 5
       fold_base_learners = {name: clone(model)
for name, model in base_learners.items()}
       train_base_learners(
           fold_base_learners, fold_xtrain, fold_ytrain, verbose=False)

       fold_P_base = predict_base_learners(
           fold_base_learners, fold_xtest, verbose=False)

       cv_preds.append(fold_P_base)
       cv_y.append(fold_ytest)
print("Fold %i done" % (i + 1))

print("CV-predictions done")

# Be careful to get rows in the right order
   cv_preds = np.vstack(cv_preds)
   cv_y = np.hstack(cv_y)

# Train meta learner
print("Fitting meta learner...", end="")
   meta_learner.fit(cv_preds, cv_y)
print("done")

return base_learners, meta_learner

Давайте взглянем на шаги, связанные здесь. Во-первых, мы подгоняем базового учащегося ко всем данным: в отличие от предыдущего гибридного ансамбля, базовый учащийся обучается на всех данных во время тестирования. Затем мы перебираем все складки, а затем все базовые обучающиеся, чтобы сгенерировать перекрестно проверенные прогнозы. Эти прогнозы складываются вместе, чтобы сформировать обучающий набор мета-обучения, который также обучается на всех данных.

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

from sklearn.model_selection import KFold

# Train with stacking
cv_base_learners, cv_meta_learner = stacking(
   get_models(), clone(meta_learner), xtrain.values, ytrain.values, KFold(2))

P_pred, p = ensemble_predict(cv_base_learners, cv_meta_learner, xtest, verbose=False)
print("\nEnsemble ROC-AUC score: %.3f" % roc_auc_score(ytest, p))

Интегрированная оценка ROC-AUC: 0,889


Стекирование дает значительный прирост производительности: на самом деле, он достигает текущего лучшего результата. Эта оценка очень типична для небольших и средних наборов данных, где эффект смешивания сильно взвешен. По мере увеличения размера набора данных производительность микширования и суммирования будет постепенно сходиться.

Стекирование также имеет свои недостатки, особенно скорость. Обычно в случае перекрестной проверки нам необходимо знать эти вопросы:

1. Вычислительная сложность

2. Структурная сложность (риск утечки информации)

3. Использование памяти

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


1. Вычислительная сложность

Предположим, нам нужно использовать 10-кратный стек. Это требует обучения всех базовых обучающихся 10 раз на 90% данных, а затем еще раз на всех данных. С 4 базовыми учениками ансамбль занимает примерно в 40 раз больше времени, чем лучший базовый ученик.

Но каждая cv-подгонка независима, поэтому нам не нужно подбирать модели последовательно. Если бы мы могли разместить все складки параллельно, ансамбль был бы всего в 4 раза медленнее, чем лучший базовый обучаемый модуль — огромное улучшение. Интеграция — это наибольшая выгода от распараллеливания, и крайне важно уметь пользоваться преимуществами этого механизма. Подходит для всех складок для всех моделей, а потеря времени для ансамбля незначительна. Чтобы представить это, приведенный ниже график представляет собой контрольный показатель для ML-Ensemble, показывающий время, необходимое для укладки или гибридных фитингов последовательно или параллельно на 4 потоках.

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


2. Структурная сложность

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


3. Использование памяти

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


Результат: использование инструментария

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

К счастью, сегодня существует множество инструментов для каждого популярного языка программирования, хотя и в разных вариантах. Я перечислю некоторые из них в конце этой статьи. Теперь давайте возьмем один из них и посмотрим, как ансамблевый подход работает с набором данных о политических пожертвованиях. Здесь мы используем ML-Ensemble для создания обобщенного ансамбля, о котором мы упоминали ранее, но теперь с 10-кратной перекрестной проверкой.

from mlens.ensemble import SuperLearner

# Instantiate the ensemble with 10 folds
sl = SuperLearner(
   folds=10,
   random_state=SEED,
   verbose=2,
   backend="multiprocessing"
)

# Add the base learners and the meta learner
sl.add(list(base_learners.values()), proba=True) 
sl.add_meta(meta_learner, proba=True)

# Train the ensemble
sl.fit(xtrain, ytrain)

# Predict the test set
p_sl = sl.predict_proba(xtest)

print("\nSuper Learner ROC-AUC score: %.3f" % roc_auc_score(ytest, p_sl[:, 1]))

Fitting 2 layers
Processing layer-1             done | 00:02:03
Processing layer-2             done | 00:00:03
Fit complete                        | 00:02:08

Predicting 2 layers
Processing layer-1             done | 00:00:50
Processing layer-2             done | 00:00:02
Predict complete                    | 00:00:54

Super Learner ROC-AUC score: 0.890

Это так просто!


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

plot_roc_curve(ytest, p.reshape(-1, 1), P.mean(axis=1), ["Simple average"], "Super Learner") 

продолжать

Помимо нескольких ансамблей, описанных в этой статье, существует множество других ансамблей, но все они имеют одинаковую основу: библиотека базовых обучаемых, мета-обучаемый и обучающая программа. Регулируя координацию этих компонентов, мы можем разработать различные формы коллекции. Смотрите эту статью для более продвинутых коллекций:ml wave.com/Card Reformed - Unther….

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


В следующей таблице перечислены некоторые из этих инструментов:

Оригинальная ссылка:woohoo.data quest.IO/blog/intro's…

Эта статья составлена ​​для ядра машины, пожалуйста, свяжитесь с этой общедоступной учетной записью для разрешения на перепечатку.