728x90
반응형
SMALL
728x90
반응형
LIST
728x90
반응형
SMALL

이번 포스팅에서는 일변량 분석과 모델 특성 분석에 대하여 알아보도록 하겠습니다. 

새로운 특성을 만들어 내는 방법은 많기 때문에 데이터의 차원이 원본 특성의 수 이상으로 증가하기가 쉽습니다. 하지만 특성이 추가된다는 이야기는 곧 모델이 복잡해진다는 이야기이고, 과대적합이 될 가능성이 커지게 됩니다.

보통 고차원 데이터셋을 사용할 때, 가장 유용한 특성만 선택하고 나머지는 무시해서 특성의 수를 줄이는 것이 좋습니다. 즉 모델의 복잡도를 줄이고 일반화 성능을 끌어올릴 수 있다는 이야기가 됩니다.

특성을 자동으로 선택하기 위한 방법은 총 3가지가 있습니다.

  • 일변량 통계 unvariable statistics
  • 모델 기반 선택 model-based selection
  • 반복적 선택 iterative selection

위 방법은 모두 지도학습 방법이므로 최적값을 찾기 위해서는 타깃이 필요합니다.

일변량 통계

일변량 통계에서는 개개의 특성과 타깃 사이에 중요한 통계적 관계가 있는지를 계산합니다. 그러고 나서 깊이 연관되어 있는 특성을 선택하게 됩니다. 분류에서는 분산 분석 이라고도 합니다.

이 방법의 핵심 요소는 일변량, 즉 각 특성이 독립적으로 평가된다는 점입니다. 따라서 다른 특성과 깊게 연관된 특성은 선택되지 않을 것입니다. 일변량 분석은 계산이 매우 빠르고 평가를 위해 모델을 만들 필요가 없습니다. 한편으로 이 방식은 특성을 선택한 후 적용하려는 모델이 무엇인지 상관하지 않고 사용할 수 있습니다.

scikit-learn에서 일변량 분석으로 특성을 선택하려면 분류에서는 f_classif(기본값)을 사용하고, 회귀에서는 f_regression을 보통 선택하여 테스트하고, 계산한 p-값( 특성이 선택되었을 때 그 특성을 지지하는 확률 )에 기초하여 특성을 제외하는 방식을 선택합니다.

이런 방식들은 타깃값과 연관성이 작을 것 같다는 뜻으로 받아들일 수 있는 매우 높은 p-값을 가진 특성을 제외할 수 있도록 임계값을 조정하는 매개변수를 사용합니다.

임계값을 계산하는 방법은 총 두 가지입니다.

  • SelectKBest - 고정된 K개의 특성을 선택
  • SelectPercentile - 지정된 비율만큼 특성을 선택

cancer 데이터셋의 분류를 위한 특성 선택을 적용시켜 보겠습니다. 의미가 없는 노이즈 특성(예측에 아무 쓸모없는 특성)을 추가하여 특선 선택이 의미 없는 특성을 식별해서 제거하는지 보도록 하겠습니다.

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~~~~~')


from sklearn.datasets import load_breast_cancer
from sklearn.feature_selection import SelectPercentile

cancer = load_breast_cancer()

#난수 발생
rng = np.random.RandomState(42)
noise = rng.normal(size=(len((cancer.data)), 50))

# 위에서 발생시킨 랜덤 데이터를 노이즈 특성으로 사용, 처음 30개는 원본 특성, 다음 50개는 노이즈
X_w_noise = np.hstack([cancer.data, noise])

X_train, X_test, y_train, y_test = train_test_split(X_w_noise, cancer.target, random_state=0, test_size=.5)
# f_classif와 SelectPercentile을 이용하여 특성의 50%를 선택하기
select = SelectPercentile(percentile=50)
select.fit(X_train, y_train)

#  훈련세트에 적용하기
X_train_selected = select.transform(X_train)

print("X_train.shape: {}".format(X_train.shape))
print("X_train_selected.shape: {}".format(X_train_selected.shape))

