Random Forests
Key words
Random Forests, Bagging, OOB, Bootstrap, ensemble(앙상블), Ordinal Encoding
Details
개념
- 전날 Decision Tree를 배웠는데, 이어져 나오는 개념이 바로 Random Forest이다. 이름 그대로 나무가 모여 숲을 이룬 것이다! 이름 참 잘 지었다.
- 이전 글에서 말한 것처럼 결정 트리는 과적합이 상당한 문제가 될 수 있다. (물론 max_depth를 조절하면 되긴 하지만 상대적으로!) 또한 한번에 하나의 특성을 node로 잡고 쭉 내려오게 되는데, 만약 한 노드에서 에러가 생기면 그 문제가 하위노드까지도 쭉 이어지게 되는 문제도 있다고 한다. (이 말은 직관적으로는 이해가 가는데, 정확히 어떤 사례가 있을지는 모르겠다)
- 분류 문제를 만났다? 그럼 이 Random Forest를 먼저 적용해보라고 부트캠프 세션에서 추천하고 있다.
- 모델에 대한 본격적인 설명 이전에 용어 몇 개 정리해둔다.
- Bootstrap: 복원추출 (중복 허용)
- 앙상블: 여러 간단한 모델을 합쳐서 더 나은 성능의 하나의 모델을 만드는 기법을 말한다. 랜덤 포레스트도 여기에 해당한다. 참고로 앙상블 기법에는 bootstrap 말고도 stacking이나 Boosting도 있다고 한다.
- Random Forest의 주요 원리는 다음과 같다.
- 참고로 이 영상을 보고 원리를 쉽게 잘 이해할 수 있었다. 나중에 글로만 봐서 헷갈린다면 영상을 한번 더 보자.
- 원본데이터 셋에서 Bootstrap 데이터 셋(샘플을 복원 추출)을 만든다.
- Bootstrap 데이터 셋에서 랜덤하게 K개의 피쳐만 선택하여 root node를 만들고, 그 다음 Node에서는 root node에 들어간 피쳐를 제외한 나머지 중 K개의 피쳐를 골라 그 중 node를 만들고 ... 이 과정을 반복하여 여러 Bootstrap 데이터 셋을 만든다. (K가 몇이 제일 좋을지는 사후적으로 확인해야 함.)(이상적으로는 100번 하는 것이 좋다고 한다)
- 만약 100번 반복하면 100개의 결정 트리 모델을 가지게 된 것이다. 이 결정트리들을 기본 모델 (weak base learner)라고 부른다. (기준 모델Baseline model과 헷갈리지 말자) 랜덤 포레스트에서는 결정트리가 기본모델이지만, 다른 앙상블 모델에는 기본모델이 결정트리, 선형회귀 등 섞여있는 경우도 있는 것 같다.
- 랜덤 포레스트는 이 100개 결정트리의 예측결과 중 가장 많은 예측값을 사용한다. (= 분류 모델에서는 다수결, 회귀모델에서는 평균)
- 이 과정을 Bagging(Bootstrap Aggregation)이라고 부른다.
- 각각의 bootstrapped model들이 합쳐져서 하나의 모델이 되는 것.
- 예를 들어 예측하고자 하는 샘플 a가 있을 때 a에 대해 이 100개의 기본 모델을 각각 다 돌려보고 거기서 가장 많이 분류된 클래스가 랜덤 포레스트 모델의 결과로 반환!
- 모델의 평가 지표
- 처음에 Bootsrap Dataset을 만들 때 복원추출을 했는데, 이때 선택되지 못한 샘플을 OOB(Out-of-Bag)이라고 부른다. 데이터 수가 충분할 때 약 34%? 정도의 oob가 생긴다고 한다. 이 데이터셋을 모델의 검증에 사용할 수 있다.
oob_score
가 있어 쉽게 구할 수 있는 듯? (이 oob set을 그냥 버리면 너무 아깝다! 잘 활용하자)
- 전체 분류 대비 잘못 분류한 정도를
Out-of-bag error
라고 한다.
- 왜 앙상블 기법에서는 과적합을 방지할 수 있는가?
- bagging을 통해 만들어지는데, 이때 각 모델에 선택된 데이터는 랜덤하게 정해진다.
- 사용하는 피쳐들 또한 랜덤하게 정해진다.
- 결정 트리는 모든 특성을 다 고려하다보니 과적합이 일어나는 건데, 랜덤 포레스트에서는 이 선택의 랜덤성이 가장 주요한 특징이라고 할 수 있겠다.
- Ordinal Encoding(순서형 인코딩)
- 트리 모델에서 범주형 데이터를 다룰 때에는 원핫인코딩이 아닌 순서형 인코딩을 해주는 것이 좋다. (선형회귀 모델에서 원핫인코딩이 아니라 순서형 인코딩을 하면 각 클래스별로 가중치가 매겨져서 제대로 된 분석이 될 수 없으니 유의할 것. 회귀모델은 피쳐 간의 관계를 통해 중요도를 파악한다!)
- 원핫인코딩하면 카디널리티 만큼 피쳐가 늘어나기 때문에 중요한 특성이 상위 노드에서 선택될 확률이 낮아지는 문제가 있기 때문이다. (다른 말로 하면 한 가지 특성이 여러 특성으로 나뉘어 버리기 때문이다. 원핫인코딩이 되지 않은 수치형 데이터가 중요한 피쳐로 인식될 확률이 높아진다)
- 순서형 인코딩은 '순서에 따라 가치를 부여할 수 있는 값'들이 있는 경우 적용하는 것이 좋다.
- 나는 사이킷런에서 제공하는 거 말고
from category_encoders import OrdinalEncoder
를 사용했는데, 정보가 은근히 많지 않아 공부하는데 상당히 애를 먹었다. 뒤에 실습 부분에서 더 자세한 소감을 남겨보겠다.
- 이와 관련해서 내가 했던 질문
- 순서형 인코딩할 때 순서에 따른 크기를 직접 mapping해주지 않았을 때, 해줬을 때보다 성능이 떨어질 수 있나요? 예를 들어 연령대 데이터에서 10대, 20대, 30대를 1,2,3으로 직접 매핑하지 않았고, 대신 자동으로 3,2,1로 매핑되었다면 이에 따라 손해가 있는지 궁금합니다.
- 내 추가 질문: 연령대가 올라갈 수록 심혈관질환이 있을 가능성이 높다는 연구자의 직관이 반영되는게 직접 매핑의 의도로 볼 수도 있나요? (ex. 연령대가 높을 수록 더 높은 숫자를 주어서 가중치를 더 주겠다) → 그렇다고 함. 매핑을 직접 한다는 건 직관이 들어갈 수 밖에 없음.
- 실습 부분에서 얘기하겠지만, 순서형 인코딩을 할 때는 웬만하면 mapping을 해주는게 좋을 것 같다.
실습- 전에 했던 캐글 대회를 이어서 진행했다
- 전날 했던 EDA, Feature Engineering 업데이트 (ordinal Encoding 포함)
- ordinal Encoding에서 아주 상당히 헤맸다. 결과적으로는 profiling을 통해 아래와 같이 4개의 피쳐를 순서형 인코딩을 해야겠다고 정했다. mapping은 따로 해주지 않았다. (해줄껄!!)
'''
1. opinion_h1n1_vacc_effective - 백신 효과는 어떨 것 같다?
2. opinion_h1n1_risk - 백신을 맞지 않았을 때 h1n1에 걸릴 것 같다?
3. agegrp - 연령대
4. opinion_h1n1_sick_from_vacc
'''
from category_encoders import OrdinalEncoder
enc = OrdinalEncoder(cols = ['opinion_h1n1_vacc_effective', 'opinion_h1n1_risk', 'agegrp', 'opinion_h1n1_sick_from_vacc'], handle_unknown= 'value')
enc.fit(train)
- Feature Engineering 부분은 어제 했던 것에서 수정하지 않고 그대로 적용했다.
- RandomForest model 적용
from sklearn.pipeline import make_pipeline
from category_encoders import OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
pipe = make_pipeline(
OneHotEncoder(use_cat_names=True),
SimpleImputer(),
RandomForestClassifier(n_jobs=-1, oob_score=True, n_estimators= 50, max_depth= 8)
)
pipe.fit(X_train, y_train)
print(pipe.score(X_train, y_train))
print(pipe.score(X_val, y_val))
- 참고로 ordinal encoding만 하고 랜덤 포레스트 모델 돌리려니까 object값이 있으면 안된다고 오류가 나서 원핫인코딩도 추가로 해주었다.
- 캐글 제출
- 훈련, 검증 데이터에서 적절히 나와서 테스트 데이터에 적용하고 제출해봤더니? 0.48677이 나왔다ㅠㅠ 어제 결정 트리로 했을 때는 0.50492였는데 더 떨어졌다.
그래서 왜 그럴지 나름 원인을 분석해보았다. 그 내용을 옮겨둔다.
왜 랜덤포레스트를 적용하고 더 떨어졌을까?
- 일반적으로 결정트리만 사용하면 과적합의 문제가 있어 랜덤 포레스트를 통해 그 문제를 개선할 수 있다고 말한다. 그럼에도 내 rf 모델이 훈련, 검증 데이터에서는 좋은 성능이 나왔음에도 테스트 데이터에서는 더 안 좋은 결과가 나왔다. (과적합 의심) 왜 그럴까?
두 가정을 정해 생각해볼 것이다.
이 가정이 맞는지는 실제 데이터나 모델을 수정하여서 사후적으로 판단할 수밖에 없겠지만, 그래도 오늘 모델링을 복기한다는 관점에서 살펴보자.
가정 1. 데이터 셋 준비를 잘 못한걸까?
- 오늘은 target과 상관없는
season flu
에 대한 feature 3개를 아예 먼저 제거해주었다. => 이건 모델 복잡도를 낮추는 것이니 좋은 영향을 주었지 악영향을 주었을 것 같지는 않다.
- encoding: ordinal encoding을 4개의 특정 칼럼을 지정해 진행하고, 나머지에 대해서 OneHotEncoding을 진행했다. => ordinal encoding을
auto
로 하지 않은 건 순서형이 아닌 피쳐에 대해서까지 인코딩이 될까 우려되어서였다.
- ordinal encoding할 때 난 따로 mapping하지 않았었는데, 해주는 게 더 나았을 수도 있겠다. 예를 들어
vacc_effective
, h1n1_risk
등 내가 사용한 opinion 피쳐는 백신 접종에 충분히 영향을 줄 수 있는 특성이기 때문이다. 또 두 질문 간의 가중치도 랜덤하게가 아니라 동일하게 주었으면 성능이 올라갈 수도 있지 않을까? (e.g. 백신이 매우 효과적일 것이라고 답한 것이랑, h1n1_risk에 대해서는 very high로 답한 사람이랑 가중치를 똑같이 준다)
agegrp
은 연령대와 백신 접종의 관계에 대해 속단하기는 어렵지만, 취약계층인 어린이와 고령층은 백신 접종이 의무/권장되었다는 사실이 확인된다면 매핑을 해볼 수 있겠다.
- 오늘 과제에서는 rf로 사용할 모델이 fix되었었기 때문에 model의 성능 측정만이 필요하므로, train데이터를 굳이 validation 데이터로까지 나눠야 했을까? oob data가 상당히 많이 있을 것 같은데, 이 데이터를 활용해 검증했다면 더 좋은 결과를 주지 않았을까?
- high cardinality 피쳐를 제외하기만 했던 피쳐 엔지니어링은 우선 오늘 고민에서 제외한다.
- 결론: 필요없는 피쳐를 더 쳐내서 복잡도를 줄이고, 검증데이터로 굳이 쪼개지 않고 Oob로 검증을 해 더 많은 데이터로 훈련할 수 있게 하고, 인코딩을 할 때는 좀 더 세심히 해봤으면 어떨까?
가정 2. 모델 하이퍼파라미터가 잘못 설정되었다.
n_estimators= 50, max_depth= 8
파라미터를 아예 설정하지 않았을 때 너무 과적합이 되어서 이 두 파라미터를 조절하면서 훈련, 검증 데이터에서의 score를 확인했었다.
- 훈련 데이터에서는 약 0.835, 검증 데이터에서는 약 0.819가 나와 이 정도면 과적합이 아니겠다고 생각했는데.. 더 떨어뜨렸어야 할까? 근데 기준모델이 0.76이면 그 정도도 과하지 않을 것 같은데..
- 결론: 사후적으로 봤을 때, 결국 test data에서 score가 굉장히 낮게 나왔으므로, 어제 결정트리 모델의 성능보다 올리는 걸 목표로 했다면, 기준 모델보다 낮더라도 과적합을 막기 위해 하이퍼 파라미터를 더 타이트하게 걸어줬다면 어떨까?
하이퍼 파라미터 조정을 통한 모델 성능 개선도 중요하지만, 모델이 아무리 좋아도 들어온 데이터가 좋아야 결과도 좋을테니까, 데이터 전처리 과정에서 부족한 점이 많았을 것 같다. 데이터가 거지 같아도 랜덤 포레스트라는 모델 자체는 그에 fit하게는 잘 만들어주긴 하니까. 다음부터는 데이터 전처리 과정에 더 신중하자.
참고 - 랜덤포레스트 하이퍼 파라미터: https://injo.tistory.com/30