인공지능/Merchine_Learning

[파이썬 머신러닝 완벽가이드] Ch4. Classification

아네스 2020. 11. 10. 23:15
반응형
ch4_summary_hyeon

분류 개요와 결정트리 소개

정보 균일도 측정 방법

  • 엔트로피 : 데이터 집합의 혼잡도. 다른값 섞이면 엔트로피 높음. 같으면 낮음.
  • 정보 이득 지수 : 1-엔트로피 지수. 데이터 혼잡도가 낮으면 1-엔트로피는 높은값을 가짐. 즉 혼잡도가 낮을수록 높은 지수를 가진다.
    정보이득 지수가 높은 속성을 기준으로 분할
  • 지니계수 : 데이터가 다양한 값을 가질수록 평등하며 특정값으로 쏠릴 경우에는 불평등. 불평등할수록 1값
    즉, 지니계수가 높은 속성을 기준으로 분할(혼잡도 낮음)

결정트리의 특징

  • 장점 : 쉽고 직관적임, 피처스케일링이나 정규화 등의 사전 데이터 가공 영향도가 적음.
  • 단점 : 과적합으로 알고리즘 성능 저하(recursive하게 계속 파고들어서) -> 사전에 트리의 크기 제한하는 튜닝 필요.

결정트리 주요 하이퍼 파라미터

  • max_depth : 과적합 방지. 사전 트리 크기 제한.
  • max_feature : default값 None이고, default면 모든 피처를 사용해 분할 수행. float으로 지정하면 대상 피처의 퍼센트로 지정 가능
    sqrt, auto(sqrt와 동일), log등 파라미터 지정 가능. $$sqrt : \sqrt {total features}$$
  • min_samples_split : 과적합 방지. 노드를 분할하기위한 최소한의 sample수.
  • min_samples_leaf : 과적합 방지. leaf가 되기위한 최소한의 샘플 데이터 수
    imbalanced 데이터의 경우 특정 클래스의 데이터가 ㅣ극도로 작을 수 있으므로 작게 설정 필요.
  • max_leaf_nodes : leaf의 최대 개수

사용자 행동 인식데이터를 이용한 결정트리 실습

https://archive.ics.uci.edu/ml/machine-learning-databases/00240/

In [ ]:
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

#features.txt 파일에는 피쳐 이름 index와 피쳐명이 공백으로 분리 되어 있음. DataFrame으로 로드
feature_name_df = pd.read_csv('./human_activity/features.txt', sep='\s+', header = None, names=['column_index', 'column_name'])

feature_name_df.head()
In [ ]:
#피처명 index를 제거하고, 피처명만 리스트 객체로 생성한 뒤 샘플로 10개만 추출
feature_name = feature_name_df.iloc[:, 1].tolist()
feature_name

print('전체 피처명에서 10개만 추출\n', feature_name[:10])

질문. 왜 values.tolist()하는지 위 cell에서 바로 tolist하니까 되는데?

'./human_activity/train/X_train.txt'에서 .txt꼭 붙여야하는건지?

In [ ]:
def get_human_dataset():
    
    #각 데이터 파일들은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 serp으로 할당.
    feature_name_df = pd.read_csv('./human_activity/features.txt', sep = '\s+', header = None , names=['column_index', 'coluumn_name'])
    
    #DataFrame에 피처명을 컬럼으로 부여하기 위해 리스트 객체로 다시 변환
    feature_name = feature_name_df.iloc[:,1].values.tolist()
    
    #학습 피쳐 데이터 셋과 테스트 피쳐 데이터 셋을 DataFrame으로 로딩, 컬럼명은 feature_name 적용.
    X_train = pd.read_csv('./human_activity/train/X_train.txt', sep='\s+', names = feature_name)
    X_test = pd.read_csv('./human_activity/test/X_test.txt', sep = '\s+', names = feature_name)
    
    #학습 레이블과 테스트 레이블 데이터를 DataFrame으로 로딩하고 컬럼명은 action으로 부여
    y_train = pd.read_csv('./human_activity/train/y_train.txt', sep='\s+', header = None , names=['action'])
    y_test = pd.read_csv('./human_activity/test/y_test.txt', sep='\s+',  header = None , names=['action'])
    
    return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = get_human_dataset()

561개 feature에서 len(set(feature_name)) 하니 477개가 나왔음 90개정도 중복되고있는데. feature name이 중복되도 괜찮은지...

In [ ]:
print('## 학습 피쳐 데이터셋 info()')
print(X_train.info())
In [ ]:
y_train.head()
In [ ]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

#예제 반복 시 마다 동일한 예측 결과 도출을 위해 random_state 설정
dt_clf = DecisionTreeClassifier(random_state = 156)
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
accuracy = accuracy_score(y_test, pred)
print(f'결정 트리 예측 정확도 {accuracy:.4f}')

