ML/분류(Classification)

앙상블 학습 - 보팅 & 배깅

야뤼송 2024. 5. 29. 08:23
반응형


1. 앙상블 학습-보팅(Voting)

 

보팅은 여러 개의 분류기가 투표를 통해 최종 예측 결과를 결정하는 방식으로 서로 다른 알고리즘을 가진 분류기를 결합하는 형태이다.

 

 

2. 보팅 - Soft Voting & Hard Voting

 

Hard Voting은 다수의 Classifier 간 다수결로 최종 Class를 결정한다.

아래 그림에서 Classifer1~4가 있다고 가정한다. Classifier1, 3, 4는 클래스 값 ①로 예측을 하고 Classifier2는 클래스 값 ②로 예측을 하게 되면 다수결로 최종 클래스값 로 예측하게 된다.

 

 

Soft Voting은 다수의 classifier들의 class 확률을 평균하여 결정한다.

아래 그림에서 Classifer1~4가 있다고 가정한다. 각각의 Classifie가 1일때와 2일때의 확률을 각각 구한다. 아래 그림에서 보면 일 확률이 더 크므로 최종 클래스 값은 로 예측하게 된다. 로 예측하게 된다.

 

일반적으로 하드 보팅보다는 소프트 보팅이 예측 성능이 상대적으로 우수하여 주로 사용된다. 사이킷런에서는VoltingClassifier 클래스를 통해 보팅(Voting)을 지원한다.

 

 실습

Voting에 대해 실습을 하기 위해 사이킷런에서는 제공하는 유방암 데이터를 활용해보자.

먼저 사이킷런에서 제공하는 유방암 데이터를 불러온다. 그리고 어떠한 feature로 되어있는지 살펴보면 30개의 colums으로 구성된 것을 확인할 수 있다.

import pandas as pd

from sklearn.datasets import load_breast_cancer

cancer = load_breast_cancer()

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

▶ Out

 

 

이제 로지스틱회귀와 KNN을 보팅방식으로 결합하여 성능을 측정해보자. Soft Voting과 Hard Voting의 정확도 각각 측정해보면 Soft Voting의 정확도가 더 높은 것을 확인할 수 있다.

from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import warnings 
warnings.filterwarnings('ignore')


# 개별 모델은 로지스틱 회귀와 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('Soft Voting 분류기 정확도: {0:.4f}'.format(accuracy_score(y_test , pred)))

# 개별 모델을 하드 보팅 기반의 앙상블 모델로 구현한 분류기 
vo_clf1 = VotingClassifier( estimators=[('LR',lr_clf),('KNN',knn_clf)] , voting='hard' )

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

# VotingClassifier 학습/예측/평가. 
vo_clf1.fit(X_train , y_train)
pred1 = vo_clf1.predict(X_test)

▶ Out

Soft Voting 분류기 정확도: 0.9561
Harfd Voting 분류기 정확도: 0.9298

 

 

KNN과 로지스틱 회귀를 Soft Voting으로 측정한 것과 KNN, 로지스틱 회귀를 개별 모델의 성능을 비교해보자.

위에서 Soft Voting으로 정확도를 측정한 정확도는 약  95%로  KNN 93%,  로지스틱 회귀 94% 보다 Soft Voting의 성능이 더 뛰어난 것을 확인할 수 있다.

# 개별 모델의 학습/예측/평가.
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)))

▶ Out

LogisticRegression 정확도: 0.9474
KNeighborsClassifier 정확도: 0.9386

 

 

3. 앙상블 학습 - 배깅(Bagging)

 

배깅도 보팅과 동일하게 여러 개의 분류기를 투표를 통해 예측 결과를 결정하는 방식이다. 

보팅과는 다르게 배깅은 각각의 분류기가 모두 같은 유형의 알고리즘 기반이지만, 데이터 샘플링을 서로 다르게 가져가면서 학습을 수행해 보팅을 수행하는 것이다.

 

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

 

여러 개의 결정 트리 분류기가 전체 데이터에서 배깅 방식으로 각자의 데이터를 샘플링해 개별적으로 학습을 수행한 뒤 최종적으로 모든 분류기가 보팅을 통해 예측 결정을 하게된다.

 

랜덤 포레스트의 개별적인 분류기 알고리즘은 결정 트리이다. 개별 트리가 학습하는 데이터 세트는 전체 데이터에서 일부가 중첩되게 샘플링된 데이터 세트이다. 이렇게 여러 개의 데이터 세트를 중첩되게 분리하는 것을 부트스트래핑(bootstrapping) 분할 방식이라고 한다.

(그렇기 때문에 Bagging은 bootstrap aggregating의 줄임말이다)

 

예를 들어 원본 데이터의 건수가 10개인 학습 데이터 세트에 랜덤 프레스트를 3개의 결정 트리 기반으로 학습하려고 하는 경우 n_estimators=3으로 하이퍼 파라미터를 부여하면 다음과 같이 데이터 서브세트가 만들어진다.

 

 

랜덤 포레스트의 주요 하이퍼파라미터는 결정트리에서 사용된 파라미터와 거의 유사하나 약간의 차이점이 존재한다.

  • n_estimators : 결정 트리의 개수를 지정한다. default는 100으로 많이 설정할수록 좋은 성능을 기대할수도 있지만 무조건 증가할수록 성능이 향상되는 것은 아니다. 또한, 개수가 늘어날 수록 학습 수행에 시간이 오래걸리게 된다.
  • max_features : 최적의 분할을 위해 고려할 최대 피처 개수로 결정트리의 max_feautres와 동일하다. 그러나 default는 None이 아닌 Auto, 즉 sqrt와 동일하다. 따라서 트리를 분할하는 피처를 참조할 때는 전체가 아닌 √전체피처개수 만큼 참조한다.
    (전체 피처가 16인 경우 4개(√16)를 참조)
  • 나머지 max_depth나 min_samples_leaf와 같은 파라미터는 결정트리에 사용되던 것과 같이 동일하게 적용 가능하다.

 

 실습

지난 결정트리 학습에 사용하였던 사용자 행동 인식 데이터 세트를 활용하여 랜덤 포레스트 실습을 한다.

 

사용자 행동 인식 데이터 세트는 중복된 피처들이 존재하였고 이를 수정하여 새로운 DataFrame을 생성하는 함수를 생성하였다.

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

 

 

이제 랜덤 포레스트를 이용하여 예측 성능을 평가해본다. 램덤 포레스트의 주요 하이퍼 파라미터 중 n_estimators 는 100으로, max_depth는 8로 셋팅한 후 학습시킨다. 그리고 나서 예측하고 정확도를 측정해보자.

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

▶ Out

랜덤 포레스트 정확도: 0.9196

 

 

이제 GridSearchCV를 이용하여  최적 하이퍼 파라미터를 구해보고 최고 예측 정확도를 구해본다. n_jobs는 병렬 처리에 사용할 CPU 코어 수를 지정하는데 사용되는 값이고 -1로 설정하는 경우 모든 가용한 CPU코어를 사용한다는 의미이다.

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

 

▶ Out

최적 하이퍼 파라미터:
 {'max_depth': 16, 'min_samples_leaf': 6, 'min_samples_split': 2}
최고 예측 정확도: 0.9165

 

 

이제 feauture별 특성 중요도를 구해보자. 

먼저, 앞서 구한 최적 하이퍼 파라미터를 이용하여 예측을 구해본다.

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

▶ Out

예측 정확도: 0.9260

 

 

컬럼별 중요도에서 20개만 가져오고 이를 그래프로 표현해보면 다음과 같다.

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

▶ Out

 

반응형