Что происходит в MAE при дифференциале?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Дифференциация 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 часто предпочитают:
- Робастность к выбросам: MAE менее чувствителен к больших ошибкам
- Интерпретируемость: MAE в тех же единицах, что целевая переменная
- Практически: на реальных данных проблема дифференциации редко возникает
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