#DecisionTreeClassifier의 하이퍼 파라미터 추출
print('DecisionTreeClassifier 기본 하이퍼 파라미터:\n', dt_clf.get_params())
In [ ]:
from sklearn.model_selection import GridSearchCV
params = {
    'max_depth' : [6, 8, 10, 12, 16, 20, 24]
}
grid_cv = GridSearchCV(dt_clf, param_grid = params, scoring = 'accuracy', cv=5, verbose =1)
grid_cv.fit(X_train, y_train)
print(f'GridSearchCV 최고 평균 정확도 수치:{grid_cv.best_score_:.4f}')
print(f'GridSearchCV 최적 하이퍼 파라미터:{grid_cv.best_params_}')
In [ ]:
#GridSearchCV 객체이ㅡ cv_results_ 속성을 DataFrame으로 생성
cv_results_df = pd.DataFrame(grid_cv.cv_results_)


#max_depth 파라미터 값과 그때의 테스트(Evaluation)셋, 학습데이터 셋의 정확도 수치 추출
cv_results_df[['param_max_depth', 'mean_test_score']]
In [ ]:
max_depths = [6,8,10,12,16,20,24]
# max_depth값을 변화 시키면서 그때마다 학습과 테스트 셋에서의 예측 성능 측정
for depth in max_depths:
    dt_clf = DecisionTreeClassifier(max_depth = depth, random_state =156)
    dt_clf.fit(X_train, y_train)
    pred = dt_clf.predict(X_test)
    accuracy = accuracy_score(y_test , pred)
    print(f'max_depth = {depth}, 정확도 : {accuracy:.4f}')
In [ ]:
params = {
    'max_depth' : [ 8 , 12, 16 ,20], 
    'min_samples_split' : [16,24],
}
grid_cv = GridSearchCV(dt_clf, param_grid = params, scoring = 'accuracy', cv=5, verbose=1)
grid_cv.fit(X_train, y_train)
print(f'GridSearchCV 최고 평균 정확도 수치 : {grid_cv.best_score_:.4f}')
print(f'GridSearchCV 최적 하이퍼 파라미터 :{grid_cv.best_params_}')
In [ ]:
best_df_clf = grid_cv.best_estimator_

pred1 = best_df_clf.predict(X_test)
accuracy = accuracy_score(y_test , pred1)
print(f'결정 트리 예측 정확도:{accuracy:.4f}')
In [ ]:
import seaborn as sns

ftr_importances_values = best_df_clf.feature_importances_

#Top 중요도로 정렬을 쉽게 하고, seaborn의 막대그래프로 쉽게 표현하기 위해 Series변환
ftr_importances = pd.Series(ftr_importances_values, index =X_train.columns)
display(ftr_importances.sort_values(ascending = False).head(20))
#중요도 값 순으로 Series를 정렬
ftr_top20 = ftr_importances.sort_values(ascending = False)[:20]
print(ftr_top20.index)
plt.figure(figsize=(8,6)) #그래프 크기.
plt.title('Feature importances Top 20')
sns.barplot(x=ftr_top20, y = ftr_top20.index) #
plt.show()

앙상블(Ensemble) 학습의 개요와 보팅(voting)의 이해

앙상블 학습

  • 앙상블 학습을 통한 분류는 여러개의 분류기를 생성하고
    그 예측을 결합함으로써 보다 정확한 최종 예측을 도입하는 기법을 말한다.
  • 보팅(voting), 배깅(bagging), 부스팅(Boosting)으로 구분할 수 있으며, 이외에 스태킹(stacking)등의 기법이 있음.
    • 배깅 : 랜덤포레스트(Random Forest)알고리즘
    • 부스팅 : 에이다 부스팅, 그래디언트부스팅(GBM), XGBoost, LightGBM
  • 넓은 의미로는 서로 다른 모델을 결합한 것.
  • 성능은 각기 떨어지는 모델이지만 이질적인 모델들을 합치면 전체 성능에 도움이 될 수있음.
    성능이 좋은모델+ 좋은모델의 단점을 커버할 수 있는 이질적인 모델들 = 좋은 성능
    ex)Tree기반 + SVM기반 + Linear기반 앙상블 = 좋은 성능
    결정트리의 단점인 과적합(overfitting)을 수십~ 수천개의 많은 분류기를 결합해서 보완하고 장점인 직관적인 분류기준은 강화

    #### 보팅과 배깅

  • 보팅(voting) : 같은 data set + 다른 알고리즘 모델 (일반적으로 Soft voting이 성능이 우수)
    • Hard voting : 각 모델들이 예측 후 다수결
    • Soft voting : 각 모델들이 예측전 확률값을 평균내어 결정
  • 배깅(bagging) : split된 data set + 같은 알고리즘 모델

