728x90
반응형

 지난 포스트에서 "제곱오차(SE) > 오차제곱합(SSE) > 평균제곱오차(MSE)" 순으로 알아보았다. 이번 포스트에서는 SSE의 또 다른 파생 형제인 평균제곱근오차(RMSE)에 대해 알아보도록 하겠다.

 

 

평균제곱근오차(Root Mean Square Error, RMSE)

  • 평균제곱오차(MSE)는 "각 원소의 평균까지의 편차 제곱의 평균"인 분산과 굉장히 유사한 개념이다. 
  • 평균제곱오차 역시 분산과 마찬가지로 편차 제곱 합을 하였기 때문에 이것이 실제 편차라 보기 힘들며, 그로 인해 분산과 표준편차처럼 평균제곱오차에도 제곱근(Root)을 씌운 것이 평균제곱근오차다.
  • 그 공식은 다음과 같다.

$$RMSE = \sqrt{\frac{1}{n}\sum_{k}^{n}(y_k - \hat{y_k})^2}$$

 

 

 

 

1. 어째서 평균제곱근오차(RMSE)를 사용하는 것일까?

  • 분산 대신 표준편차를 사용하는 이유와 비슷한데, 평균제곱오차는 실제 오차의 편차 평균이 아니라, 오차의 편차 제곱의 평균이기 때문에, 실제 편차를 반영한다고 볼 수 없다.
  • 이는 평균제곱오차의 장점이자 단점으로 "큰 오류를 작은 오류에 비해 확대시킨다"는 것을 제곱근을 사용함으로써 어느 정도 보정할 수 있다.
  • 예를 들어, (1-0.01)과 (1.0.95)의 차이와 (1-0.01)^2과 (1-0.95)^2의 차이를 비교해보자.
>>> print((1-0.01) - (1-0.95))
>>> print(np.round((1-0.01)**2 - (1-0.95)**2, 3)) 
0.94
0.978
  • 위를 보면, 편차의 제곱을 하는 것이 그렇지 않은 것보다 차이가 크게 확대되는 것을 알 수 있다.
  • 때문에, 이를 보정해주기 위해  제곱근(Root)을 사용하는 것이다.

 

  • 물론, 제곱근을 사용한다고 하여, 평균절대값오차(MAE)에 비해 실제 편차라고 할 수는 없으나, MSE가 편차를 제곱시켜, 큰 오류를 작은 오류보다 확대시킨다는 장점은 제곱근을 사용하여도 유지되기 때문에 오차의 존재를 인지하는 데엔 더욱 도움이 된다.
  • 평균절대값오차(MAE)0에서 미분이 불가능하기 때문에 경사하강법을 이용해 최적의 값에 가까워지더라도 이동거리가 일정해 최적 값에 수렴하지 않으므로, 개인적으로는 추천하지 않는다.
  • 즉, "평균제곱근오차(RMSE)는 제곱근을 사용함으로써 평균제곱오차(MSE)의 왜곡을 줄여주기 때문에 오차를 보다 실제 편차와 유사하게 볼 수 있게 되어 사용한다"라고 할 수 있다.
  • 평균제곱근오차(RMSE) 역시 연속형 데이터를 대상으로 할 때 사용한다.

 

 

 

 

2.  구현해보자.

  • 지금까지 만들었던 오차제곱(SE)에서 파생된 손실함수들의 결과를 비교해보자.
  • Sample Dataset은 이전 포스트에서 만들었던 함수를 그대로 사용하겠다.
>>> import numpy as np

>>> def SSE(real, pred):
>>>     return 0.5 * np.sum((real - pred)**2)

>>> def MSE(real, pred):
>>>     return (1/len(real)) * np.sum((real - pred)**2)

>>> def RMSE(real, pred):
>>>     return np.sqrt((1/len(real)) * np.sum((real - pred)**2))


# sample Data를 만들어보자.
>>> def make_sample_dataset(data_len, one_index):

>>>     label = np.zeros((data_len,))
>>>     pred = np.full((data_len,), 0.05)

>>>     # 특정 index에 실제 데이터엔 1을 예측 데이터엔 0.8을 넣어보자.
>>>     label[one_index] = 1
>>>     pred[one_index] = 0.8
    
>>>     return label, pred
  • np.sqrt(x) 함수는 제곱근을 해준다.
>>> label, pred = make_sample_dataset(100, 30)

>>> print("SSE: ", np.round(SSE(label, pred), 5))
>>> print("MSE: ", np.round(MSE(label, pred), 5))
>>> print("RMSE: ", np.round(RMSE(label, pred), 5))

