웹 서비스의 다음 주 주문 금액을 예측하는 모델을 만들었다고 하자. 예측 결과가 53만 원이고 실제 금액이 50만 원이었다면 예측은 3만 원 빗나갔다.
하지만 평가 대상이 수백 건이라면 오차를 하나씩 나열해서는 모델의 품질을 판단하기 어렵다. 여러 예측의 오차를 하나의 통계량으로 요약해야 한다.
이때 자주 쓰는 지표가 MAE, MSE, RMSE, R²다.
보통 다음 기호를 사용한다.
y: 실제값ŷ(y-hat): 모델의 예측값ȳ(y-bar): 실제값의 평균y - ŷ: 잔차 또는 예측 오차예를 들어 다음처럼 예측했다고 하자.
import numpy as np
y_true = np.array([20, 30, 40, 50], dtype=float)
y_pred = np.array([22, 27, 37, 54], dtype=float)
error = y_true - y_pred
print(error)
# [-2. 3. 3. -4.]
오차를 그대로 평균 내면 양수와 음수가 상쇄된다. 이 예에서는 오류가 존재하지만 오차 평균은 0이다. 그래서 평가에서는 절댓값 또는 제곱을 활용한다.
MAE(Mean Absolute Error)는 오차에 절댓값을 취해 평균을 낸 값이다.
[
MAE = \frac{1}{n}\sum_{i=1}^{n}|y_i-\hat y_i|
]
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(y_true, y_pred)
print(mae) # 3.0
이 예제에서 MAE가 3이라면 평균적으로 3만큼 빗나간다고 해석할 수 있다. 다만 반드시 "모든 예측이 3만큼 틀린다"는 뜻은 아니다.
장점은 원래 Target과 단위가 같다는 것이다. 주문 금액 단위가 원이라면 MAE도 원이다.
MSE(Mean Squared Error)는 오차를 제곱한 뒤 평균을 계산한다.
[
MSE = \frac{1}{n}\sum_{i=1}^{n}(y_i-\hat y_i)^2
]
from sklearn.metrics import mean_squared_error
mse = mean_squared_error(y_true, y_pred)
print(mse) # 9.5
-2, 3, 3, -4를 제곱하면 4, 9, 9, 16이 되고 평균은 9.5다.
오차가 두 배 커지면 제곱 오차는 네 배 커진다. 따라서 큰 실수에 높은 불이익을 주고 싶을 때 유용하다.
단점은 결과의 단위가 제곱된다는 점이다. 원 단위의 예측값이라면 MSE의 단위는 원²이므로 직관적으로 설명하기 어렵다.
참고로 제곱 오차의 합을 SSE(Sum of Squared Errors)라 한다. MSE는 SSE를 관측치 개수로 나눈 값이다.
RMSE(Root Mean Squared Error)는 MSE의 제곱근이다.
[
RMSE = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(y_i-\hat y_i)^2}
]
from sklearn.metrics import root_mean_squared_error
rmse = root_mean_squared_error(y_true, y_pred)
print(round(rmse, 3)) # 3.082
RMSE는 원래 값과 단위가 같으면서도 큰 오차에 민감하다.
root_mean_squared_error()는 scikit-learn 1.4부터 제공된다. 이전 버전에서는np.sqrt(mean_squared_error(...))로 계산할 수 있다.
MAE와 RMSE는 서로 다른 관점을 보여준다. 두 값 모두 작을수록 좋지만 단순히 RMSE가 MAE보다 크다는 이유만으로 모델이 잘못된 것은 아니다.
매출액 1만 원을 2만 원으로 예측한 경우와 1천만 원을 1,001만 원으로 예측한 경우는 절대 오차가 같아도 의미는 크게 다르다. 이럴 때 오차를 실제값으로 나누는 MAPE(Mean Absolute Percentage Error)를 생각할 수 있다.
[
MAPE = \frac{1}{n}\sum_{i=1}^{n}\left|\frac{y_i-\hat y_i}{y_i}\right|
]
from sklearn.metrics import mean_absolute_percentage_error
mape = mean_absolute_percentage_error(y_true, y_pred)
print(f"MAPE: {mape:.2%}")
주의: Scikit-learn의 MAPE 반환값은 100을 곱하지 않은 비율이다. 0.12가 반환되면 12%로 표시하는 것이 일반적이다.
또한 실제값 y가 0이거나 0에 가까우면 계산값이 매우 커지거나 의미가 불안정해진다. 주문이 없는 날짜가 많은 데이터에서는 MAPE 사용을 신중하게 결정해야 한다.
오차 지표만 보면 "MAE가 3이니 좋은 모델인가?"를 판단하기 어렵다. 예측하려는 값의 변동 폭에 따라 의미가 달라지기 때문이다.
R²(결정계수)는 평균을 항상 예측하는 단순 기준 모델과 비교할 때 유용하다.
[
R^2 = 1 - \frac{\sum (y_i-\hat y_i)^2}{\sum (y_i-\bar y)^2}
]
from sklearn.metrics import r2_score
r2 = r2_score(y_true, y_pred)
print(round(r2, 3))
기본적인 해석은 다음과 같다.
| R² 값 | 해석 |
|---|---|
| 1 | 예측이 실제값과 완전히 일치 |
| 0 | 실제값 평균만 예측하는 기준 모델과 동일 수준 |
| 음수 | 그 기준 모델보다 제곱 오차가 더 큼 |
R²가 0.8이라고 정확도가 80%라는 뜻은 아니다. R²는 회귀의 성능 지표이고 분류 정확도와 정의가 다르다.
R²는 음수가 나올 수 있으며, 인공적으로 완벽한 규칙이 있는 데이터에서는 1도 가능하다. 극단적인 값이 나왔다면 데이터와 평가 방식부터 점검하자.
이제 가상의 주문량 데이터를 예측해 보자. 외부 파일이 필요 없도록 데이터를 생성한다.
import numpy as np
from sklearn.dummy import DummyRegressor
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, r2_score
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(7)
X = rng.integers(1, 101, size=(300, 1)) # 광고 클릭 수
y = 100 + X[:, 0] * 2.8 + rng.normal(0, 15, size=300) # 가상 주문 수
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42
)
baseline = DummyRegressor(strategy="mean")
model = LinearRegression()
for name, estimator in [("평균 예측", baseline), ("선형회귀", model)]:
estimator.fit(X_train, y_train)
pred = estimator.predict(X_test)
print(name)
print(" MAE:", round(mean_absolute_error(y_test, pred), 2))
print(" R²:", round(r2_score(y_test, pred), 3))
정답값의 평균만 예측하는 기준 모델과 비교하면 모델을 새로 만든 의미가 있는지 판단하기 쉬워진다. 난수 시드와 라이브러리 버전에 따라 출력되는 수치는 달라질 수 있다.
| 상황 | 고려할 지표 | 이유 |
|---|---|---|
| 일반적인 예측 오차를 설명 | MAE | 원래 값과 단위가 같고 직관적 |
| 큰 예측 실패가 특히 위험 | RMSE | 큰 오차에 민감 |
| 제곱 오차를 최적화 | MSE | 여러 최적화 문제에서 활용 |
| 실제값 대비 상대 오차 비교 | MAPE | 크기 차이를 비율로 표현, 0 근처 주의 |
| 평균 예측 기준 대비 설명력 | R² | 기준 모델과 비교하기 쉬움 |
지표의 절대적인 합격 기준은 없다. 데이터의 단위, 업무에서 중요한 실패 유형, 기준 모델 성능을 함께 봐야 한다.
model.score(X_test, y_test)는 보통 회귀 모델에서 R², 일반적인 분류기에서 Accuracy를 반환한다. 어떤 모델인지 확인 없이 score() 하나로 비교하지 않는다.