🐬 ML & Data/🎫 라이트 λ¨Έμ‹ λŸ¬λ‹

[라이트 λ¨Έμ‹ λŸ¬λ‹] Session 12. 순차 νŠΉμ„± 선택 μ•Œκ³ λ¦¬μ¦˜κ³Ό 랜덀 포레슀트 νŠΉμ„± μ€‘μš”λ„ μ‚¬μš©

darly213 2020. 2. 16. 00:56
728x90

이번 μ„Έμ…˜μ—μ„œλŠ” 순차 νŠΉμ„± 선택을 ν•˜λŠ” 방법과 랜덀 ν¬λ ˆμŠ€νŠΈμ—μ„œ νŠΉμ„± μ€‘μš”λ„λ₯Ό μ‚¬μš©ν•˜λŠ” 방법을 μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. κ°€λ³ΌκΉŒμš”?

1. 순차 νŠΉμ„± 선택 μ•Œκ³ λ¦¬μ¦˜

 λͺ¨λΈ λ³΅μž‘λ„λ₯Ό μ€„μ΄λŠ” 방법을 Session 11μ—μ„œ μ†Œκ°œν–ˆμ—ˆλŠ”λ°μš”, λ‹€λ₯Έ 방법은 νŠΉμ„± 선택을 ν†΅ν•œ 차원 μΆ•μ†Œ(dimensionality reduction)κ°€ μžˆμŠ΅λ‹ˆλ‹€. κ·œμ œκ°€ μ—†λŠ” λͺ¨λΈμ—μ„œ μœ μš©ν•˜μ£ . 차원 μΆ•μ†Œμ—λŠ” μ£Όμš” μΉ΄ν…Œκ³ λ¦¬μΈ νŠΉμ„± 선택(feature selection)κ³Ό νŠΉμ„± μΆ”μΆœ(feature extraction)이 μžˆμŠ΅λ‹ˆλ‹€. 

 νŠΉμ„± 선택은 νŠΉμ„± μ€‘μ—μ„œ μ„ νƒν•˜λŠ” 것이고, μΆ”μΆœμ€ νŠΉμ„±μ—μ„œ 얻은 μ •λ³΄λ“€λ‘œ μƒˆ νŠΉμ„±μ„ λ§Œλ“œλŠ” κ²ƒμž…λ‹ˆλ‹€. νŠΉμ„± 선택에 μžˆμ–΄μ„œ μ€‘μš”ν•œ 것은 λ¬Έμ œμ— κ°€μž₯ 관련이 높은 νŠΉμ„± 뢀뢄집합을 μžλ™μ„ νƒν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. μ΄λ²ˆμ—λŠ” νŠΉμ„± 선택 μ•Œκ³ λ¦¬μ¦˜μΈ 순차 νŠΉμ„± 선택(sequential feature selection) μ•Œκ³ λ¦¬μ¦˜μ— λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. 순차 νŠΉμ„± 선택 μ•Œκ³ λ¦¬μ¦˜μ€ 탐색 μ•Œκ³ λ¦¬μ¦˜(greedy search algorithm)으둜 d μ°¨μ›μ΄μ—ˆλ˜ νŠΉμ„±κ³΅κ°„μ„ d보닀 μž‘μ€ k μ°¨μ›μœΌλ‘œ μΆ•μ†Œμ‹œν‚΅λ‹ˆλ‹€. 

 μˆœμ°¨ νŠΉμ„± μ•Œκ³ λ¦¬μ¦˜ 쀑 전톡적인 것은 순차 ν›„μ§„ 선택(sequential backward selection, SBS)μž…λ‹ˆλ‹€. SBSλŠ” 초기 νŠΉμ„±μ˜ λΆ€λΆ„κ³΅κ°„μœΌλ‘œ 차원을 μΆ•μ†Œμ‹œν‚΅λ‹ˆλ‹€. 

 SBS μ•Œκ³ λ¦¬μ¦˜μ€ μƒˆ νŠΉμ„±μ˜ 뢀뢄곡간이 λͺ©ν‘œν•œ νŠΉμ„± κ°œμˆ˜κ°€ 될 λ•ŒκΉŒμ§€ 전체 νŠΉμ„±μ—μ„œ 순차적으둜 νŠΉμ„±μ„ μ œκ±°ν•©λ‹ˆλ‹€. μ΄λ•Œ νŠΉμ„±μ˜ 제거 기쀀을 μœ„ν•΄ μ΅œμ†Œν™”ν•  κΈ°μ€€ ν•¨μˆ˜κ°€ ν•„μš”ν•©λ‹ˆλ‹€. κΈ°μ€€ν•¨μˆ˜μ—μ„œ κ³„μ‚°ν•œ 값은 제거 μ „ν›„μ˜ λͺ¨λΈμ˜ μ„±λŠ₯ μ°¨μ΄μž…λ‹ˆλ‹€. κ°€μž₯ κΈ°μ€€ 값이 큰 νŠΉμ„±μ„ μ œκ±°ν•˜κ²Œ 되겠죠. κ°„λ‹¨νžˆ λ„€ λ‹¨κ³„λ‘œ μ •λ¦¬ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

  1. μ•Œκ³ λ¦¬μ¦˜μ„ k=d (dλŠ” 전체 νŠΉμ„±κ³΅κ°„μ˜ 차원)둜 μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€.
  2. 쑰건 x = argmax J(Xk - x)λ₯Ό μ΅œλŒ€ν™”ν•˜λŠ” νŠΉμ„± x'λ₯Ό κ²°μ •ν•©λ‹ˆλ‹€.
  3. νŠΉμ„± μ§‘ν•©μ—μ„œ νŠΉμ„± x'λ₯Ό μ œκ±°ν•©λ‹ˆλ‹€. 
  4. kκ°€ λͺ©ν‘œν•œ κ°œμˆ˜κ°€ 되면 μ’…λ£Œν•˜κ±°λ‚˜ 2둜 λŒμ•„κ°‘λ‹ˆλ‹€.

 SBS μ•Œκ³ λ¦¬μ¦˜μ€ μ‚¬μ΄ν‚·λŸ°μ— κ΅¬ν˜„λ˜μ–΄μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 파이썬으둜 직접 κ΅¬ν˜„ν•œ μ½”λ“œλŠ” μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