특성의 개수가 원래 80개에서 50% 수준인 40개로 줄어든 것이 확인됩니다. get_support 메소드는 선택된 특성을 불리언 값으로 표시해 주어 어떤 특성이 선택되었는지 확인할 수 있습니다.

mask = select.get_support()
print(mask)

# True는 검은색, False는 흰색으로 마스킹
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("특성 번호")
plt.yticks([0])
plt.show()

마스킹된 그래프를 보면 제거된 특성(흰색)은 대부분 노이즈 특성이고, 선택된 특성은 대부분 원본 특성입니다. 전체 특성을 이용했을 때와 선택된 특성만을 사용했을 때의 차이점을 보겠습니다.

from sklearn.linear_model import LogisticRegression

# 테스트 데이터 변환
X_test_selected = select.transform(X_test)

lr = LogisticRegression()
lr.fit(X_train, y_train)
print("전체 특성을 사용한 점수: {:.3f}".format(lr.score(X_test, y_test)))

lr.fit(X_train_selected, y_train)
print("선택된 일부 특성을 사용한 점수 : {:.3f}".format(lr.score(X_test_selected, y_test)))

이 경우에서는 원본 특성이 몇 개 없더라도 노이즈 특성을 제거한 쪽의 성능이 더 좋다는 것을 확인할 수 있습니다. 이 예는 인위적으로 간단하게 만든 예제이고, 엇갈리는 경우도 많습니다.

하지만 너무 많은 특성 때문에 모델을 만들기가 현실적으로 어려울 때 일변량 분석을 사용하여 특성을 선택하면 큰 도움이 될 수도 있습니다. 또는 많은 특성들이 확실히 도움이 안 된다고 생각될 때 사용하면 좋습니다.

모델 기반 특성 선택

모델 기반 특성 선택은 지도 학습 머신러닝 모델을 사용하여 특성의 중요도를 평가해서 가장 중요한 특성만 선택합니다. 특성 선택에 사용하는 지도 학습 모델은 최종적으로 사용할 지도 학습 모델과 같을 필요는 없습니다. 단지 특성 선택을 위한 모델은 각 특성의 중요도를 측정하여 순서를 매길 수 있어야 합니다.

결정 트리와 이를 기반으로 한 모델은 각 특성의 중요도가 담겨있는 featureimportance 속성을 사용합니다. 선형 모델 계수의 절댓값도 특성의 중요도를 재는 데 사용할 수 있습니다.

L1 규제를 사용한 선형 모델은 일부 특성의 계수만 학습에 사용됩니다. 이를 그 모델 자체를 위해 특성이 선택된다고도 볼 수 있지만, 다른 모델의 특성 선택을 위해 전처리 단계로도 활용할 수가 있습니다.

사용된 모델이 특성 간의 상호작용을 잡아낼 수 있다면 일변량 분석과는 반대로 모델 기반 특성 선택은 한 번에 모든 특성을 고려하므로 상호작용 부분을 반영할 수 있습니다.

모델 기반 특성 선택은 SelectFromModel에 구현되어 있습니다.

from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier

select = SelectFromModel(RandomForestClassifier(n_estimators=100, random_state=42), threshold='median')

L1규제가 없는 모델을 사용할 경우 SelectFromModel은 지도 학습 모델로 계산된 중요도가 지정한 임계치(threshold 매개변수의 기본값인 mean) 보다 큰 모든 특성을 선택합니다.

위에서 만든 SelectFromModel은 일변량 분석과 비교하기 위해 동일하게 절반의 특성을 선택하기 위해 중간값인 median을 사용하였습니다. 트리 100개로 만든 RandomForest 모형을 사용해 특성 중요도를 계산하게 됩니다.

이는 매우 복잡한 모델이고 일변량 분석보다는 훨씬 강력한 방법입니다.

select.fit(X_train, y_train)
X_train_l1 = select.transform(X_train)

