Что происходит с MAE при дифференцировании?

Ответ

MAE (Mean Absolute Error) не является гладкой функцией из-за использования модуля разности. Её производная (градиент) в точке, где разность между предсказанием и истинным значением равна нулю, не определена (имеет разрыв). Это создаёт проблемы для градиентных методов оптимизации, таких как SGD.

Почему это проблема? Градиент MAE равен знаку ошибки (sign(y_pred - y_true)), который в нуле совершает скачок от -1 к +1. Это может привести к нестабильности и замедлению сходимости, когда обновления параметров колеблются около оптимума.

Пример градиента MAE в NumPy:

import numpy as np

def mae_gradient(y_true, y_pred):
    """Возвращает градиент MAE по предсказаниям (y_pred)."""
    # np.sign возвращает -1 для отрицательных значений, 0 для нуля, 1 для положительных.
    # Именно ноль (точка недифференцируемости) вызывает сложности.
    return np.sign(y_pred - y_true) / len(y_true)

Практические решения:

  • Использовать Huber Loss, которая ведёт себя как MAE при больших ошибках, но как MSE (гладкая) вблизи нуля, что обеспечивает определённый градиент.
  • Применить сглаживание MAE, добавив очень маленький параметр epsilon (например, 1e-8) внутрь квадратного корня: sqrt((y_pred - y_true)^2 + epsilon). Это даёт приближённый, но гладкий градиент.

Ответ 18+ 🔞

Ну, представляешь, сидит наш алгоритм, тупо считает себе ошибки, всё вроде норм. А потом бац — и упёрся в эту самую MAE. И тут начинается цирк, ёпта.

Смотри, в чём прикол. Эта функция, она же как упрямый осёл — в самой главной точке, где разница между тем, что нагадал, и правдой равна нулю, у неё производная с ума сходит. Ну то есть не определена вообще, блядь. Градиент там делает прыжок с -1 на +1, как сумасшедший. И представь, наш бедный SGD пытается по этому обрыву спуститься к оптимуму. Он же там будет прыгать туда-сюда, как блоха на сковородке! Сходимость замедлится, стабильность — к ебеням. Волнение ебать просто, когда видишь эти колебания.

Почему так вышло? Да потому что внутри у неё модуль, эта манда с ушами. Его градиент — это просто знак ошибки. А знак в нуле — он что? Ноль? Ага, щас. На практике np.sign для нуля как раз ноль и возвращает, но сама математическая производная — она там с разрывом. Это как пытаться съехать с горки, а посередине у неё отвесная ступенька в метр. Хуй с горы скатишься, а не градиент.

Вот, полюбуйся, как это в коде выглядит:

import numpy as np

def mae_gradient(y_true, y_pred):
    """Возвращает градиент MAE по предсказаниям (y_pred)."""
    # np.sign возвращает -1 для отрицательных значений, 0 для нуля, 1 для положительных.
    # Именно ноль (точка недифференцируемости) вызывает сложности.
    return np.sign(y_pred - y_true) / len(y_true)

Видишь этот np.sign? Вот он-то и есть корень всех бед. Когда y_pred - y_true близко к нулю, градиент становится либо -0.0, либо +0.0, либо вообще 0. И всё, приехали. Оптимизатор начинает бздеть на месте, не зная, куда двигаться. Терпения ноль ебать с такой штукой работать.

И что делать-то, спросишь? А вот что, умные люди уже всё придумали:

  • Юзать Huber Loss. Это такая хитрая жопа. При больших косяках она суровая, как MAE, а когда ошибка маленькая — становится гладкой и пушистой, как MSE. И градиент у неё в нуле определён! Красота.
  • Сделать MAE чуть менее резкой. Можно добавить крошечный epsilon (типа 1e-8) под корень: sqrt((y_pred - y_true)^2 + epsilon). Получится почти MAE, но градиент будет гладким, без этих дурацких скачков. Овердохуища простая, но рабочая идея.

Короче, MAE — она хоть и понятная, но для градиентного спуска — пизда рулю в чистом виде. Нужно либо мириться с её прыжками, либо брать что-то более цивильное.