데이터 전처리 : 순차적 특성 선택
March 20, 2022, 8:27 p.m.
모델을 훈련하다 보면 훈련셋에서의 성능이 테스트셋에서의 성능보다 많이 높게 나오는 경우가 있습니다. 즉, 일반화 성능이 안좋은 것인데요. 모델이 훈련셋에 과대적합 되었기 때문에 이러한 일이 발생하게 됩니다.
과대적합을 막기 위해서는 모델을 최대한 간단하게 만드는 것이 중요합니다. 필요없는 특성까지 반영하여 모델을 훈련시키게 되면 데이터의 의미를 찾지 못하기 때문에 일반화하지 못하고 훈련 데이터에만 과대적합하게 되는 것이죠.
이번 포스트에서는 의미 있는 특성을 탐욕적으로 찾는(Greedy Search) 방법인 순차적 특성 선택 알고리즘에 대해서 알아보도록 하겠습니다.
1. 순차적 특성 선택 알고리즘이란?
순차적 특성 선택이란 훈련 데이터의 특성들 중에서 제일 의미 없는 특성부터 한개씩 줄여 나가면서 성능이 좋은 가장 간단한 모델을 찾는 알고리즘입니다. 각 특성관의 관계에 대해서 하나도 몰라도 됩니다. 제거했을때 성능이 가장 좋은 경우를 쫓아 가는 것입니다.
먼저 임의로 특성을 1개 없애보고 훈련시킨후 모델의 성능을 평가합니다.
이것을 모든 특성에 대해서 진행하고 그 중에서 제거 했을 때 평가 성능이 제일 좋았던 특성을 제거합니다.
이 과정을 반복하여 특성의 개수가 정해진 개수까지 줄여지면 종료합니다. 간단하죠?
파이썬으로 한번 구현해 보겠습니다.
2. 파이썬 구현
python machine learning 2nd ed. 책을 참고 하였습니다.
from itertools import combinations //특성간의 조합을 쉽게 얻어낼 수 있는 Combinations(조합)
from sklearn.metrics import accuracy_score // 모델의 성능을 평가할 accuracy score
from sklearn.model_selection import train_test_split //훈련할 데이터와 테스트할 데이터를 나누어줌
class Sequential_Selection():
def __init__(self, estimator, k_features, scoring=accuracy_score, test_size=0.25, random_state=1):
self.scoring = scoring
self.estimator = estimator
self.k_features = k_features
self.test_size = test_size
self.random_state = random_state
def fit(self, X, y):
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=self.test_size, random_state=self.random_state)
//훈련 셋과 테스트 셋으로 나누어줌.
dim = X_train.shape[1] // 특성의 차원
self.indices_ = tuple(range(dim)) // 사용할 특성 튜플
self.subsets_ = [self.indices_] // 튜플을 담을 최종 리스트
score = self._calc_score(X_train, y_train, X_test, y_test, self.indices_) // 모델 평가(현재는 모든 특성 사용)
self.scores_ = [score] //정확도 점수 추가
while dim > self.k_features: //정해진 특성 개수에 도달할때까지
scores = []
subsets = []
for p in combinations(self.indices_, r=dim -1): // 특성에서 1개를 뺀 조합을 내놓는 이터레이터
score = self._calc_score(X_train, y_train, X_test, y_test, p) // 해당 조합으로 모델 평가
scores.append(score) //점수 추가
subsets.append(p) //해당 조합 추가
best = np.argmax(scores) //추가된 점수중에서 가장 큰 점수의 인덱스를 구함
self.indices_ = subsets[best] // 해당 인덱스로 해당 조합을 불러옴
self.subsets_.append(self.indices_) // 해당 조합을 최종 리스트에 추가
dim = dim -1 //특성 1개 줄임!
self.scores_.append(scores[best])
self.k_score_ = self.scores_[-1] //최종 점수
return self
def transform(self, X): // fit()을 통해 특성제거된 데이터 리턴
return X.iloc[:, list(self.indices_)]
def _calc_score(self, X_train, y_train, X_test, y_test, indices): // 데이터와 특성 정보를 받아 모델 훈련후 점수 리턴
self.estimator.fit(X_train.iloc[:, list(indices)], y_train)
y_pred = self.estimator.predict(X_test.iloc[:,list(indices)])
score = self.scoring(y_test, y_pred)
return score
3. 적용하기
해당 알고리즘을 직접 적용해 보고, 특성 조합에 따른 모델의 성능을 그래프로 그려볼게요.
import numpy as np
import pandas as pd
data = pd.read_csv('/content/drive/MyDrive/Kaggle/hand_gesture_data/train.csv')
id sensor_1 sensor_2 sensor_3 sensor_4 sensor_5 sensor_6 \
0 1 -6.149463 -0.929714 9.058368 -7.017854 -2.958471 0.179233
1 2 -2.238836 -1.003511 5.098079 -10.880357 -0.804562 -2.992123
2 3 19.087934 -2.092514 0.946750 -21.831788 9.119235 17.853587
3 4 -2.211629 -1.930904 21.888406 -3.067560 -0.240634 2.985056
4 5 3.953852 2.964892 -36.044802 0.899838 26.930210 11.004409
... ... ... ... ... ... ... ...
2330 2331 -3.971043 39.913391 16.034626 -19.067697 8.061361 -70.916786
2331 2332 -3.011710 -4.060355 -1.046067 4.178137 -2.003243 -2.895017
2332 2333 -9.001824 5.985711 -8.146347 -10.902201 5.102105 8.133692
2333 2334 -3.987992 3.011460 -11.949323 -3.810885 16.880234 -5.150117
2334 2335 -1.838225 -7.023497 -45.877365 20.026927 4.058551 8.062100
sensor_7 sensor_8 sensor_9 ... sensor_24 sensor_25 sensor_26 \
0 -0.956591 -0.972401 5.956213 ... -7.026436 -6.006282 -6.005836
1 26.972724 -8.900861 -5.968298 ... -1.996714 -7.933806 -3.136773
2 -21.069954 -15.933212 -9.016039 ... -6.889685 54.052330 -6.109238
3 -29.073369 0.200774 -1.043742 ... -2.126170 -1.035526 2.178769
4 -21.962423 -11.950189 -20.933785 ... -2.051761 10.917567 1.905335
... ... ... ... ... ... ... ...
2330 -39.937026 12.834223 -21.937973 ... 3.086417 -4.954858 -11.106802
2331 -2.766757 -29.099123 -4.208953 ... 6.871938 -0.134367 -0.867018
2332 32.877614 -3.017438 -3.174442 ... -7.952857 2.049467 -5.825790
2333 9.182801 4.960190 -21.002525 ... 3.080276 2.054739 -1.052350
2334 19.083782 -21.881795 -9.106341 ... 31.130021 5.121935 -1.003704
sensor_27 sensor_28 sensor_29 sensor_30 sensor_31 sensor_32 target
0 7.043084 21.884650 -3.064152 -5.247552 -6.026107 -11.990822 1
1 8.774211 10.944759 9.858186 -0.969241 -3.935553 -15.892421 1
2 12.154595 6.095989 -40.195088 -3.958124 -8.079537 -5.160090 0
3 10.032723 -1.010897 -3.912848 -2.980338 -12.983597 -3.001077 1
4 -13.004707 17.169552 2.105194 3.967986 11.861657 -27.088846 2
... ... ... ... ... ... ... ...
2330 -37.863399 31.069292 -4.097017 -13.095192 -5.150284 8.016265 3
2331 23.892336 -11.977934 1.984203 0.891666 28.822082 -0.878670 3
2332 -37.989569 15.014132 1.160272 -11.135889 -7.035763 -0.930067 3
2333 -6.019488 -7.075333 -5.826058 -3.989168 14.916905 -12.093426 1
2334 -58.953961 -22.095226 -0.898581 1.164833 -21.977991 -13.060285 2
데이터를 가져왔습니다. 센서 데이터와 target 레이블로 이루어져 있는 데이터네요.
y = data.iloc[:, -1]
X = data.iloc[:,1:-1]
레이블값과 데이터를 나눕니다.
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(solver='liblinear', multi_class='auto',C=100.0, random_state=78)
간단한 로지스틱 회귀 분류 모델을 가져왔습니다.
불러온 데이터와 모델로 순차적 특성 선택을 적용해 보겠습니다.
ss = Sequential_Selection(lr, 6)
ss.fit(X, y)
특성을 6개로 줄이는 것을 목표로 훈련이 완료되었습니다.
그럼 Sequential_Selection 클래스에 있는 scores_ 리스트를 그래프로 그려볼까요?
x축은 특성의 개수입니다.
import matplotlib.pyplot as plt
plt.plot([len(k) for k in ss.subsets_], ss.scores_, marker='o')
와우! 특성 선택의 효과가 있는 것 같네요.
특성이 32개일때는 정확도가 낮다가 특성의 개수가 17~21사이에서 제일 좋은 성능을 보이고, 그다음엔 다시 정확도가 내려가는 모습을 볼 수 있습니다.
transform() 메서드를 적용하면 특성을 6개로 줄인 것이 적용됩니다.
x_trans = ss.transform(X)
sensor_3 sensor_5 sensor_6 sensor_12 sensor_15 sensor_18
0 9.058368 -2.958471 0.179233 -4.061254 -13.956994 -1.957662
1 5.098079 -0.804562 -2.992123 -5.046353 -25.072542 -3.998035
2 0.946750 9.119235 17.853587 -9.000630 -10.954367 -6.118940
3 21.888406 -0.240634 2.985056 -0.069867 5.127194 -1.099702
4 -36.044802 26.930210 11.004409 5.961219 -9.970728 -3.161698
... ... ... ... ... ... ...
2330 16.034626 8.061361 -70.916786 11.739764 62.908697 -5.847474
2331 -1.046067 -2.003243 -2.895017 28.906574 -1.957465 -1.883162
2332 -8.146347 5.102105 8.133692 3.966698 -40.966145 -5.022308
2333 -11.949323 16.880234 -5.150117 -4.015727 65.066757 8.861321
2334 -45.877365 4.058551 8.062100 -9.064997 -62.952969 -9.053823
가장 중요한 특성 6개만 남은 모습을 볼 수 있습니다.
그렇지만 아까 그래프를 봤듯이 특성의 개수가 17~21사이일때 가장 좋은 성능을 냈었죠? subsets_ 리스트에서 직접 해당 특성 리스트를 불러와 적용할 수도 있습니다.
x_trans_best = X.iloc[:, list(ss.subsets_[32-17])] // 특성을 17개로 줄였을 때
sensor_3 sensor_5 sensor_6 sensor_9 sensor_10 sensor_12 \
0 9.058368 -2.958471 0.179233 5.956213 4.145636 -4.061254
1 5.098079 -0.804562 -2.992123 -5.968298 -4.060134 -5.046353
2 0.946750 9.119235 17.853587 -9.016039 -5.975194 -9.000630
3 21.888406 -0.240634 2.985056 -1.043742 2.099845 -0.069867
4 -36.044802 26.930210 11.004409 -20.933785 -4.000506 5.961219
... ... ... ... ... ... ...
2330 16.034626 8.061361 -70.916786 -21.937973 14.942994 11.739764
2331 -1.046067 -2.003243 -2.895017 -4.208953 -4.793855 28.906574
2332 -8.146347 5.102105 8.133692 -3.174442 -5.724941 3.966698
2333 -11.949323 16.880234 -5.150117 -21.002525 -1.881519 -4.015727
2334 -45.877365 4.058551 8.062100 -9.106341 -1.056355 -9.064997
sensor_13 sensor_14 sensor_15 sensor_17 sensor_18 sensor_19 \
0 0.996632 -3.837345 -13.956994 2.130210 -1.957662 -1.149930
1 1.083819 3.978378 -25.072542 2.912269 -3.998035 6.069698
2 9.115957 12.097318 -10.954367 -19.069594 -6.118940 -5.001346
3 -0.114247 -1.896109 5.127194 2.970044 -1.099702 3.116767
4 9.907115 -0.067754 -9.970728 1.892233 -3.161698 -9.225990
... ... ... ... ... ... ...
2330 12.078842 7.000148 62.908697 -2.804120 -5.847474 -78.034200
2331 -1.865353 5.974470 -1.957465 -2.107972 -1.883162 -4.918032
2332 1.066745 3.988601 -40.966145 -5.877931 -5.022308 -25.948680
2333 -6.948209 -6.052921 65.066757 6.877978 8.861321 -15.161502
2334 10.027128 -2.950048 -62.952969 -26.725765 -9.053823 51.213548
sensor_20 sensor_21 sensor_22 sensor_25 sensor_26
0 6.082028 0.878612 5.093102 -6.006282 -6.005836
1 4.966187 1.994051 -1.132059 -7.933806 -3.136773
2 -9.105371 -9.894885 10.107614 54.052330 -6.109238
3 8.124209 -0.917418 -1.027199 -1.035526 2.178769
4 3.953956 -17.959652 -3.115491 10.917567 1.905335
... ... ... ... ... ...
2330 -6.052717 -8.027884 27.992994 -4.954858 -11.106802
2331 0.032044 -1.043068 -3.980494 -0.134367 -0.867018
2332 -15.880140 3.976966 10.996415 2.049467 -5.825790
2333 2.050648 -0.060606 -1.081436 2.054739 -1.052350
2334 -10.993737 7.971206 7.820410 5.121935 -1.003704
[2335 rows x 17 columns]
특성이 17개로 줄여진 모습입니다.
특성 선택을 통해서 차원을 줄이고 일반화 성능을 높이는 방법을 알아보았는데요, 가장 무식하게 접근하는 방법이기도 하지만 가장 간단한 방법이기도 합니다. 데이터에 따라서 순차적 특성 선택으로 특성을 줄여 모델의 차원을 낮추고 일반화 성능을 올려보세요.