print("X_train.shape : {}".format(X_train.shape))
print("X_train_l1.shape : {}".format(X_train_l1.shape))

이어서 선택된 특성을 일변량 통계와 같은 방식으로 그려보겠습니다.

mask = select.get_support()
# True는 검은색, False는 흰색으로 마스킹
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("특성 번호")
plt.yticks([0])
plt.show()

이번에는 두 개를 제외한 모든 원본 특성이 선택되었습니다. 특성을 40개 선택하도록 지정했기 때문에 일부 노이즈 특성까지 선택되었습니다. 성능을 확인해 보겠습니다.

X_test_l1 = select.transform(X_test)
score = LogisticRegression().fit(X_train_l1, y_train).score(X_test_l1, y_test)
print("테스트 점수: {:.3f}".format(score))

일변량 통계를 사용했을 때보다 점수가 약간 좋아졌습니다.

반복적 특성 선택

일변량 분석에서는 모델을 사용하지 않고, 모델 기반 선택에서는 하나의 모델을 사용해 특성을 선택하였습니다.

반복적 특성 선택 Iterative Feature Selection에서는 특성의 수가 각기 다른 일련의 모델이 생성됩니다. 기본적으로 두 가지 방법이 있습니다.

  • 특성을 하나도 선택하지 않은 상태로 어떠한 종료 조건에 도달할 때까지 특성을 하나씩 추가하는 방법
  • 모든 특성을 가지고 시작하여 어떤 종료 조건이 될 때까지 특성을 하나씩 제거해 가는 방법

위 방법은 일단 모델이 만들어지고 시작하기 때문에 일변량 분석과 모델 기반 선택보다 계산 비용이 훨씬 많이 듭니다. 재귀적 특성 제거 (RFE, recursive feature elimination)가 이런 방법의 하나입니다. 이 방법은 모든 특성으로 시작해서 모델을 만들고 특성 중요도가 가장 낮은 특성을 제거합니다. 그런 다음 제거한 특성을 빼고 나머지 특성을 전체로 새로운 모델을 만들어 냅니다.

이런 식으로 미리 정의한 특성 개수가 남을 때까지 반복하게 됩니다. 이를 위해 모델 기반 선택에서처럼 특성 선택에 사용할 모델은 특성의 중요도를 결정하는 방법을 제공해야 합니다.

from sklearn.feature_selection import RFE
select = RFE(RandomForestClassifier(n_estimators=100, random_state=42), n_features_to_select=40)

select.fit(X_train, y_train)
#선택한 특성을 표시
mask = select.get_support()
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel("특성 번호")
plt.yticks([0])
plt.show()

일변량 분석과 모델 분석에 비해 특성을 선택하는 것은 나아졌지만, 여전히 특성 한 개를 놓치긴 했습니다. 랜덤 포레스트 모델은 특성이 누락될 때마다( 특성이 하나씩 제거될 때마다 ) 다시 학습을 하기 때문에 시간이 조금 걸립니다. RFE를 사용해서 특성을 선택했을 때 로지스틱 회귀의 정확도를 보겠습니다.

X_train_rfe = select.transform(X_train)
X_test_rfe = select.transform(X_test)

score = LogisticRegression().fit(X_train_rfe, y_train).score(X_test_rfe, y_test)
print("테스트 점수 : {:.3f}".format(score))

또한 RFE에 사용된 모델을 이용해서도 예측을 수행할 수 있습니다. 이 경우에는 선택된 특성만 사용됩니다.

print("테스트 점수: {:.3f}".format(select.score(X_test, y_test)))

RFE 안에 있는 랜덤 포레스트의 성능이 이 모델에서 선택한 특성으로 만든 로지스틱 회귀의 성능과 같습니다. 즉 특성 선택이 제대로만 된다면 선형 모델의 성능은 랜덤 포레스트와 견줄 수 있다는 이야기입니다.

