랜덤 포레스트(Random Forest)

※ 결정 트리를 모른다면?(아래 클릭 시 링크로 이동)
결정 트리(Decision Tree)

랜덤 포레스트가 뭐지?

기계 학습에서의 랜덤 포레스트(영어: random forest)는 분류, 회귀 분석 등에 사용되는 앙상블 학습 방법$^*$$^1$의 일종으로, 훈련 과정에서 구성한 다수의 결정 트리로부터 분류 또는 평균 예측치(회귀 분석)를 출력함으로써 동작한다.
- 위키백과

랜덤 포레스트는 단순하게 말하면 결정 트리 여러 개를 모아 놓은 것이다.
이름 그대로 나무(Tree)가 모여 있으니 숲(Forest)이 되는 것이다.

랜덤(Random, 무작위)이라는 말은 왜 붙어 있을까? 이는 아래 내용을 참고하면 이해 가능할 것이다.

초기 랜덤 포레스트는 단일 트리를 확장할 때 가능한 결정(available decisions)중 임의의 부분집합(random subset)에서 검색하는 아이디어를 도입한 얄리 아미트(Yali Amit)와 도널드 게먼(Donald Geman)의 연구에 영향을 받았다. 또한 임의의 부분공간(random subspace)을 선택하는 틴 캄 호(Tin Kam Ho)의 아이디어 역시 랜덤 포레스트의 디자인에 영향을 미쳤다. - 위키백과(랜덤 포레스트 - 1 도입 - 1.2 역사 中)

랜덤 포레스트는 왜 만들어졌을까?
랜덤 포레스트는 결정 트리로부터 나온 것이다. 결정 트리는 한 개의 트리만을 사용하기에 한 노드에서의 에러가 그의 하부 노드까지 계속해서 영향을 주기도 하고, 트리의 깊이에 따라 과적합되는 문제 등이 있다. 이러한 이유로 새로운 데이터의 분류에 유연하게 대응하지 못하는데, 이를 보완하기 위해 나온 것이 랜덤 포레스트다.


랜덤 포레스트는 어떻게 하는거지?

랜덤 포레스트는 아래와 같은 순서로 진행된다.

  1. 초기 데이터셋(Original Dataset)으로부터 파생된 여러 개의 데이터셋을 만든다. 이 과정은 부트스트랩 샘플링$^*$$^2$을 통해 이루어진다.
  2. 1번에서 만들어진 각각의 데이터셋에 대한 결정 트리를 만든다. 여기서 주의할 점은 트리를 만들 때 트리의 각 분기마다 해당 데이터셋의 특성 중 일부를 무작위로 가져온다는 것이다.

이렇게 해서 만들어진 결정 트리들을 모아놓은 것이 바로 랜덤 포레스트이다.

그럼 랜덤 포레스트는 타겟을 어떻게 예측할까?

만약 어떤 특성을 가진 데이터의 타겟을 예측하기 위해 랜덤 포레스트에 넣는다고 하자.
이 랜덤 포레스트 안에는 여러 개의 결정 트리가 있고, 각각의 결정 트리는 이 데이터에 대한 타겟 예측을 내놓는다. 그 후 이들의 예측 모두를 종합하여 최종 타겟 예측 값을 결정하는데, 이를 배깅(Bagging, Bootstrap Aggregating)이라고 한다.

회귀 문제에서는 예측을 종합한 값의 평균이, 분류 문제에서는 다수결로 선택된 클래스가 최종 예측 값이 된다.

잠깐, 예측하기 전에 검증은 어떻게 하지?

랜덤 포레스트를 만들 때 필요한 여러 데이터셋을 만드는 과정에서 부트스트랩 샘플링을 사용한다고 하였다. 여기서 샘플링 과정에 포함되지 않는 데이터가 생기는데, 이를 Out-Of-Bag(OOB) 데이터라고 한다.
랜덤 포레스트의 검증은 이 OOB 데이터를 통해 이루어진다. 이를 통해 OOB 데이터에 대한 예측 결과와 실제 값의 오차, 즉 OOB 오차(Out-Of-Bag Error)를 얻을 수 있다.

이후 검증 결과를 바탕으로 랜덤 포레스트의 하이퍼 파라미터(개별 트리의 최대 깊이, 노드 분기를 위한 최소한의 샘플 수 등)를 조절하여 OOB 오차를 최소화시킨다.

scikit-learn을 통해 랜덤 포레스트 사용하기

  1. 데이터 불러오기 및 전처리
# 데이터 불러오기
import pandas as pd

target = 'vacc_h1n1_f'
train = pd.merge(pd.read_csv('https://ds-lecture-data.s3.ap-northeast-2.amazonaws.com/vacc_flu/train.csv'),
pd.read_csv('https://ds-lecture-data.s3.ap-northeast-2.amazonaws.com/vacc_flu/train_labels.csv')[target], left_index=True, right_index=True)
test = pd.read_csv('https://ds-lecture-data.s3.ap-northeast-2.amazonaws.com/vacc_flu/test.csv')
sample_submission = pd.read_csv('https://ds-lecture-data.s3.ap-northeast-2.amazonaws.com/vacc_flu/submission.csv')
# 데이터를 훈련 / 검증 세트로 분리
from sklearn.model_selection import train_test_split