위스콘신 유방암 데이터로드 (Voting방식 실습)

In [ ]:
import pandas as pd

from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

cancer = load_breast_cancer()

data_df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
data_df.head(3)

VotingClassifier안의 voting = 'soft'를 지정해서 소프트보팅 객체 만듦.

  • LogisticRegression모델 + KNeighborsClassifier모델
In [ ]:
# 개별 모델은 로지스틱 회귀와 KNN 임. 
lr_clf = LogisticRegression()
knn_clf = KNeighborsClassifier(n_neighbors=8)

# 개별 모델을 소프트 보팅 기반의 앙상블 모델로 구현한 분류기 
vo_clf = VotingClassifier( estimators=[('LR',lr_clf),('KNN',knn_clf)] , voting='soft' )

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, 
                                                    test_size=0.2 , random_state= 156)

# VotingClassifier 학습/예측/평가. 
vo_clf.fit(X_train , y_train)
pred = vo_clf.predict(X_test)
print('Voting 분류기 정확도: {0:.4f}'.format(accuracy_score(y_test , pred)))

# 개별 모델의 학습/예측/평가.
classifiers = [lr_clf, knn_clf]
for classifier in classifiers:
    classifier.fit(X_train , y_train)
    pred = classifier.predict(X_test)
    class_name= classifier.__class__.__name__
    print('{0} 정확도: {1:.4f}'.format(class_name, accuracy_score(y_test , pred)))

배깅(bagging) - 랜덤 포레스트(Random Forest)

  • 배깅의 대표적인 알고리즘 : 랜덤 포레스트
  • 빠른수행속도.
  • 여러개의 결정트리 분류기가 전체데이터에서 배깅방식으로 각자의 데이터 샘플링
    개별적으로 학습을 수행하고 최종적으로 모든 분류기가 보팅을 통해 예측 결정
  • bootstrap aggregating의 줄임말 : 개별트리가 학습하는 데이터세트는 전체 데이터에서 일부가 중첩되게 샘플링된 데이터세트
    이렇게 중복 sampling하는 방식을 bootstraping이라 한다.
  • 하이퍼 파라미터
    • n_estimators : 결정트리의 개수(default 10) 많으면 좋은 성능을 기대할 수 있지만 무조건적인 성능 향상x. 학습수행시간과 trade-off
    • max_features : 기본설정이 None이 아님. sqrt라서 9개의 feature면 3개의 feature를 참조.
In [ ]:
import pandas as pd

def get_new_feature_name_df(old_feature_name_df):
    feature_dup_df = pd.DataFrame(data=old_feature_name_df.groupby('column_name').cumcount(), columns=['dup_cnt'])
    feature_dup_df = feature_dup_df.reset_index()
    new_feature_name_df = pd.merge(old_feature_name_df.reset_index(), feature_dup_df, how='outer')
    new_feature_name_df['column_name'] = new_feature_name_df[['column_name', 'dup_cnt']].apply(lambda x : x[0]+'_'+str(x[1]) 
                                                                                           if x[1] >0 else x[0] ,  axis=1)
    new_feature_name_df = new_feature_name_df.drop(['index'], axis=1)
    return new_feature_name_df
In [ ]:
import pandas as pd

def get_human_dataset( ):
    
    # 각 데이터 파일들은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
    feature_name_df = pd.read_csv('./human_activity/features.txt',sep='\s+',
                        header=None,names=['column_index','column_name'])
    
    # 중복된 feature명을 새롭게 수정하는 get_new_feature_name_df()를 이용하여 새로운 feature명 DataFrame생성. 
    new_feature_name_df = get_new_feature_name_df(feature_name_df)
    
    # DataFrame에 피처명을 컬럼으로 부여하기 위해 리스트 객체로 다시 변환
    feature_name = new_feature_name_df.iloc[:, 1].values.tolist()
    
    # 학습 피처 데이터 셋과 테스트 피처 데이터을 DataFrame으로 로딩. 컬럼명은 feature_name 적용
    X_train = pd.read_csv('./human_activity/train/X_train.txt',sep='\s+', names=feature_name )
    X_test = pd.read_csv('./human_activity/test/X_test.txt',sep='\s+', names=feature_name)
    
    # 학습 레이블과 테스트 레이블 데이터을 DataFrame으로 로딩하고 컬럼명은 action으로 부여
    y_train = pd.read_csv('./human_activity/train/y_train.txt',sep='\s+',header=None,names=['action'])
    y_test = pd.read_csv('./human_activity/test/y_test.txt',sep='\s+',header=None,names=['action'])
    
    # 로드된 학습/테스트용 DataFrame을 모두 반환 
    return X_train, X_test, y_train, y_test


