1.앙상블 학습의 개요
앙상블 학습(Ensemble Learning)을 통한 분류는 여러 개의 분류기(Classifier)를 생성하고 그 예측을 결합함으로써 보다 정확한 최종 예측을 도출하는 기법
어려운 문제의 결론을 내기 위해 여러 명의 전문가로 위원회를 구성해 다양한 의견을 수렴하고 결정하듯이 앙상블 학습의 목표는
다양한 분류기의 예측 결과를 결합함으로써 단일 분류기보다 신뢰성이 높은 예측값을 얻는 것
다양한 유형의 앙상블 알고리즘이 머신러닝의 선도 알고리즘으로 사용
ex) XGBoost, LightGBM
앙상블의 유형
넓은 의미로는 서로 다른 모델을 결합한 것들을 앙상블로 지칭하기도 한다
- Voting(보팅)
- 서로 다른 알고리즘을 가진 분류기가 동일한 데이터셋을 가지고 투표를 통해 최종 예측 결과를 결정하는 방식
- Bagging(배깅): Random Forest
- 서로 같은 알고리즘을 가진 분류기가 각각 다른 데이터 샘플링으로 투표를 통해 최종 예측 결과를 결정하는 방식
- Boosting(부스팅): Ada Boosting, Gradient Boosting, XGBoost, LightGBM
- 여러 weak learner가 순차적으로 학습을 진행하면서, 앞의 분류기가 틀리게 예측한 데이터에 가중치를 부여하면서 학습을 진행하는 방식
- 정형 데이터의 분류나 회귀에서는 GBM(Gradient Boosting Machine) Boosting 계열(XGBoost, LightGBM)의 앙상블이 전반적으로 높은 예측 성능을 나타냄
- Stacking(스태킹)
앙상블의 특징
- 단일 모델의 약점을 다수의 모델들을 결합하여 보완
- 뛰어난 성능을 가진 모델들로만 구성하는 것보다 성능이 떨어지더라도 서로 다른 유형의 모델을 섞는 것이 오히려 전체 성능에 도움이 될 수 있음
- 랜덤 포레스트 및 뛰어난 부스팅 알고리즘들은 모두 결정 트리 알고리즘을 기반 알고리즘으로 적용함
- 결정 트리의 단점인 과적합(Overfitting)을 수십 ~ 수천 개의 많은 분류기를 결합해 보완하고 장점인 직관적인 분류 기준을 강화함
2. Voting(보팅) & Bagging (배깅) 개요
보팅과 배깅은 여러 개의 분류기가 투표를 통해 최종 예측 결과를 결정하는 방식
Voting(보팅)의 경우: 서로 다른 알고리즘을 가진 분류기가 동일한 데이터셋을 가지고 투표를 통해 최종 예측 결과를 결정하는 방식
Bagging(배깅)의 경우: 서로 같은 알고리즘을 가진 분류기가 각각 다른 데이터 샘플링으로 투표를 통해 최종 예측 결과를 결정하는 방식
3. Voting(보팅)
서로 다른 알고리즘을 가진 분류기가 동일한 데이터셋을 가지고 투표를 통해 최종 예측 결과를 결정하는 방식
Hard Voting(다수결): 다수의 분류기가 결정한 예측값을 최종 보팅값으로 선정하는 방식
Soft Voting: 분류기들의 레이블 값 결정 확률을 모두 더하고 이를 평균해서 이들 중 확률이 가장 높은 레이블 값을 최종 결과값으로 선정하는 방식
일반적으로 Hard Voting 보다는 Soft Voting이 예측성능이 상대적으로 우수하여 주로 사용됨
사이킷런은 VotingClassifier Class를 통해 Voting을 지원함
VotingClassifier(): 보팅 분류기 생성
vo_clf = VotingClassifier(estimators, voting)
- estimators = [('LR', lr_clf), ('KNN', knn_clf)]
- list형식 안에 Classifier들을 tuple형식으로 입력받음
- tuple 구성: (classifier_name, classifier)
- [('KNN', knn_clf), ('LR', lr_clf), ('DT', dt_clf), ('LGBM', lgbm_wrapper)]
- voting = 'soft' or 'hard'(default)
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(f'{class_name} 정확도: {accuracy_score(pred, y_test):.4f}')
Voting의 장단점
장점
- 앙상블 효과
- 안정성과 일반화 (Overfitting 문제 최소화)
- 비교적 간단한 구현
단점
- 모든 알고리즘 성능에 의존
- 계산 비용
- HyperParameter 조정이 어려움
4. Bagging(배깅) : Bootstrap Aggregating
배깅의 대표적인 알고리즘은 랜덤 포레스트이다.
Random Forest
결정 트리(계산량이 적고, 비모수적 모델)를 기본 모델로 사용하는 앙상블 기법
여러 개의 결정 트리가 개별적으로 학습을 진행한 뒤 보팅을 통해 최종 예측 결과를 결정
Decision Tree + Bagging + Random Subspace: 다양성과 무작위성 확보
- 랜덤 포레스트는 다재 다능한 알고리즘
- 앙상블 알고리즘 중 비교적 빠른 수행 속도를 가지고 있으며, 다양한 영역에서 높은 예측 성능을 보이고 있음
- 서로 같은 알고리즘을 가진 분류기가 각각 다른 데이터 샘플링으로 투표를 통해 최종 예측 결과를 결정하는 방식
Bootstrap Aggregating
부트스트랩 샘플링을 이용함으로써 하나의 데이터로 학습된 예측모형보다 더 좋은 모형을 만드는 앙상블 기법
Bootstrapping: 샘플을 일부 중첩되게 여러 번 뽑음
Aggregating: 각 모델을 학습시킨 결과물 집계
Bootstrapping
복원 추출을 통해 기존 데이터셋과 같은 크기를 갖도록 샘플링하는 방법
데이터가 중복되어 뽑히고, 선택되지 않는 데이터도 존재
-> 똑같은 데이터셋으로 학습시킬 때 결과의 다양성이 떨어지는 문제 해결
- Random Forest는 개별적인 분류기의 기반 알고리즘은 결정 트리이지만 개별 트리가 학습하는 데이터 세트는 전체 데이터에서 일부가 중첩되게 샘플링된 데이터 세트임
- 이렇게 여러 개의 데이터 세트를 중첩되게 분리하는 것을 Bootstrapping 분할 방식이라 함
- 원본 데이터의 건수가 10개인 학습 데이터 세트에 랜덤 포레스트를 3개의 결정 트리 기반으로 학습하려고 n_estimators=3으로 하이퍼 파라미터를 부여하면 다음과 같이 데이터 서브세트가 만들어짐
Out - Of - Bag (OOB)
앙상블 학습에서 훈련 세트를 샘플링할때 중복을 허용하는 배깅 방식을 채택할 경우,
일반적으로 훈련 샘플의 63%만 샘플링이 되는데, 이는 나머지 37%의 데이터는 훈련에 쓰이지 않는다.
샘플링 과정에서 선택되지 않는 데이터 OOB (전체의 36.8%)-> 모델 검증에 이용가능
BaggingClassifier 객체 생성 시 oob_score를 True로 지정하면 모델의 정확도를 oob_score_ 변수에 기록한다.
rf_clf = RandomForestClassifier(
n_estimators=10,
random_state=0,
max_leaf_nodes=11,
min_samples_split=3,
oob_score=True,
)
rf_clf.fit(X_train, y_train)
pred = rf_clf.predict(X_test)
print(f"OOB Score: {rf_clf.oob_score_}")
print(f"Accuracy Score: {accuracy_score(pred, y_test)}")
# OOB Score: 0.9296703296703297
# Accuracy Score: 0.9473684210526315
Aggregating
Bootstrapp Dataset으로 만들어진 기본 결정트리 모델들을 합치는 과정
- 회귀 - 결과의 평균
- 분류 - 다수결로 가장 많은 모델이 선택한 것
Random Subspace
원래 변수의 수보다 적은 수의 변수를 임의로 선택해서 분기하는 방법
각 분기마다 서로 다른 변수가 사용됨
-> 서로 독립적인 개별 모델
Generalization Error
트리의 개수가 충분히 많을 때:
Bagging + Random Subspace: 각 모델들의 독립성, 무작위성을 최대화하는 방법
-> Random Forest 성능이 높아진다
변수 중요도
OOB 데이터를 이용한 변수의 중요도 측정
-> sklearn의 feature_importance 속성
5. Random Forest
Random Forest Hyperparameter
- n_estimators: 랜덤 포레스트에서 결정 트리의 개수를 지정.
- 디폴트는 100개
- 많이 성장할수록 좋은 성능을 기대할 수 있지만 계속 증가시킨다고 성능이 무조건 향상되는건 아님.
- 늘릴수록 학습 수행 시간이 오래 걸리는 것 또한 감안해야함
- max_features: 최적의 분할을 위해 고려할 최대 feature 개수 / 노드 분할 시 무작위로 선택되는 변수의 수
- 결정 트리에 사용된 max_feature 파라미터와 같으나 RandomForestClassifier의 기본 max_features는 'None'이 아니라 'auto', 즉 'sqrt'와 같음
- 따라서 랜덤 포레스트의 트리를 분할하는 피처를 참조할 때 전체 피처가 아니라 sqrt(전체 피처 개수)만큼 참조함
- max_depth: 트리의 최대 깊이를 규정
- max_samples: 여러 개의 데이터를 중첩되게 분리할 때 분리할 데이터의 총 개수를 조절할 수 있음(권장하지 않음, 하면 성능 떨어짐)
- min_samples_leaf: 분할이 될 경우 왼쪽과 오른쪽의 브랜치 노드에서 각각 가져야 할 최소한의 샘플 데이터 수
- min_weight_fraction_leaf: 개별 샘플에 weight 값을 주어 leaf node가 되는지 / 안 되는지를 결정
- max_leaf_nodes: 말단 노드(leaf node)의 최대 개수
- class_weight: unbalanced data set에서 가중치를 부여할 때 사용
- n_jobs: 'None'이 default인데 CPU 1 Core만 쓰겠다는 뜻, n_jobs=-1일 경우 CPU의 모든 Processor를 사용한다는 뜻
- random_state
- 하나는 DecisionTreeClassifier의 random_state와 동일한 역할, 다른 하나는 bootstrapping 방식으로 데이터를 sampling 할때의 randomness를 고정하는 역할을 수행합니다.
Random Forest 실습
Data Preprocessing
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
Model train, predict, evaluation
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: Hyperparameter tuning + cross-validation
교차 검증과 최적 하이퍼 파라미터 튜닝을 한번에 해서 Model 의 학습(fit)을 수행
이후 .best_estimator_를 이용하여 Model의 활용 예측 / 예측 성능 평가 수행
GridSearchCV 객체 만든 후 fit() 수행!!
- scoring: 예측 성능 평가함수 - Test Data가 아니라 Validation Data에 적용하여 성능 도출
- n_jobs: 'None'이 default인데 CPU 1 Core만 쓰겠다는 뜻, n_jobs=-1일 경우 CPU의 모든 Processor를 사용한다는 뜻
- n_jobs=-1일 경우 모든 CPU의 Core를 다 쓰기 때문에 다른 작업을 하고 있을 경우 컴퓨터가 느려진다
- verbose
- verbose=0(default)면 메시지 출력 안함
- verbose=1이면 간단한 메시지 출력
- verbose=2이면 하이퍼 파라미터별 메시지 출력
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_))
# 최적 하이퍼 파라미터:
# {'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
feature_importances_를 다루는 방법
- ftr_importances_values = rf_clf.feature_importances_
- ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns)
- ftr_importances.sort_values(ascending=False)[: 상수]
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]
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()