본문 바로가기
머신러닝/혼공머신

[혼공머신] chapter 5. 결정트리

by 다이노소어 2023. 1. 16.
혼자 공부하는 머신러닝 + 딥러닝의 Chapter 5를 공부하면서 정리한 내용을 기반으로 작성하였다.

 

 

05-1.  결정트리


0. 결정트리 특징

  • 이전 챕터에서 배운 로지스틱 함수는 이해하고 설명하기 어렵다. process의 flow chart를 그리는 듯이 쉽게 설명해 주는 모델이 있을까? 바로 결정트리 (Decision Tree) 모델이다.
  • 다음과 같이 분류문제를 적용할 수도 있고, DecisionTreeRegressor를 이용해 회귀문제를 이용할 수 있다.
  • 특성의 scale이 결정트리에 영향을 미치지 않기 때문에, 표준화 전처리(input_data - mean /std)를 할 필요 없다!
  • 결정트리는 앙상블 학습 알고리즘(신경망과 함께 높은 성능을 냄)의 기반이 된다
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier()
dt.fit(train_data, train_target)
dt.score(test_data, test_target)
  • DecisionTreeClassifier()의 매개변수 "가지치기(과대적합방지)"
    • max_depth : 최대 깊이지정 → max_depth가 너무 크면 일반화가 잘 되지 않아 과대적합이 될 수 있다.
    • min_impurity_decrease :  이 매개변수보다 특성 중요도 값이 더 작으면 더이상 분할하지 않는다.

 

 

1. 결정트리를 그려보자.

plot_tree에 결정트리 모델을 인자로 주는데, 여기에 추가하여 max_depth, filled, feature_names를 매개변수로 줄 수 있다.

  - filled : 색 여부 (예를 들어, filled = True 하면, 클래스에 따라 색을 준다.)

  - feature_names :  특성의 이름을 배열로 지정

import matplotlib.pyplot as plt
fromsklearn.tree import plot_tree
plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()

 

 

2. 결정 트리에서의 예측 결과

  • 분류 문제 : 리프 노드에서 가장 많은 클래스가 곧 예측 클래스가 되는 것이다.
  • 회귀 문제 : 리프 노드에 도달한 샘플의 타깃의 평균이 예측값이 된다.
  • 특성 중요도는 feature_importances_속성에 저장된다.

 

 

3. DecisionTreeClassifier 클래스에서의 criterion  : 기준이 무엇일까?

  •  지니불순도 (Gini impurity; gini)
    • 결정트리 분류모델의 criterion 매개변수 기본 값
    • 지니 불순도 = 1 - (음성클래스 비율^2 + 양성클래스 비율^2)
  • 엔트로피 불순도 (criterion = 'entropy')

엔트로피 불순도

  • 정보 이득(information gain) : 부모와 자식 노드사이의 불순도 차이
    • 부모 노드의 불순도 - (자식 노드의 불순도를 샘플 개수에 비례해 모두 더한 값)
    • 결정 트리는 위와 같은 불순도 기준을 사용해서 정보 이득이 최대가 되도록 노드를 분할한다.
※ 특성 중요도 : 그 특성이 불순도를 감소하는데 얼마나 기여했는가
(1 ) 노드의 정보 이득 * 전체 샘플에 대한 비율를 각 특성별로 더한 값.
(2) 결정 트리 모델의 feature_importances_속성에 저장되어 df.feature_importances_ 이런 식으로 부를 수 있다.
(3) 특성 중요도 값이 min_inmpurity_decrease에 지정한 값보다 작으면 더 이상 분할하지 않게 설정할 수 있다.

 

 

 

05-2. 교차 검증과 그리드 서치


교차 검증

1. 검증세트

  • 테스트 세트를 사용해서 모델이 과대적합/과소적합인지, 성능은 어떤지 사용해왔는데, 계속해서 테스트 세트를 적용시키다 보면 모델의 성능은 테스트 세트에 맞춰지게 된다. 이제, 검증 세트(validation set)를 이용해 보자.
  • 총 데이터 (100%) = 훈련세트 (60%) + 검증세트(20%) + 테스트세트(20%) 이런 식으로 훈련세트를 더 쪼개는 것이다.
  • train_test_split 2번 해서 검증세트를 만들 수도 있다 : 훈련세트 + 테스트세트(20%) → 훈련세트 + 검증세트(20%)
from sklearn.model_selection import train_test_Split

tmp_train_input, test_input, tmp_train_target, test_target 
= train_test_split(data, target, test_Size=0.2)