X_train, X_test, y_train, y_test = get_human_dataset()

학습/테스트 데이터로 분리하고 (get_human_dataset) 랜덤 포레스트로 학습/예측/평가

In [ ]:
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

# 결정 트리에서 사용한 get_human_dataset( )을 이용해 학습/테스트용 DataFrame 반환
X_train, X_test, y_train, y_test = get_human_dataset()

# 랜덤 포레스트 학습 및 별도의 테스트 셋으로 예측 성능 평가
rf_clf = RandomForestClassifier(random_state=0)
rf_clf.fit(X_train , y_train)
pred = rf_clf.predict(X_test)
accuracy = accuracy_score(y_test , pred)
print('랜덤 포레스트 정확도: {0:.4f}'.format(accuracy))

GridSearchCV로 교차검증 및 하이퍼 파라미터 튜닝

In [ ]:
from sklearn.model_selection import GridSearchCV

params = {
    'n_estimators':[100],
    'max_depth' : [6, 8, 10, 12], 
    'min_samples_leaf' : [8, 12, 18 ],
    'min_samples_split' : [8, 16, 20]
}
# RandomForestClassifier 객체 생성 후 GridSearchCV 수행
rf_clf = RandomForestClassifier(random_state=0, n_jobs=-1)
grid_cv = GridSearchCV(rf_clf , param_grid=params , cv=2, n_jobs=-1 )
grid_cv.fit(X_train , y_train)

print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도: {0:.4f}'.format(grid_cv.best_score_))

튜닝된 하이퍼 파라미터로 재 학습 및 예측/평가

In [ ]:
rf_clf1 = RandomForestClassifier(n_estimators=300, max_depth=10, min_samples_leaf=8, \
                                 min_samples_split=8, random_state=0)
rf_clf1.fit(X_train , y_train)
pred = rf_clf1.predict(X_test)
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test , pred)))

개별 feature들의 중요도 시각화

In [ ]:
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

ftr_importances_values = rf_clf1.feature_importances_
ftr_importances = pd.Series(ftr_importances_values,index=X_train.columns  )
ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]

plt.figure(figsize=(8,6))
plt.title('Feature importances Top 20')
sns.barplot(x=ftr_top20 , y = ftr_top20.index)
plt.show()

부스팅

여러개의 약한 학습기(weak learner)를 순차적으로 학습-예측 하면서
잘못 예측한 데이터에 가중치 부여를 통해 오류를 개선해 나가면서 학습하는 방식

  • 순차적으로 하다보니 수행시간이 오래걸림
  • 에이다부스팅 프로세스 : 오류들에 가중치 부여 - 분류기준 - 예측 반복.
  • GBM(Gradient Boost Machine) : 에이다부스트와 유사하나, 가중치 업데이트를 경사하강법을 이용하는 것이 큰차이
    h(x) = y-F(x)를 최소화하는 방향성을 가지고 가중치 업데이트.

사이킷런 GBM 하이퍼파라미타

  • loss : 경사하강법에 사용할 비용함수. default = deviance
  • learning_rate : 학습률 - 딥러닝의 알파와 같음. 아는 내용이니 설명 생략.
  • n_estimators : weak learner의 개수. 약한 학습기가 순차적으로 오류를 보정하므로 개수가 많을수록 성능상승
    그러나 개수가 많을수록 수행시간 오래걸림. default = 100
  • subsample : weak learner가 학습에 사용하는 데이터의 샘플링 비율. default =1. 과적합 우려시 1보다 작은값설정
In [ ]:
from sklearn.ensemble import GradientBoostingClassifier
import time
import warnings
warnings.filterwarnings('ignore')

X_train, X_test, y_train, y_test = get_human_dataset()

# GBM 수행 시간 측정을 위함. 시작 시간 설정.
start_time = time.time()

gb_clf = GradientBoostingClassifier(random_state=0)
gb_clf.fit(X_train , y_train)
gb_pred = gb_clf.predict(X_test)
gb_accuracy = accuracy_score(y_test, gb_pred)

print('GBM 정확도: {0:.4f}'.format(gb_accuracy))
print("GBM 수행 시간: {0:.1f} 초 ".format(time.time() - start_time))
In [ ]:
scores_df = pd.DataFrame(grid_cv.cv_results_)
scores_df[['params', 'mean_test_score', 'rank_test_score',
'split0_test_score', 'split1_test_score']]
In [ ]:
# GridSearchCV를 이용하여 최적으로 학습된 estimator로 predict 수행. 
gb_pred = grid_cv.best_estimator_.predict(X_test)
gb_accuracy = accuracy_score(y_test, gb_pred)
print('GBM 정확도: {0:.4f}'.format(gb_accuracy))

