728x90
반응형
SMALL

이번 포스팅에서는 수치 변경에 대하여 알아보도록 하겠습니다.

 

제곱항이나 세제곱 항을 추가하면 선형 회귀 모델에 도움이 되는 것이 확인되었습니다. 한편 log, exp, sin 같은 수학 함수를 적용하는 방법도 특성 변환에 유용하게 사용됩니다.

 

트리 기반 모델은 특성의 순서에만 영향을 받지만 선형 모델과 신경망은 각 특성의 스케일과 분포에 밀접하게 연관되어 있습니다.

그리고 특성과 타깃값 사이에 비선형성이 있다면, 특히 선형 회귀에서는 모델을 만들기가 어렵습니다. log와 exp 함수는 데이터의 스케일을 변경해 선형 모델과 신경망의 성능을 올리는데 도움을 줍니다.

또한, sin, cos 함수 같은 경우는 예전에 컴퓨터 메모리 가격 데이터를 사용한 예제처럼 주기적인 패턴이 들어있는 데이터를 다룰 때 편리하게 사용할 수 있습니다.

 

대부분의 모델은 각 특성이 (회귀에서는 타깃도) 정규분포와 비슷할 때 최고의 성능을 냅니다. log와 exp 같은 수학 함수를 사용하는 것은 약간의 편법이라고 할 수 있으나, 이런 정규분포 모양을 만드는데 쉽고 효율적입니다. 이런 변환이 도움 되는 전형적인 경우는 정수 카운트 데이터를 다룰 때입니다. 예를 들어 사용자가 얼마나 자주 로그인 하는가? 같은 특성들을 의미합니다. 여기서 실제 데이터의 속성과 비슷한 카운트 데이터를 만들어 사용하겠습니다.

이 특성들은 모두 정수이며 응답은 실수입니다.

from IPython.display import display
import numpy as np
import matplotlib.pyplot as plt
import mglearn
import platform
from sklearn.model_selection import train_test_split

plt.rcParams['axes.unicode_minus'] = False
%matplotlib inline
path = 'c:/Windows/Fonts/malgun.ttf'
from matplotlib import font_manager, rc
if platform.system() == 'Darwin':
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
else:
    print('Unknown system... sorry!')
rnd = np.random.RandomState(0)
X_org = rnd.normal(size=(1000, 3)) # 3열씩 1000개의 랜덤 숫자가 들어있는 다차원 배열로 만듦
w = rnd.normal(size=3) # 각 열별 무작위 샘플 추출하기

X = rnd.poisson(10 * np.exp(X_org)) # 포아송 분포(숫자가 적은 데이터가 더 많이 배치되기 위함임)
y = np.dot(X_org, w) # 랜덤값으로 생성된 값과 포아송 분포(X)와  가우스 분포상의 무작위 샘플과의 벡터 내적을 구함

print(X[:10, 0])

첫 번째 특성의 제일 앞을 살펴보면 모두 양의 정수이지만 특정한 패턴은 보이지 않습니다. 하지만 각 값이 나타난 횟수를 세면 그 분포가 잘 드러납니다.

print("특성 출현 횟수:\n{}".format(np.bincount(X[:, 0])))

2가 68번으로 가장 많이 나타나며 큰 값의 수는 빠르게 줄어듭니다. 그러나 85나 86처럼 아주 큰 값도 약간은 있습니다. 그래프로 확인해 보겠습니다.

bins = np.bincount(X[:, 0])
plt.bar(range(len(bins)), bins, color='gray')
plt.ylabel('출현 횟수')
plt.xlabel('값')
plt.show()

X [:, 1]과 X [:,2] 특성도 비슷합니다. 이런 종류의 분포는 작은 수치가 많고 큰 수치는 몇 안 되는 실제 자주 나타나는 데이터 분포입니다. 그러나 선형 모델은 이런 데이터를 잘 처리하지 못합니다. Ridge regression로 학습시켜 보겠습니다.

from sklearn.linear_model import Ridge
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
score = Ridge().fit(X_train, y_train).score(X_test, y_test)
print("테스트 점수: {:.3f}".format(score))

낮은 R^2 점수가 나왔습니다. Ridge는 X와 y의 관계를 제대로 모델링하지 못했습니다. 하지만 로그스케일로 변환하면 많은 도움이 됩니다. 데이터에 0이 있으면 log 함수를 적용할 수가 없기 때문에 log(X + 1)을 사용합니다.

X_train_log = np.log(X_train + 1)
X_test_log  = np.log(X_test + 1)

변환 후를 살펴보면 데이터의 분포가 덜 치우쳐 있으며 매우 큰 값을 가진 이상치가 보이지 않습니다.

plt.hist(X_train_log[:, 0], bins=25, color='gray')
plt.ylabel('출현 횟수')
plt.xlabel('값')
plt.show()

이 데이터에 Ridge 모델을 만들면 훨씬 좋은 결과가 등장합니다.

score = Ridge().fit(X_train_log, y_train).score(X_test_log, y_test)
print("테스트 점수: {:.3f}".format(score))

이런 방법이 항상 들어맞는 것은 아닙니다. 모든 특성이 같은 속성을 가지고 있었기 때문에 이 예제는 잘 들어맞았지만, 항상 그런 것은 아닙니다. 따라서 일부 특성만 변환하거나 특성마다 모두 다르게 변환시키기도 합니다.

트리 모델에서는 이러한 변환자체가 불필요하지만 선형 모델에서는 필수입니다. 가끔 회귀에서 타깃 변수 y를 변환하는 것이 좋을 때도 있습니다. 카운트를 예측하는 경우가 전형적인 예로 log(y + 1)를 사용해 변환하면 도움이 많이 됩니다.

언제 사용해야 하는가?

구간 분할, 다항식, 상호작용은 데이터가 주어진 상황에서 모델의 성능에 큰 영향을 줄 수 있습니다. 특별히 선형 모델이나 나이브 베이즈 모델 같은 덜 복잡한 모델일 경우입니다.

반면에 트리 기반 모델은 스스로 중요한 상호작용을 찾아낼 수 있고 대부분의 경우 데이터를 명시적으로 변환하지 않아도 됩니다. SVM, k-NN, 신경망 같은 모델은 가끔 구간분할, 상호작용, 다항식으로 이득을 볼 수 있지만 선형모델보다는 영향이 그렇게 뚜렷하지는 않습니다.

지금까지 모델 성능 향상을 위한 수치 변경에 대하여 알아보았습니다.

728x90
반응형
LIST

+ Recent posts