3.4 예측 정확도 평가

학습 데이터와 테스트 데이터

정답지를 사용하여 예측 정확도를 평가하는 것은 중요합니다. 결과적으로, 잔차(residual)의 크기는 참 예측 오차(forecast error)가 얼마나 클 지에 대해 믿을만한 지표가 아닙니다. 예측치의 정확도는 모델이 모델을 맞출 때 사용하지 않은 새로운 데이터를 얼마나 잘 맞추는 지 여부로만 결정할 수 있습니다.

모델을 선택할 때, 흔히 사용할 수 있는 데이터를 학습 데이터(training data)테스트 데이터(test data) 이렇게 두 부분으로 나눕니다. 여기에서 학습 데이터는 예측 기법의 어떠한 매개변수를 추정하는데 사용하고, 테스트 데이터는 정확도를 평가할 때 사용합니다. 테스트 데이터를 예측치를 결정할 때 사용하지 않기 때문에, 모델이 새로운 데이터에 대한 예측을 얼마나 잘 하는지 평가하는 믿을만한 지표를 제공합니다.

테스트 데이터(test data)의 크기는 표본이 얼마나 긴지와 얼마나 멀리 예측할 지에 따라 달라지긴 하지만 보통은 전체 표본의 약 20% 정도입니다. 테스트 데이터(test data)는 적어도 이상적으로는 필요한 최대 예측수평선(forecast horizon)만큼 커야합니다. 다음과 같은 사항을 주의해야합니다.

  • 학습 데이터를 잘 맞추는 모델이 반드시 예측을 잘 하지는 않을 수도 있습니다.
  • 충분한 매개변수를 고려하는 모델을 이용하여 완벽하게 맞추는 모델을 항상 얻을 수 있습니다.
  • 모델을 데이터에 과도하게 맞춘 것은 데이터에서 조직적인 패턴을 찾아내지 못한 것만큼 나쁩니다.

이러한 데이터가 맞추는 작업에서 “빠지기” 때문에 테스트 데이터를 몇몇 참고 문헌에서는 “빠지는 데이터 모음(hold-out set)”으로 부릅니다. 다른 참조 문헌에서는 학습 데이터를 “표본 내 데이터(in-sample data)”로 테스트 데이터를 “표본 외 데이터(out-of-sample data)”라고 부릅니다. 이 책에서는 “학습 데이터(training data)”와 “테스트 데이터(test data)”로 부르겠습니다.

시계열 일부분을 다루기 위한 함수들

2 장에서 소개한 window() 함수는 데이터를 학습 데이터(training data)와 테스트 데이터(test data)로 나누는 것 같이 시계열의 일부분을 추출할 때 유용합니다. window() 함수에서 필요한 시간값에 해당하는 시작이나 끝, 또는 둘 중에 하나, 아니면 둘 다 정할 수 있습니다. 예를 들면,

window(ausbeer, start=1995)

위의 명령으로 1995년 이후의 모든 데이터를 추출할 수 있습니다.

여러 가지 다양한 방법으로 일부분을 추출할 수 있도록 해주는 subset() 함수도 유용합니다. 이 함수의 가장 큰 장점은 데이터의 일부를 추출할 때 인덱스를 사용할 수 있도록 만들어주는 것입니다. 예를 들면,

subset(ausbeer, start=length(ausbeer)-4*5)

위와 같이 ausbeer에서 마지막 5년의 관측값을 얻을 수 있습니다. 특정한 계절의 모든 값을 추출할 수도 있습니다. 예를 들면,

subset(ausbeer, quarter = 1)

이렇게 하면, 모든 1분기 값을 얻을 수 있습니다.

마지막으로, head()tail()도 처음 몇 개 또는 마지막 몇 개의 관측값을 추출할 때 도움이 됩니다. 예를 들어, ausbeer의 마지막 5년은 다음과 같이 얻을 수 있습니다.

tail(ausbeer, 4*5)

예측 오차

예측 “오차”는 관측값과 관측값의 예측치의 차이입니다. 여기에서 “오차”는 실수(mistake)를 의미하는 것이 아니라 관측값에서 예측할 수 없는 부분을 의미합니다. 예측 오차(forecast error)를 다음과 같이 적을 수 있습니다.

