다시 이음

트리 기반 모델 - 2 랜덤포레스트 (Random Forests) 본문

AI 일별 공부 정리

트리 기반 모델 - 2 랜덤포레스트 (Random Forests)

Taeho(Damon) 2021. 8. 18. 23:39

Random Forests 

 

랜덤 포레스트란?

 

  • 랜덤포레스트는 결정트리(Decision trees)를 기본모델로 사용하는 앙상블 방법이라 할 수 있습니다.
  • 앙상블 방법은 한 종류의 데이터로 여러 머신러닝 학습모델(weak base learner, 기본모델)을 만들어 그 모델들의 예측결과를 다수결이나 평균을 내어 예측하는 방법을 말합니다. 이론적으로 기본모델 몇가지 조건을 충족하는 여러 종류의 모델을 사용할 수 있습니다.
  • 결정트리들은 독립적으로 만들어지며 각각 랜덤으로 예측하는 성능보다 좋을 경우 랜덤포레스트는 결정트리보다 성능이 좋습니다.

 

  • 그렇기에 데이터가 선형이든 비선형이든, 여러분들이 분류문제를 풀어야 한다면 랜덤포레스트를 먼저 적용해 보시기를 바랍니다.
  • 앞서 배운 결정트리모델은 한 개의 트리만 사용하기 때문에 한 노드에서 생긴 에러가 하부 노드에서도 계속 영향을 주는 특성이 있으며, 트리의 깊이에 따라 과적합되는 경향이 있습니다. 이러한 문제는 앙상블모델인 랜덤포레스트를 사용하면 쉽게 해결할 수 있습니다.

 

랜덤 포레스트의 구성

 

1) 부트스트랩(Bootstrap) 샘플링

 

 

앙상블에 사용하는 작은 모델들은 부트스트래핑(bootstraping)이라는 샘플링과정으로 얻은 부트스트랩세트를 사용해 학습을 합니다.

 

즉 원본 데이터에서 샘플링을 하는데 복원추출을 한다는 것인데 복원추출은 샘플을 뽑아 값을 기록하고 제자리에 돌려놓는 것을 말합니다.

 

이렇게 샘플링을 특정한 수 만큼 반복하면 하나의 부트스트랩세트가 완성됩니다. 복원추출이기 때문에 부트스트랩세트에는 같은 샘플이 반복될 수 있습니다.

 

부트스트랩 샘플링

부트스트랩 샘플링으로 데이터셋을 생성하면 아래와 같은 특징이 생깁니다.

 

  • n개의 크기를 가진 데이터셋을 샘플링한다면 원본데이터셋에서 뽑히지 않은 데이터의 확률은 n-1/n 입니다.
  • 그 확률은 무한히 가져가면 0.368 이라는 수치로 수렴됩니다.
  • 즉, 원본데이터셋에서 부트스트랩 샘플링을 사용하면 원본데이터의 63.2%의 샘플만을 가집니다.
  • 그외의 36.8%의 데이터Out-Of-Bag 샘플(OOB)이라고하며, 이것을 사용해 모델을 검증할 수 있습니다. (oob_score 메소드로 확인 가능)

 

2) Bagging (Bootstraping Aggregating)

 

 

Bagging을 설명하기 위해선 몇가지에 대해서 먼저 설명이 필요합니다.

 

 

🄐 기본모델(Weak learner)

 

  • 이전에 분석모델에서 최초의 비교를 위해 세웠던 기본모델과는 다릅니다.
  • 한 종류의 데이터로 기본모델을 여러가지 생성하여 그 모델들의 예측결과를 다수결이나 평균을 내어 예측하는 방법을 우리는 앙상블 방법(Ensemble)이라고 합니다.

 

🄑 기본모델을 어떻게 만드는가?

 

  • 랜덤포레스트는 기본모델들의 트리를 만들 때 무작위로 선택한 특성세트를 사용합니다
  • 결정트리에서 분할을 위한 특성을 선택할 때, 모든 특성(n개)을 고려하여 최적의 특성을 고르고 분할하였습니다.
  • 하지만 랜덤포레스트에서는 특성 n개 중 일부분 k개의 특성을 선택(sampling) 하고 이 k개에서 최적의 특성을 찾아내어 분할합니다. 이때 k개는 일반적으로 𝑙𝑜𝑔2𝑛 를 사용합니다.
  • 랜덤으로 설정된 특성을 노드로 삼아 분할을 거쳐 트리를 만드는 과정을 반복하여 여러개의 트리를 생성합니다.

 

