Что такое переобучение и как его избежать: основы
Если вы только начинаете свой путь в машинном обучении или уже успели столкнуться с его непредсказуемыми сюрпризами, то сегодняшняя статья для тех, кто хочет понять и победить переобучение (оно же overfitting).
Что такое переобучение?
Итак, представьте себе, что вы учите свою модель машинного обучения танцевать сальсу. Если вы показываете ей только один и тот же танец снова и снова, она, возможно, станет мастером этого конкретного танца. Она идеально запомнит каждое движение, каждую паузу, каждое искривление тела. Но когда вы попросите её исполнить другую комбинацию шагов — бац — она потеряет ритм и запнется. Вот в чем суть переобучения: модель слишком хорошо запоминает тренировочные данные, включая все шумы и случайности, и, как результат, плохо справляется с новыми, невиданными ранее данными.
В ML цель состоит в том, чтобы модель научилась обобщать — то есть, делать правильные предсказания не только на тех данных, на которых она училась, но и на новых, ранее невиданных данных. Когда модель переобучается, она превращается в своего рода «эксперта» по тренировочным данным, но при этом теряет способность к обобщению.
С технической точки зрения, переобучение происходит, когда модель имеет слишком много параметров относительно объёма и разнообразия тренировочных данных. Модель начинает подстраиваться под шумы и случайные отклонения в данных, вместо того чтобы уловить истинные закономерности. Это часто встречается в сложных моделях с высокой степенью свободы (например, глубокие нейронные сети с большим количеством слоев и узлов).
Как избежать переобучения?
Существует множество проверенных методов, которые помогут вашей модели оставаться в форме и не превращаться в заученного робота.
Разделение данных на тренировочные и тестовые
Первый и, пожалуй, самый очевидный шаг — разделить данные на тренировочные и тестовые. Тренировочные данные используются для обучения модели, а тестовые — для оценки её способности обобщать информацию.
Важно не только разделить данные, но и убедиться, что они разделены случайно. Если данные упорядочены каким‑либо образом (например, по времени или по классу), модель может получить неправильное представление о распределении данных, что приведёт к завышенным результатам на тренировке и ужасным — на тесте.
Кроме того, иногда полезно использовать отдельную валидационную выборку, в особенности при настройке гиперпараметров модели.
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_boston
import pandas as pd
# Загружаем датасет
data = load_boston()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = data.target
# Разделяем данные: 80% на тренировку, 20% на тест
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f'Тренировочных примеров: {X_train.shape[0]}')
print(f'Тестовых примеров: {X_test.shape[0]}')
Всегда проверяйте, чтобы разделение было случайным. Иначе модель может запомнить порядок данных, что приведет к завышенным результатам на тренировке и ужасным — на тесте.
Кросс-валидация
Кросс-валидация — это способ более надежно оценить модель, проверив ее на нескольких поднаборах данных. Один из самых популярных методов — k-fold кросс-валидация. Кросс-валидация помогает избежать случайных ошибок, связанных с разделением данных.
k-fold кросс-валидация делит данные на k равных частей (фолдов). Модель обучается на k-1 фолдах и тестируется на оставшемся. Этот процесс повторяется k раз, при этом каждый фолд используется как тестовый ровно один раз. Ср. значение метрики по всем фолдам даёт итоговую оценку модели.
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LinearRegression
import numpy as np
model = LinearRegression()
# Используем 5-фолд кросс-валидацию
scores = cross_val_score(model, X_train, y_train, cv=5, scoring='neg_mean_squared_error')
print(f'Средняя ошибка: {-np.mean(scores):.2f}')
print(f'Стандартное отклонение: {np.std(scores):.2f}')
Кросс‑валидация позволяет модели пройти несколько «экзаменов» на разных наборах данных, что делает оценку её способностей более объективной. Условно: если пройти несколько тестов вместо одного — шанс провалиться заметно уменьшается.
Регуляризация
Регуляризация добавляет штраф за сложность модели, помогая предотвратить переобучение. Существует несколько видов регуляризации, но мы остановимся на Lasso (L1) и Ridge (L2) регрессиях.
Регуляризация помогает контролировать весовые коэффициенты модели, уменьшая их величину и, соответственно, снижая сложность модели.
Ridge (L2) регрессия добавляет штраф, пропорциональный квадрату коэффициентов. Это приводит к уменьшению величины коэффициентов, но не делает их нулевыми.
Lasso (L1) регрессия добавляет штраф, пропорциональный абсолютному значению коэффициентов. Это может привести к обнулению некоторых коэффициентов, что эффективно выполняет отбор признаков.
from sklearn.linear_model import Ridge, Lasso
from sklearn.metrics import mean_squared_error
# Ridge регрессия с alpha=1.0
ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)
y_pred_ridge = ridge.predict(X_test)
mse_ridge = mean_squared_error(y_test, y_pred_ridge)
print(f'MSE Ridge: {mse_ridge:.2f}')
# Lasso регрессия с alpha=0.1
lasso = Lasso(alpha=0.1)
lasso.fit(X_train, y_train)
y_pred_lasso = lasso.predict(X_test)
mse_lasso = mean_squared_error(y_test, y_pred_lasso)
print(f'MSE Lasso: {mse_lasso:.2f}')
Меньше параметров — меньше шансов на переобучение.
Отсечение в деревьях решений
Если вы используете деревья решений, важно не дать им разрастись как дикие кусты. Отсечение помогает уменьшить их сложность.
Деревья решений имеют тенденцию расти до максимальной глубины, чтобы точно соответствовать тренировочным данным. Это приводит к созданию сложных и неинтерпретируемых моделей, которые плохо обобщают. Отсечение сокращает дерево, удаляя ветви, которые не приносят значимого улучшения производительности.
Параметры, влияющие на отсечение:
max_depth: максимальная глубина дерева.
min_samples_split: минимальное количество образцов, необходимых для разделения узла.
min_samples_leaf: минимальное количество образцов, необходимых в листовом узле.
max_leaf_nodes: максимальное количество листьев.
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error
# Дерево решений с ограничением глубины до 5 и минимальным числом образцов для разделения
tree = DecisionTreeRegressor(max_depth=5, min_samples_split=10, random_state=42)
tree.fit(X_train, y_train)
y_pred_tree = tree.predict(X_test)
mse_tree = mean_squared_error(y_test, y_pred_tree)
print(f'MSE Decision Tree: {mse_tree:.2f}')
Меньше уровней — меньше возможностей для переобучения.
Использование большего объема данных
Если модель все еще склонна к переобучению, возможно, ей просто не хватает данных. Сбор большего объёма данных или использование методов аугментации может значительно помочь.
Больший объем данных предоставляет модели больше примеров для обучения, что способствует лучшему выявлению истинных закономерностей и уменьшает влияние случайных шумов. В случаях, когда собрать больше данных сложно, можно использовать методы аугментации — искусственно увеличивать размер набора данных путём модификации существующих примеров.
Если данные ограничены, модель вынуждена гадать и, возможно, запоминать детали вместо обобщения.
Dropout для нейронных сетей
Если вы работаете с нейросетями, dropout — отличный способ избежать переобучения. Этот метод случайно «выключает» нейроны во время обучения, заставляя сеть учиться более устойчивым признакам.
Dropout предотвращает переобучение, устраняя зависимость нейронов друг от друга. Это вынуждает сеть находить более обобщённые признаки, т.к каждый нейрон должен быть полезен независимо от других.
rate: доля нейронов, которые будут случайно отключены (обычно 0.2–0.5).
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from sklearn.metrics import mean_squared_error
model = Sequential([
Dense(64, activation='relu', input_shape=(X_train.shape[1],)),
Dropout(0.5),
Dense(64, activation='relu'),
Dropout(0.5),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
history = model.fit(X_train, y_train, epochs=100, batch_size=32,
validation_split=0.2, verbose=0)
loss = model.evaluate(X_test, y_test)
print(f'Test Loss: {loss:.2f}')
Dense(64, activation='relu', input_shape=(X_train.shape[1],))
: первый скрытый слой с 64 нейронами и функцией активации ReLU. input_shape
указывает размер входных данных.
Dropout(0.5)
: слой dropout, который случайно отключает 50% нейронов предыдущего слоя во время каждой итерации обучения.
Второй Dense(64, activation='relu')
и Dropout(0.5)
: аналогичные слои для второго скрытого слоя.
Dense(1)
: выходной слой с одним нейроном для предсказания целевой переменной.
Раннее прекращение обучения
Раннее прекращение обучения — это метод, который останавливает процесс обучения, как только модель начинает переобучаться на валидационных данных.
Параметры Early Stopping:
monitor: метрика, за которой нужно следить (например, 'val_loss').
patience: количество эпох без улучшения, после которых обучение будет остановлено.
restore_best_weights: восстанавливает веса модели с лучшей производительностью.
from tensorflow.keras.callbacks import EarlyStopping
early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
model = Sequential([
Dense(64, activation='relu', input_shape=(X_train.shape[1],)),
Dropout(0.5),
Dense(64, activation='relu'),
Dropout(0.5),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
history = model.fit(X_train, y_train, epochs=100, batch_size=32,
validation_split=0.2, callbacks=[early_stop], verbose=0)
loss = model.evaluate(X_test, y_test)
print(f'Test Loss with Early Stopping: {loss:.2f}')
monitor='val_loss'
: наблюдать за метрикой потерь на валидационной выборке.
patience=10
: если val_loss
не улучшается в течение 10 эпох, остановить обучение.
restore_best_weights=True
: восстановить веса модели с лучшей производительностью.
Отбор признаков
Иногда переобучение возникает из-за слишком большого количества признаков. Отбор наиболее значимых признаков помогает модели сосредоточиться на важном.
Избыточные или нерелевантные признаки могут вносить шум и усложнять модель, что увеличивает риск переобучения. Отбор признаков позволяет уменьшить размерность данных, повысить интерпретируемость модели и улучшить её обобщающую способность.
Методы отбора признаков:
Filter methods: используют статистические тесты для оценки значимости признаков (например, SelectKBest).
Wrapper methods: оценивают различные комбинации признаков на основе производительности модели (например, Recursive Feature Elimination).
Embedded methods: встроенные методы отбора признаков в процессе обучения модели (например, Lasso).
from sklearn.feature_selection import SelectKBest, f_regression
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# Отбор 5 лучших признаков
selector = SelectKBest(score_func=f_regression, k=5)
X_train_selected = selector.fit_transform(X_train, y_train)
X_test_selected = selector.transform(X_test)
model = LinearRegression()
model.fit(X_train_selected, y_train)
y_pred_selected = model.predict(X_test_selected)
mse_selected = mean_squared_error(y_test, y_pred_selected)
print(f'MSE after Feature Selection: {mse_selected:.2f}')
SelectKBest(score_func=f_regression, k=5)
: выбирает 5 лучших признаков на основе теста регрессии Фишера.
fit_transform
: обучает селектор на тренировочных данных и трансформирует их, выбирая только лучшие признаки.
Заключение
Экспериментируйте с этими методами, комбинируйте их и настраивайте под ваши конкретные задачи. Важно не только создать модель, которая работает на бумаге, но и та, которая готова к любым проблемам.
Все актуальные методы и инструменты машинного обучения вы можете изучить в рамках практических онлайн-курсов от экспертов отрасли. Также приглашаем всех желающих на открытый урок 2 декабря — «ML Advanced: всё, что вы хотели знать о методах машинного обучения, но боялись спросить». Записывайтесь на урок по ссылке.