Machine Learning/캐글스터디(파이썬 머신러닝)

[4장-2] 앙상블, 랜덤포레스트, 부스팅

뉴욕킴 2023. 4. 18. 22:13

분류 

- 학습 데이터로 주어진 데이터의 피처와 레이블값을 머신러닝 알고리즘으로 학습해 모델을 생성하고, 생성된 모델에 새로운 데이터 값이 주어졌을 때 미지의 레이블 값을 예측하는 것

 

분류를 구현할 수 있는 다양한 머신러닝 알고리즘

  • 베이즈(Bayes) 통계와 생성 모델에 기반한 나이브 베이즈(Naive Bayes)
  • 독립변수와 종속변수의 선형 관계성에 기반한 로지스틱 회귀(Logistic Regression)
  • 데이터 균일도에 다른 규칙 기반의 결정 트리(Decision Tree)
  • 개별 클래스 간의 최대 분류 마진을 효과적으로 찾아주는 서포트 벡터 머신(Support Vector Machine)
  • 근접 거리를 기준으로 하는 최소 근접(Nearest Neighbor) 알고리즘
  • 심층 연결 기반의 신경망(Neural Network)
  • 서로 다른/같은 머신러닝 알고리즘을 결합한 앙상블(Ensemble)

 

결정트리란?

- 매우 쉽고 유연하고 적용될 수 있는 알고리즘, 데이터의 스케일링이나 정규화 등의 사전 가공의 영향이 매우 적음

- 데이터에 있는 규칙을 학습을 통해 자동으로 찾아내는 트리 기반의 분류규칙을 만든다

- 장점: 쉽다, 직관적이다, 사전 가공 영향도가 크지 않다.

- 단점: 과적합으로 알고리즘 성능이 떨어진다. 이를 극복하기 위해 트리의 크기를 사전에 제한하는 튜닝이 필요하다.

- 트리의 깊이가 깊어질수록 결정 트리의 예측 성능이 저하될 가능성 높다 → 최대한 균일한 데이터 세트 구성할 수 있도록 분할 해야됨 

 

  • min_samples_split: 노드 분할을 위한 최소한의 샘플 데이터 수 
  • min_samples_leaf: 분할될 경우 왼쪽, 오른쪽의 브랜치 노드에서 가져야 할 최소한의 샘플 데이터 수 
  • max_features: 최적의 분할을 위해 고려할 최대 피처 개수 
  • max_depth: 트리의 최대 깊이를 규정 
  • max_leaf_nodes: 말단 노드의 최대 개수 

 

앙상블이란?

- 매우 많은 여러개의 약한 학습기를 결합해 확률적 보완과 오류가 발생한 부분에 대한 가중치를 계속 업데이트하면서 예측 성능을 향상시키는데 결정트리가  좋은 약한 학습기가 됨

- 앙상블의 유형은 일반적으로 voting, bagging, boosting으로 구분할 수 있으며, 이외에 stacking 등의 기법이 있다.

- bagging: 랜덤포레스트

- boosting: 에이다 부스팅, 그래디언트 부스팅, XGBoost, LightGBM, stacking 

 

특징

- 단일 모델의 약점을 다수 모델들을 결합하여 보완

- 성능이 떨어지더라도 서로 다른 유형의 모델을 섞으면 전체 성능이 향상될 수 있음

- 결정트리의 단점인 과적합을 수십~수천개 많은 분류기를 결합해 보완하고 장점인 직관적인 분류 기준은 강화됨

 

Voting(서로 다른 알고리즘 분류기 결합) & Bagging(같은 분류기) 

- 여러개의 분류기가 투표를 통해 최종 예측 결과를 결정하는 방법

  •  일반적으로 하드 보팅(다수결)보다는 소프트 보팅(확률)이 예측 성능이 상대적으로 우수하여 자주 사용함
  •  사이킷런은 VotingClassifier 클래스를 통해 voting을 지원

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
import warnings 
warnings.filterwarnings('ignore')

cancer = load_breast_cancer()

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

# 개별 모델은 로지스틱 회귀와 KNN 임. 
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)

# 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)))
Voting 분류기 정확도: 0.9561
LogisticRegression 정확도: 0.9474
KNeighborsClassifier 정확도: 0.9386
  • 보팅과 스태킹: 서로 다른 알고리즘 기반
  • 배깅과 부스팅: 대부분 결정 트리 알고리즘 기반(결정트리 알고리즘은 쉽고 직관적인 분류 기준을 가지고 있지만, 과적합 발생이 쉬운 단점이 있음)

 

랜덤포레스트란?

- 배깅의 대표적인 알고리즘

- 비교적 빠른 수행 속도를 가지며 다재 다능한 알고리즘으로 다양한 영역에서 높은 예측 성능 보이고 있음

  •  랜덤포레스트의 부트스트래핑 분할: 여러개의 데이터 세트를 중첩되게 분리하는 것
  • ex. 원본 데이터의 건수가 10개인 학습 데이터 세트에 랜덤포레스트를 3개의 결정트리 기반으로 학습하려고 n_estimators=3으로 하이퍼 파라미터를 부여하면 다음과 같이 데이터 서브세트가 만들어짐
  •  n_estimators: 랜덤 포레스트에서 결정트리의 개수를 지정. 디폴트는 10개이며 많이 설정할수록 좋은 성능을 기대할 수 있지만 계속 증가시킨다고 성능이 무조건 향상되는 것은 아님. 또한 늘릴수록 학습 수행시간이 오래 걸릴 수 있음.
  •  max_features: 결정트리에 사용된 max_features 파라미터와 같다. default=auto(전 피처가 16개라면 분할을 위해 4개 참조)
  •  max_depth: int, default=none
  •  max_depth, min_samples_leaf, min_samples_split와 같이 결정트리에서 과적합을 개선하기 위해 사용되는 파라미터가 랜덤포레스트에도 똑같이 적용될 수 있음

 

