← Назад к вопросам

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

2.4 Senior🔥 91 комментариев
#Машинное обучение#Метрики и оценка моделей

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Дифференциация MAE (Mean Absolute Error): проблема недифференцируемости

MAE — популярная функция потерь в регрессии, но она имеет критическую проблему при дифференциации, о которой нужно знать каждому Data Scientist.

Определение MAE

MAE (Mean Absolute Error) вычисляется как средняя абсолютная ошибка:

MAE = (1/n) * Σ |y_i - ŷ_i|

То есть мы суммируем абсолютные значения ошибок (разность между истинным и предсказанным значениями).

Проблема: производная при нуле

Для дифференциации MAE нужно найти градиент:

dMAE/dŷ = ??

Разберем по частям. Абсолютная ошибка для одного примера:

Loss_i = |y - ŷ|

Производная абсолютной функции:

d|x|/dx = {
  +1,   если x > 0
  -1,   если x < 0
  ???,  если x = 0  ← ПРОБЛЕМА!
}

Визуально, функция |x| имеет "острый" угол в нуле:

    |
    |  /
    | /
    |/
----+-------- x
    |\
    | \
    |  \

Производная в точке x=0 не определена (недифференцируемая).

Практическое следствие

Когда ошибка (y - ŷ) близка к нулю, градиент имеет разрыв:

E = |y - ŷ|

Eсли y - ŷ = 0.00001:  gradE = +1.0
Если y - ŷ = 0:        gradE = ??? (не определен)
Если y - ŷ = -0.00001: gradE = -1.0

Как решают эту проблему

1. Игнорируют проблему на практике

Если градиент спускается к оптимуму, вероятность точно попасть в y = ŷ мала (floating point precision). На практике это редко становится проблемой:

import numpy as np
from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_absolute_error

X, y = make_regression(n_samples=100, n_features=5, noise=10, random_state=42)

# MAE работает, несмотря на теоретическую проблему
model = Ridge(alpha=1.0)
model.fit(X, y)
y_pred = model.predict(X)

mae = mean_absolute_error(y, y_pred)
print(f"MAE: {mae}")  # Работает без проблем

2. Сглаживание в точке нуля (Smooth L1 Loss)

Популярный подход — использовать Huber loss или Smooth L1 Loss:

Smooth_L1(x) = {
  0.5 * x^2,     если |x| <= 1
  |x| - 0.5,     если |x| > 1
}

Эта функция дифференцируема везде:

def smooth_l1_loss(y_true, y_pred, beta=1.0):
    x = np.abs(y_true - y_pred)
    mask = x <= beta
    return np.where(mask, 0.5 * x**2 / beta, x - 0.5 * beta)

3. Субградиент вместо градиента

В выпуклом анализе для недифференцируемых функций используют субградиент:

∂|x| = {
  {+1},    если x > 0
  [-1, +1], если x = 0  ← выбираем любое значение из интервала
  {-1},    если x < 0
}

Можно выбрать 0, и градиентный спуск будет работать:

def mae_gradient(y_true, y_pred, epsilon=1e-8):
    diff = y_pred - y_true
    # Используем субградиент
    grad = np.sign(diff)
    # В точке diff==0, np.sign вернет 0
    return grad

Сравнение MAE и MSE

MSE (Mean Squared Error):

MSE = (1/n) * Σ (y_i - ŷ_i)^2
dMSE/dŷ = 2 * (ŷ - y)  ← гладко везде!

MSE дифференцируемо везде. Это одна из причин, почему MSE используется чаще в глубоком обучении:

import torch

# MSE — гладкая функция, идеальна для SGD
mse_loss = torch.nn.MSELoss()

# MAE — имеет проблему с дифференцировкой, но работает
mae_loss = torch.nn.L1Loss()

y_true = torch.tensor([1.0, 2.0, 3.0])
y_pred = torch.tensor([1.1, 2.1, 3.1], requires_grad=True)

loss = mae_loss(y_true, y_pred)
loss.backward()  # Работает, PyTorch обрабатывает этот случай

Когда MAE лучше

Несмотря на проблемы с дифференциацией, MAE часто предпочитают:

  1. Робастность к выбросам: MAE менее чувствителен к больших ошибкам
  2. Интерпретируемость: MAE в тех же единицах, что целевая переменная
  3. Практически: на реальных данных проблема дифференциации редко возникает
import numpy as np
import matplotlib.pyplot as plt

# Сравнение MAE и MSE поведения
errors = np.linspace(-2, 2, 100)
mae_loss = np.abs(errors)
mse_loss = errors ** 2

plt.plot(errors, mae_loss, label='MAE', linewidth=2)
plt.plot(errors, mse_loss, label='MSE', linewidth=2)
plt.xlabel('y - ŷ')
plt.ylabel('Loss')
plt.legend()
plt.grid()
plt.title('MAE vs MSE')
plt.show()

# MSE растет квадратично (быстрее)
# MAE растет линейно (медленнее, устойчивее к выбросам)

Практический совет

В PyTorch и TensorFlow обработка недифференцируемости происходит автоматически:

import torch

y_true = torch.tensor([1.0, 2.0, 3.0, 3.0])
y_pred = torch.tensor([1.0, 2.0, 3.0, 2.9], requires_grad=True)

# Даже если y == ŷ, градиент считается корректно
mae = torch.nn.L1Loss()
loss = mae(y_true, y_pred)
loss.backward()

print(loss.item())        # 0.025
print(y_pred.grad)        # tensor([-0.2500, -0.2500, -0.2500,  0.2500])

Между строк: фреймворки используют численное дифференцирование или хитрые трюки, чтобы обойти проблему.

Итог

  • MAE недифференцируемо в нуле из-за абсолютного значения
  • На практике это редко вызывает проблемы (используется субградиент)
  • MSE гладче, но менее устойчив к выбросам
  • В глубоком обучении обычно используют MSE из-за гладкости
  • Если ценишь робастность — используй MAE или Huber Loss