🄒 기본모델을 만들기 전에 전처리 과정 (OrdinalEncoder)

 

  • 다른 분석모델과 같이 SimpleImputer, StandardScaler 을 통해 전처리하는 것은 동일합니다.
  • 하지만 트리구조 학습의 특성때문에 Onehotencoder를 사용하는 것이 권장되지 않습니다.

 

  • 그래서 우리는 범주형 특성을 순서형으로 인코딩해주는 OrdinalEncoder을 사용할 것입니다.
  • OrdinalEncoder(순서형 인코딩)은 범주에 숫자를 맵핑합니다.
  • ['a', 'b', 'c'] 세 범주가 있다면 이것을 -> [1, 2, 3] 이렇게 숫자로 인코딩 합니다.

 

  • 트리구조 학습에서는 원핫인코딩을 사용하면 문제가 있습니다.그래서 원핫인코딩 영향을 안 받는 수치형 특성이 상위노드를 차지할 기회가 높아지고 전체적인 성능 저하가 생길 수 있습니다.
  • 트리구조에서는 중요한 특성이 상위노드에서 먼저 분할이 일어납니다. 그래서 범주 종류가 많은(high cardinality) 특성은 원핫인코딩으로 인해 상위노드에서 선택될 기회가 적어집니다.

 

from category_encoders import OrdinalEncoder

enc = OrdinalEncoder(handle_missing="value")

# 적용하기
enc.transform()

# 어떻게 mapping되었는지 확인하기 
enc.category_mapping

# mapping 하이퍼 파라미터를 사용하여 자동으로 설정된 mapping 변경하기(예시)
enc = OrdinalEncoder(handle_missing="value", mapping=ordinal_cols_mapping)

ordinal_cols_mapping = [{'col': 'opinion_h1n1_vacc_effective','mapping': {'Very Effective':7, 'NaN':1,'Dont Know':5,
                         'Not At All Effective': 3,'Somewhat Effective':6,'Not Very Effective':4,'Refused':2}},
                        {'col': 'opinion_h1n1_risk','mapping': {'Somewhat High':7,'NaN':1,'Somewhat Low': 5,'Dont Know':4,'Very High':6,
                          'Very Low':3,'Refused':2}}]

 

궁금증타임) 왜 트리구조 학습에서는 순서형 인코딩을 해도 문제가 안되나요?

 

이러한 궁금증은 선형회귀분석모델에서는 범주형 데이터를 순서형으로 인코딩했을 시에 순서정보가 생기는 일이 발생할 수 도 있으며 이것이 모델의 성능에 영향을 끼치기 때문에 범주형 데이터를 onehotencoder를 사용하여 처리하기 때문에 나왔습니다.

 

트리구조 학습에서는 위와 같이 상위노드에서의 중요도 덕분에 순서형 인코딩을 사용하기도 하지만 기본적으로 기본모델이 여러개가 병렬화 되어 범주형 데이터에 대해 순서정보가 생기더라도 분기 시에 데이터를 병렬로 보기 때문에 그 순서정보는 무시할 수 있게 됩니다.

 

그러나, 사실 순서형 인코딩은 범주들 간에 분명한 순위가 있을때 그 연관성에 맞게 숫자를 정해주는 것이 좋습니다. 예를들어 영화 평점과 같은 특성은 분명히 순서형 인코딩이 적절합니다.

 

 

우리는 위의 과정을 거쳐 만들어진 여러개의 기본모델들을 합치는 과정을 Aggregation 이라고 합니다.

  • 회귀문제일 경우 기본모델 결과들의 평균으로 결과를 내고,
  • 분류문제일 경우 다수결로 가장 많은 모델들이 선택한 범주로 예측합니다.

 

3) 특성중요도

 

랜덤포레스트에서는 학습 후에 특성들의 중요도 정보(Gini importance)를 기본으로 제공합니다.

중요도는 노드들의 지니불순도(Gini impurity)를 가지고 계산하는데 노드가 중요할 수록 불순도가 크게 감소한다는 사실을 이용합니다.

노드는 한 특성의 값을 기준으로 분리가 되기 때문에 불순도를 크게 감소하는데 많이 사용된 특성이 중요도가 올라갈 것입니다.

 

