Lasso, Ridge и кастомные регуляризаторы: основы

1fa6a30657d5dfd3a58d974568273c7c.png

Сегодня разберем тему, которая хоть и звучит скромно — Lasso, Ridge и кастомные регуляризаторы, —, но на практике буквально спасает модели от переобучения. Если у вас бывало так, что модель на тренировочных данных показывает отличные результаты, а при проверке на валидации теряет весь блеск — поздравляю, вы столкнулись с тем самым переобучением! Регуляризация здесь как раз для того и нужна: помогает «усмирить» модель, добавляя ограничения, которые не дают ей запоминать лишние детали.

В этой статье кратко рассмотрим, как применить классические регуляризаторы Lasso и Ridge в Keras, а также создадим кастомные регуляризаторы, чтобы лучше контролировать поведение моделей.

Основы: Lasso и Ridge

Зачем нужны Lasso и Ridge?

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

  • Lasso (L1-регуляризация) добавляет штраф, пропорциональный абсолютной величине весов. Представьте, что есть веса, и хочется постепенно убрать из модели незначительные признаки — Lasso идеально подходит для этой задачи. Его фича в том, что при достаточно сильном регуляризационном параметре некоторые веса могут буквально обнуляться, благодаря чему модель отбрасывает лишние признаки. Это полезным для моментов, когда нужно сократить количество признаков или провести что-то вроде feature selection.

  • Ridge (L2-регуляризация) действует по-другому: он добавляет штраф, пропорциональный квадрату значений весов. Вместо того чтобы обнулять веса, Ridge уменьшает их, сглаживая большие значения. Этот подход хорош, когда нужно посчитать, что все признаки полезны, но некоторым из них стоит «снизить тон». Ridge удобен в задачах, где каждый признак несет определенный вклад в результат, и ни один из них нельзя совсем убрать.

В итоге:

  • Lasso имеет тенденцию к созданию разреженных моделей, где многие веса обнуляются.

  • Ridge снижает веса, не доводя их до нуля, поэтому он работает «мягче».

Реализация Lasso и Ridge в Keras

Можно добавить L1 или L2 регуляризацию, буквально указав параметр kernel_regularizer при создании слоев.

Пример Lasso-регуляризации

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.regularizers import l1

# Создаем модель с L1-регуляризацией
model = Sequential([
    Dense(64, input_dim=100, activation='relu', kernel_regularizer=l1(0.01)),
    Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam', loss='binary_crossentropy')

Указали kernel_regularizer=l1(0.01). Этот коэффициент определяет, насколько сильно L1-регуляризация влияет на веса. Если значение штрафа слишком высокое, модель может стать «скупой» на зависимости, теряя сложные, но нужные признаки. Для большинства задач полезно начать с небольшого значения, например 0.001 или 0.01, и постепенно его увеличивать, наблюдая за изменениями в качестве модели.

Чем больше значение, тем сильнее штраф и тем больше весов обнуляется.

Пример Ridge-регуляризации

from tensorflow.keras.regularizers import l2

# Создаем модель с L2-регуляризацией
model = Sequential([
    Dense(64, input_dim=100, activation='relu', kernel_regularizer=l2(0.01)),
    Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam', loss='binary_crossentropy')

Здесь указали kernel_regularizer=l2(0.01), что также контролирует величину штрафа. Этот параметр действует как «приглушение» для значений весов, уменьшая их, но сохраняя неравенство между ними. L2-регуляризация рекомендуется, если мы уверены, что все признаки значимы и не хотим их обнулять.

Комбинированные методы: L1 + L2

Для сложных задач часто используют комбинацию L1 и L2, которая называется Elastic Net. Keras поддерживает такую комбинацию через l1_l2.

Пример Elastic Net

from tensorflow.keras.regularizers import l1_l2

model = Sequential([
    Dense(64, input_dim=100, activation='relu', kernel_regularizer=l1_l2(l1=0.01, l2=0.01)),
    Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam', loss='binary_crossentropy')

Этот метод позволяет одновременно сокращать лишние признаки (L1) и уменьшать большие значения весов (L2), предлагая баланс между двумя типами регуляризации.

Кастомные регуляризаторы: когда L1 и L2 недостаточно

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

К примеру, обнулим все веса, которые превышают пороговое значение 0.1.

from tensorflow.keras.regularizers import Regularizer
import tensorflow as tf

class CustomThresholdRegularizer(Regularizer):
    def __init__(self, threshold=0.1):
        self.threshold = threshold

    def __call__(self, x):
        return tf.reduce_sum(tf.where(tf.abs(x) > self.threshold, tf.square(x), tf.zeros_like(x)))

    def get_config(self):
        return {'threshold': float(self.threshold)}

# Используем кастомный регуляризатор в модели
model = Sequential([
    Dense(64, input_dim=100, activation='relu', kernel_regularizer=CustomThresholdRegularizer(0.1)),
    Dense(1, activation='sigmoid')
])

Здесь tf.where проверяет, превышает ли значение веса порог 0.1, и если да, применяет штраф (в данном случае, квадрат веса).

Dropout как регуляризатор

Dropout — один из самых хороших методов регуляризации, хотя формально он не является L1 или L2. Суть Dropout в том, что на каждой итерации обучения случайно отключаются определенные нейроны, тем самым модель «привыкает» не полагаться на конкретные узлы. Dropout применяется в основном к Dense слоям, а также к свёрточным (для сверточных слоев применяется SpatialDropout2D).

Пример использования Dropout

from tensorflow.keras.layers import Dropout

model = Sequential([
    Dense(64, input_dim=100, activation='relu'),
    Dropout(0.5),
    Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam', loss='binary_crossentropy')

Dropout(0.5) означает, что у каждого нейрона есть 50% вероятность отключения на каждой итерации.

Начните с Dropout вероятности от 0.2 до 0.5 и подберите оптимальное значение экспериментально.

Комбинированная модель с L2 и Dropout

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

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.regularizers import l2

model = Sequential([
    Dense(64, input_dim=100, activation='relu', kernel_regularizer=l2(0.01)),
    Dropout(0.5),
    Dense(64, activation='relu', kernel_regularizer=l2(0.01)),
    Dropout(0.5),
    Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# Обучение модели
# model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=50)

Здесь на каждом слое применяется L2-регуляризация с коэффициентом 0.01, а также Dropout с вероятностью 0.5.

Заключение

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

18 ноября в Otus пройдет открытый урок на тему «AutoML и подбор гипер-параметров». Участников занятия ждет практика на примере популярных библиотек pycaret и hyperopt. Если интересно, записывайтесь по ссылке.

© Habrahabr.ru