XGBoost (eXtra Gradient Boost)

  • 뛰어난 예측 성능, GBM 대비 빠른 수행시간 CPU병렬, GPU 지원
  • 다양한 성능 향상기능 (regularization)기능, Tree Pruning
    • Tree pruning(가지치기) : 노드를 모두 만들었다가 역으로 노드들이 제 역할을 하는지 검증. 불필요하다고 판단되면 가지치기 해버림.
  • 다양한 편의 기능 추가(조기중단, 내장 교차검증, 결손값 자체처리 ### 파이썬 Wrapper vs 사이킷런 Wrapper
    • 파이썬 래퍼를 쓰던 안쓰던 어느정도 이해 필요.
    • XGBoost의 하이퍼파라미터가 많지만 엄청 공들여서 튜닝하지 말 것.
    • 사이킷런 Wrapper의 경우 GBM에 동일한 하이퍼 파라미터가 있다면
      이를 사용하고 그렇지 않다면 파이썬 Wrapper의 하이퍼 파라미터를 사용
    • 다른 하이퍼파라미터들 설명은 생략.
    • Early Stopping
      • 특정 반복 횟수(n)만큼 더이상 비용함수가 감소하지 않으면 지정된 반복횟수를 다 완료하지 않고 수행 종료.
      • n값이 너무 적으면 성능 최적화가 안된상태에서 학습이 종료될 수 있음.
      • early_stooping_rounds : n값 설정해줌
      • eval_metric : 반복 수행시 사용하는 비용 평가 지표
      • eval_set : 평가를 수행하는 별도의 검증 데이터 세트(validation set)
In [ ]:
import xgboost 
xgboost.__version__

파이썬 래퍼 XGBoost 적용
위스콘신 Breast Cancer 데이터 셋

데이터 세트 로딩

In [ ]:
import xgboost as xgb
from xgboost import plot_importance
import pandas as pd
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')

dataset = load_breast_cancer()
X_features = dataset.data
y_label = dataset.target

cancer_df = pd.DataFrame(data = X_features, columns = dataset.feature_names)
cancer_df['target'] = y_label
cancer_df.info()
In [ ]:
print(dataset.target_names)
print(cancer_df['target'].value_counts())
In [ ]:
#전체 데이터 중 80%는 학습용 데이터, 20%는 테스트용 데이터 추출
X_train, X_test, y_train, y_test = train_test_split(X_features, y_label, test_size = 0.2, random_state=156)
print(X_train.shape, X_test.shape)

학습과 예측 데이터 세트를 DMatrix로 변환

In [ ]:
dtrain = xgb.DMatrix(data=X_train, label=y_train)
dtest = xgb.DMatrix(data=X_test, label = y_test)

하이퍼 파라미터 설정

In [ ]:
params = {'max_depth':3,
          'eta' : 0.1,
          'objective':'binary:logistic',
          'eval_metric':'logloss',
          'early_stopings' : 100
         }
num_rounds = 400

주어진 하이퍼 파라미터와 early stopping 파라미터를 train() 함수의 파라미터로 전달하고 학습

In [ ]:
#train 데이터 셋은 'train', evaluation(test) 데이터 셋은 'eval' 로 명기한다.
wlist = [(dtrain,'train'),(dtest,'eval')]
#하이퍼 파라미터와 early stopping 파라미터를 train() 함수의 파라미터로 전달
xgb_model = xgb.train(params = params, dtrain = dtrain,
                      num_boost_round = num_rounds, evals = wlist )

predict()를 통해 예측 확률값을 반환하고 예측 값으로 변환

In [ ]:
pred_probs = xgb_model.predict(dtest)
print('predict() 수행 결과값을 10개만 표시, 예측 확률 값으로 표시됨')
print(np.round(pred_probs[:10],3))

#예측 확률이 0.5보다 크면 1, 그렇지 않으면 0으로 예측값 결정하여 List객체인 preds에 저장
preds = [1 if x>0.5 else 0 for x in pred_probs]
print('예측값 10개만 표시:', preds[:10])

get_clf_eval()을 통해 예측 평가

In [ ]:
from sklearn.metrics import confusion_matrix, accuracy_score
from sklearn.metrics import precision_score, recall_score
from sklearn.metrics import f1_score, roc_auc_score

#수정된 get_clf_eval()함수
def get_clf_eval(y_test, pred=None, pred_proba = None):
    confusion = confusion_matrix(y_test, pred)
    accuracy = accuracy_score(y_test, pred)
    precision = precision_score(y_test, pred)
    recall = recall_score(y_test, pred)
    f1 = f1_score(y_test, pred)
    
    #ROC-AUC 추가
    roc_auc = roc_auc_score(y_test, pred_proba)
    print('오차 행렬')
    print(confusion)
    #ROC-AUC print 추가
    print(f'정확도: {accuracy:.4f}, 정밀도:{precision:.4f}, 재현률: {recall:.4f},\
    F1: {f1:.4f}, AUC:{roc_auc:.4f}')
In [ ]:
get_clf_eval(y_test, preds, pred_probs)

Feature importance 시각화

In [ ]:
import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10,12))
plot_importance(xgb_model, ax = ax)