import matplotlib.pyplot as plt

# 특성 중요도(onehot)
rf = pipe.named_steps['randomforestclassifier']
colnames = pipe.named_steps['onehotencoder'].get_feature_names()
importances = pd.Series(rf.feature_importances_, colnames)

n = 10
plt.figure(figsize=(10,n/4))
plt.title(f'Top {n} features with onehotencoder')
importances.sort_values()[-n:].plot.barh();


# 특성 중요도(ordinal)
rf_ord = pipe_ord.named_steps['randomforestclassifier']
importances_ord = pd.Series(rf_ord.feature_importances_, X_train.columns)

plt.figure(figsize=(10,n/4))
plt.title(f'Top {n} features with ordinalencoder')
importances_ord.sort_values()[-n:].plot.barh();

 

 

랜덤 포레스트의 구현

 

%%time  # 계산에 필요한 시간을 확인할 수 있다.

from category_encoders import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer 
from sklearn.pipeline import make_pipeline

pipe = make_pipeline(
    OneHotEncoder(use_cat_names=True), 
    SimpleImputer(), 
    RandomForestClassifier(n_jobs=-1, random_state=10, oob_score=True)
)
# n_jops=-1 파라미터의 뜻은 현재 컴퓨터 환경에서 사용할 수 있는 모든 프로세스를 사용한다는 뜻이다.

pipe.fit(X_train, y_train)
print('검증 정확도: ', pipe.score(X_val, y_val))

#oob_score 확인하기
pipe.named_steps['randomforestclassifier'].oob_score_

 

 

궁금증타임) 랜덤포레스트 모델이 결정트리모델보다 상대적으로 과적합을 피할 수 있는 이유가 무엇일까요?

 

랜덤포레스트의 랜덤성은 다음 두 가지에서 나옵니다.

결정트리는 데이터 일부에 과적합하는 경향이 있습니다.

그래서 다르게 샘플링된 데이터로 과적합된 트리를 많이 만들고 그 결과를 평균내 사용하는 모델이 랜덤 포레스트 입니다.

이렇게 하면 과적합이 줄고 성능이 유지 된다는 것이 알려져 있습니다. 랜텀포레스트에서 트리를 랜덤하게 만드는 방법은 두 가지 입니다.

 

1. 랜덤포레스트에서 학습되는 트리들은 배깅을 통해 만들어집니다.(bootstrap = true) 이때 각 기본트리에 사용되는 데이터가 랜덤으로 선택됩니다.

2. 각각 트리는 무작위로 선택된 특성들을 가지고 분기를 수행합니다.(max_features = auto.)

 

 

랜덤포레스트 하이퍼 파라미터 튜닝

 

RandomForestClassifier() 메소드에 다음과 같은 하이퍼 파라미터를 조정함으로써 랜덤포레스트를 튜닝할 수 있습니다.

 

n_estimators - 결정트리의 갯수를 지정
- Default = 10
- 무작정 트리 갯수를 늘리면 성능 좋아지는 것 대비 시간이 걸릴 수 있음
min_samples_split - 노드를 분할하기 위한 최소한의 샘플 데이터수
→ 과적합을 제어하는데 사용
- Default = 2 → 작게 설정할 수록 분할 노드가 많아져 과적합 가능성 증가
min_samples_leaf - 리프노드가 되기 위해 필요한 최소한의 샘플 데이터수
- min_samples_split과 함께 과적합 제어 용도
- 불균형 데이터의 경우 특정 클래스의 데이터가 극도로 작을 수 있으므로 작게 설정 필요
max_features - 최적의 분할을 위해 고려할 최대 feature 개수
- Default = 'auto' (결정트리에서는 default가 none이었음)
- int형으로 지정 →피처 갯수 / float형으로 지정 →비중
- sqrt 또는 auto : 전체 피처 중 √(피처개수) 만큼 선정
- log : 전체 피처 중 log2(전체 피처 개수) 만큼 선정
max_depth - 트리의 최대 깊이
- default = None
→ 완벽하게 클래스 값이 결정될 때 까지 분할
또는 데이터 개수가 min_samples_split보다 작아질 때까지 분할
- 깊이가 깊어지면 과적합될 수 있으므로 적절히 제어 필요
max_leaf_nodes 리프노드의 최대 개수

참조) https://injo.tistory.com/30