#!/usr/bin/env python # coding: utf-8 # # 머신 러닝 교과서 3판 # # 4장 - 좋은 훈련 데이터셋 만들기 – 데이터 전처리 # **아래 링크를 통해 이 노트북을 주피터 노트북 뷰어(nbviewer.jupyter.org)로 보거나 구글 코랩(colab.research.google.com)에서 실행할 수 있습니다.** # # # # #
# 주피터 노트북 뷰어로 보기 # # 구글 코랩(Colab)에서 실행하기 #
# ### 목차 # - 누락된 데이터 다루기 # - 테이블 형태 데이터에서 누락된 값 식별 # - 누락된 값이 있는 훈련 샘플이나 특성 제외 # - 누락된 값 대체 # - 사이킷런 추정기 API 익히기 # - 범주형 데이터 다루기 # - 판다스를 사용한 범주형 데이터 인코딩 # - 순서가 있는 특성 매핑 # - 클래스 레이블 인코딩 # - 순서가 없는 특성에 원-핫 인코딩 적용 # - 데이터셋을 훈련 데이터셋과 테스트 데이터셋으로 나누기 # - 특성 스케일 맞추기 # - 유용한 특성 선택 # - 모델 복잡도 제한을 위한 L1 규제와 L 2 규제 # - L 2 규제의 기하학적 해석 # - L1 규제를 사용한 희소성 # - 순차 특성 선택 알고리즘 # - 랜덤 포레스트의 특성 중요도 사용 # - 요약 #
# In[1]: from IPython.display import Image # # 4.1 누락된 데이터 다루기 # ## 4.1.1 테이블 형태 데이터에서 누락된 값 식별 # In[2]: import pandas as pd from io import StringIO import sys csv_data = \ '''A,B,C,D 1.0,2.0,3.0,4.0 5.0,6.0,,8.0 10.0,11.0,12.0,''' # 파이썬 2.7을 사용하는 경우 # 다음과 같이 문자열을 유니코드로 변환해야 합니다: if (sys.version_info < (3, 0)): csv_data = unicode(csv_data) df = pd.read_csv(StringIO(csv_data)) df # In[3]: df.isnull().sum() # In[4]: # `values` 속성으로 넘파이 배열을 얻을 수 있습니다 df.values #
# ## 4.1.2 누락된 값이 있는 훈련 샘플이나 특성 제외 # In[5]: # 누락된 값이 있는 행을 삭제합니다 df.dropna(axis=0) # In[6]: # 누락된 값이 있는 열을 삭제합니다 df.dropna(axis=1) # In[7]: # 모든 열이 NaN인 행을 삭제합니다 df.dropna(how='all') # In[8]: # NaN 아닌 값이 네 개보다 작은 행을 삭제합니다 df.dropna(thresh=4) # In[9]: # 특정 열에 NaN이 있는 행만 삭제합니다(여기서는 'C'열) df.dropna(subset=['C']) #
# ## 4.1.3 누락된 값 대체 # In[10]: # 원래 배열 df.values # In[11]: # 행의 평균으로 누락된 값 대체하기 from sklearn.impute import SimpleImputer import numpy as np imr = SimpleImputer(missing_values=np.nan, strategy='mean') imr = imr.fit(df.values) imputed_data = imr.transform(df.values) imputed_data # In[12]: from sklearn.preprocessing import FunctionTransformer ftr_imr = FunctionTransformer(lambda X: imr.fit_transform(X.T).T) imputed_data = ftr_imr.fit_transform(df.values) imputed_data # `SimpleImputer` 클래스의 `add_indicator` 매개변수를 `True`로 지정하면 `indicator_` 속성이 추가되고 `transform()` 메서드가 누락된 값의 위치를 포함된 배열을 반환합니다. # In[13]: imr = SimpleImputer(add_indicator=True) imputed_data = imr.fit_transform(df.values) imputed_data # 추가된 `indicator_` 속성은 `MissingIndicator` 클래스의 객체입니다. # In[14]: imr.indicator_ # `MissingIndicator` 객체의 `features_` 속성은 누락된 값이 있는 특성의 인덱스를 담고 있습니다. # In[15]: imr.indicator_.features_ # `MissingIndicator` 객체의 `fit_transform()` 메서드를 호출하면 `features_` 속성에 담긴 특성에서 누락된 값의 위치를 나타내는 배열을 반환합니다. 여기에서는 세 번째, 네 번째 특성의 누락된 값의 위치입니다. 이 배열의 값이 `SimpleImputer` 객체의 `transfrom()` 메서드가 반환한 배열의 마지막 두 열에 해당합니다. # In[16]: imr.indicator_.fit_transform(df.values) # In[17]: # 사이킷런 0.24 버전에서 추가되었습니다. imr.inverse_transform(imputed_data) # `SimpleImputer`는 한 특성의 통곗값을 사용하여 누락된 값을 채웁니다. 이와 달리 `IterativeImputer` 클래스는 다른 특성을 사용하여 누락된 값을 예측합니다. 먼저 `initial_strategy` 매개변수에 지정된 방식으로 누락된 값을 초기화합니다. 그다음 누락된 값이 있는 한 특성을 타깃으로 삼고 다른 특성을 사용해 모델을 훈련하여 예측합니다. 이런 식으로 누락된 값이 있는 모든 특성을 순회합니다. # # `initial_strategy` 매개변수에 지정할 수 있는 값은 `SimpleImputer`와 동일하게 `'mean'`, `'median'`, `'most_frequent'`, `'constant'`가 가능합니다. # # 예측할 특성을 선택하는 순서는 누락된 값이 가장 적은 특성부터 선택하는 `'ascending'`, 누락된 값이 가장 큰 특성부터 선택하는 `'descending'`, 왼쪽에서 오른쪽으로 선택하는 `'roman'`, 오른쪽에서 왼쪽으로 선택하는 `'arabic'`, 랜덤하게 고르는 `'random'`이 있습니다. 기본값은 `'ascending'`입니다. # # 특성 예측은 종료 조건을 만족할 때까지 반복합니다. 각 반복 단계에서 이전 단계와 절댓값 차이 중 가장 큰 값이 누락된 값을 제외하고 가장 큰 절댓값에 `tol` 매개변수를 곱한 것보다 작을 경우 종료합니다. `tol` 매개변수 기본값은 1e-3입니다. 또는 `max_iter` 매개변수에서 지정한 횟수에 도달할 때 종료합니다. `max_iter`의 기본값은 10입니다. # # 예측에 사용하는 모델은 `estimator` 매개변수에서 지정할 수 있으며 기본적으로 `BayesianRidge` 클래스를 사용합니다. 예측에 사용할 특성 개수는 `n_nearest_features`에서 지정할 수 있으며 상관 계수가 높은 특성을 우선하여 랜덤하게 선택합니다. 기본값은 `None`으로 모든 특성을 사용합니다. # In[18]: from sklearn.experimental import enable_iterative_imputer from sklearn.impute import IterativeImputer iimr = IterativeImputer() iimr.fit_transform(df.values) # `KNNImputer` 클래스는 K-최근접 이웃 방법을 사용해 누락된 값을 채웁니다. 최근접 이웃의 개수는 `n_neighbors` 매개변수로 지정하며 기본값은 5입니다. 샘플 개수가 `n_neighbors` 보다 작으면 `SimpleImputer(strategy='mean')`과 결과가 같습니다. # In[19]: from sklearn.impute import KNNImputer kimr = KNNImputer() kimr.fit_transform(df.values) # In[20]: df.fillna(df.mean()) # `fillna()` 메서드의 `method` 매개변수를 사용하여 누락된 값을 채울 수도 있습니다. `bfill` 또는 `backfill`은 누락된 값을 다음 행의 값으로 채웁니다. `ffill` 또는 `pad`는 누락된 값을 이전 행의 값으로 채웁니다. # In[21]: df.fillna(method='bfill') # method='backfill'와 같습니다 # In[22]: df.fillna(method='ffill') # method='pad'와 같습니다 # `axis=1`로 지정하면 행이 아니라 열을 사용합니다. 예를 들어 다음처럼 지정하면 이전 열의 값으로 누락된 값을 채웁니다. # In[23]: df.fillna(method='ffill', axis=1) #
# ## 4.1.4 사이킷런 추정기 API 익히기 # In[24]: Image(url='https://git.io/Jtmwv', width=400) # In[25]: Image(url='https://git.io/JtYZW', width=300) #
# # 4.2 범주형 데이터 다루기 # ## 4.2.1 판다스를 사용한 범주형 데이터 인코딩 # In[26]: import pandas as pd df = pd.DataFrame([['green', 'M', 10.1, 'class2'], ['red', 'L', 13.5, 'class1'], ['blue', 'XL', 15.3, 'class2']]) df.columns = ['color', 'size', 'price', 'classlabel'] df #
# ## 4.2.2 순서가 있는 특성 매핑 # In[27]: size_mapping = {'XL': 3, 'L': 2, 'M': 1} df['size'] = df['size'].map(size_mapping) df # In[28]: inv_size_mapping = {v: k for k, v in size_mapping.items()} df['size'].map(inv_size_mapping) #
# ## 4.2.3 클래스 레이블 인코딩 # In[29]: import numpy as np # 클래스 레이블을 문자열에서 정수로 바꾸기 위해 # 매핑 딕셔너리를 만듭니다 class_mapping = {label: idx for idx, label in enumerate(np.unique(df['classlabel']))} class_mapping # In[30]: # 클래스 레이블을 문자열에서 정수로 바꿉니다 df['classlabel'] = df['classlabel'].map(class_mapping) df # In[31]: # 클래스 레이블을 거꾸로 매핑합니다 inv_class_mapping = {v: k for k, v in class_mapping.items()} df['classlabel'] = df['classlabel'].map(inv_class_mapping) df # In[32]: from sklearn.preprocessing import LabelEncoder # 사이킷런의 LabelEncoder을 사용한 레이블 인코딩 class_le = LabelEncoder() y = class_le.fit_transform(df['classlabel'].values) y # In[33]: # 거꾸로 매핑 class_le.inverse_transform(y) # In[34]: class_le.classes_ #
# ## 4.2.4 순서가 없는 특성에 원-핫 인코딩 적용 # In[35]: X = df[['color', 'size', 'price']].values color_le = LabelEncoder() X[:, 0] = color_le.fit_transform(X[:, 0]) X # `OrdinalEncoder`와 `ColumnTransformer` 함께 사용하기 # In[36]: from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OrdinalEncoder ord_enc = OrdinalEncoder(dtype=int) col_trans = ColumnTransformer([('ord_enc', ord_enc, ['color'])]) X_trans = col_trans.fit_transform(df) X_trans # In[37]: col_trans.named_transformers_['ord_enc'].inverse_transform(X_trans) # In[38]: from sklearn.preprocessing import OneHotEncoder X = df[['color', 'size', 'price']].values color_ohe = OneHotEncoder() color_ohe.fit_transform(X[:, 0].reshape(-1, 1)).toarray() # In[39]: from sklearn.compose import ColumnTransformer X = df[['color', 'size', 'price']].values c_transf = ColumnTransformer([ ('onehot', OneHotEncoder(), [0]), ('nothing', 'passthrough', [1, 2])]) c_transf.fit_transform(X) # `OneHotEncoder`의 `dtype` 매개변수를 `np.int`로 지정하여 정수로 원-핫 인코딩합니다. # In[40]: c_transf = ColumnTransformer([ ('onehot', OneHotEncoder(dtype=int), [0]), ('nothing', 'passthrough', [1, 2])]) c_transf.fit_transform(X) # In[41]: # 원-핫 인코딩 via 판다스 pd.get_dummies(df[['price', 'color', 'size']]) # `column` 매개변수를 사용하여 변환하려는 특성을 구체적으로 지정할 수 있습니다. # In[42]: pd.get_dummies(df[['price', 'color', 'size']], columns=['size']) # In[43]: # get_dummies에서 다중 공선성 문제 처리 pd.get_dummies(df[['price', 'color', 'size']], drop_first=True) # In[44]: # OneHotEncoder에서 다중 공선성 문제 처리 color_ohe = OneHotEncoder(categories='auto', drop='first') c_transf = ColumnTransformer([ ('onehot', color_ohe, [0]), ('nothing', 'passthrough', [1, 2])]) c_transf.fit_transform(X) # ### 추가 내용: 순서가 있는 특성 인코딩 # 순서가 있는 특성의 범주 사이에서 수치적 크기에 대해 확신이 없거나 두 범주 사이의 순서를 정의할 수 없다면 임곗값을 사용해 0/1로 인코딩할 수 있습니다. 예를 들어 M, L, XL 값을 가진 특성 `size`를 2개의 새로운 특성 "x > M"와 "x > L"로 나눌 수 있습니다. # In[45]: df = pd.DataFrame([['green', 'M', 10.1, 'class2'], ['red', 'L', 13.5, 'class1'], ['blue', 'XL', 15.3, 'class2']]) df.columns = ['color', 'size', 'price', 'classlabel'] df # 판다스 데이터프레임의 `apply` 메서드를 사용해 임곗값 기준으로 특성을 인코딩하는 `lambda` 함수를 적용할 수 있습니다. # In[46]: df['x > M'] = df['size'].apply(lambda x: 1 if x in {'L', 'XL'} else 0) df['x > L'] = df['size'].apply(lambda x: 1 if x == 'XL' else 0) del df['size'] df #
# # 4.3 데이터셋을 훈련 데이터셋과 테스트 데이터셋으로 나누기 # In[47]: df_wine = pd.read_csv('https://archive.ics.uci.edu/' 'ml/machine-learning-databases/wine/wine.data', header=None) # UCI 머신러닝 저장소의 Wine 데이터셋에 접근되지 않을 때 # 다음 코드의 주석을 제거하고 로컬 경로에서 데이터셋을 읽으세요: # df_wine = pd.read_csv('wine.data', header=None) df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash', 'Alcalinity of ash', 'Magnesium', 'Total phenols', 'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins', 'Color intensity', 'Hue', 'OD280/OD315 of diluted wines', 'Proline'] print('Class labels', np.unique(df_wine['Class label'])) df_wine.head() # In[48]: from sklearn.model_selection import train_test_split X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values X_train, X_test, y_train, y_test =\ train_test_split(X, y, test_size=0.3, random_state=0, stratify=y) #
# # 4.4 특성 스케일 맞추기 # 최소-최대 스케일링(min-max scaling): # # $x^{(i)}_{norm} = \dfrac{x^{(i)}-x_{min}}{x_{max}-x_{min}}$ # In[49]: from sklearn.preprocessing import MinMaxScaler mms = MinMaxScaler() X_train_norm = mms.fit_transform(X_train) X_test_norm = mms.transform(X_test) # 표준화(standardization): # # $x^{(i)}_{std} = \dfrac{x^{(i)}-\mu_x}{\sigma_x}$ # In[50]: from sklearn.preprocessing import StandardScaler stdsc = StandardScaler() X_train_std = stdsc.fit_transform(X_train) X_test_std = stdsc.transform(X_test) # 예제: # In[51]: ex = np.array([0, 1, 2, 3, 4, 5]) print('표준화:', (ex - ex.mean()) / ex.std()) # 판다스는 기본적으로 ddof=1를 사용합니다(샘플 표준 편차). # 반면 넘파이 std 메서드와 StandardScaler는 ddof=0를 사용합니다. # 정규화합니다 print('정규화:', (ex - ex.min()) / (ex.max() - ex.min())) # `RobustScaler`는 중간값(q2) 값을 빼고 1사분위(q1)와 3사분위(q3)의 차이로 나누어 데이터의 스케일을 조정합니다. # # $ x_{robust}^{(i)} = \dfrac{x^{(i)} - q_2}{q_3 - q_1}$ # In[52]: from sklearn.preprocessing import RobustScaler rbs = RobustScaler() X_train_robust = rbs.fit_transform(X_train) X_test_robust = rbs.transform(X_test) # In[53]: (ex - np.percentile(ex, 50)) / (np.percentile(ex, 75) - np.percentile(ex, 25)) # `MaxAbsScaler`는 각 특성별로 데이터를 최대 절댓값으로 나눕니다. 따라서 각 특성의 최댓값은 1이 됩니다. 전체 특성은 [-1, 1] 범위로 변경됩니다. # In[54]: from sklearn.preprocessing import MaxAbsScaler mas = MaxAbsScaler() X_train_maxabs = mas.fit_transform(X_train) X_test_maxabs = mas.transform(X_test) # In[55]: ex / np.max(np.abs(ex)) # `StandardScaler`, `MinMaxScaler`, `RobustScaler`, `MaxAbsScaler`에 대응하는 `scale()`, `minmax_scale()`, `robust_scale()`, `maxabs_scale()` 함수가 있습니다. 이 함수들은 1차원 배열도 입력 받을 수 있습니다. # In[56]: from sklearn.preprocessing import scale, minmax_scale, robust_scale, maxabs_scale print('StandardScaler:', scale(ex)) print('MinMaxScaler:', minmax_scale(ex)) print('RobustScaler:', robust_scale(ex)) print('MaxAbsScaler:', maxabs_scale(ex)) # `MaxAbsScaler`, `maxabs_scaler()`는 데이터를 중앙에 맞추지 않기 때문에 희소 행렬을 사용할 수 있습니다. # In[57]: from scipy import sparse X_train_sparse = sparse.csr_matrix(X_train) X_train_maxabs = mas.fit_transform(X_train_sparse) # `RobustScaler`는 희소 행렬을 사용해 훈련할 수 없지만 변환은 가능합니다. # In[58]: X_train_robust = rbs.transform(X_train_sparse) # 마지막으로 `Normalizer` 클래스와 `normalize()` 함수는 특성이 아니라 샘플별로 정규화를 수행합니다. 또한 희소 행렬도 처리할 수 있습니다. 기본적으로 샘플의 L2 노름이 1이 되도록 정규화합니다. # In[59]: from sklearn.preprocessing import Normalizer nrm = Normalizer() X_train_l2 = nrm.fit_transform(X_train) # 예를 위해서 `ex` 데이터에 행을 하나 더 추가해 보겠습니다. 0 나눗셈 오류를 다루기 번거로우므로 편의상 0을 제외합니다. # In[60]: ex_2f = np.vstack((ex[1:], ex[1:]**2)) ex_2f # L2 노름의 공식은 다음과 같습니다. # # $ \lVert \boldsymbol{x} \rVert_2 = \sqrt{x_1^2 + x_2^2 + \cdots + x_n^2} $ # # 먼저 샘플 별로 특성의 제곱을 더하기 위해 `axis=1`을 사용합니다. 이 값의 제곱근을 구하면 L2 노름입니다. 그 다음 각 샘플의 특성을 해당 L2 노름으로 나눕니다. # In[61]: l2_norm = np.sqrt(np.sum(ex_2f ** 2, axis=1)) print(l2_norm) ex_2f / l2_norm.reshape(-1, 1) # `norm='l1'`으로 지정하면 절댓값인 L1 노름을 사용합니다. L1 노름의 공식은 다음과 같습니다. # # $ \lVert \boldsymbol{x} \rVert_1 = \lvert x_1 \rvert + \lvert x_2 \rvert + \cdots + \lvert x_n \rvert $ # # 앞에서와 마찬가지로 샘플 별로 L1 노름을 계산한 다음 각 샘플을 나눕니다. # In[62]: l1_norm = np.sum(np.abs(ex_2f), axis=1) print(l1_norm) ex_2f / l1_norm.reshape(-1, 1) # `norm='max'`로 지정하면 각 샘플의 최대 절댓값을 사용하여 나눕니다. # In[63]: max_norm = np.max(np.abs(ex_2f), axis=1) print(max_norm) ex_2f / max_norm.reshape(-1, 1) #
#
# # 4.5 유용한 특성 선택 # 과대적합을 막는 방법: # # * 더 많은 훈련 데이터 # * 파라미터가 적은 간단한 모델 # * 규제를 통한 복잡도 제어 # * 데이터의 차원 축소 # ## 4.5.1 모델 복잡도 제한을 위한 L1 규제와 L2 규제 # $L2: \lVert \boldsymbol w \rVert_2^2 = \sum_{j=1}^m w_j^2$ # # $L1: \lVert \boldsymbol w \rVert_1 = \sum_{j=1}^m \lvert w_j \rvert$ # ## 4.5.2 L2 규제의 기하학적 해석 # In[64]: Image(url='https://git.io/JtY8I', width=500) # In[65]: Image(url='https://git.io/JtY8L', width=500) #
# ## 4.5.3 L1 규제를 사용한 희소성 # In[66]: Image(url='https://git.io/JtY8t', width=500) # 사이킷런에서 L1 규제를 지원하는 모델은 `penalty` 매개변수를 `l1`으로 지정하여 희소한 모델을 만들 수 있습니다: # In[67]: from sklearn.linear_model import LogisticRegression LogisticRegression(penalty='l1', solver='liblinear') # 표준화 전처리된 Wine 데이터에 적용해 보죠. # In[68]: from sklearn.linear_model import LogisticRegression lr = LogisticRegression(penalty='l1', C=1.0, solver='liblinear', random_state=1) # C=1.0이 기본입니다. # 규제 효과를 높이거나 낮추려면 C 값을 증가시키거나 감소시킵니다. lr.fit(X_train_std, y_train) print('훈련 정확도:', lr.score(X_train_std, y_train)) print('테스트 정확도:', lr.score(X_test_std, y_test)) # In[69]: lr.intercept_ # In[70]: lr.coef_[lr.coef_!=0].shape # In[71]: lr.coef_ # In[72]: import matplotlib.pyplot as plt fig = plt.figure() ax = plt.subplot(111) colors = ['blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black', 'pink', 'lightgreen', 'lightblue', 'gray', 'indigo', 'orange'] weights, params = [], [] for c in np.arange(-4., 6.): lr = LogisticRegression(penalty='l1', C=10.**c, solver='liblinear', multi_class='ovr', random_state=0) lr.fit(X_train_std, y_train) weights.append(lr.coef_[1]) params.append(10**c) weights = np.array(weights) for column, color in zip(range(weights.shape[1]), colors): plt.plot(params, weights[:, column], label=df_wine.columns[column + 1], color=color) plt.axhline(0, color='black', linestyle='--', linewidth=3) plt.xlim([10**(-5), 10**5]) plt.ylabel('weight coefficient') plt.xlabel('C') plt.xscale('log') plt.legend(loc='upper left') ax.legend(loc='upper center', bbox_to_anchor=(1.38, 1.03), ncol=1, fancybox=True) # plt.savefig('images/04_07.png', dpi=300, # bbox_inches='tight', pad_inches=0.2) plt.show() #
# ## 4.5.4 순차 특성 선택 알고리즘 # In[73]: 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 # In[74]: 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.savefig('images/04_08.png', dpi=300) plt.show() # In[75]: k3 = list(sbs.subsets_[10]) print(df_wine.columns[1:][k3]) # In[76]: knn.fit(X_train_std, y_train) print('훈련 정확도:', knn.score(X_train_std, y_train)) print('테스트 정확도:', knn.score(X_test_std, y_test)) # In[77]: 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)) #
# # 4.6 랜덤 포레스트의 특성 중요도 사용 # In[78]: 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.savefig('images/04_09.png', dpi=300) plt.show() # In[79]: 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개를 출력해 보겠습니다: # In[80]: for f in range(X_selected.shape[1]): print("%2d) %-*s %f" % (f + 1, 30, feat_labels[indices[f]], importances[indices[f]])) # `RFE`는 재귀적 특성 제거 방법을 사용합니다. 처음에 모든 특성을 사용해 모델을 만들고 특성 중요도가 가장 낮은 특성을 제거합니다. 그다음 제외된 특성을 빼고 나머지 특성으로 새로운 모델을 만듭니다. 이런 식으로 미리 정의한 특성 개수가 남을 때까지 계속합니다. # # `RFE` 클래스의 `n_features_to_select` 매개변수에 선택할 특성의 개수를 지정합니다. 사이킷런 0.24 버전부터 [0, 1] 범위의 실수를 지정하여 선택할 특성의 비율을 지정할 수도 있습니다. 기본값은 입력 특성 개수의 절반입니다. # # `step` 매개변수에서 각 반복에서 제거할 특성의 개수를 지정합니다. (0, 1) 사이의 값을 지정하면 삭제할 특성의 비율이 됩니다. 기본값은 1입니다. # # 기본적으로 기반 모델의 `coef_`나 `feature_importances_` 속성을 기준으로 특성을 제거합니다. 사이킷런 0.24 버전에서 사용할 속성을 지정할 수 있는 `importance_getter` 매개변수가 추가되었습니다. # # `SelectFromModel`과 비교하기 위해 5개의 특성을 골라 보겠습니다. # In[81]: from sklearn.feature_selection import RFE rfe = RFE(forest, n_features_to_select=5) rfe.fit(X_train, y_train) # `ranking_` 속성에는 선택한 특성의 우선순위가 들어있습니다. 1은 기반 모델이 선택한 5개 특성입니다. # In[82]: rfe.ranking_ # 선택된 특성은 `support_` 속성에 `True`로 표시됩니다. 이 값을 `f_mask`로 저장하여 특성의 이름을 표시하는데 사용하겠습니다. # In[83]: f_mask = rfe.support_ # 훈련된 기반 모델(랜덤 포레스트)은 `estimator_` 속성에 저장되어 있습니다. 이 모델은 `n_features_to_select`개의 특성을 사용하여 훈련되어 있습니다. 이 모델의 특성 중요도를 저장하여 앞에서와 같이 중요도 순서대로 출력해 보겠습니다. # In[84]: importances = rfe.estimator_.feature_importances_ indices = np.argsort(importances)[::-1] # 기반 모델을 동일한 랜덤 포레스로 사용했기 때문에 `SelectFromModel` 클래스로 선택한 특성과 순서가 약간 바뀌었을 뿐 동일합니다. # In[85]: for i in indices: print("%2d) %-*s %f" % (f + 1, 30, feat_labels[f_mask][i], importances[i])) #