머신러닝 알고리즘에 어떤 입력값을 넣을지 확신이 안 선다면 특성 자동 선택이 도움이 될 수는 있습니다. 예측 속도를 높이거나 해석하기 더 쉬운 모델을 만드는 데 필요한 만큼 특성의 수를 줄이는데도 굉장히 효과적입니다.

대부분 실전에서는 특성 선택이 큰 성능 향상을 끌어내지는 못하나 머신러닝 엔지니어에게는 매우 중요한 도구입니다.

728x90
반응형
LIST
728x90
반응형
SMALL

이번 포스팅은 데이터 정규화(Normalisation)에 대해서 알아보도록 하겠습니다.

 

정규화(Normalisation)가 중요한 이유?

머신러닝 알고리즘은 데이터가 가진 feature(특성)들을 비교하여 데이터의 패턴을 찾습니다.

그런데 여기서 주의해야 할 점은 데이터가 가진 feature의 스케일이 심하게 차이가 나는 경우 문제가 되기 때문이다.

데이터의 차이는 모델의 성능을 좌우하기 때문에 신경망 같은 알고리즘들은 데이터의 스케일에 굉장히 민감합니다.

그래서 알고리즘들에 맞게 데이터의 특성 값들을 조절하게 되는데요, 보통 특성마다 스케일을 조정하여 데이터를 변경시킵니다.

예시를 한번 보겠습니다.

우선, 기본적으로 필요한 라이브러리를 임포트 해줍니다.

 

왼편의 그림은 데이터를 전처리 하기 전인 원본 데이터, 오른쪽은 각종 전처리 방법을 사용했을 때의 4가지 모습을 보여주고 있습니다.

본격적으로 전처리 방법에 대해 간단히 이야기해보겠습니다.

 

StandardScaler

scikit-learn의 StandardScaler는 각 특성의 평균을 0, 분산을 1로 변경하여 모든 특성이 같은 크기를 가지게 합니다. 하지만 최솟값과 최댓값 크기를 제한하지는 않습니다. 공식은 다음과 같습니다.

σ는 표준편차를 의미하고, x는 특성 값, 평균을 의미합니다. 이 결과를 표준 점수 또는 z-점수(z-score)라고도 합니다.

RobustScaler

RobustScaler는 일단 특성들이 같은 스케일을 갖게 된다는 것은 StandaradScaler와 비슷하나, 평균과 분산 대신 중간값 mediummedium과 사분위 값 quartilequartile을 사용합니다.

여기서 중간값이란 x보다 작은 수가 절반이고, x보다 큰 수가 절반인 x를 의미합니다. 단순히 평균이라고도 생각할 수 있는데요, 좌표 평면을 활용하기 때문에 각 사분위 별 개수가 중요합니다.

1 사분위 값은 x보다 작은 수가 전체 개수의 1/4인 x이고, 3 사분위는 x보다 큰 수가 전체 개수의 1/4인 x를 의미합니다. 공식은 다음과 같습니다.

q2는 중간값, q1은 1 사분위 값, q3은 3 사분위 값입니다.

이러한 방식 때문에 RobustScaler는 전체 데이터와 아주 동떨어진 데이터 포인트에 영향을 받지 않습니다. 이런 이상한 (멀리 떨어진 데이터)를 이상치(outlier) 라고 합니다. 이는 다른 스케일 조정 기법에서는 문제가 될 수도 있습니다.

MinMaxScaler

MinMaxScaler는 모든 특성이 정확하게 0과 1 사이에 위치하도록 데이터를 변경합니다. 2차원 데이터셋일 경우에는 모든 데이터가 x축의 0 ~ 1, y축은 0 ~ 1 사이의 사각 영역에 담기게 됩니다. 공식은 다음과 같습니다.

Normalizer

Normalizer는 매우 다른 스케일 조정 기법입니다. 이 방식은 특성 벡터의 유클리디안 길이가 1이 되도록 데이터 포인트를 조정합니다. 다른 정규 기법인 StandardScaler, RobustScaler, MinMaxScaler 기법은 특성들의 열 (특성)의 통계치를 사용하지만, Normalizer는 행(데이터 포인트)마다 각기 정규화됩니다.

