본문 바로가기

Self-Taught/Machine Learning

파이썬 머신러닝 완벽 가이드 _ CH.4.1~4.6

1. 분류의 개요

지도학습은 레이블이 주어진 상태에서 학습하는 머신러닝 방식이다
대표 유형인 분류는 학습 데이터의 피처와 레이블값을 학습해 모델을 생성하고, 새로운 데이터 값의 레이블을 해당 모델을 이용해 예측하는 것이다. 


분류를 구현하는 알고리즘은 굉장히 많다.
대표적으로 베이즈, 로지스틱 회귀, 결정 트리, 서포트 벡터 머신, 최소 근접 알고리즘, 신경망, 앙상블이 있다. 


이 중 앙상블은 딥러닝이 머신러닝계를 선도하고 있는 상황에서도 정형 데이터의 예측 분석 영역에서 애용되고 있다.
앙상블은 배깅과 부스팅 방식으로 나뉘게 된다.
배깅의 대표적인 예시는 랜덤 포레스트가 있다. 그러나 최근에는 부스팅 방식으로 발전하고 있다.
부스팅 방식의 효시인 그래디언트 부스팅은 단점이 너무 명확하다. 최근에는 이런 지점을 개선한 XgBoost와 LightGBM이 대신 이용되고 있다. 

앙상블을 이해하기 위해서는 기본 알고리즘인 결정 트리를 이해해야 한다.
결정트리는 장점이 명확하지만, 과적합이 발생할 가능서잉 높다는 단점이 있다.
그러나 이러한 단점이 앙상블 방식에서는 오히려 장점으로 작용하게 된다.



2. 결정 트리
결정트리는 데이터에 있는 규칙으로 학습을 통해 자동으로 찾아내 트리 기반의 분류 규칙을 만드는 것이다.
즉, 규칙을 어떻게 설정하느냐가 알고리즘의 성능을 좌우하게 된다는 것이다.

결정 트리에서 좋은 규칙 조건은 데이터를 분류할 때 최대한 많은 세트가 속해 있는 규칙 조건이다.
이는 해당 규칙 조건을 통해 얼마나 균일한 집단을 만들 것인지와 연결될 수 있다.
즉, 균일도의 문제라는 것이다.

이런 균일도를 나타내는 지수는 대표적으로 2개가 있는데 
정보이득 지수와 지니 지수이다.
정보이득 지수는 엔트로피라는 개념을 기반으로 하고, 지니 지수는 불평등 지수를 나타낸다.

정보이득 지수 = 1-엔트로피(혼잡도)
지니지수 = 0~1 사이 값 

균일도가 높을 수록 엔트로피는 낮아지고, 정보이득 지수는 높아진다.
균일도가 높을 수록 지니 지수는 낮아진다.


결정트리는 앞서 말했다시피 명확한 장점이 있다.
그러나 과적합이라는 치명적인 단점이 있기 때문에 사전에 특정 조건들을 설정해 과적합 상황을 방지해야 한다.

1. min_samples_split  = 노드를 분할하기 위한 최소한의 샘플 데이터 수 
즉, 해당 노드가 min_samples 값을 넘어야 분할을 시도할 수 있다는 것이다.

2. min_samples_leaf = 분할이 될 경우 왼쪽과 오른쪽의 브랜치 노드에서 가져야 할 최소한의 샘플 데이터 수
  즉, 해당 규칙 조건으로 생기는 2개의 노드가 min_samples로 설정된 값을 넘어야 노드로 분할될 수 있다는 것이다.

3. max_depth  = 트리의 최대 깊이
트리가 깊을 수록 과적합 상황이 생기기 쉽다. 그래서 최대한으로 갈 수 있는 깊이를 설정해 해당 깊이에 도달하면 조건 분류 생성을 멈추게 된다. 




결정트리를 시각화하는 방법은 Graphviz 패키지를 이용하게 되는데 사이킷런에서 제공하는 export_graphviz()라는 API를 이용하게 된다. 