\[ e_{T+h} = y_{T+h} - \hat{y}_{T+h|T}, \] 여기에서 학습(training) 데이터는 \(\{y_1,\dots,y_T\}\)로 주어지고 테스트(test) 데이터는 \(\{y_{T+1},y_{T+2},\dots\}\)로 주어집니다.

예측 오차는 두 가지 면에서 잔차와 다릅니다. 첫째, 잔차는 학습(training) 데이터에 대해 계산하지만, 예측 오차(forecast error)는 테스트(test) 데이터에 대해 계산합니다. 둘째, 잔차는 한-단계(one-step) 예측값에 기초하지만, 예측 오차는 여러-단계(multi-step) 예측값을 포함할 수 있습니다.

여러가지 방법으로 예측 오차를 요약하여 예측 정확도를 측정할 수 있습니다.

눈금에 의존하는 오차

예측 오차(forecast error)는 데이터와 같은 눈금 위에 있습니다. 따라서 \(e_{t}\)만 고려하는 정확도 측정값은 눈금(scale)에 의존하고 다른 단위(unit)를 포함하는 시계열을 비교하는데 사용할 수 없습니다.

절대 오차(absolute error) 또는 제곱 오차(squared error)를 고려하는 가장 흔하게 사용하는 두 가지 눈금 의존 측정값(scale-dependent measure)은 다음과 같습니다: \[\begin{align*} \text{평균 절대 오차(Mean absolute error): MAE} & = \text{mean}(|e_{t}|),\\ \text{제곱근 평균 제곱 오차(Root mean squared error): RMSE} & = \sqrt{\text{mean}(e_{t}^2)}. \end{align*}\] 한 가지 시계열을 가지고 또는 같은 단위의 몇 가지 시계열 예측 기법을 비교할 때, MAE가 이해하기 쉬우면서 계산하기 쉬워서 인기가 있습니다. MAE를 최소화하는 예측 기법은 예측값의 중앙값(median)을 내는데, RMSE를 최소화하는 예측 기법은 예측치의 평균을 냅니다. 결과적으로, 더 해석하기 어렵긴 하지만 RMSE도 널리 사용됩니다.

백분율 오차

백분율 오차(percentage error)는 \(p_{t} = 100 e_{t}/y_{t}\) 이렇게 주어집니다. 백분율 오차(percentage error)는 단위와 관련 없다(unit-free)는 장점이 있어서 데이터 모음 사이의 예측 성능을 비교할 때 자주 사용됩니다. 가장 흔하게 사용되는 측정값은 다음과 같습니다. \[ \text{평균 절대 백분율 오차(Mean absolute percentage error): MAPE} = \text{평균}(|p_{t}|). \] 백분율 오차(percentage rror)에 기초하는 측정값은 관심 있는 기간 안에서 어떤 \(t\)에 대해 \(y_{t}=0\)이면 무한대가 되거나 정의되지 않는 단점과, \(y_{t}\)가 0에 가까울 수록 극한값을 갖는 단점이 있습니다. 흔히 간과하는 백분율 오차의 또 다른 문제는 측정 단위가 의미있는 0을 갖는다고 가정한다는 것입니다.2 예를 들어, 온도에는 임의의 0점이 있기 때문에 화씨나 섭씨 눈금에 대해 온도의 정확도를 측정할 때 백분율 오차를 사용하는 것이 말이 안 됩니다.

양수 오차보다 음수 오차 경우에 더 큰 가중치를 준다는 단점도 있습니다. M3 예측 대회에서 사용됐고 Armstrong (1978, p. 348)에서 제안한 “대칭적” MAPE(sMAPE)를 사용할 때 이러한 단점이 부각됩니다. sMAPE의 정의는 다음과 같습니다. \[ \text{sMAPE} = \text{평균}\left(200|y_{t} - \hat{y}_{t}|/(y_{t}+\hat{y}_{t})\right). \] 하지만, \(y_{t}\)이 0에 가까우면, \(\hat{y}_{t}\)도 0에 가깝기 쉽습니다. 따라서, 이 측정값은 계산을 불안정하게 만드는 부분인 0에 가까운 값으로 나누는 부분을 여전히 포함하고 있습니다. sMAPE의 값은 0이 될 수도 있어서, 이건 “절대 백분율 오차(absolute percentage error)” 측정값이 절대로 아닙니다.