사이킷 런 Wrapper XGBoost 개요 및 적용

사이킷런 래퍼 클래스 import, train, predict

In [ ]:
#사이킷런 래퍼 XGBoost 클래스인 XGBClassifier import
from xgboost import XGBClassifier

evals = [(X_test, y_test)]
xgb_wrapper = XGBClassifier(n_estimators = 400, learning_rate = 0.1, max_depth=3)
xgb_wrapper.fit(X_train, y_train, early_stopping_rounds=400, eval_set = evals, eval_metric = 'logloss', verbose=True)

w_preds = xgb_wrapper.predict(X_test)
w_pred_proba = xgb_wrapper.predict_proba(X_test)[:,1]
In [ ]:
get_clf_eval(y_test, w_preds, w_pred_proba)

early stopping을 100으로 설정하고 재 학습/예측/평가

In [ ]:
from xgboost import XGBClassifier
xgb_wrapper = XGBClassifier(n_estimators = 400, learning_rate=0.1, max_depth = 3)
evals = [(X_test, y_test)]
xgb_wrapper.fit(X_train, y_train, early_stopping_rounds=100, eval_metric='logloss',
               eval_set = evals, verbose = True)
ws100_preds = xgb_wrapper.predict(X_test)
ws100_pred_proba = xgb_wrapper.predict_proba(X_test)[:,1]
predict=0
for proba in ws100_pred_proba:
    if proba >0.5 :
        predict = 1
    else:
        predict = 0
    print(f'proba : {proba:.4f}, 예측값 = {predict}')
    
In [ ]:
get_clf_eval(y_test, ws100_preds, ws100_pred_proba)

early stopping을 10으로 설정하고 재 학습/예측/평가

In [ ]:
#early_stopping_rounds를 10으로 설정하고 재학습
xgb_wrapper.fit(X_train, y_train, early_stopping_rounds= 10,
               eval_metric = "logloss", eval_set = evals, verbose = True)

ws10_preds = xgb_wrapper.predict(X_test)
ws10_pred_proba = xgb_wrapper.predict_proba(X_test)[:,1]
get_clf_eval(y_test, ws10_preds, ws10_pred_proba)
In [ ]:
from xgboost import plot_importance
import matplotlib.pyplot as plt
%matplotlib inline

fig,ax = plt.subplots(figsize = (10,12))
#사이킷런 래퍼 클래스를 입력해도 무방.
plot_importance(xgb_wrapper, ax = ax)

LightGBM

XGBoost 대비 장점

  • 더 빠른 학습과 예측 수행 시간
  • 더 작은 메모리사용량
  • 카테고리형 피처의 자동 변환과 최적 분할(One-Hot인코딩 등을 사용하지 않고도 카테고리형 피처를 최적으로 변환하고
    이에 따른 노드 분할 수행

LightGBM 트리 분할 방식 - Leaf Wise

  • 균형 트리 분할(Level Wise) : Depth최소화 - depth가 깊어지면 overfitting될 확률이 높을것이다.
  • 리프 중심 트리 분할(Leaf Wise) : Depth 신경쓰지 않음 -
    depth가 깊어지는 방향성을 가져도 오류를 줄일 수 있다면 Leaf중심으로 분할해도 괜찮다.

하이퍼 파라미터는 XGBoost에 있는 하이퍼 파라미터면 그대로 받아들이고, 없는거면 파이썬 래퍼를 따름.

In [ ]:
import lightgbm
lightgbm.__version__

LightGBM 적용 - 위스콘신 Breast Cancer Prediction

In [ ]:
#LightGBM의 파이선 패키지인 lightgbm에서 LGBMClassifier import
from lightgbm import LGBMClassifier

import pandas as pd
import numpy as np

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

dataset = load_breast_cancer()
ftr = dataset.data
target = dataset.target

#전체 데이터 중 80%는 학습용 데이터, 20%는 테스트용 데이터 추출
X_train, X_test, y_train, y_test = train_test_split(ftr, target, test_size = 0.2, random_state = 156)

#앞서 XGBoost와 동일하게 n_estimators는 400설정
lgbm_wrapper = LGBMClassifier(n_estimators = 400)

#LightGBM도 XGBoost와 동일하게 조기 중단 수행 가능.
evals = [(X_test, y_test)]
lgbm_wrapper.fit(X_train, y_train, early_stopping_rounds = 100, eval_metric="logloss",
                 eval_set=evals, verbose = True)
preds = lgbm_wrapper.predict(X_test)
pred_proba = lgbm_wrapper.predict_proba(X_test)[:, 1]
In [ ]:
from sklearn.metrics import confusion_matrix, accuracy_score
from sklearn.metrics import precision_score, recall_score
from sklearn.metrics import f1_score, roc_auc_score

# 수정된 get_clf_eval() 함수 
def get_clf_eval(y_test, pred=None, pred_proba=None):
    confusion = confusion_matrix( y_test, pred)
    accuracy = accuracy_score(y_test , pred)
    precision = precision_score(y_test , pred)
    recall = recall_score(y_test , pred)
    f1 = f1_score(y_test,pred)
    # ROC-AUC 추가 
    roc_auc = roc_auc_score(y_test, pred_proba)
    print('오차 행렬')
    print(confusion)
    # ROC-AUC print 추가
    print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f},\
    F1: {3:.4f}, AUC:{4:.4f}'.format(accuracy, precision, recall, f1, roc_auc))
