Можно ли дешево исправить проблему использования модуля в градиентном спуске
Что проверяют
Понимаете ли вы, что именно ломает модуль: не «нестабильность вообще», а излом в нуле и градиент постоянной величины. Следом просят написать сглаженную версию и объяснить, чем она лучше простой замены на MSE.
Как отвечать
Сначала назовите проблему точно. У |x| нет производной в нуле, а вокруг него градиент равен sign(x), то есть ±1, как бы близко к минимуму вы ни были. С постоянным шагом спуск не сходится в точку, а прыгает вокруг неё с амплитудой порядка learning rate. Это касается и MAE как функции потерь, и L1-регуляризации.
Дешёвые исправления, от самого простого:
- субградиент: брать
sign(x)и доопределить нулём в нуле. Фреймворки так уже делают, уtorch.absградиент в нуле равен 0, падения нет; - затухающий шаг: субградиентный метод сходится при уменьшающемся learning rate, хватает обычного scheduler;
- гладкая замена:
sqrt(x² + ε²)или Huber loss, квадратичная около нуля и линейная дальше. В PyTorch этоnn.HuberLoss(delta=...)илиnn.SmoothL1Loss(beta=...), одна строка вместоnn.L1Loss; - для L1-регуляризации: проксимальный шаг (soft thresholding, как в ISTA). Он даёт точные нули в весах, чего субградиент не даёт.
Главное преимущество Huber перед заменой на MSE: он сохраняет устойчивость к выбросам, ради которой модуль обычно и выбирали. Параметр delta задаёт, с какой ошибки точка считается выбросом, его подбирают под масштаб таргета.
Где спотыкаются
Предлагают сразу перейти на MSE. Это другая задача: квадрат раздувает вклад выбросов, а оптимум сдвигается от медианы к среднему.
Говорят, что Adam или RMSprop решат проблему. Адаптивный шаг масштабирует градиент, но излом в нуле никуда не девается.
Берут крошечный ε в сглаживании «чтобы было точнее» и получают почти тот же модуль с огромной кривизной около нуля. ε выбирают в масштабе ошибок, а не как 1e-12.