Hyndman & Koehler (2006) 는 sMAPE를 사용하지 말 것을 추천합니다. 이 책에서는 이 양을 사용하지 않을 것이지만 단지 널리 사용되기 때문에 여기에서 소개하였습니다.

눈금 조정된 오차

Hyndman & Koehler (2006) 에서 단위가 다른 시계열에 대해 예측 정확도를 비교할 때 사용할 백분율 오차(percentage error)의 대안으로 눈금 조정된 오차(scaled error)를 소개하였습니다. 어떤 단순한 예측 기법의 학습(training) MAE에 기초하여 오차의 눈금(scale)을 조정하는 방법을 제안했습니다.

비-계절성 시계열에 대해, 단순 예측값(naïve forecast)을 사용하여 눈금 조정된 오차(scaled error)를 정의하는 방법은 다음과 같습니다: \[ q_{j} = \frac{\displaystyle e_{j}} {\displaystyle\frac{1}{T-1}\sum_{t=2}^T |y_{t}-y_{t-1}|}. \] 분모와 분자 둘 다 원본 데이터의 눈금에 대한 값을 포함하고 있기 때문에, \(q_{j}\)는 데이터의 눈금과 독립적입니다. 눈금이 조정된 오차(scaled error)는 학습 데이터에 대해 단순 예측값(naïve forecast)의 평균보다 좋은 예측치를 낼 때 1보다 작습니다. 반대로, 학습 데이터에 대한 예측치가 단순 예측값(naïve forecast)의 평균보다 나쁘면 1보다 큽니다.

계절성 시계열에 대해, 눈금 조정된 오차(scaled error)는 계절성 단순 예측치(seasonal naïve forecast)를 이용하여 다음과 같이 정의할 수 있습니다. \[ q_{j} = \frac{\displaystyle e_{j}} {\displaystyle\frac{1}{T-m}\sum_{t=m+1}^T |y_{t}-y_{t-m}|}. \]

눈금 조정된 평균 절대 오차(mean absolute scaled error)는 단순히 다음과 같습니다. \[ \text{MASE} = \text{평균}(|q_{j}|). \]

예제

beer2 <- window(ausbeer,start=1992,end=c(2007,4))
beerfit1 <- meanf(beer2,h=10)
beerfit2 <- rwf(beer2,h=10)
beerfit3 <- snaive(beer2,h=10)
autoplot(window(ausbeer, start=1992)) +
  autolayer(beerfit1, series="평균", PI=FALSE) +
  autolayer(beerfit2, series="단순", PI=FALSE) +
  autolayer(beerfit3, series="계절성 단순", PI=FALSE) +
  xlab("연도") + ylab("백만 리터") +
  ggtitle("분기별 맥주 생산량 예측값")  +
  guides(colour=guide_legend(title="예측"))
2007년 말까지 호주 분기별 맥주 생산량 데이터를 이용한 예측값.

Figure 3.9: 2007년 말까지 호주 분기별 맥주 생산량 데이터를 이용한 예측값.

그림 3.9은 3가지 예측 기법을 2007년말까지의 호주 분기별 맥주 생산량에 적용한 것입니다. 2008–2010 기간에 대한 실제 값도 있습니다. 이 기간에 대한 예측 정확도 값을 계산해봅시다.

beer3 <- window(ausbeer, start=2008)
accuracy(beerfit1, beer3)
accuracy(beerfit2, beer3)
accuracy(beerfit3, beer3)
RMSE MAE MAPE MASE
평균 기법 38.45 34.83 8.28 2.44
단순 기법 62.69 57.40 14.18 4.01
계절성 단순 기법 14.31 13.40 3.17 0.94