## 결정 트리 모델 실습 + 시각화 
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')

dt_clf = DecisionTreeClassifier(random_state=156)

iris_data = load_iris()
X_train , X_test , y_train , y_test = train_test_split(iris_data.data, iris_data.target,
                                                       test_size=0.2,  random_state=11)

dt_clf.fit(X_train , y_train)


from sklearn.tree import export_graphviz

export_graphviz(dt_clf, out_file="tree.dot", class_names=iris_data.target_names , \
feature_names = iris_data.feature_names, impurity=True, filled=True)


import graphviz
with open("tree.dot") as f:
    dot_graph = f.read()
graphviz.Source(dot_graph)

 

## 결정 트리 하이퍼 파라미터 관련 실습

import seaborn as sns
import numpy as np
%matplotlib inline

print("Feature importances:\n{0}".format(np.round(dt_clf.feature_importances_, 3)))

for name, value in zip(iris_data.feature_names , dt_clf.feature_importances_):
    print('{0} : {1:.3f}'.format(name, value))

sns.barplot(x=dt_clf.feature_importances_ , y=iris_data.feature_names)

 

##결정 트리 과적합 실습

from sklearn.datasets import make_classification
import matplotlib.pyplot as plt
%matplotlib inline

plt.title("3 Class values with 2 Features Sample data creation")

X_features, y_labels = make_classification(n_features=2, n_redundant=0, n_informative=2,
                             n_classes=3, n_clusters_per_class=1,random_state=0)

plt.scatter(X_features[:, 0], X_features[:, 1], marker='o', c=y_labels, s=25, cmap='rainbow', edgecolor='k')



from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier(random_state=156).fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)


dt_clf = DecisionTreeClassifier(min_samples_leaf=6, random_state=156).fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)




3. 앙상블 학습

여러 개의 Classifier를 생성하고 그 예측들을 결합해 더 정확한 최종 예측을 도출하는 기법을 말한다.

--> 전통적으로는 보팅, 배깅, 부스팅 방식으로 분류 가능
--> 그 외에도 스태깅 등 다양한 방법이 있음

>> 보팅과 배깅 방식은 여러 개의 분류기가 투표를 통해 최종 예측 결괄르 결정한다.
그러나 보팅의 경우 서로 다른 알고리즘을 가진 분류기를 이용한 예측을 결합하게 되고,
배깅의 경우 분류기는 동일하지만, 각 분류기가 데이터 샘플링을 다르게 하면서 학습을 수헹하게 된다.

부스팅은 여러 개의 분류기가 순차적으로 학습을 수행하는데, 앞에서 학습한 분류기가 예측이 틀렸을 경우 다음에 학습을 진행하는 분류기에는 가중치를 부여해 결과를 맞힐 수 있도록 한다. 

스태깅은 여러 가지 다른 모델의 예측 결괏값을 다시 학습 데이터로 만들어 다른 모델로 재학습 시켜 결과를 예측하는 방식이다.



#보팅 방식  <== 하드 보팅 /// 소프트 보팅
하드 보팅은 다수결의 원칙과 비슷하다.
다수의 결정기가 예측한 결괏값을 최종 보팅 결괏값으로 선정하는 것


소프트 보팅은 각 결정기의 레이블 값 결정 확률을 모두 더하고, 이를 평균해 이 중 확률이 가장 높은 레이블 값을 최종 보팅 결괏값으로 선정한다. 

ex) 레이블 3개 --> 각 결정기는 최종 레이블이 레이블 a일 확률 / b일 확률 / c일 확률 을 구하게 됨
===> 각 레이블에 해당하는 확률을 모두 더해 평균 값을 구하고 가장 높은 확률 값의 레이블이 최종 선택됨

>> 보팅 분류기 - Voting Classifier 클래스 제공



