Методы работы со смещением и дисперсией в модельках машинного обучения
В давние времена были вечные битвы с переобучением и недообучением в модельках машинного обучения. Вечная битва между смещением и дисперсией. Подходит ли модель к данным как идеальный ключик, или же она скорее еле пытается влезть туда...
Эта дилемма определяет, насколько хорошо модель будет работать на реальных данных.
Переобучение и недообучение – это Сцилла и Харибда в ML, между которыми нужно лавировать. С тех давних времен появилось множество методов для решения этой проблемы. Рассмотрим их кратко.
Что такое смещения и дисперсия?
Смещение – это своего рода предубеждение модели, когда она не учитывает все особенности данных. Если мы обучаем модельку распознавать котиков на основе данных, в которых преобладают изображения сфинксов, модель, скорее всего, будет плохо справляться с идентификацией других пород. Это смещение, поскольку модель слишком узко знает мир котиков.
С такой проблемой смещения сталкиваются достаточно часто. Один из проектов, над которым мы работали, касался анализа пользовательского поведения на сайте. Мы использовали данные, собранные в определенном регионе, но когда компания масштабировалась на всю страну, модель была неэффективна. Причина? Смещение – модель была обучена на слишком узком наборе данных.
Визуально это можно представить, как если бы мы попытались аппроксимировать сложную кривую прямой линией – мы упускаем множество нюансов.
Это явление также известно как недообучение.
Дисперсия – это способность модели реагировать на незначительные колебания в тренировочных данных. Если модель слишком чувствительна и пытается подстроиться под каждый шум или аномалию в данных, это приводит к высокой дисперсии. Такие модели хорошо работают на тренировочных данных, но плохо – на новых, неизвестных данных.
Нередко инженеры создают чрезмерно сложные модели, которые идеально подходят под тренировочные данные, но полностью терпят крах на реальных данных.
Это явление также известно как переобучение.
Итак, смещение измеряет, насколько предсказания модели в среднем отличаются от истинных значений. Дисперсия же показывает, насколько предсказания модели изменятся, если обучать её на разных наборах данных внутри одного и того же распределения.
Методы уменьшения смещения и дисперсии
Регуляризация
Регуляризация - это техника, используемая для предотвращения переобучения модели на обучающих данных, которое обычно происходит за счет увеличения дисперсии. Смысл регуляризации заключается в введении дополнительных ограничений или штрафов на величину и/или сложность модели.
Основная цель регуляризации - обеспечить более гладкую и устойчивую модель, которая лучше обобщает закономерности и уменьшает риск переобучения, сохраняя при этом адекватный уровень смещения. Существует два типа регуляризации:
L1 (Lasso) регуляризация
L1 регуляризация добавляет к функции потерь модели штраф, равный сумме абсолютных значений коэффициентов (весов) модели. Этот подход направлен на минимизацию не только ошибки прогнозирования, но и сложности самой модели.
Математически, если рассматривать функцию потерь L, то штраф L1 регуляризации представляется как
, где wi - веса модели, а λ - гиперпараметр, контролирующий силу регуляризации.
Подбор λ часто осуществляется с использованием кросс-валидации. Большое значение λ приводит к большему штрафу, что может привести к чрезмерному сжатию весов и потере важной информации. Но вот дилемма - слишком маленькое значение делает эффект регуляризации недостаточным, сохраняя риск переобучения.
Штраф заставляет модель сжимать веса. В отличие от L2 регуляризации (рассмотрим ее чуть ниже), которая стремится уменьшать веса более плавно, L1 может привести к тому, что некоторые веса станут равными нулю. Это означает, что L1 регуляризация может полностью исключить некоторые признаки из модели.
Основной плюс L1 регуляризации - это способность создавать разреженные модели, т.е. модели, где только небольшое количество признаков имеет ненулевые веса. Все это полезно в ситуациях, где признаков много, но только некоторые из них важны для прогнозирования.
Поскольку L1 регуляризация может обнулить веса некоторых признаков, она естественным образом включает механизм выбора признаков.
Пример:
import numpy as np
from sklearn.linear_model import Lasso
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_regression
import matplotlib.pyplot as plt
X, y = make_regression(n_samples=100, n_features=10, noise=0.1, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
lasso = Lasso(alpha=0.1) # alpha здесь это λ
lasso.fit(X_train, y_train)
print("Коэффициенты модели:", lasso.coef_)
plt.plot(lasso.coef_, marker='o')
plt.title("Веса признаков с L1 регуляризацией")
plt.xlabel("Номер признака")
plt.ylabel("Величина веса")
plt.show()
L2 (Ridge) регуляризация
В L2 регуляризации к функции потерь добавляется штраф, пропорциональный квадрату величины весов модели. Это заставляет модель не только минимизировать ошибку на обучающих данных, но и сохранять веса как можно меньшими.
Если функция потерь модели обозначена как L, штраф L2 регуляризации выражается как
wi - веса модели, а λ - гиперпараметр, контролирующий силу регуляризации.
Гиперпараметр λ в L2 регуляризации определяет степень, в которой веса будут уменьшаться. Его выбор часто осуществляется с помощью кросс-валидации.
Большие значения λ приведут к значительному уменьшению весов, что может увеличить смещение модели, в то время как слишком маленькие значения могут оставить модель подверженной переобучению.
Путём уменьшения величины весов, L2 регуляризация помогает снизить дисперсию модели, что делает её менее чувствительной к отдельным точкам данных, и таким образом уменьшает риск переобучения.
В отличие от L1 регуляризации, L2 не склонна обнулять веса. Вместо этого она уменьшает веса постепенно, делая модель более "гладкой" и менее подверженной влиянию шума в данных.
L2 регуляризация полезна в ситуациях, когда количество признаков в данных велико или когда они сильно коррелированы. Она помогает справляться с мультиколлинеарностью, сохраняя все признаки, но уменьшая их влияние.
Бэггинг
Бэггинг, или Bootstrap Aggregating, является ансамблевым методом, предназначенным для улучшения стабильности и точности моделей, снижения вариативности и избежания переобучения. Он основан на принципе объединения нескольких простых моделей для создания одной устойчивой и надежной модели.
В бэггинге создаются множественные подвыборки из обучающих данных с использованием метода бутстрапа (повторной выборки с возвращением). Каждая подвыборка имеет тот же размер, что и исходный обучающий набор данных. На каждой подвыборке обучается отдельная модель. Модели обычно одного типа.
Прогнозы от всех моделей агрегируются для получения окончательного результата. Это может быть среднее (для задач регрессии) или голосование по большинству (для задач классификации).
Основной эффект бэггинга — снижение дисперсии модели без увеличения смещения. Поскольку отдельные модели обучаются на немного разных данных, их ошибки частично компенсируют друг друга при агрегировании, в результате чего общая вариативность уменьшается.
Одним из наиболее известных примеров бэггинга является метод случайных лесов, где используются множественные деревья решений. В случайных лесах вводится дополнительный элемент случайности при выборе признаков для каждого разделения, что еще больше увеличивает разнообразие среди деревьев
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X, y = make_classification(n_samples=1000, n_features=20,
n_informative=15, n_redundant=5,
random_state=42)
# делим данные на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# модель случайного леса
rf_model = RandomForestClassifier(n_estimators=100, # количество деревьев
max_features='sqrt', # количество признаков для каждого разделения
random_state=42)
rf_model.fit(X_train, y_train)
predictions = rf_model.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
Бустинг
Бустинг — это ансамблевая стратегия, предназначенная для создания сильного классификатора из последовательности слабых классификаторов.Метод фокусируется на постепенном улучшении прогнозов путем исправления ошибок предыдущих моделей в ансамбле.
В отличие от бэггинга, где модели обучаются независимо, бустинг обучает каждую новую модель, чтобы исправить ошибки, сделанные предыдущими. Это достигается путем увеличения весов для неправильно классифицированных примеров.
Бустинг начинается с простой модели и постепенно увеличивает её сложность, добавляя новые модели, которые фокусируются на самых сложных для предсказания примерах.
Прогнозы всех моделей комбинируются (например, через взвешенное голосование или среднее) для получения окончательного предсказания. Веса каждой модели обычно зависят от её точности.
Основная способность бустинга - уменьшать смещение. Каждая последующая модель в ансамбле стремится исправить оставшиеся ошибки, что приводит к улучшению общей точности ансамбля.
Хотя бустинг эффективно уменьшает смещение, он может увеличить дисперсию, особенно если количество моделей в ансамбле велико. Переобучение становится возможным, если бустинг продолжает уменьшать ошибки на обучающем наборе данных, потеряв при этом способность к обобщению.
Существует несколько реализаций бустинга, в ихчисле: XGBoost, LightGBM, CatBoost.
Рассмотрим XGBoost:
XGBoost - это реализация градиентного бустинга. Библиотека на питоне оптимизирована для работы как на одном компьютере, так и в распределенных системах, и кстати его часто можно увидеть в соревнованиях на Kaggle.
XGBoost поддерживает различные типы данных, включая числовые и категориальные признаки, а также включает в себя L1 и L2 регуляризацию, которая помогает предотвратить переобучение, улучшая обобщающую способность модели.
Пример использования XGBoost для задачи классификации:
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)
# зададим параметры
params = {
'max_depth': 3,
'eta': 0.1,
'objective': 'multi:softprob',
'num_class': 3
}
bst = xgb.train(params, dtrain, num_boost_round=100)
# предсказание
preds = bst.predict(dtest)
predictions = np.asarray([np.argmax(line) for line in preds])
accuracy = accuracy_score(y_test, predictions)
Используем набор данных Iris, далее разделяем данные на обучающую и тестовую выборки. Преобразуем данные в DMatrix
, оптимизированный формат данных, используемый XGBoost. Задаём параметры модели, включая глубину дерева и скорость обучения. Обучаем модель и делаем предсказания.
Кросс-валидация
Кросс-валидация включает в себя разделение датасета на несколько подмножеств и последовательное обучение модели на этих подмножествах, что позволяет более точно оценить её способность к обобщению на новых данных.
При кросс-валидации датасет делится на k частей (или фолдов). Модель обучается на k-1 из этих фолдов и проверяется на оставшемся фолде. Этот процесс повторяется k раз, так что каждый фолд используется в качестве тестового набора данных ровно один разз.
Кросс-валидация помогает в двух основных моментах управления смещением и дисперсией:
Кросс-валидация обеспечивает точную оценку того, как модель будет работать на невидимых данных. Это помогает выявить и избежать дисперсии и смещения.
При помощи кросс-валидации можно сравнивать различные модели и их конфигурации. Таким образом, можно выбрать модель и гиперпараметры, которые обеспечивают оптимальный баланс между смещением и дисперсией.
К примеру:
from sklearn.datasets import make_regression
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LinearRegression
import numpy as np
X, y = make_regression(n_samples=100, n_features=2, noise=0.1)
model = LinearRegression()
scores = cross_val_score(model, X, y, cv=5, scoring='neg_mean_squared_error')
print("Средняя ошибка MSE по 5 фолдам:", np.mean(np.abs(scores)))
Средняя ошибка MSE по 5 фолдам: 0.012018403847732537
Завершить статью хочу рекомендацией специализации Machine Learning от моих коллег из OTUS. В рамках специализации за 12 месяцев можно прокачаться с нуля до Middle ML инженера. А на странице курса любой желающий может зарегистрироваться на бесплатный вебинар курса.