from sklearn.base import clone
from itertools import combinations
import numpy as np
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split


class SBS():
    def __init__(self, estimator, k_features, scoring=accuracy_score,
                 test_size=0.25, random_state=1):
        self.scoring = scoring
        self.estimator = clone(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):
                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 -= 1	
            
        	self.scores_.append(scores[best])
        self.k_score_ = self.scores_[-1]

        return self

    def transform(self, X):
        return X[:, self.indices_]

    def _calc_score(self, X_train, y_train, X_test, y_test, indices):
        self.estimator.fit(X_train[:, indices], y_train)
        y_pred = self.estimator.predict(X_test[:, indices])
        score = self.scoring(y_test, y_pred)
        return score

 

 μ—¬κΈ°μ„œ λͺ©ν‘œν•œ νŠΉμ„± 개수 kλŠ” k_feature λ§€κ°œλ³€μˆ˜μž…λ‹ˆλ‹€. accuracy_score ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄ λͺ¨λΈμ˜ μ„±λŠ₯을 ν‰κ°€ν•˜κ³ , fit λ©”μ„œλ“œμ˜ 반볡문 μ•ˆμ—μ„œ itertools.combination ν•¨μˆ˜μ— μ˜ν•΄ μƒμ„±λœ νŠΉμ„± 쑰합을 ν‰κ°€ν•˜κ³  μ€„μž…λ‹ˆλ‹€. 그리고 X_test 에 κΈ°μ΄ˆν•œ μ‘°ν•©μ˜ 정확도 점수λ₯Ό self.scores_λ¦¬μŠ€νŠΈμ— λͺ¨μλ‹ˆλ‹€. 이 점수둜 λ‚˜μ€‘μ— κ²°κ³Όλ₯Ό ν‰κ°€ν•©λ‹ˆλ‹€. μ΅œμ’…μœΌλ‘œ λ§Œλ“€μ–΄μ§„ νŠΉμ„±μ˜ μ—΄ μΈλ±μŠ€λŠ” self.indices_에 ν• λ‹Ήλ©λ‹ˆλ‹€. 이것은 transformμ—μ„œ μ„ νƒλœ νŠΉμ„±μœΌλ‘œ κ΅¬μ„±λœ μƒˆλ‘œμš΄ 배열을 λ°˜ν™˜ν•  λ•Œ μ“°μž…λ‹ˆλ‹€.

 μ΄μ œ μ‚¬μ΄ν‚·λŸ°μ˜ KNN λΆ„λ₯˜κΈ°λ₯Ό ν†΅ν•΄μ„œ ν™•μΈν•΄λ³ΌκΉŒμš”?