그래프에서 보면 계절성 단순 기법(seasonal naïve method)이 이러한 데이터에 대해 가장 좋은 것을 알 수 있습니다. 더 좋게 만들 수 있긴 하지만, 나중에 살펴보겠습니다. 때때로, 다른 정확도 값은 어떤 예측 기법이 가장 좋은 것인지에 대한 결과가 서로 다릅니다. 하지만, 이 경우에는, 모든 결과가 이 데이터 모음에서 계절성 단순 기법이 이러한 3개의 기법 중에서 가장 좋다고 알려줍니다.

비-계절성 예제를 다루기 위해, 구글 주가 데이터를 고려해봅시다. 다음의 그래프는 마지막 값이 2013년 12월 6일인 200개의 관측값과 3개의 다른 기법에서 다음 40일 예측치를 얻은 것을 나타냅니다.

googfc1 <- meanf(goog200, h=40)
googfc2 <- rwf(goog200, h=40)
googfc3 <- rwf(goog200, drift=TRUE, h=40)
autoplot(subset(goog, end = 240)) +
  forecast::autolayer(googfc1, PI=FALSE, series="평균") +
  forecast::autolayer(googfc2, PI=FALSE, series="단순") +
  forecast::autolayer(googfc3, PI=FALSE, series="표류") +
  xlab("날짜") + ylab("종가(미국 달러)") +
  ggtitle("구글 일별 주가 (2013년 12월 6일까지)") +
  guides(colour=guide_legend(title="예측"))
2013년 12월 7일부터의 구글 주가 예측값.

Figure 3.10: 2013년 12월 7일부터의 구글 주가 예측값.

googtest <- window(goog, start=201, end=240)
accuracy(googfc1, googtest)
accuracy(googfc2, googtest)
accuracy(googfc3, googtest)
RMSE MAE MAPE MASE
평균 기법 114.21 113.27 20.32 30.28
단순 기법 28.43 24.59 4.36 6.57
표류 기법 14.08 11.67 2.07 3.12

여기에서 가장 좋은 기법은 (어떤 정확도 측정값을 사용하는지에 상관없이) 표류 기법(drift method)입니다.

시계열 교차 검증

시계열 교차 검증(cross-validation)은 데이터를 더 세련되게 학습/테스트하는 방법입니다. 이 과정에서, 각각이 한 가지 관측으로 구성된 테스트(test) 데이터가 있습니다. 대응되는 학습(training) 데이터는 테스트(test) 데이터을 구성하는 관측에 앞서(prior) 일어난 관측만으로 구성됩니다. 그래서 예측치를 구성할 때 미래 측정치를 전혀 사용하지 않습니다. 작은 학습 데이터에서 신뢰할만한 예측을 얻을 수 없기 때문에, 초반부의 관측값을 테스트 데이터로 고려하지 않습니다.

다음의 도표는 학습 데이터와 테스트 데이터를 연속하여 나타낸 것입니다. 여기에서 파란 관측값은 학습 데이터, 빨간 관측값은 테스트 데이터입니다.

예측 정확도(forecast accuracy)는 테스트(test) 데이터에 대한 평균으로 계산합니다. 예측하는 원점(origin)을 시간에 따라 앞으로 굴리기 때문에 때때로 이 과정을 “예측 원점 굴리기에 대한 평가(evaluation on a rolling forecasting origin)”라고도 합니다.

시계열 예측에서, 한 단계 예측치는 여러 단계 예측치와 그렇게 관련이 있지 않을 수도 있습니다. 이런 경우에는, 예측 원점 굴리기(rolling forecasting origin)에 기초한 교차 검증(cross-validation) 과정을 여러 단계 오차(multi-step forecast)를 사용할 수 있도록 변형할 수 있습니다. 4단계 앞 예측치를 잘 내는 모델에 관심이 있다고 합시다. 그러면 대응되는 그래프는 아래와 같습니다.

시계열 교차 검증(cross-validation)은 tsCV() 함수로 구현합니다. 다음의 예제에서, 잔차 RMSE와 시계열 교차 검증(cross-validation)을 통해 얻은 RMSE를 비교합니다.

e <- tsCV(goog200, rwf, drift=TRUE, h=1)
sqrt(mean(e^2, na.rm=TRUE))
#> [1] 6.233
sqrt(mean(residuals(rwf(goog200, drift=TRUE))^2, na.rm=TRUE))
#> [1] 6.169