In [ ]:
get_clf_eval(y_test, preds, pred_proba)
In [ ]:
from lightgbm import plot_importance
import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10, 12))
# 사이킷런 래퍼 클래스를 입력해도 무방. 
plot_importance(lgbm_wrapper, ax=ax)

numpy로 들어가있기때문에 column명을 알 수가 없음.

In [ ]:
print(dataset.feature_names)

산탄데르, 신용카드 사기 예측 실습 보고오기

스태킹(Stacking)

  • 여러모델들이 예측한 결과값을 쌓아서(stacking) Meta model이 재학습하여 최종 predict
  • 성능이 더 좋아질거란 보장은 없음

Basic 스태킹 모델

데이터 로딩

In [ ]:
import numpy as np

from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

cancer_data = load_breast_cancer()

X_data = cancer_data.data
y_label = cancer_data.target

X_train , X_test , y_train , y_test = train_test_split(X_data , y_label , test_size=0.2 , random_state=0)

개별 Classifier와 최종 Stacking 데이터를 학습할 메타 Classifier 생성

In [ ]:
# 개별 ML 모델을 위한 Classifier 생성.
knn_clf  = KNeighborsClassifier(n_neighbors=4)
rf_clf = RandomForestClassifier(n_estimators=100, random_state=0)
dt_clf = DecisionTreeClassifier()
ada_clf = AdaBoostClassifier(n_estimators=100)

# 최종 Stacking 모델을 위한 Classifier생성. 
lr_final = LogisticRegression(C=10)

개별 Classifier 학습/예측/평가

In [ ]:
# 개별 모델들을 학습. 
knn_clf.fit(X_train, y_train)
rf_clf.fit(X_train , y_train)
dt_clf.fit(X_train , y_train)
ada_clf.fit(X_train, y_train)
In [ ]:
# 학습된 개별 모델들이 각자 반환하는 예측 데이터 셋을 생성하고 개별 모델의 정확도 측정. 
knn_pred = knn_clf.predict(X_test)
rf_pred = rf_clf.predict(X_test)
dt_pred = dt_clf.predict(X_test)
ada_pred = ada_clf.predict(X_test)

print('KNN 정확도: {0:.4f}'.format(accuracy_score(y_test, knn_pred)))
print('랜덤 포레스트 정확도: {0:.4f}'.format(accuracy_score(y_test, rf_pred)))
print('결정 트리 정확도: {0:.4f}'.format(accuracy_score(y_test, dt_pred)))
print('에이다부스트 정확도: {0:.4f} '.format(accuracy_score(y_test, ada_pred)))

개별 모델의 예측 결과를 메타 모델이 학습할 수 있도록 스태킹 형태로 재 생성

In [ ]:
pred = np.array([knn_pred, rf_pred, dt_pred, ada_pred])
print(pred.shape)

# transpose를 이용해 행과 열의 위치 교환. 컬럼 레벨로 각 알고리즘의 예측 결과를 피처로 만듦. 
pred = np.transpose(pred)
print(pred.shape)

메타 모델 학습/예측/평가

In [ ]:
lr_final.fit(pred, y_test)
final = lr_final.predict(pred)

print('최종 메타 모델의 예측 정확도: {0:.4f}'.format(accuracy_score(y_test , final)))

CV 셋 기반의 Stacking (2Step)

  • Step 1 : 각 모델별로 원본 학습/테스트 데이터로 예측한 결과 값을 기반으로 메타 모델을 위한 학습용/테스트용 데이터를 생성합니다.
  • Step 2 : 크로스폴드 - 검증셋 예측(메타에게 넘김) - Test셋 예측(메타에게 넘김)

