이번 포스팅에서는 일변량 분석과 모델 특성 분석에 대하여 알아보도록 하겠습니다.
새로운 특성을 만들어 내는 방법은 많기 때문에 데이터의 차원이 원본 특성의 수 이상으로 증가하기가 쉽습니다. 하지만 특성이 추가된다는 이야기는 곧 모델이 복잡해진다는 이야기이고, 과대적합이 될 가능성이 커지게 됩니다.
보통 고차원 데이터셋을 사용할 때, 가장 유용한 특성만 선택하고 나머지는 무시해서 특성의 수를 줄이는 것이 좋습니다. 즉 모델의 복잡도를 줄이고 일반화 성능을 끌어올릴 수 있다는 이야기가 됩니다.
특성을 자동으로 선택하기 위한 방법은 총 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 안에 있는 랜덤 포레스트의 성능이 이 모델에서 선택한 특성으로 만든 로지스틱 회귀의 성능과 같습니다. 즉 특성 선택이 제대로만 된다면 선형 모델의 성능은 랜덤 포레스트와 견줄 수 있다는 이야기입니다.
머신러닝 알고리즘에 어떤 입력값을 넣을지 확신이 안 선다면 특성 자동 선택이 도움이 될 수는 있습니다. 예측 속도를 높이거나 해석하기 더 쉬운 모델을 만드는 데 필요한 만큼 특성의 수를 줄이는데도 굉장히 효과적입니다.
대부분 실전에서는 특성 선택이 큰 성능 향상을 끌어내지는 못하나 머신러닝 엔지니어에게는 매우 중요한 도구입니다.
'Programming > 특성 공학' 카테고리의 다른 글
[Machine Learning]지도 학습 (0) | 2023.04.14 |
---|---|
[데이터 전처리]수치 변환 (1) | 2023.04.04 |
[데이터 전처리]구간분할과 이산화 & 상호작용과 다항식 (0) | 2023.03.21 |
[데이터 전처리]연속형과 범주형 (One Hot Encoding) (0) | 2023.03.16 |
[데이터 전처리]정규화(Normalisation)와 스케일 조정 (0) | 2021.04.07 |