Почему одна хорошая модель может быть слишком капризной
Некоторые алгоритмы, особенно деревья решений, очень чувствительны к конкретной обучающей выборке. Чуть изменились данные — структура решения уже заметно другая. Bagging появился как очень практичная идея: если одна модель нестабильна, можно обучить много похожих моделей на слегка разных подвыборках и потом объединить их ответы. Отдельные случайные ошибки начнут сглаживаться, а итоговое решение станет устойчивее.
Это важное инженерное мышление: мы не пытаемся сделать один алгоритм идеальным. Мы уменьшаем его хрупкость за счет ансамбля.
Как выглядит усреднение в bagging
$$ \hat y = \frac{1}{B}\sum_{b=1}^{B} \hat y_b $$Bagging усредняет предсказания B моделей, обученных на разных bootstrap-выборках. За счет этого шум и нестабильность отдельных моделей частично компенсируются.
\hat y— итоговый ансамблевый прогнозB— число моделей в ансамбле\hat y_b— предсказание b-й модели
Если речь о классификации, вместо обычного среднего часто используют голосование по классам. Но смысл остается тем же: один ответ мало надежен, а коллективный ответ обычно устойчивее.
Почему это особенно хорошо работает с деревьями
Деревья — сильные, но нестабильные learners. Они легко подстраиваются под данные и потому дают большой разброс поведения на разных подвыборках. Именно поэтому bagging так естественно сцепляется с деревьями. Случайный лес — самый известный пример этой идеи, где к bootstrap добавляется еще и случайный выбор подмножества признаков.
Но bagging полезен не как исторический термин, а как способ понять, откуда вообще появляется устойчивость ансамблей.
Как это показать на Python
import pandas as pd # задаем компактный набор данных для классификации
from sklearn.tree import DecisionTreeClassifier # берем нестабильную базовую модель
from sklearn.ensemble import BaggingClassifier # строим ансамбль на bootstrap-подвыборках
frame = pd.DataFrame({ # создаем поведенческие признаки пользователя
'sessions': [1, 2, 3, 4, 5, 6, 7, 8], # количество сессий
'avg_time': [2, 3, 3, 4, 5, 6, 8, 9], # длительность активности
'target': [0, 0, 0, 0, 1, 1, 1, 1], # бинарный target
})
X = frame[['sessions', 'avg_time']] # выделяем матрицу признаков
y = frame['target'] # сохраняем target отдельно
bag = BaggingClassifier(estimator=DecisionTreeClassifier(max_depth=3), n_estimators=100, random_state=42) # усредняем 100 деревьев на bootstrap-выборках
bag.fit(X, y) # обучаем ансамбль уменьшать нестабильность базовой модели
print(bag.predict(X).tolist()) # смотрим итоговые решения ансамбляBagging полезно понимать как универсальную идею: усреднение — это не только про повышение качества, но и про снижение нервозности модели по отношению к случайным колебаниям выборки.