보팅으로 여러 개의 기반 분류기를 결합한다고 무조건 기반 분류기보다 예측 성능이 향상되는 것은 아니다. 

정확한 예측을 위한 학습 데이터의 예외 상황에 집착하는 결정트리의 특징이 유발하는 과적합 상황을 앙상블은 여러 개의 분류기를 결합해 다양한 상황을 학습함으로써 극복하게 된다. 
>> 결정트리의 장점은 그대로 가져오면서 단점은 보완하는 것

## 보팅 분류기 실습 _ 위스콘신 유방암 데이터 세트

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)


lr_clf = LogisticRegression(solver='liblinear')
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)

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)))




4. 랜덤 포레스트
# 배깅 방식 = 하나의 알고리즘으로 여러 개의 분류기 만들어서 최종 결정하는 알고리즘
Ex) 랜덤 포레스트  by,. RandomForestClassifier


부트스트래핑 분할 방식 ==> 랜덤 포레스트의 각 분류기가 학습하는 데이터 세트는 전체 데이터에서 일부가 중첩되게 샘플링 된다.

>> 랜덤 포레스트의 파라미터
- n_estimator    = 결정 트리의 개수 지정
- max_features   = 결정 트리에서 사용된 max_features와 같다! 
- max_depth  /  min_samples_leaf, min_samples_split 같이 결정 트리에서 사용됐던 파라미터도 이용 가능



>> 하이퍼 파라미터 튜닝 by GridSearchCV   -- 각 파라미터 별로 여러개의 값들을 설정하고, 이 값들의 최적 조합을 찾아내는 것 == 하이퍼 파라미터 튜닝 

--- 랜덤 포레스트는 예측 성능이 약간 떨어질 수는 있으나, 병렬 처리를 기반으로 빠르게 학습이 가능하다.
그래서 일단 랜덤 포레스트로 기반 모델을 구축하는 경우가 많다. 

## 랜덤 포레스트 기본 실습

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))
## 랜덤 포레스트 파라미터 튜닝

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_))



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)))


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)
fig1 = plt.gcf()
plt.show()
plt.draw()
fig1.savefig('rf_feature_importances_top20.tif', format='tif', dpi=300, bbox_inches='tight')




5. GBM  = Gradient Boosting Machine    by. GradientBoostingClassifier 클래스
# 부스팅 방식 : 여러 개의 약한 학습기를 순차적으로 학습-예측하면서 잘못 예측한 데이터에 가중치를 부여해, 오류를 개선하면서 학습하는 방식 

Ex) 그래디언트 부스트 . AdaBoost   --> 가중치 업데이트에서 차이가 있다.

1. 에이다 부스트

2. GBM ==> 경사 하강법을 통해 가중치 업데이트 
오류값 = 실제값 - 예측값
h(x) 함수 = y     - F(x) 예측함수

이 오류식 h(x)를 최소화하는 방향성을 가지고 반복적으로 가중치 값을 업데이트하는 것이 경사 하강법이다.


GBM이 랜덤 포레스트보다 예측 성능이 뛰어나지만, 소요 시간이 길고 하이터 파라미터 튜닝에 더 신경 써야 한다는 단점이 있다. 

>> GBM의 파라미터 
- loss : 경사 하강법에서 사용할 비용 함수 지정  ex) 디폴트값 = deviance

- n_estimator : weak learner의 개수 
- learning_rate : 학습을 진행할 때마다 적용하는 학습률 
>> 이 두 파라미터는 서로 조합해서 사용하는 경우가 많다.
learning_rate 를 작게 하고, n_estimator을 크게 하면 한계점까지 예측 성능 키울 수 있음 그러나 시간 오래 걸림   & 예측 성능이 드라마틱하게 좋아지지는 않는다. 

- subsample  :  weak learner가 학습에 사용하는 데이터의 샘플링 비율 default = 1 (전체 데이터 이용)