train, val = train_test_split(train, train_size=0.80, test_size=0.20,
stratify=train[target], random_state=2)

train.shape, val.shape, test.shape

((33723, 39), (8431, 39), (28104, 38))

# 데이터 전처리
def  engineer(df):
    """특성을 엔지니어링 하는 함수입니다."""
    # 새로운 특성을 생성합니다.
    behaviorals = [col for col in df.columns if  'behavioral'  in col]
    df['behaviorals'] = df[behaviorals].sum(axis=1)

    dels = [col for col in df.columns if  ('employment'  in col or  'seas'  in col)]
    df.drop(columns=dels, inplace=True)

    return df

train = engineer(train)
val = engineer(val)
test = engineer(test)
# 데이터를 특성과 타겟으로 분리
features = train.drop(columns=[target]).columns  

X_train = train[features]
y_train = train[target]
X_val = val[features]
y_val = val[target]
X_test = test[features]
  1. 랜덤 포레스트 사용하기
from category_encoders import OrdinalEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline  

# 범주형 특성들의 값을 숫자로 대체하기 위하여 OrdinalEncoder 사용
# handle_missing="return_nan"은 범주형 특성에 있는 NaN을 항상 -2로 대체시킴
# 만약 handle_missing="value"(기본값)인 경우 NaN이 -2가 아닌 일반적인 범주 종류로 인식되어 값이 대체될 수도 있다.
pipe = make_pipeline(
    OrdinalEncoder(handle_missing="return_nan"),
    SimpleImputer(),
    RandomForestClassifier(max_depth=13, n_estimators=120, random_state=10, n_jobs=-1, oob_score=True)
)  
# RandomForestClassifier의 n_estimators는 랜덤 포레스트 내의 결정 트리 개수를 의미함
# 그 외 결정 트리의 과적합을 줄이기 위해 사용하는 max_depth, min_samples_split, min_samples_leaf도 설정 가능

pipe.fit(X_train, y_train)
print('훈련 정확도', pipe.score(X_train, y_train))
print('검증 정확도', pipe.score(X_val, y_val))
print('f1 score', f1_score(y_val, pipe.predict(X_val)))

훈련 정확도 0.8894523025828069
검증 정확도 0.8315739532677026
f1 score 0.5520504731861199

# OOB 스코어(OOB 오차의 반대 개념) 확인
pipe.named_steps['randomforestclassifier'].oob_score_

0.821516472437209


*1 앙상블 학습 방법?

앙상블(프랑스어: ensemble)은 전체적인 어울림이나 통일. ‘조화’로 순화한다는 의미의 프랑스어이며 음악에서 2인 이상이 하는 노래나 연주를 말한다.
- 위키백과(앙상블)

보통 우리가 아는 '앙상블'이라는 말의 의미는 위와 같다.

앙상블 방법은 한 종류의 데이터로 여러 머신러닝 학습 모델(weak base learner, 기본 모델)을 만들어 그 모델들의 예측 결과를 다수결이나 평균을 내어 예측하는 방법이다.
랜덤 포레스트는 결정 트리를 기본 모델로 사용하는 앙상블 방법이다.


*2 부트스트랩 샘플링?


부트스트랩 샘플링이란 어떠한 데이터셋에 대하여 샘플링을 할 때 복원 추출, 즉 뽑았던 것을 또 뽑을 수 있도록 한 것이다. 이렇게 샘플링을 반복하여 만들어지는 것이 부트스트랩 세트(Bootstrap Set)이다. 복원 추출을 했기 때문에 각각의 부트스트랩 세트 안에는 같은 샘플이 중복되어 나타날 수 있다.

부트스트랩 세트의 크기가 n이라 할 때 한 번의 추출 과정에서 어떤 한 샘플이 추출되지 않을 확률은 다음과 같다.

$\displaystyle \frac {n-1}{n}$

n회 복원 추출을 진행했을 때 그 샘플이 추출되지 않을 확률은 다음과 같다.

$\displaystyle \left({\frac {n-1}{n}}\right)^{n}$

n을 무한히 크게 했을 때 이 식은 다음과 같다.

$\displaystyle \lim _{{n\to \infty }}\left({1 - \frac {1}{n}}\right)^{n} = e^{-1} = 0.368$
※ 참고 : $\displaystyle e = \lim _{{n\to \infty }}\left(1+{\frac {1}{n}}\right)^{n}$

즉, 데이터가 충분히 크다고 가정했을 때 한 부트스트랩 세트는 표본의 63.2% 에 해당하는 샘플을 가진다.
여기서 추출되지 않는 36.8%의 샘플이 Out-Of-Bag 샘플이며 이것을 사용해 모델을 검증한다.


<참고 자료>

'Machine Learning > Trees' 카테고리의 다른 글

[ML] 결정 트리(Decision Tree)  (0) 2021.10.26

+ Recent posts