다른 말로 하면 지름이 1인 원( 3차원 일 땐 구 )에 데이터 포인트를 투영하게 됩니다. 이 말은 각 데이터 포인트가 다른 비율로 (길이에 반비례하여) 스케일이 조정된다는 뜻이 됩니다. 이러한 정규화(normalizationnormalization)는 특성 벡터의 길이는 상관없고 데이터의 방향(또는 각도) 만이 중요할 때 많이 사용합니다.

데이터 변환 적용하기

여러 종류의 변환을 알아보았습니다. cancer 데이터셋에 커널 SVM(SVC)를 적용시키고 MinMaxScaler를 적용시켜 보겠습니다.

sklearn.preprocessing 모듈은 사이킷런에서 각종 전처리 작업을 위한 파이썬 클래스를 내장하고 있습니다.

MinMaxScaler 내부에는 fit 메서드가 존재합니다.

이 메서드는 모델을 훈련시키기 위해 존재하는 메서드가 아닌 각 특성마다의 최소, 최댓값을 계산해 줍니다.

따라서 특성을 의미하는 X_train만 넘겨줍니다.(y_train)은 사용하지 않습니다.

실제 fit 메서드를 통한 변환을 적용시키려면 transform() 메서드를 사용합니다.

transform은 새로운 데이터 표현(representation)을 만들 때 사용하는 메서드입니다.

변환된 배열 크기(shape)는 원본 배열과 동일합니다.

스케일 조정 후를 살펴보면 모든 특성의 최솟값과 최댓값이 각각 0과 1로 바뀐 것이 확인됩니다.

이 데이터에 SVM 모델을 적용하려면 테스트 데이터셋도 스케일 조정이 되어야 합니다. 이번엔 X_test를 조정하겠습니다.

이상하게도 똑같이 스케일을 조정했지만 0 또는 1이 나오지 않습니다. 그리고 0과 1의 범위를 넘어가 버리는 값도 존재하는 것이 확인됩니다.

모든 스케일 모델들은 항상 훈련 세트와 테스트 세트에 같은 변환을 적용시켜야 합니다. transform 메서드는 테스트 세트의 최솟값과 범위를 사용하지 않고, 항상 훈련 세트의 최솟값을 빼고 훈련 세트의 범위로 나눕니다. 간단하게 공식으로 살펴보면 다음과 같습니다.

훈련 데이터와 테스트 데이터의 스케일을 같은 방법으로 조정하기

지도 학습 모델에서 테스트 세트를 사용하려면 훈련 세트와 테스트 세트에 같은 변환을 적용해야 한다는 점이 중요합니다.

일단 이번 예제에서는 위와 반대로, 테스트 세트의 최솟값과 범위를 사용했을 때 어떤 일들이 일어나는지 보겠습니다.

from sklearn.datasets import make_blobs
# 인위적인 데이터 생성하기
X, _ = make_blobs(n_samples=50, centers=5, random_state=4, cluster_std=2)
# 훈련 세트와 테스트 세트로 나누기
X_train, X_test = train_test_split(X, random_state=5, test_size=.1)

# 훈련 세트와 테스트 세트의 산점도 그리기
fig, axes = plt.subplots(1,3,figsize=(13, 4))
axes[0].scatter(X_train[:, 0], X_train[:, 1], c=mglearn.cm2(0), label="훈련 세트", s=60)
axes[0].scatter(X_test[:, 0], X_test[:, 1],c=mglearn.cm2(1), label="테스트 세트", s=60, marker='^')
axes[0].legend(loc='upper left')
axes[0].set_title("원본 데이터")

# MinMaxScaler를 사용해 스케일 조정하기
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled  = scaler.transform(X_test)