## GBM 기본 실습

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))







6. XGBoost    = eXtra Gradient Boost
 _ 분류에 있어서 다른 머신러닝보다 뛰어난 예측 성능을 나타낸다.

> XGBoost 의 파라미터  = 일반 파라미터  / 부스터 파라미터  . 학습 태스크 파라미터

## 부스터 파라미터  _ 과적합 감소 효과가 있는 파라미터 & ... 
- eta = GBM의 learning rate 파라미터 
- sub_sample = GBM의 subsample과 동일한 파라미터
- colsample_bytree = GBM의 max_features과 유사한 파라미터

- max_depth = 결정 트리의 max_depth 파라미터와 동일
- min_child_weight = 트리에서 추가적으로 가지를 나눌지를 결정하기 위해 필요한 데이터의 weight 총합
                                 >> 값이 클 수록 분할을 자제하게 된다
- gamma  = 트리의 리프 노드를 추가적으로 나눌지를 결정하는 최소 손실 감소 값

..... 

과적합 상황이 발생했다면 eta / max_depth 값을 낮추거나  & min_child_weight /gamma 값을 높이는 방법이 있다. 그 외에도 subsample과 colsample_bytree 를 조정하는 것도 과적합 문제에 도움이 된다. 



> 사이킷런 래퍼 XGBoost 

##파라미터
- eta를 learning rate로 
- sub_sample를 subsample로
- lambda를 reg_lambda로
- alpha를 reg_alpha로 

변경해서 사용함

 

 

 

## 위스콘신 유방암 예측에 파이썬 래퍼 XGBoost 적용하기


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.head(3)



print(dataset.target_names)
print(cancer_df['target'].value_counts())


X_features = cancer_df.iloc[:, :-1]
y_label = cancer_df.iloc[:, -1]

X_train, X_test, y_train, y_test=train_test_split(X_features, y_label,
                                         test_size=0.2, random_state=156 )

X_tr, X_val, y_tr, y_val= train_test_split(X_train, y_train, test_size=0.1, random_state=156 )
print(X_train.shape , X_test.shape)
print(X_tr.shape, X_val.shape)

dtr = xgb.DMatrix(data=X_tr, label=y_tr)
dval = xgb.DMatrix(data=X_val, label=y_val)
dtest = xgb.DMatrix(data=X_test , label=y_test)


params = { 'max_depth':3,
          'eta': 0.05,
          'objective':'binary:logistic',
          'eval_metric':'logloss'
         }
num_rounds = 400


eval_list = [(dtr,'train'),(dval,'eval')]

xgb_model = xgb.train(params = params , dtrain=dtr , num_boost_round=num_rounds , \
                      early_stopping_rounds=50, evals=eval_list )
                     
pred_probs = xgb_model.predict(dtest)
print('predict( ) 수행 결과값을 10개만 표시, 예측 확률 값으로 표시됨')
print(np.round(pred_probs[:10],3))

preds = [ 1 if x > 0.5 else 0 for x in pred_probs ]
print('예측값 10개만 표시:',preds[:10])        


get_clf_eval(y_test , preds, pred_probs)



import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots(figsize=(10, 12))
plot_importance(xgb_model, ax=ax)
plt.savefig('p239_xgb_feature_importance.tif', format='tif', dpi=300, bbox_inches='tight')

 

위스콘신 유방암 예측에 사이킷런 래퍼 XGBoost 적용하기

from xgboost import XGBClassifier

xgb_wrapper = XGBClassifier(n_estimators=400, learning_rate=0.1, max_depth=3)
xgb_wrapper.fit(X_train, y_train)
w_preds = xgb_wrapper.predict(X_test)
w_pred_proba = xgb_wrapper.predict_proba(X_test)[:, 1]


get_clf_eval(y_test , w_preds, w_pred_proba)


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]



get_clf_eval(y_test , ws100_preds, ws100_pred_proba)


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)



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)