SSE:  0.14375
MSE:  0.00288
RMSE:  0.05362
  • 위 출력 결과를 보면 다음과 같이 해석할 수 있다.
  • SSE는 데이터의 수에 지나치게 영향을 받아, 오차가 가장 크게 나온다.
  • MSE는 편차를 지나치게 확대하므로, 오차가 가장 작게 나왔다.
  • RMSE는 MSE에 비해 편차가 확대된 정도를 보정하므로, 실제 편차와 어느 정도 유사한 결과가 나왔다고 할 수 있다.
  • 혹시, 데이터의 편차가 너무 일정해서 이런 결과가 나온 것이 아닐까? 하는 의구심이 들 수도 있으니, 이번엔 어느정도 랜덤한 데이터 셋을 사용해보자.
# sample Data를 만들어보자.
>>> def make_sample_dataset2(data_len, one_index):

>>>     label = np.zeros((data_len,))
    
>>>     # 0.01을 간격으로 0에서 0.3 사이인 값이 일부 섞인 배열을 만들어보자
>>>     pred_sample = np.arange(0, 0.3, 0.01)
    
>>>     # 전체 데이터의 절반은 값을 넣도록 하겠다.
>>>     random_data_len = int(data_len/2)
>>>     pred_1 = np.random.choice(pred_sample, random_data_len, replace = True)
>>>     pred_2 = np.zeros((data_len - random_data_len))
>>>     pred = np.concatenate((pred_1, pred_2), axis = 0)
>>>     np.random.shuffle(pred)

>>>     # 특정 index에 실제 데이터엔 1을 예측 데이터엔 0.95을 넣어보자.
>>>     label[one_index] = 1
>>>     pred[one_index] = 0.95
    
>>>     return label, pred
  • np.arange(시작, 끝, 간격) 함수를 이용해 샘플을 추출할 데이터 셋을 만들었다.
  • np.random.choice(데이터셋, 샘플 수, 복원 추출 여부) 함수를 이용해 랜덤한 배열을 만들었다.
  • np.concatenate((배열1, 배열2), axis=0) 함수를 이용해 배열을 합쳤다.
  • np.random.shuffle(배열) 함수를 이용해 배열을 섞었다.
>>> label, pred = make_sample_dataset2(10000, 30)
>>> pred[:100]
array([0.  , 0.33, 0.37, 0.  , 0.11, 0.  , 0.  , 0.  , 0.26, 0.  , 0.  ,
       0.  , 0.14, 0.1 , 0.26, 0.21, 0.1 , 0.07, 0.34, 0.  , 0.  , 0.  ,
       0.19, 0.14, 0.  , 0.  , 0.13, 0.17, 0.  , 0.  , 0.95, 0.  , 0.07,
       0.  , 0.03, 0.39, 0.  , 0.25, 0.32, 0.  , 0.  , 0.27, 0.  , 0.  ,
       0.  , 0.1 , 0.  , 0.3 , 0.  , 0.  , 0.  , 0.  , 0.19, 0.04, 0.2 ,
       0.28, 0.  , 0.  , 0.32, 0.  , 0.  , 0.  , 0.  , 0.03, 0.  , 0.26,
       0.08, 0.39, 0.  , 0.24, 0.  , 0.15, 0.  , 0.  , 0.  , 0.  , 0.  ,
       0.  , 0.14, 0.  , 0.  , 0.  , 0.  , 0.22, 0.  , 0.24, 0.  , 0.05,
       0.12, 0.12, 0.  , 0.09, 0.  , 0.19, 0.  , 0.  , 0.01, 0.23, 0.08,
       0.15])
  • 다음과 같은 형태의 데이터셋이 만들어졌다.
>>> print("SSE: ", np.round(SSE(label, pred), 5))
>>> print("MSE: ", np.round(MSE(label, pred), 5))
>>> print("RMSE: ", np.round(RMSE(label, pred), 5))

SSE:  127.74995
MSE:  0.02555
RMSE:  0.15984
  • 랜덤한 데이터셋을 사용한다 할지라도 손실함수가 비슷하게 반환되는 것을 알 수 있다.
  • 위 결과를 보면, 연속형 데이터를 대상으로 손실함수를 사용한다고 하면, SSE는 가능한 사용하지 않는 것을 추천하며, MSE는 실제 오차가 있는 수준보다 과소평가된 결과가 나올 수 있다. 반면에 RMSE는 오차를 보다 보정하여 나타내기 때문에 실제 오차와 꽤 가까운 것을 알 수 있다.

 

 

 

 

 지금까지 오차 제곱(SE)에서 파생된 손실함수들인 SSE, MSE, RMSE에 대해 알아보았다. 해당 손실함수는 연속형 데이터를 대상으로 사용하며, 평균절대오차(MAE)에 비해 미분이 잘되어, 학습률에 따른 이동 거리가 달라 학습에 유리하다. 가능하면 RMSE를 사용하길 추천한다.

 다음 포스트에서는 데이터를 분류하는 경우 사용되는 손실함수인 교차 엔트로피 오차(Cross Entropy Error, CEE)를 학습해보도록 하겠다.

728x90
반응형

+ Recent posts