import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=5)

# νŠΉμ„±μ„ μ„ νƒν•©λ‹ˆλ‹€.
sbs = SBS(knn, k_features=1)
sbs.fit(X_train_std, y_train)

# νŠΉμ„± μ‘°ν•©μ˜ μ„±λŠ₯ κ·Έλž˜ν”„λ₯Ό 좜λ ₯ν•©λ‹ˆλ‹€.
k_feat = [len(k) for k in sbs.subsets_]

plt.plot(k_feat, sbs.scores_, marker='o')
plt.ylim([0.7, 1.02])
plt.ylabel('Accuracy')
plt.xlabel('Number of features')
plt.grid()
plt.tight_layout()
plt.show()

 

 fit μ•ˆμ—μ„œ SBSκ°€ 데이터셋을 ν›ˆλ ¨κ³Ό ν…ŒμŠ€νŠΈλ‘œ λ‚˜λˆ„κΈ°λŠ” ν•˜μ§€λ§Œ μ—¬μ „νžˆ 이 μ½”λ“œμ—μ„œλŠ” X_train λ°μ΄ν„°λ§Œ μ£Όμž…ν•©λ‹ˆλ‹€. μ΄λ•Œ SBS의 fit λ©”μ„œλ“œκ°€ λ‚˜λˆ„λŠ” 데이터셋 쀑 ν…ŒμŠ€νŠΈ μ„ΈνŠΈλ₯Ό κ²€μ¦μ„ΈνŠΈ(validation set)이라고 λΆ€λ₯΄κΈ°λ„ ν•©λ‹ˆλ‹€. 이 κ²½μš°μ—λŠ” ν›ˆλ ¨ 데이터와 ν…ŒμŠ€νŠΈ 데이터λ₯Ό 미리 λΆ„λ¦¬ν•΄λ†“μ•„μ•Όν•©λ‹ˆλ‹€.

 SBS둜 각 λ‹¨κ³„μ—μ„œ κ°€μž₯ 쒋은 νŠΉμ„±μ‘°ν•©μ˜ 점수λ₯Ό λͺ¨μ•„λ†“μ•˜μœΌλ―€λ‘œ 이 μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜λ©΄ 검증 μ„ΈνŠΈλ‘œ κ³„μ‚°ν•œ KNN λΆ„λ₯˜κΈ°μ˜ 정확도λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. 

 μœ„ κ·Έλž˜ν”„μ—μ„œ 확인할 수 μžˆλ“―μ΄ KNN λΆ„λ₯˜κΈ°μ˜ μ •ν™•λ„λŠ” νŠΉμ„± κ°œμˆ˜κ°€ μ€„μ—ˆμ„ λ•Œ ν–₯μƒλ©λ‹ˆλ‹€. κ·Έλ ‡λ‹€λ©΄ 이제 μ›λž˜ ν…ŒμŠ€νŠΈ μ„ΈνŠΈμ—μ„œμ˜ KNN λΆ„λ₯˜κΈ° μ„±λŠ₯을 평가해보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€. 