# 스케일이 조정된 데이터의 산점도 그리기
axes[1].scatter(X_train_scaled[:, 0], X_train_scaled[:, 1], c=mglearn.cm2(0), label="훈련 세트", s=60)
axes[1].scatter(X_test_scaled[:, 0], X_test_scaled[:, 1], c=mglearn.cm2(1), label="테스트 세트", s=60, marker='^')
axes[1].set_title("스케일 조정된 데이터")


# 테스트 세트의 스케일을 따로 조정하기
# 테스트 세트의 최솟값은 0, 최댓값이 1이 됨
# 절대로 이렇게 사용하지 말것!

test_scaler = MinMaxScaler()
test_scaler.fit(X_test)
X_test_scaled_badly = test_scaler.transform(X_test)

# 잘못 조정된 데이터의 산점도 그리기
axes[2].scatter(X_train_scaled[:, 0], X_train_scaled[:, 1], c=mglearn.cm2(0), label="훈련 세트", s=60)
axes[2].scatter(X_test_scaled_badly[:, 0], X_test_scaled_badly[:, 1], 
                                    marker='^', c=mglearn.cm2(1), label="테스트 세트", s=60)
axes[2].set_title("잘못 조정된 데이터")

for ax in axes:
    ax.set_xlabel("특성 0")
    ax.set_ylabel("특성 1")

첫 번째 그림은 단순히 2차원 원본 데이터셋이며, 훈련 세트는 파란 동그라미, 테스트 세트는 빨간 세모로 표시합니다.

두 번째 그래프는 같은 데이터를 MinMaxScaler를 이용해 스케일을 조절하였는데, 각 그래프의 눈금 빼고는 모양이 변한 것이 없습니다.

하지만, 테스트 데이터(세모)의 최솟값과 최댓값이 0과 1은 아닙니다.

문제는 세 번째 데이터입니다. 훈련 세트와 테스트 세트에 대해 각각 스케일을 조정한 경우, 각 데이터셋이 서로 다르게 스케일 되었기 때문에 삼각형의 배치가 뒤죽박죽 되었습니다.

지도 학습에서 데이터 전처리 효과

다시 cancer 데이터셋으로 돌아와서, SVC를 학습시킬 때 MinMaxScaler의 효과를 확인해 보도록 하겠습니다.

from sklearn.svm import SVC

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0)

svm = SVC(C=100)
svm.fit(X_train, y_train)
print("테스트 세트 정확도: {:.2f}".format(svm.score(X_test, y_test)))

스케일이 조정되지 않은 원본 데이터의 정확도는 63%의 정확도 밖에 표현하지 못합니다. 다음은 스케일 조정 후의 학습 결과입니다.

# MinMaxScaler 사용
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled  = scaler.transform(X_test)

# 조정된 데이터로 SVM 학습
svm.fit(X_train_scaled, y_train)

# 스케일 조정된 테스트 세트의 정확도
print("스케일 조정된 테스트 세트의 정확도: {:.2f}".format(svm.score(X_test_scaled, y_test)))

단순히 스케일만 조정했을 뿐인데 그 효과는 매우 큰 것이 확인됩니다.

스케일을 조정하는 모든 파이썬 클래스들은 fit과 transform을 제공하므로 손쉽게 전처리 알고리즘을 바꿔낼 수 있습니다.

# 평균 0, 분선 1을 갖도록 스케일 조정하기
from sklearn.preprocessing import StandardScaler

# StandaradScaler 사용
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled  = scaler.transform(X_test)

# 조정된 데이터로 SVM 학습
svm.fit(X_train_scaled, y_train)

# 스케일 조정된 테스트 세트의 정확도
print("스케일 조정된 테스트 세트의 정확도: {:.2f}".format(svm.score(X_test_scaled, y_test)))

지금까지 데이터 학습 훈련을 위한 스케일링에 대하여 알아보았습니다.

다음 포스팅에서는 One-Hot-Encoding에 대하여 알아보도록 하겠습니다.

728x90
반응형
LIST

+ Recent posts