랜덤포레스트 실행코드 

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

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'])
    
    # 중복된 피처명을 수정하는 get_new_feature_name_df()를 이용, 신규 피처명 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
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(n_estimators=100, random_state=0, max_depth=8)
rf_clf.fit(X_train , y_train)
pred = rf_clf.predict(X_test)
accuracy = accuracy_score(y_test , pred)
print('랜덤 포레스트 정확도: {0:.4f}'.format(accuracy))
더보기
랜덤 포레스트 정확도: 0.9196

# GridSearchCV를 이용해 랜덤 포레스트의 하이퍼 파라미터 튜닝 

from sklearn.model_selection import GridSearchCV

params = {
    'max_depth': [8, 16, 24],
    'min_samples_leaf' : [1, 6, 12],
    'min_samples_split' : [2, 8, 16]
}
# RandomForestClassifier 객체 생성 후 GridSearchCV 수행
rf_clf = RandomForestClassifier(n_estimators=100, 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_))
  •  n_jobs =-1 파라미터를 추가하면 모든 cpu 코어 이용해 학습 가능
더보기
최적 하이퍼 파라미터:
 {'max_depth': 16, 'min_samples_leaf': 6, 'min_samples_split': 2}
최고 예측 정확도: 0.9165

# 추출된 최적 하이퍼 파라미터로 다시 랜덤포레스트를 학습시킨 뒤, 별도의 테스트 데이터 세트에서 예측 성능 측

rf_clf1 = RandomForestClassifier(n_estimators=100,  min_samples_leaf=6, max_depth=16,
                                 min_samples_split=2, 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)))
더보기
예측 정확도: 0.9260
  • 별도의 테스트 데이터 세트에서 수행한 예측 정확도 수치: 92.60% 
ftr_importances_values = rf_clf1.feature_importances_
ftr_importances = pd.Series(ftr_importances_values,index=X_train.columns)
ftr_importances.sort_values(ascending=False)[:20]
더보기
angle(X,gravityMean)               0.035312
tGravityAcc-mean()-Y               0.033729
tGravityAcc-min()-X                0.030276
angle(Y,gravityMean)               0.029013
tGravityAcc-max()-X                0.027538
tGravityAcc-mean()-X               0.024708
tGravityAcc-energy()-X             0.024389
tGravityAcc-min()-Y                0.023339
tGravityAcc-max()-Y                0.021011
fBodyAccJerk-bandsEnergy()-1,8     0.018636
tGravityAcc-energy()-Y             0.015461
tBodyAcc-max()-X                   0.013750
tBodyAccJerk-entropy()-X           0.013433
tGravityAcc-arCoeff()-Z,1          0.013150
tGravityAcc-max()-Z                0.013125
fBodyAccMag-energy()               0.012262
tGravityAcc-energy()-Z             0.010683
fBodyAccJerk-bandsEnergy()-1,16    0.010095
fBodyAcc-mad()-X                   0.010036
angle(Z,gravityMean)               0.009483
dtype: float64

# 시각화 진행 

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

 

부스팅이란?

- 여러개의 약한 학습기를 순차적으로 학습-예측하면서 잘못 예측한 데이터나 학습트리에 가중치 부여를 통해 오류를 개선해 나가면서 학습하는 방식으로 대표적인 구현은 AdaBoost와 그래디언트부스트가 있다.

 

AdaBoost

Adaptive Boost의 줄임말로 약한 학습기의 오류 데이터에 가중치를 부여하면서 부스팅을 수행하는 대표적인 알고리즘이다.
속도나 성능적인 측면에서 decision tree를 약한 학습기로 사용한다.

약한 학습기를 순차적으로 학습을 시켜 개별 학습기에 가중치를 부여하여 모두 결함함으로써 개별 약한 학습기보다 높은 정확도의 예측 결과를 만든다.

 

Gradient Boost Machine(GBM)

AdaBoost와 유사한 성격을 가지고 있지만, 가중치 업데이트를 경사하강법(Gradient Descent)를 이용하여 최적화된 결과를 얻는 알고리즘으로 GBM은 예측 성능이 높지만 Greedy Algorithm으로 과적합이 빠르게되고, 시간이 오래 걸린다는 단점이 있다.

→ 반면 랜덤 포레스트는 상대적으로 빠른 수행시간 보장해줘 더 쉽게 예측 결과 도출 가능 

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

### 아래는 강의에서 설명드리지는 않지만 GridSearchCV로 GBM의 하이퍼 파라미터 튜닝을 수행하는 예제 입니다. 
### 사이킷런이 1.X로 업그레이드 되며서 GBM의 학습 속도가 현저하게 저하되는 문제가 오히려 발생합니다. 
### 아래는 수행 시간이 오래 걸리므로 참고용으로만 사용하시면 좋을 것 같습니다. 
from sklearn.model_selection import GridSearchCV

params = {
    'n_estimators':[100, 500],
    'learning_rate' : [ 0.05, 0.1]
}
grid_cv = GridSearchCV(gb_clf , param_grid=params , cv=2 ,verbose=1)
grid_cv.fit(X_train , y_train)
print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도: {0:.4f}'.format(grid_cv.best_score_))
# 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))