knn.fit(X_train_std, y_train)
print('ν›ˆλ ¨ 정확도:', knn.score(X_train_std, y_train))
print('ν…ŒμŠ€νŠΈ 정확도:', knn.score(X_test_std, y_test)

 ν›ˆλ ¨ μ„ΈνŠΈμ—μ„œλŠ” 97% μ •λ„μ˜ 정확도λ₯Ό, ν…ŒμŠ€νŠΈ μ„ΈνŠΈμ—μ„œλŠ” 96% μ •λ„μ˜ 정확도λ₯Ό λ³΄μ—¬μ£Όλ„€μš”. 그럼 μ„ νƒλœ μ„Έ 개의 νŠΉμ„±μ—μ„œμ˜ μ„±λŠ₯도 ν™•μΈν•΄λ³ΌκΉŒμš”?

knn.fit(X_train_std[:, k3], y_train)
print('ν›ˆλ ¨ 정확도:', knn.score(X_train_std[:, k3], y_train))
print('ν…ŒμŠ€νŠΈ 정확도:', knn.score(X_test_std[:, k3], y_test))

 μ „체 νŠΉμ„±μ˜ 1/4도 μ•ˆλ˜λŠ” νŠΉμ„±μ„ μ‚¬μš©ν–ˆμ§€λ§Œ ν…ŒμŠ€νŠΈ μ„ΈνŠΈμ˜ μ •ν™•λ„λŠ” 크게 λ–¨μ–΄μ‘Œλ‹€κ³  ν•˜κΈ΄ νž˜λ“€μ–΄λ³΄μž…λ‹ˆλ‹€. 이 μ„Έ 개의 νŠΉμ„±μ˜ νŒλ³„μ •λ³΄κ°€ μ›λž˜ 데이터셋보닀 그리 μž‘μ§€ μ•Šλ‹€λŠ” λœ»μž…λ‹ˆλ‹€. 

 Wine 데이터셋은 μ›λž˜λ„ 그리 크지 μ•Šμ€ 데이터셋이라 데이터셋을 ν›ˆλ ¨κ³Ό ν…ŒμŠ€νŠΈλ‘œ λ‚˜λˆˆ 것과 λ‹€μ‹œ ν›ˆλ ¨κ³Ό κ²€μ¦μœΌλ‘œ λ‚˜λˆˆ 것에 영ν–₯을 많이 λ°›μŠ΅λ‹ˆλ‹€.

 μ—¬κΈ°μ„œ μ•Œ 수 μžˆλŠ” 점은 νŠΉμ„± 개수λ₯Ό μ€„μ΄λŠ” 것이 KNN λͺ¨λΈμ˜ μ„±λŠ₯을 높이진 μ•Šμ§€λ§Œ ν…Œμ΄ν„° 크기λ₯Ό 쀄일 수 μžˆμ—ˆλ‹€λŠ” 점이고, κ·Έ λ•Œλ¬Έμ— 더 κ°„λ‹¨ν•œ λͺ¨λΈμ„ 얻을 수 μžˆμ—ˆλ‹€λŠ” μ μž…λ‹ˆλ‹€.

 

2. 랜덀 포레슀트의 νŠΉμ„± μ€‘μš”λ„ μ‚¬μš©

 μ΄μ „ μ„Έμ…˜μ—μ„œ 앙상블을 μ†Œκ°œν•  λ•Œ 잠깐 λ“±μž₯ν–ˆλ˜ 랜덀 포레슀트λ₯Ό κΈ°μ–΅ν•˜μ‹œλ‚˜μš”? 랜덀 포레슀트λ₯Ό μ‚¬μš©ν•˜λ©΄ κ²°μ • νŠΈλ¦¬μ—μ„œ κ³„μ‚°ν•œ 평균 λΆˆμ†λ„λ₯Ό κ°μ†Œμ‹œν‚΄μœΌλ‘œμ¨ νŠΉμ„±μ˜ μ€‘μš”λ„λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. μ‚¬μ΄ν‚·λŸ°μ—μ„œ RandomForestClassifier λͺ¨λΈμ„ ν›ˆλ ¨ν•˜κ³  feature_importances_μ†μ„±μ—μ„œ νŠΉμ„± μ€‘μš”λ„ 값을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

from sklearn.ensemble import RandomForestClassifier

feat_labels = df_wine.columns[1:]

forest = RandomForestClassifier(n_estimators=500,
                                random_state=1)

forest.fit(X_train, y_train)
importances = forest.feature_importances_

indices = np.argsort(importances)[::-1]

for f in range(X_train.shape[1]):
    print("%2d) %-*s %f" % (f + 1, 30, 
                            feat_labels[indices[f]], 
                            importances[indices[f]]))

plt.title('Feature Importance')
plt.bar(range(X_train.shape[1]), 
        importances[indices],
        align='center')

plt.xticks(range(X_train.shape[1]), 
           feat_labels[indices], rotation=90)
plt.xlim([-1, X_train.shape[1]])
plt.tight_layout()
plt.show()

 μœ„ μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜λ©΄ 각 νŠΉμ„±μ˜ μƒλŒ€μ  μ€‘μš”λ„μ— λ”°λ₯Έ μˆœμœ„λ₯Ό ν‘œλ‘œ λ³΄μ—¬μ€λ‹ˆλ‹€. 이 μ€‘μš”λ„λŠ” 합이 1이 λ˜λ„λ‘ μ •κ·œν™” λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€. 500개 κ²°μ • νŠΈλ¦¬μ—μ„œ κ°€μž₯ νŒλ³„λ ₯이 쒋은 νŠΉμ„±μ€ prolineλΆ€ν„° alcoholκΉŒμ§€μž…λ‹ˆλ‹€. 이 κ·Έλž˜ν”„μ—μ„œ μƒμœ„ νŠΉμ„± 쀑 두 κ°œλŠ” μœ„μ—μ„œ κ΅¬ν˜„ν•œ SBS μ•Œκ³ λ¦¬μ¦˜μ—μ„œ μ„ νƒν•œ 3개의 νŠΉμ„±μ— λ“€μ–΄μžˆμŠ΅λ‹ˆλ‹€. 

 λžœλ€ ν¬λ ˆμŠ€νŠΈμ—μ„œ 두 개 μ΄μƒμ˜ νŠΉμ„±μ΄ μ„œλ‘œ 상관관계가 κΉŠλ‹€λ©΄, ν•˜λ‚˜λŠ” μ•„μ£Ό 잘 μž‘μ•„λ‚΄μ§€λ§Œ λ‹€λ₯Έ μ •λ³΄λŠ” 잘 μ°Ύμ•„λ‚΄μ§€ λͺ»ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ§Œμ•½ νŠΉμ„± μ€‘μš”λ„ 값보닀 λͺ¨λΈμ˜ μ„±λŠ₯μ—λ§Œ 관심이 μžˆλ‹€λ©΄ λ¬΄μ‹œν•˜μ…”λ„ μ’‹μŠ΅λ‹ˆλ‹€λ§Œ, μ•Œμ•„λ‘μ…”λ„ μ’‹μŠ΅λ‹ˆλ‹€.

 μ‚¬μ΄ν‚·λŸ°μ˜ SelectFromModel은 λͺ¨λΈ ν›ˆλ ¨μ΄ λλ‚œ λ‹€μŒμ— μ‚¬μš©μžκ°€ μ •ν•œ 값을 기반으둜 νŠΉμ„±μ„ μ„ νƒν•©λ‹ˆλ‹€. λ‚˜μ€‘μ— λ“±μž₯ν•  Pipeline의 λ‹¨κ³„μ—μ„œ RondomForestClassifierλ₯Ό νŠΉμ„± μ„ νƒκΈ°λ‘œ μ‚¬μš©ν•  λ•Œ μœ μš©ν•©λ‹ˆλ‹€. μ•„λž˜ μ½”λ“œλŠ” μ‚¬μš©μž μ§€μ • κ°’, 즉 μž„κ³„κ°’μ„ 0.1둜 ν•΄ νŠΉμ„±μ„ μ€‘μš”ν•œ 5개둜 μ€„μ—¬μ€λ‹ˆλ‹€.

from sklearn.feature_selection import SelectFromModel

sfm = SelectFromModel(forest, threshold=0.1, prefit=True)
X_selected = sfm.transform(X_train)
print('이 μž„κ³„ 쑰건을 λ§Œμ‘±ν•˜λŠ” μƒ˜ν”Œμ˜ 수:', X_selected.shape[1])
# 이 μž„κ³„ 쑰건을 λ§Œμ‘±ν•˜λŠ” μƒ˜ν”Œμ˜ 수: 5

for f in range(X_selected.shape[1]):
    print("%2d) %-*s %f" % (f + 1, 30, 
                            feat_labels[indices[f]], 
                            importances[indices[f]]))


μ—¬κΈ°κΉŒμ§€ 순차 νŠΉμ„± 선택 μ•Œκ³ λ¦¬μ¦˜κ³Ό 랜덀 ν¬λ ˆμŠ€νŠΈμ— λŒ€ν•΄μ„œ μ•Œμ•„λ³΄μ•˜μŠ΅λ‹ˆλ‹€. 주둜 차원 μΆ•μ†Œλ‚˜ λ°μ΄ν„°μ˜ 크기λ₯Ό μ€„μ΄λŠ” κΈ°λ²•λ“€μ΄μ—ˆλŠ”λ°μš”, λ‹€μŒμœΌλ‘œ 기닀리고 μžˆλŠ” μ„Έ 개의 μ„Έμ…˜λ“€μ—μ„œλ„ 차원 μΆ•μ†Œλ₯Ό μ‚¬μš©ν•œ 데이터 압좕을 λ‹€λ£° μ˜ˆμ •μž…λ‹ˆλ‹€. 그럼 μ €λŠ” λ‹€μŒ μ„Έμ…˜μ—μ„œ PCAλ₯Ό λ“€κ³  λŒμ•„μ˜€λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€. λ‹€μŒ μ„Έμ…˜μ—μ„œ λ΄¬μš”!

728x90