예측한 것처럼, 대응되는 “예측값(forecast)”이 참 예측치가 되는 것 대신에, 전체 데이터 모음을 맞추는 모델이 될 수록 잔차에서 얻은 RMSE가 더 작습니다.

가장 뛰어난 예측 모델을 선택하는 좋은 방법은 시계열 교차 검증(cross-validation)으로 RMSE를 계산하여 가장 작은 모델을 찾는 것입니다.

파이프 연산자

위의 지저분한 R 코드 덕분에 R 함수들을 함께 묶는 방법을 소개할 기회가 생겼습니다. 위의 코드에서 함수 안에 함수 안에 함수를 넣어서, 코드를 읽으려면 안팎으로 왔다갔다해야 해서 무엇이 계산되고 있는지 이해하기 어렵습니다. 대신에, 다음과 같이 파이프 연산자(pipe operator) %>%를 사용할 수 있습니다.

goog200 %>% tsCV(forecastfunction=rwf, drift=TRUE, h=1) -> e
e^2 %>% mean(na.rm=TRUE) %>% sqrt()
#> [1] 6.233
goog200 %>% rwf(drift=TRUE) %>% residuals() -> res
res^2 %>% mean(na.rm=TRUE) %>% sqrt()
#> [1] 6.169

각 파이프의 좌변은 우변에 있는 함수의 첫 번째 입력값으로 넘겨집니다. 이러한 방식은 영어에서 왼쪽에서 오른쪽으로 읽는 방식과 일치합니다. 파이프를 사용할 때는, 가독성을 위해 모든 다른 입력값에 이름을 붙여야 합니다. 파이프를 사용할 때, 왼쪽 화살표 대신에 오른쪽 화살표 할당 ->을 쓰는 것이 자연스럽습니다. 예를 들어, 위의 예제의 3번째 줄을 다음과 같이 읽을 수 있습니다. “goog200 시계열을 골라서, drift=TRUE와 함께 rwf()에 넘겨, 결과 잔차를 계산하고, res로 저장하십시오.”

코드를 읽기 쉽게 만들 수 있는 상황이라면 언제든지 파이프 연산자(pipe operator)를 사용할 것입니다. 내용의 일관성을 위해 입력값이 없는 경우에도 다른 객체들과 구별하기 위해 함수 이름 옆에 괄호를 항상 사용할 것입니다. 위의 코드에서 sqrt()residuals()를 사용한 방식을 참조하시길 바랍니다.

3.4.1 예제: tsCV() 사용하기

그림 3.5에 나타낸 goog200 데이터에는 NASDAQ에 상장된 Google Inc 주식의 2013년 2월 25일부터 시작하는 200 거래일 동안의 매일 마지막 가격 정보가 있습니다.

아래의 코드는 MSE를 예측 오차로 사용하고 tsCV()로 1단계부터 8단계 naive (단순; naïve) 예측값의 예측 성능을 계산합니다. 그래프는 자연스럽게 예측할 수 있는 것과 같이, 예측 오차(forecast error)가 예측 범위(forecast horizon)에 따라 증가하는 것을 나타냅니다.

e <- tsCV(goog200, forecastfunction=naive, h=8)
# MSE 값을 계산하고 결측값을 제거합니다
mse <- colMeans(e^2, na.rm = T)
# MSE 값을 예측 범위에 대해 그래프로 나타냅니다.
data.frame(h = 1:8, MSE = mse) %>%
  ggplot(aes(x = h, y = MSE)) + geom_point()

참고 문헌

Armstrong, J. S. (1978). Long-range forecasting: From crystal ball to computer. John Wiley & Sons. [Amazon]

Hyndman, R. J., & Koehler, A. B. (2006). Another look at measures of forecast accuracy. International Journal of Forecasting, 22, 679–688. https://robjhyndman.com/publications/another-look-at-measures-of-forecast-accuracy/


  1. 즉, 백분율은 비율눈금(ratio scale)에서 유효하지만, 간격눈금(interval scale)에서는 유효하지 않습니다. 비율 눈금 변수만 의미있는 0값을 갖습니다. (역자 주: 비율 눈금에서는 0보다 작은 값이 정의상 존재할 수 없습니다.)↩︎