train_input, val_input, train_target, val_target
= train_test_split(tmp_train_input, tmp_train_target, test_Size=0.2)

 

2. 교차 검증

  • 검증 세트 크기가 너무 작다면 검증 점수가 일정하지 않고 불안정할 것이다. 이때, 교차검증을 통해 해결할 수 있다.
  • 교차 검증은 훈련세트에서 검증세트를 떼어내고 평가하는 과정을 반복하는 것이다.
  • k-폴드 교차 검증(k-겹교차 검증)
    • 훈련세트를 5 부분으로 나눠 교차 검증 수행하면 5-폴드 교차 검증이 된다.
    • 물론 많이 쪼갤수록 검승 세트 크기 또한 줄어든다. 다만, 각 부분에서의 검증 점수를 평균하기 때문에 안정된 점수를 받을 수 있다.
    • 5-폴드 교차 검증일 때, 훈련세트(전체 데이터의 80%) =
      • 훈련세트 + 훈련세트 + 훈련세트 + 훈련세트 + 검증세트,
      • 훈련세트 + 훈련세트 + 훈련세트 + 검증세트 + 훈련세트,
      • 훈련세트 + 훈련세트 + 검증세트 + 훈련세트 + 훈련세트,
      • 훈련세트 + 검증세트 + 훈련세트 + 훈련세트 + 훈련세트,
      • 검증세트 + 훈련세트 + 훈련세트 + 훈련세트 + 훈련세트 
    • 교차 검증 : cross_validate()
      • 5-폴드 교차 검증을 수행하며, 훈련시간과 검증시간, 검증 폴드 점수(fit_time, score_time, train_target)를 반환한다. 
      •  train_target의 배열값을 평균해서  평균 교차 검증 점수를 구할 수 있다.
    • 분할기(splitter) 지정 : StratifiedKFold() 혹은 KFold()
      • train_test_split()으로 훈련세트와 테스트세트를 나눴다면, splitter을 지정할 필요 없다. 다만, 교차검증을 할 때, 훈련세트를 섞고 싶다면, 분할기를 지정해야 한다.
      • 분류 모델은 StratifiedFold, 회귀 모델은 KFold를 보통 지정한다.
from sklearn.model_selection import cross_validate
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())

 

그리드서치와 랜덤서치

1. 그리드 서치

  • 하이퍼 파라미터 탐색과 교차 검증 한 번에 수행 → 각 하이퍼 파라미터의 최적값을 찾는 것을 자동화해 준다.
  • n_jobs : 병렬 실행하는 데 사용할 CPU 코어수 (-1은 전부)
import numpy as np

params = {
	'min_impurity_decrease' : np.arange(0.0001, 0.001, 0.0001),
 	'max_depth' : range(1, 10, 1),
	'min_samples_split' : range(1, 100, 10)
	}
from sklearn.model_selection import GridSearchCV
gs = GridSearchCV(DecisionTreeClassifier(), params, n_jobs = -1)
gs.fit(train_input, train_target)
gs.best_params_ #파라미터 최적값
np.max(gs.cv_results_['mean_test_score']) #검증 점수 최댓값

 

2. 랜덤 서치

  • 확률분포를 이용하기 때문에, 연속적인 매개변수의 최적값을 찾을 때 유용하다.
  • 그리드 서치에서의 params를 보면, 각 파라미터의 값들을 나열해서 전달하였다. 하지만, 랜덤 서치에서는 확률 분포를 대신 전달해서 샘플링할 수 있게 한다.
  • n_iter : 샘플링 횟수 지정하여 시스템 자원을 상황에 맞게 쓸 수 있다.
from scipy.stats import uniform, randint

params = {
	'min_impurity_decrease' : uniform(0.0001, 0.001),
	'max_depth' : randint(10, 20),
 	'min_samples_split' : randint(2, 20),
 	'min_samples_leaf' : randint(1, 20)
	}
from sklearn.model_selection import RandomizedSearchCV

rs = RandomizedSearchCV(DecisionTreeClassifier(), params, n_iter=100, n_jobs=-1)
rs.fit(train_input, train_target)
rs.best_params_
np.max(rs.cv_results_['mean_test_score'])

 

 

05-3. 트리의 앙상블


앙상블 학습(ensemble learning)

- 비정형 데이터에는 신경망 알고리즘을 사용한다.

  하지만, csv와 같은 정형데이터를 다루는 데에는 앙상블 학습 알고리즘을 사용해 높은 성과를 낼 수 있다.

 

 

