Long Tail Event는 Right Skewed Distribution 에서 발생하는 특징 중 하나로, Outlier와도 비슷해보이지만, Outlier대비 상당히 길게 꼬리형태로 길게 이어지는 Data Point들이 보이고, 이들의 빈도가 상대적으로 높은 편이다. 이러한 형태는 이커머스나 배달앱 등 여러 곳에서 자주 보이는 형태이다. 전체 회원을 RFM 관점으로 나눠서 봐도 Long Tail Event를 찾아 볼 수 있다.
Long Tail을 예측하는 것은 비즈니스 관점에서 매우 중요하다. 빈도가 많다고 하지만, 그래도 상대적으로는 드물게 발생하고 발생시점이 불규칙적인데 비해서, 비즈니스 관점에서 매출 등에서 꽤 중요한 위치를 차지하기 때문에 적절히 예측하고 관리할 수 있다면 비즈니스의 지속가능성에서 꽤 중요하다.
Normal Distribution 대비 대칭 구조가 아니기도 하고, 데이터 양도 적다 보니 상대적으로 Ground Truth를 찾기 어렵렵다보니 Long Tail Event은 상대적으로 예측이 어렵다.
예측시 정확도 높이기
Doordash는 이런 Long Tail Event가 포함된 Data를 예측하기 위해서 다음과 같은 접근을 취하였다. 첫 두개의 액션은 Feature Engineering 관점에서 도메인의 지식을 빌어서, 세 번째는 Training 관점에서 Modeling 지식을 바탕으로 접근한 것으로 보인다.
Historical Feature
도메인 지식을 바탕으로 적절한 Feature를 선정하되 Target Value에 기반한 bucketing / encoding을 통해서 Feature값을 Scaling 해주고 Feature의 Range을 Normalization해주었다. 이렇게 해서 Normal Distribution 형태로 Transformation을 시도하였다.
Real-time Feature
다양한 상황을 모델에 모두 넣고 훈련하는 것은 쉽지않다. 대신 Inference 시점에 다양한 실시간 정보를 받아 처리함으로써 Model이 그 상황에 맞춰 적절한 예측값을 내보내도록 할 수는 있다.
Custom Loss Function
MSE(Mean Squared Error) 등의 Quadratic Loss Function을 이용해서 Long Tail Event에 보다 민감하게 반응하도록 유도를 할 수 있다. 하지만 Long Tail Event은 앞서 언급한 바와 같이 Symmetric 하지 않기 때문에 Asymmetric하게 값이 변하도록 바꿔줄 수 있다. 아래 식은 Doordash에서 제시한 Custom Loss Function이다. $${1 \over n} \sum\limits^n_{i=1} |\alpha - \mathbb{1}_{g(x_i) - \hat{g}(x_i)<0}| {(g(x_i) - \hat{g}(x_i))^2}, \space for \space \alpha \in (0,1)$$
${(g(x_i) - \hat{g}(x_i))}^2$ 이 이제 예측치와 실제 값의 Residual에 대한 부분을 계산하는 L2 Loss Function이라면 앞 부분이 Asymmetric하게 Long Tail Event에 대해서 대응할 수 있는 부분이다. 실제 값이 예측치보다 작으면 항등함수가 1을 뱉을 것이고, 아니면 0을 뱉을 것인데 여기에 $\alpha$를 이용해서 조절해준다.
이 때 MSE가 아닌 MAE를 쓸 수도 있겠지만, 앞서 언급한 것처럼 Long Tail Event에 대해서 더욱더 Sensitive하게 반응을 하도록 하기 위해서 MSE를 쓴 것을 확인할 수 있다. 아래 코드를 봐도 MSE가 더욱 민감하다. Doordash가 Base Loss Function으로 MSE를 쓴 이유이다. 여기에 $\alpha$를 이용해서 늦은 배달과 빠른 배달의 패널티 차이를 조절하고 있다.
from sklearn.metrics import mean_squared_error, mean_absolute_error
actual = [100,120,80,110]
predicted = [90,120,50,140]
mse = mean_squared_error(actual, predicted)
mae = mean_absolute_error(actual, predicted)
print(f"mse: {mse}")
print(f"mae: {mae}")