한 모델에서 교차검증 세트기반 스태킹 process [3 K Fold]

1st Stage

image.png

2nd Stage

image-2.png

3rd Stage

image-3.png

Final

image-4.png

그런데 이렇게 하면 결과값으로 학습하는거 아닌가 ?feature가 이래도 되는건가

In [ ]:
from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error

# 개별 기반 모델에서 최종 메타 모델이 사용할 학습 및 테스트용 데이터를 생성하기 위한 함수. 
def get_stacking_base_datasets(model, X_train_n, y_train_n, X_test_n, n_folds ):
    # 지정된 n_folds값으로 KFold 생성.
    kf = KFold(n_splits=n_folds, shuffle=False, random_state=0)
    
    #추후에 메타 모델이 사용할 학습 데이터 반환을 위한 넘파이 배열 초기화 
    train_fold_pred = np.zeros((X_train_n.shape[0] ,1 ))
    test_pred = np.zeros((X_test_n.shape[0],n_folds))
    print(model.__class__.__name__ , ' model 시작 ')
    
    for folder_counter , (train_index, valid_index) in enumerate(kf.split(X_train_n)):
        #입력된 학습 데이터에서 기반 모델이 학습/예측할 폴드 데이터 셋 추출 
        print('\t 폴드 세트: ',folder_counter,' 시작 ')
        X_tr = X_train_n[train_index] 
        y_tr = y_train_n[train_index] 
        X_te = X_train_n[valid_index]  
        
        #폴드 세트 내부에서 다시 만들어진 학습 데이터로 기반 모델의 학습 수행.
        model.fit(X_tr , y_tr)
        
        #폴드 세트 내부에서 다시 만들어진 검증 데이터로 기반 모델 예측 후 데이터 저장.
        train_fold_pred[valid_index, :] = model.predict(X_te).reshape(-1,1)
        
        #입력된 원본 테스트 데이터를 폴드 세트내 학습된 기반 모델에서 예측 후 데이터 저장. 
        test_pred[:, folder_counter] = model.predict(X_test_n)
            
    # 폴드 세트 내에서 원본 테스트 데이터를 예측한 데이터를 평균하여 테스트 데이터로 생성 
    test_pred_mean = np.mean(test_pred, axis=1).reshape(-1,1)    
    
    #train_fold_pred는 최종 메타 모델이 사용하는 학습 데이터, test_pred_mean은 테스트 데이터
    return train_fold_pred , test_pred_mean
In [ ]:
knn_train, knn_test = get_stacking_base_datasets(knn_clf, X_train, y_train, X_test, 7)
rf_train, rf_test = get_stacking_base_datasets(rf_clf, X_train, y_train, X_test, 7)
dt_train, dt_test = get_stacking_base_datasets(dt_clf, X_train, y_train, X_test,  7)    
ada_train, ada_test = get_stacking_base_datasets(ada_clf, X_train, y_train, X_test, 7)
In [ ]:
Stack_final_X_train = np.concatenate((knn_train, rf_train, dt_train, ada_train), axis=1)
Stack_final_X_test = np.concatenate((knn_test, rf_test, dt_test, ada_test), axis=1)
print('원본 학습 피처 데이터 Shape:',X_train.shape, '원본 테스트 피처 Shape:',X_test.shape)
print('스태킹 학습 피처 데이터 Shape:', Stack_final_X_train.shape,
      '스태킹 테스트 피처 데이터 Shape:',Stack_final_X_test.shape)
In [ ]:
lr_final.fit(Stack_final_X_train, y_train)
stack_final = lr_final.predict(Stack_final_X_test)

print('최종 메타 모델의 예측 정확도: {0:.4f}'.format(accuracy_score(y_test, stack_final)))

Feature Selection

  • 어떤 피쳐를 제거할 것인가
    • 피쳐값의 분포 ( 모두 단일 값인경우 의미가 없음 )
    • NULL값이 대부분
    • 피처간 너무 높은 상관도( B = A/2 등등.. )
    • Target값과 독립된 피쳐
  • 대부분 모델의 피쳐 중요도(Feature Importance) 기반 ### 사이킷런의 Feature Selection 지원
  • RFE(Recursive Feature Elimination)
    • 모델 최초 학습 후 Feature 중요도 선정
    • feature 중요도가 낮은 속성들을 차례로 제거하고 반복적으로 학습/평가 추출하여 최적 feature 추출
    • 경우의 수가 너무 많고, 수행시간 너무 오래걸림.
  • SelectionFromModel
    • 모델 최초 학습 후 선정된 Feature 중요도에 따라 평균/중앙값 특정 비율 이상인 Feature들을 선택
In [ ]:
 
반응형