앙상블 학습(Ensemble Learning)
랜덤 포레스트
랜덤 포레스트는 배깅의 대표적인 알고리즘이며, 일반적으로 배깅 방법(또는 페이스팅)을 적용한 결정 트리의 앙상블이다.
※ 배깅: 같은 알고리즘으로 여러 개의 분류기를 만들어서 보팅으로 최종 결정하는 알고리즘.
[배깅과 페이스팅 관련]
[결정 트리 관련]
랜덤 포레스트는 앙상블 알고리즘 중 비교적 빠른 수행 속도를 가지고 있으며, 다양한 영역에서 높은 예측 성능을 보이고 있다.
이 알고리즘은 여러 개의 결정 트리 분류기가 전체 데이터에서 배깅 방식으로 각자의 데이터를 샘플링해 개별적으로 학습을 수행한 뒤 최종적으로 모든 분류기가 보팅을 해 예측 결정을 하게 된다.
랜덤 포레스트는 일반적으로 max_samples를 훈련 세트의 크기로 지정한다. BaggingClassifier에 DecisionTreeClassifier를 넣어 만드는 대신 사이킷런은 RadomForestClassifier 클래스를 통해 랜덤 포레스트 기반의 분류를 지원한다.
(비슷하게 회귀 문제를 위한 클래스로는 RandomForestRegressor 가 있다.)
다음은 500개의 트리로 이뤄진 랜덤 포레스트 분류기를 가능한 모든 CPU 코어(n_jobs=-1)에서 훈련시키는 코드이다.
from sklearn.ensemble import RandomForestClassifier
rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16,
n_jobs=-1, random_state=42)
rnd_clf.fit(X_train, y_train)
y_pred_rf = rnd_clf.predict(X_test)
RandomForestClassifier는 몇 가지 예외가 있지만 트리의 성장을 조절하기 위한 DecisionTreeClassifier의 매개변수와 앙상블 자체를 제어하는 데 필요한 BaggingClassifier 의 매개변수를 모두 가지고 있다.
랜덤 포레스트 알고리즘은 트리의 노드를 분할할 때 전체 특성 중에서 최선의 특성을 찾는 대신 랜덤으로 선택한 특성 후보 중에서 최적의 특성을 찾는 식으로 무작위성을 더 주입하는데, 기본적으로 √n 개의 특성을 선택한다. (n은 전체 특성 개수)
이는 결국 트리를 더우 다양하게 만들고 편향을 손해보는 대신 분산을 낮추어 전체적으로 더 훌륭한 모델을 만들어 낸다.
랜덤 포레스트 하이퍼 파라미터 및 튜닝
랜덤 포레스트 같은 트리 기반의 앙상블 알고리즘의 단점
- 하이퍼 파라미터가 많다. ▶ 튜닝을 위한 시간이 많이 소요된다.
- 튜닝을 위한 시간을 많이 소모했음에도 예측 성능이 향상되지 않는 경우가 많다.
※ 트리 기반 자체의 하이퍼 파라미터는 원래 많은데다가 배깅, 부스팅, 학습, 정규화 등을 위한 하이퍼 파라미터까지 추가되어 다른 머신러닝 알고리즘에 비해 많을 수 밖에 없다.
[랜덤 포레스트 하이퍼 파라미터]
- n_estimators: 랜덤 포레스트에서 결정 트리의 개수 지정 (디폴트 10개). 많을수록 좋은 성능을 기대할 수 있으나, 학습수행 시간 증가
- max_features: 최적의 분할을 위해 고려할 최대 피처 개수(디폴트 'auto' = 'sqrt') 전체 피처 중 전체 피처의 루트 만큼 선정 (예를 들어 전체 피처가 16개라면 분할을 위해 4개 참조)
- max_depth, min_samples_leaf, min_samples_split 등 결정 트리에서 과적합을 개선하기 위해 사용되는 파라미터 역시 랜덤 포레스트에서 동일하게 적용
엑스트라 트리
랜덤 포레스트에서 트리를 만들 때 각 노드는 랜덤으로 특성의 서브셋을 만들어 분할에 사용하는데, 트리를 더욱 랜덤하게 만들기 위해 보틍의 결정 트리처럼 최적의 임곗값을 찾는 대신 후보 특성을 사용해 랜덤으로 분할한 다음 그중에서 최상의 분할을 선택한다.
이렇게 하려면 DecisionTreeClassifier를 만들 때 splitter='random' 으로 지정하기만 하면 되며, 이와 같이 극단적으로 랜덤한 트리의 랜덤 포레스트를 익스트림 랜덤 트리 또는 줄여서 엑스트라 트리라고 부른다.
여기서도 역시 편향이 늘어나는 대신 분산이 낮아진다.
모든 노드에서 특성마다 가장 최적의 임곗값을 찾는 것은 트리 알고리즘에서 가장 시간이 많이 소요되는 작업이므로 일반적인 랜덤 포레스트보다 엑스트라 트리의 훈련 속도가 훨씬 빠르다.
엑스트라 트리를 만들려면 사이킷런의 ExtraTreesClassifier를 사용한다.
bootstarp 매개변수가 기본적으로 False인 것을 제외하고 사용법은 RandomForestClassifier와 같다.
마찬가지로 ExtraTreesRegressor도 bootstrap 매개변수가 기본적으로 False인 것을 제외하고 RandomForestRegressor와 같은 API를 제공한다.
단, RandomForestClassifier가 ExtraTreesClassifier 보다 더 나을지(또는 나쁠지) 예단하긴 어려우므로, 둘 다 시도해보고 교차 검증으로 판단해보는 것이 유일한 방법이다.
특성 중요도
랜덤 포레스트의 또다른 장점은 특성의 상대적 중요도를 측정하기 쉽다는 점이다.
사이킷런은 어떤 특성을 사용한 노드가 랜덤 포레스트에 있는 모든 트리에 걸쳐서 평균적으로 불순도를 얼마나 감소시키는지 확인하여 특성의 중요도를 측정하는데, 더 정확하게는 가중치 평균이며, 각 노드의 가중치는 연관된 훈련 샘플 수와 같다.
사이킷런은 훈련이 끝난 뒤 특성마다 자동으로 이 점수를 계산하고 중요도의 전체 합이 1이 되기도록 결괏값을 정규화하며, 이 값은 featrues_importances_ 변수에 저장되어 있다.
아래 코드는 iris 데이터셋에 RandomForestClassifier를 훈련시키고 각 특성의 중요도를 출력한다.
from sklearn.datasets import load_iris
iris = load_iris(as_frame=True)
rnd_clf = RandomForestClassifier(n_estimators=500, random_state=42)
rnd_clf.fit(iris.data, iris.target)
for score, name in zip(rnd_clf.feature_importances_, iris.data.columns):
print(round(score, 2), name)
▶ 가장 중요한 특성은 꽃잎의 길이 (44%) 와 너비(42%)이고 꽃받침의 길이와 너비는 비교적 덜 중요한 것으로 나타난다.
이와 유사하게 MNIST 데이터셋에 랜덤 포레스트 분류기를 훈련시키고 각 픽셀의 중요도를 그래프로 나타내면 아래와 같다.
from sklearn.datasets import fetch_openml
X_mnist, y_mnist = fetch_openml('mnist_784', return_X_y=True,
as_frame=False, parser='auto')
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
rnd_clf.fit(X_mnist, y_mnist)
heatmap_image = rnd_clf.feature_importances_.reshape(28, 28)
plt.imshow(heatmap_image, cmap="hot")
cbar = plt.colorbar(ticks=[rnd_clf.feature_importances_.min(),
rnd_clf.feature_importances_.max()])
cbar.ax.set_yticklabels(['중요하지 않음', '매우 중요'], fontsize=14)
plt.axis("off")
plt.show()
랜덤 포레스트는 특히 특성을 선택해야 할 때 어떤 특성이 중요한지 빠르게 확인할 수 있어 매우 편리하다.
행동 인식 데이터 세트를 이용해 예측 수행
새로운 주피터 노트북에서 진행하되, 재수행 시마다 동일한 예측 결과를 출력하기 위해, 그리고 사용자 행동 데이터 세트에 DataFrame을 반환하는 get_human_dataset() 함수를 가져오기 위해 기존의 예측 분석과 동일한 디렉터리에 생성한다.
기존에 진행했던 행동 인식 데이터 관련 내용은 하단 참고
(물론, 새로운 디렉터리에 수행해도 된다. 이러한 경우, 행동 인식 데이터 코드는 get_human_dataset() 함수 생성까지 동일하다 - 혹시 몰라서 하단에 코드를 남긴다.)
※ 물론, 사이킷런에서 앙상블의 랜덤 포레스트 분류기를 import 하는 것도 다르다 (하단 첫째 줄 참고)
# 랜덤 포레스트 (사용자 행동 인식 데이터 세트 활용)
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
# features.txt 파일에는 피처 이름 index와 피처명이 공백으로 분리되어 있음
# 이를 DataFrame으로 로드
feature_name_df = pd.read_csv('C:/Users/niceq/Documents/DataScience/Python ML Guide/human_activity/features.txt',
sep='\s+', header=None, names=['column_index', 'column_name'])
# 피처명 index를 제거하고, 피처명만 리스트 객체로 생성한 뒤 샘플로 10개만 추출
feature_name = feature_name_df.iloc[:, 1].values.tolist()
print('전체 피처명에서 10개만 추출:', feature_name[:10])
# 중복된 피처명 파악하기
feature_dup_df = feature_name_df.groupby('column_name').count()
print(feature_dup_df[feature_dup_df['column_index'] > 1].count())
feature_dup_df[feature_dup_df['column_index'] > 1].head()
# 중복된 피처명 파악하기
feature_dup_df = feature_name_df.groupby('column_name').count()
print(feature_dup_df[feature_dup_df['column_index'] > 1].count())
feature_dup_df[feature_dup_df['column_index'] > 1].head()
# get_human_dataset 함수 생성
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'])
# 중복된 피처명을 수정하는 get_new_feature_name_df()를 이용, 신규 피처명 DF 생성
new_feature_name_df = get_new_feature_name_df(feature_name_df)
# DF에 피처명을 칼럼으로 부여하기 위헤 리스트 객체로 다시 변환
feature_name = new_feature_name_df.iloc[:, 1].values.tolist()
# 학습 피처 데이터세트와 테스트 피처 데이터를 DF로 로딩. 칼럼명은 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)
# 학습 레이블과 테스트 레이블 데이터를 DF로 로딩하고 칼럼명은 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'])
# 로드된 학습/테스트용 DF 모두 반환
return X_train, X_test, y_train, y_test
X_train, X_test, y_train, y_test = get_human_dataset()
여기까지는 동일하고,
하단 내용을 입력하여 랜덤 포레스트를 통한 예측 성능 평가를 수행하면 된다.
# 결정 트리에서 사용한 get_human_dataset() 이용해 학습/테스트용 DF 반환
X_train, X_test, y_train, y_test = get_human_dataset()
# 랜덤 포레스트 학습 및 별도의 테스트 세트로 예측 성능 평가
rf_clf = RandomForestClassifier(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))
[GridSearchCV를 이용해 랜덤 포레스트의 하이퍼 파라미터 튜닝]
(사용자 행동 데이터 세트 그대로 사용, n_estimators = 100, CV = 2)
# 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 수행
# 멀티 코어 환경에서는 n_jobs=-1 파라미터 추가 시 모든 CPU 코어를 이용해 학습 가능
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 일 때 2개의 CV 세트에서 약 91.65%의 정확도가 측정되었다.
[추출된 최적 하이퍼 파라미터로 별도의 테스트 데이터 세트에서 예측 성능 측정]
# 추출된 최적 하이퍼 파라미터로 별도의 테스트 데이터 세트에서 예측 성능 측정
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)))
[피처의 중요도를 파악해 막대그래프로 시각화]
RandomForestClassifier 역시 feature_importances_ 속성을 이용해 알고리즘이 선택한 피처의 중요도를 파악할 수 있다.
#피처의 중요도를 파악해 막대그래프로 시각화
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
ftr_importances_values = rf_clf1.feature_importances_
# Top 중요도로 정렬을 쉽게 하고, 시본(Seaborn)의 막대그래프로 쉽게 표현하기 위해 Series 변환
ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns)
# 중요도 값 순으로 Series를 정렬
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()
▶ angle(X.gravityMean), tGravityAcc-mean()-Y, tGravityAcc-min()-X 등이 높은 피처 중요를 가지고 있다.
다음 내용
[출처]
파이썬 머신러닝 완벽 가이드
핸즈 온 머신러닝
'[파이썬 Projects] > <파이썬 머신러닝>' 카테고리의 다른 글
[머신러닝] 앙상블 : XG Boost (1) (0) | 2024.07.01 |
---|---|
[머신러닝] 앙상블 : GBM (0) | 2024.06.30 |
[머신러닝] 앙상블 학습(Ensemble Learning) (0) | 2024.06.27 |
[머신러닝] 결정트리 - 사용자 행동 인식 데이터 세트 (0) | 2024.06.23 |
[머신러닝] 결정 트리 - 3 (0) | 2024.06.23 |