Random Forest(랜덤 포레스트)

  • 안정적인 성능을 내는 앙상블 학습 대표 알고리즘
  • 앞서 배운 DecisionTree를 랜덤하게 여러 개 만들어 숲을 만든다.
    • 기본 100개의 결정트리를 아래의 방식으로 훈련한다.  
      1. 훈련 데이터를 부트스트랩 방식으로 샘플링하고 분류해서 각 트리를 위한 훈련 데이터를 만든다.
      2. 분류의 경우, 특성 집합에서 특성을 제곱근만큼 랜덤하게 선택한다. (ex. 9의 특성이 있다면 3개를 선택하는 식)
      3. 단, 회귀의 경우 전체 특성을 사용한다.
      4. 최선의 분할을 선택한다.
    • 분류일 때는, 각 트리마다 클래스별 확률을 평균해서 가장 높은 확률의 클래스를 예측으로 한다.
    • 회귀일 때는, 각 트리의 예측값을 평균한다.
  • 훈련 데이터와 특성을 모두 랜덤하게 선택하기 때문에, overfitting을 막을 수 있고 검증 세트와 테스트 세트에서 안정적인 성능을 얻을 수 있다.
from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs =-1) #RandomForestClassifier : 회귀
rf.fit(train_input, train_target)
print(rf.feature_importances_)
  • rf.feature_importances_를 확인해보면
    • 일부 특성의 특성 중요도가 결정트리에서보다 상승한 것을 볼 수 있다.
    • 이는 랜덤 포레스트가 결정 트리보다 하나의 특성에 과도하게 집중하지 않고 많은 특성이 훈련에 기여할 수 있게 한다는 것을 알 수 있다.
  • 물론, 교차 검증을 통해 검증 점수와 테스트 점수를 확인할 수도 있다.

 

Extra Tree(엑스트라 트리)

  • Random Forest와 매우 비슷하게 동작하지만, 차이점 2개가 있다.
    • 부트스트랩 샘플을 사용하지 않고, 전체 훈련세트를 사용한다.
    • 노드를 분할할 대, 최선의 분할을 선택하는 것이 아니라, 무직위로 분할한다.
  • 랜덤 포레스트보다 무작위성이 좀 더 크기 때문에, 계산 속도가 빠르지만 더 많은 결정트리를 훈련해야 한다.
from sklearn.ensemble import ExtraTreesClassifier
et = ExtraTreesClassifier(n_jobs=-1) #ExtraTreesRegressor : 회귀
et.fit(train_input, train_target)
print(et.feature_importances_)
  • 물론, 교차 검증을 통해 검증 점수와 테스트 점수를 확인할 수도 있다.

 

Gradient Boosting(그레이디언트 부스팅)

  • 경사 하강법을 사용하여 트리를 앙상블에 추가한다.
    • 깊이가 얕은 결정 트리를 사용한다.
    • 깊이가 얕기 때문에, 결정 트리의 개수를 늘려도 과대적합이 잘 일어나지 않는다.
    • 학습률(learning_rate) 매개변수를 이용해 학습률을 증가시키고 트리 수를 늘리면, 성능을 향상시킬 수 있다.
  • 분류 : 로지스틱 손실 함수를 사용
  • 회귀 : 평균 제곱 오차 함수를 사용
from sklearn.ensemble import GradientBoostingClassifier

gb = GradientBoostingClassifier(n_estimators = 500, learning_rate=0.2)
scores = cross_validate(gb, train_input, train_target, return_train_Score = True, n_jobs = -1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))

gb.fit(train_input, train_target)
print(gb.feature_importances_)
  • 물론, feature_importances_를 통해, 특성 중요도를 제공한다.
  • 랜덤 포레스트보다 일부 특성에 더 집중할 수 있다.

 

 


**참고

pandas 데이터프레임 상세정보보기
1. dataFrame.head()
2. dataFrame.info() : 각 열의 데이터 타입과 null값인지 아닌지를 알 수 있다.
3. dataFrame.desrcibe() : mean, std, min, max, median, 상위/하위 25%의 수를 볼 수 있다.
- 모델 파라미터 : 머신러닝 모델이 학습하는 파라미터
- 하이퍼 파라미터 : 우리가 직접 지정하는 것 (ex. max_depth)
부트스트랩 방식
- 샘플 1000개 중 100개의 샘플을 뽑는다면, 샘플을 꺼내고 다시 넣고를 반복해 하나의 샘플을 중복해서 뽑을 수 있게 한다.
- 예를 들어 주머니 내 흰돌 1개 검은돌 1개가 있고 부트스트랩 방식으로 3개를 뽑으면, 흰/흰/흰이라는 샘플이 나올 수 있다.