Post

5.트리 알고리즘

5.트리 알고리즘

5.1 결정 트리

로지스틱 회귀로 와인 분류하기

데이터 준비하기

1
2
3
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine-date')
wine.head()

Alt text

  • 0: 레드 와인 / 1: 화이트 와인(양성 클래스) -> 이진 분류 문제

결측치 확인하기

1
wine.info()

Alt text

총 6497개의 샘플이 있고 4개의 열은 모두 실숫값입니다. Non-Null Count가 모두 6497이므로 누락된 값은 없는 것 같습니다.

🔖 누락된 값이 있으면 어떻게 하나요?

  1. 데이터를 버린다.
  2. 평균값으로 채운다. ❗항상 훈련 세트의 통계 값으로 테스트 세트를 변환해야한다. 즉, 누락된 테스트 세트의 값은 훈련 세트의 평균값으로 채워야 한다.

간단한 통계 확인하기

1
wine.describe()

Alt text

  • 평균, 표준편차, 최소, 최대, 중간값(50%), 1사분위수(25%), 3사분위수(75%)를 알려준다.

❗사분위수는 데이터를 순서대로 4등분 한 값입니다. 예를 들어 2사분위수(중간값)는 데이터를 일렬로 늘어놓았을 때 정중아으이 값입니다. 만약 데이터 개수가 짝수개라 중앙값을 선택할 없다면 가운데 2개 값의 평균을 사용합니다.

훈련 세트와 테스트 세트로 나누기

1
2
3
4
5
6
7
8
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)
# 설정값을 지정하지 않으면 25%를 테스트 세트로 지정합니다.
print(train_input.shape, test_input.shape)
# (5197, 3) (1300, 3)

전처리 하기

1
2
3
4
5
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

로지스틱 회귀 모델 훈련하기

1
2
3
4
5
6
7
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
# 0.7808350971714451
# 0.7776923076923077

🔖 과소 적합을 해결하기 위해?

  1. 규제 매개변수 C의 값을 바꾸자.
  2. solver 매개변수에서 다른 알고리즘을 선택하자.
  3. 다항 특성을 만들어 추가하자.

설명하기 쉬운 모델과 어려운 모델

🔖 계수와 절편 출력

1
2
print(lr.coef_, lr.intercept_)
# [[ 0.51270274  1.6733911  -0.68767781]] [1.81777902]
  • 알코올 도수와 당도가 높을수록 화이트 와인일 가능성이 높고, PH가 높을수록 레드 와인일 가능성이 높은 것 같습니다.
    => 하지만 이 숫자가 어떤 의미인지 설명하긴 어렵다

결정 트리

Alt text

1
2
3
4
5
6
7
8
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state = 42)
# 사이킷런의 결정 트리 알고리즘은 노드에서 최적의 분할을 찾기 전에 특성을 순서를 섞습니다.
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
# 0.996921300750433
# 0.8592307692307692

❗과대 적합!

1
2
3
4
5
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()

Alt text

1
2
3
4
5
6
7
plt.figure(figsize=(10, 7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
# plot_tree() 함수에서 트리의 깊이를 제한해서 출력합니다.
# max_depth 매개변수를 1로 주면 루트 노드를 제외하고 하나의 노드를 더 확장하여 그립니다.
# filled 매개변수에서 클래스에 맞게 노드의 색을 칠할 수 있습니다.
# feature_names 매개변수에는 특성의 이름을 전달할 수 있다.
plt.show()

Alt text

결정 트리 해석

Alt text

🔖 루트 노드

Alt text

  • 루트 노드는 당도가 -0.239 이하인지 질문
    • 당도가 -0.239와 같거나 작으면 왼쪽 가지로 갑니다.(Yes)
    • 그렇지 않으면 오른쪽으로 갑니다.(No)
  • 총 샘플 수 5197개
    • 음성 클래스(레드 와인): 1258개
    • 양성 클래스(화이트 와인): 3939개
      => 이 값이 value에 나타나 있습니다.

🔖 왼쪽 노드

Alt text

  • 당도가 더 낮은지를 물어본다.
    • -0.802와 같거나 낮다면 다시 왼쪽 가지로
    • 그렇지 않으면 오른쪽 가지로
  • 음성 클래스: 1177개
  • 양성 클래스: 1745개
    => 루트 노드보다 양성 클래스, 즉 화이트 와인의 비율이 줄었다.

🔖 오른쪽 노드

Alt text

  • 음성 클래스: 81개
  • 양성 클래스: 2194개
    => 대부분의 화이트 와인이 이 노드로 온 것!
    => filled=True로 지정하면 클래스 마다 색깔을 부여하고, 어떤 클래스의 비율이 높아지면 점점 진한 색으로 표시한다.

❗리프 노드에서 가장 많은 클래스가 예측 클래스가 된다. ❗먄약 결정 트리를 회귀 문제에 적용하면 리프 노드에 도달한 샘플의 타깃을 평균하여 예측값으로 사용한다. 사이킷런의 결정 트리 회귀 모델은 DecisionTreeRegressor 입니다.

지니 불순도

  • gini(지니 불순도)
    • DecisionTreeClassifier 클래스의 criterion 매개변수의 기본값이 ‘gini’입니다.
      • criterion: 노드에서 데이터를 분할할 기준을 정하는 것.

Alt text

🔖 루트 노드 계산

  • 총: 5197개
  • 양성 클래스: 3939개
  • 음성 클래스: 1258개

alt text

🔖 최악의 노드

alt text

🔖 순수 노드 계산

alt text

엔트로피 불순도

  • 엔트로피 불순도
    • 엔트로피 불순도도 노드의 클래스 비율을 사용하지만 지니 불순도처럼 제곱이 아니라 밑이 2인 로그를 사용하여 곱합니다.

정보 이득

결정 트리 모델은 부모 노드와 자식 노드의 불순도 차이(정보 이득)가 가능한 크도록 트리를 성장시킵니다.
이때 지니 불순도를 사용해도 되고 엔트로피 불순도를 사용해도 됩니다.

  1. 먼저 자식 노드의 불순도를 샘플 개수에 비례하여 모두 다 더한다.
  2. 부모 노드의 불순도에서 뺀다.

🔖 예시()

alt text

불순도의 차이(정보 이득)는 다음과 같이 계산합니다.

  1. 지니 불순도

alt text

  1. 엔트로피 불순도

alt text

가지치기

  1. 트리의 최대 깊이를 지정하기.
1
2
3
4
5
6
7
8
9
10
11
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
# 루트 노드 아래로 최대 3개의 노드까지만 성장
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
# 0.8454877814123533
# 0.8415384615384616

plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()

alt text

❗ 특성값의 스케일은 결정 트리 알고리즘에 아무런 영향을 미치지 않습니다. 따라서 표준화 전처리 과정이 필요가 없습니다.

표준화 전처리하기 전에 훈련을 시킨다면?

1
2
3
4
5
6
7
8
9
10
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))
# 0.8454877814123533
# 0.8415384615384616
# 결과가 표준화 했을때와 동일하다!
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()

alt text

상관없이 결과가 동일하다!

특성 중요도

1
2
3
print(dt.feature_importances_)
# [0.12345626 0.86862934 0.0079144 ]
# 당도가 가장 높음.
  • 모두 더하면 1이 된다.
  • 특성 중요도는 각 노드의 정보 이득과 전체 샘플에 대한 비율을 곱한 후 특성별로 더하여 계산한다.
    => (현재 노드의 샘플 비율 x 불순도) - (왼쪽 자식 노드의 샘플 비 x 불순도) - (오른쪽 자식 노드의 샘플 비율 x 불순도)

❗ 특성 중요도를 이용하면 결정 트리 모델을 특성 선택에 활용할 수 있습니다.

정리

키워드

  • 결정 트리: 예/아니오에 대한 질문을 이어나가면서 정답을 찾아 학습하는 알고리즘. 비교적 예측 과정을 이해하기 쉽고 성능도 뛰어나다.
  • 불순도: 결정 트리가 최적의 질문을 찾기 위한 기준입니다. 사이킷런은 지니 불순도와 엔트로피 불순도를 학습합니다.
  • 정보 이득: 부모 노드와 자식 노드의 불순도 차이입니다. 결정 트리 알고리즘은 정보 이득이 최대화되도록 학습합니다.
  • 가지치기: 결정 트리가 과대적합 되지 않도록 성장을 제한하는 방법입니다.
  • 특성 중요도: 결정 트리에 사용된 특성이 불순도를 감소하는데 기여한 정도를 나타내는 값입니다.

핵심 패키지와 함수

  • Pandas
    • info(): 요약된 정보 출력. 인덱스와 칼럼 타입을 출력하고 널이 아닌 값의 개수, 메모리 사용량을 제공. verbose 매개변수의 기본값 True를 False로 바꾸면 각 열에 대한 정보를 출력하지 않습니다.
    • describe(): 열의 통계 값 제공.
      • 수치형: 최소, 최대 등
      • 문자열 같은 객체 타입: 가장 자주 등장하는 값과 횟수
  • scikit-learn
    • DecisionTreeClassifier(): 결정 트리 분류 클래스
      • criterion: 불순도 지정(gini, entropy)
      • splitter: 노드를 분할하는 전략
        • best: 정보 이득이 최대화
        • random: 임의의 노드를 분할
      • max_depth: 트리가 성장할 최대 깊이
        • 기본값은 None. 리프 노드가 순수하거나 min_samples_split보다 샘플 개수가 적을 때까지 성장
      • min_samples_split: 노드를 나누기 위한 최소 샘플 개수
      • max_features: 최적의 분할을 위해 탐색할 특성의 개수
    • plot_tree(): 결정 트리 모델을 시각화
      • max_depth: 매개변수로 나타낼 트리의 깊이 결정
      • feature_names: 특성의 이름 지정
      • filled: True면 타깃값에 따라 노드 안에 색을 채움

5.2 교차 검증과 그리드 서치

검증 세트

alt text

  1. 훈련 세트에서 모델을 훈련하고 검증 세트로 모델을 평가한다.
  2. 테스트하고 싶은 매개변수를 바꿔가며 가장 좋은 모델을 고른다.
  3. 이 매개변수를 사용해 훈련 세트와 검증 세트를 합쳐 전체 훈련 데이터에서 모델을 다시 훈련한다.
  4. 테스트 세트에서 최종 점수를 평가한다.

데이터 불러오기

1
2
3
4
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine-date')
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

훈련 세트와 테스트 세트로 나누기

1
2
3
4
5
6
7
8
from sklearn.model_selection import train_test_split
# 훈련, 테스트
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)
# 훈련, 검증
sub_input, val_input, sub_target, val_target = train_test_split(train_input, train_target, test_size=0.2, random_state=42)

print(sub_input.shape, val_input.shape)
# (4157, 3) (1040, 3) / 원래는 훈련세트가 5197개

모델 훈련 / 평가

1
2
3
4
5
6
7
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)
print(dt.score(sub_input, sub_target))
print(dt.score(val_input, val_target))
# 0.9971133028626413
# 0.864423076923077

교차 검증

  • 교차 검증: 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복합니다. 그다음 이 점수를 평균하여 최종 검증 점수를 얻습니다.

3-폴드 교차 검증

alt text

  • 3-폴드 교차 검증: 훈련 세트를 세 부분으로 나눠서 교차 검증을 수행하는 것. 통칭 k-폴드 교차 검증이라고 하며, 훈련 세트를 몇 부분으로 나누냐에 따라 다르게 불림.

❗ 보통 5-폴드나 10-폴드 교차 검증을 많이 사용합니다. 이렇게 하면 데이터의 80~90%까지 훈련에 사용할 수 있다.

교차 검증 수행하기

  • cross_validate(): 교차 검증 함수
    • 훈련 세트 전체를 전달
  • cross_val_score(): cross_validate() 함수의 결과 중에서 test_score 값만 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
# {'fit_time': array([0.01276922, 0.0118928 , 0.01284957, 0.01299977, 0.01185656]), 'score_time': array([0.00196719, 0.00233841, 0.00209379, 0.00197005, 0.0025394 ]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}

# fit_time, score_time: 각각 모델을 훈련하는 시간과 검증하는 시간
# test_score: 검증 폴드의 점수

import numpy as np
print(np.mean(scores['test_score']))
# 0.855300214703487

# cross_validate()는 훈련 세트를 섞어 폴드를 나누지 않습니다. 교차 검증을 할 때 훈련 세트를 섞으려면 분할기를 지정해야 합니다.

# 분할기: 교차 검증에서 폴드를 어떻게 나눌지 결정해 줍니다.
# cross_validate() 함수는 기본적으로 회귀 모델일 경우 KFold 분할기를 사용하고 분류 모델일 경우 타깃 클래스를 골고루 나누기 위해 stratifiedKFold를 사용합니다.

from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold()) 
# 이 코드와 동일하다.
print(np.mean(scores['test_score']))
# 0.855300214703487

# 훈련 세트 섞은 후 10-폴드 교차 검증 수행
splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
# 0.8574181117533719

하이퍼파라미터 튜닝

  • 모델 파라미터: 머신러닝 모델이 학습하는 파라미터
  • 하이퍼파라미터: 모델이 학습할 수 없어서 사용자가 지정해야만 하는 파라미터

GridSearchCV

  • 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행한다.(별도의 cross_validate() 호출할 필요x)

🔖 첫번째 예시(min_impurity_decrease)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from sklearn.model_selection import GridSearchCV
# 탐색할 매개변수와 탐색할 값의 리스트를 딕셔너리로 만든다.
params = {'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}

# 그리드 서치 객체 만들기
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
# cv 기본값은 5(5-폴드 교차 검증 수행). 값마다 교차 검증 수행. 따라서 5x5=25개의 모델 훈련
# n_jobs가 -1이면 모든 코어를 사용한다는 것.

gs.fit(train_input, train_target)

# 검증 점수가 가장 높은 모델의 매개변수 조합으로 다시 모델을 훈련해서 best_estimator_에 저장.
dt = gs.best_estimator_
print(dt.score(train_input, train_target))
# 0.9615162593804117

# 최고의 매개변수
print(gs.best_params_)
# {'min_impurity_decrease': 0.0001}

# 평균 점수(by 교차 검증)
print(gs.cv_results_['mean_test_score'])
# [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]

# 넘파이 argmax() 함수를 사용하면 가장 큰 값의 인덱스를 추출할 수 있다. 이 인덱스를 이용해 params 키에 저장된 매개변수를 출력할 수 있다.
best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])
# {'min_impurity_decrease': 0.0001}

🔖 두번째 예시(min_impurity_decrease, max_depth, min_samples,split)

1
2
3
4
5
6
7
8
9
10
11
12
13
params = {'min_impurity_decrease': np.arange(0.0001, 0.001, 0.0001), 'max_depth': range(5, 20, 1), 'min_samples_split': range(2, 100, 10)}
# arange(): 첫 번째 매개변수 값에서 시작하여 두 번째 매개변수에 도달할 때까지 세 번째 매개변수를 계속 더한 배열을 만든다.
# range(): (정수만 사용) 위의 함수와 동일
# 따라서 9 x 10 x 10 = 1350개의 모델을 만든다. 기본 5-폴드 교차를 수행하니 모델의 수는 6750개이다.

gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)

print(gs.best_params_)
# {'max_depth': 14, 'min_impurity_decrease': 0.0004, 'min_samples_split': 12}

print(np.max(gs.cv_results_['mean_test_score']))
# 0.8683865773302731

🔖 과정 정리

  1. 먼저 탐색할 매개변수를 지정한다.
  2. 그다음 훈련 세트에서 그리드 서치를 수행하여 최상의 평균 검증 점수가 나오는 매개변수 조합을 찾습니다. 이 조합은 그리드 서치 객체에 저자됩니다.
  3. 그리드 서치는 최상의 매개변수에서(교차 검증에 사용한 훈련 세트가 아니라) 전체 훈련 세트를 사용해 최종 모델을 훈련합니다. 이 모델도 그리드 서치 객체에 저장됩니다.

랜덤 서치

  • 매개변수의 값이 수치일 때 값의 범위나 간격을 미리 정하기 어려울 수 있다.
  • 많은 매개 변수 조건이 있어 그리드 서치 수행 시간이 오래 걸릴 수 있다.
    => 랜덤 서치

  • 랜덤 서치에는 매개변수 값의 목록을 전달하는 것이 아니라 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달(by 싸이파이)한다.

🔖 숫자 샘플링해보기(scipy 연습)

1
2
3
4
5
6
7
8
9
10
11
12
13
from scipy.stats import uniform, randint
# uniform과 randint 클래스는 모두 주어진 범위에서 고르게 값을 뽑는다. 즉, "균등 분포에서 샘플링한다"
# randint는 정숫값을 뽑고, uniform은 실숫값을 뽑는다.
rgen = randint(0, 10)
rgen.rvs(10)
# array([5, 8, 9, 9, 3, 9, 6, 8, 1, 0])

np.unique(rgen.rvs(1000), return_counts=True)
# (array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),array([ 99,  98, 112,  87, 106,  82,  99,  94, 113, 110]))

ugen = uniform(0, 1)
ugen.rvs(10)
# array([0.27790698, 0.56615871, 0.25830681, 0.20764076, 0.83323613,0.29246415, 0.82531335, 0.65395826, 0.39196671, 0.30915806])

🔖 랜덤 서치 실습

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
params = {'min_impurity_decrease': uniform(0.0001, 0.001),
'max_depth': randint(20, 50),
'min_samples_split': randint(2, 25),
'min_samples_leaf': randint(1, 25)}
# min_samples_leaf: 리프 노드가 되기 위한 최소 샘플의 수. 어떤 노드가 분할하여 만들어질 자식 노드의 샘플 수가 이 값보다 작을 경우 분할하지 않습니다.

from sklearn.model_selection import RandomizedSearchCV
gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42), params, n_iter=100, n_jobs=-1, random_state=42)
gs.fit(train_input, train_target)
# n_iter: 샘플링 횟수

print(gs.best_params_)
# {'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173, 'min_samples_leaf': 7, 'min_samples_split': 13}

print(np.max(gs.cv_results_['mean_test_score']))
# 0.8695428296438884

dt = gs.best_estimator_
print(dt.score(test_input, test_taret))
# 0.86

정리

키워드

  • 검증 세트
  • 교차 검증
  • 그리드 서치
  • 랜덤 서치

핵심 패키지와 함수

  • scikit-learn
    • cross_validate(): 교차 검증을 수행하는 함수.
      • 첫번째 매개변수: 교차 검증을 수행할 모델 객체
      • 두번째와 세번째 매개변수: 특성과 타깃 데이터 전달
      • scoring: 평가 지표 지정
      • cv: 교차 검증 폴드 수나 스플리터 객체 지정
      • n_jobs: CPU 코어 수
      • return_train_score: True로 하면 훈련 세트의 점수도 반환한다. 기본값은 False 이다.
    • GridSearchCV: 교차 검증으로 하이퍼파라미터 탐색을 수행합니다. 최상의 모델을 찾은 후 훈련 세트 전체를 사용해 최종 모델을 훈련합니다.
      • 첫번째 매개변수: 그리드 서치를 수행할 모델 객체 전달.
      • 두번째 매개변수: 매개변수와 값
    • RandomizedSearchCV: 교차 검증으로 랜덤한 하이퍼파라미터 탐색을 수행한다. 최상의 모델을 찾은 후 훈련 세트 전체를 사용해 최종 모델을 훈련
      • 첫번째 매개변수: 그리드 서치를 수행할 모델 객체
      • 두번째 매개변수: 탐색할 모델의 매개변수와 확률 분포 객체

5.3 트리의 앙상블

정형 데이터와 비정형 데이터

  • 정형 데이터: 어떤 구조로 되어있는것 => 앙상블 학습
  • 비정형 데이터: 데이터베이스나 엑셀로 표현하기 어려운 것. 사진, 음악 등등 => 신경망 알고리즘

랜덤 포레스트

  • 랜덤 포레스트: 앙상블 학습의 대표 주자. 결정 트리를 랜덤하게 만들어 결정 트리(나무)의 을 만든다. 그리고 각 결정 트리의 예측을 사용해 최종 예측을 만든다.

alt text

🔖 샘플 뽑는 법

  • 부트스트랩 샘플: 훈련 데이터에서 랜덤하게 중복되어 샘플을 추출하여 훈련 데이터를 만든 것입니다. 훈련 세트의 크기와 같게 만듭니다.(이걸 이용)

alt text

🔖 최선의 분학 찾는 방법

  • 각 노드를 분할할 때 전체 특성 중에서 일부 특성을 무작위로 고른 다음 이 중에서 최선의 분할을 찾는다.
    • RandomForestClassifier: 기본적으로 전체 특성 개수의 제곱근만큼 특성을 선택
    • RandomForestRegressor: 전체 특성 이용

alt text

🔖 예측을 어떻게 할까?

  • 분류: 각 트리의 클래스별 확률을 평균하여 가장 높은 확률을 가진 클래스를 예측으로 삼습니다.
  • 회귀: 각 트리의 예측을 평균합니다.

RandomForestClassifier를 이용해 분류!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
wine = pd.read_csv('https://bit.ly/wine-date')
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
train_input, test_input, train_target, test_target = train_test_split(data ,target, test_size=0.2, random_state=42)

from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(rf, train_input, train_target, return_train_score=True, n_jobs=-1)
# CPU를 모두 이용해 병렬로 교차 검증 수행
# return_train_score=True: 훈련 세트에 대한 점수도 같이 반환합니다. => 과대적합 파악에 유용
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
# 0.9973541965122431 0.8905151032797809

rf.fit(train_input, train_target)
print(rf.feature_importances_)
# [0.23167441 0.50039841 0.26792718]
# 랜덤 포레스트의 특성 중요도는 각 결정 트리의 특성 중요도를 취합한 것.
# 랜덤 포레스트가 특성의 일부를 랜덤하게 선택하여 결정 트리를 훈련하기 때문에 하나의 특성에 과도하게 집중하지 않고 좀 더 많은 특성이 훈련에 기여할 기회를 얻는다. => 과대적합 줄일 수o

rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
# oob_score의 기본은 false입니다.
# oob: 부트스트랩 샘플에 포함되지 않고 남은 샘플.
rf.fit(train_input, train_target)
print(rf.oob_score)
# 0.8934000384837406
# oob로 자체적 평가 => 교차 검증을 대신할 수 있어 훈련 세트에 더 많은 샘플을 이용할 수 있다.

엑스트라 트리

  • 엑스트리 트리: 랜덤 포레스트와 매우 비슷하지만 부트스트랩 샘플을 이용하지는 않습니다. 즉 각 결정 트리를 만들 때 전체 훈련 세트를 이용합니다. 대신 노드를 분할할 때 무작위로 분할합니다. (splitter을 random으로 지정한 것!)

❗ 무작위로 분할하면 성능이 낮아지겠지만 트리를 앙상블 하기에 괜찮다.

1
2
3
4
5
6
7
8
9
10
11
from sklearn.ensemble import ExtraTreesClassifier
et = ExtraTreesClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(et, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
# 0.9974503966084433 0.8887848893166506
# 랜덤 포레스트와 결과 비슷
# but 보통 엑스트라 트리가 무작위성이 좀 더 크리 때문에 랜덤 포레스트보다 더 많은 결정 트리를 훈련해야 합니다.

et.fit(train_input, train_target)
print(et.feature_importances_)
# [0.20183568 0.52242907 0.27573525]

그레이디언트 부스팅

  • 그레이디언트 부스팅: 깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블 하는 방법.
    • 기본: 깊이가 3인 결정 트리 100개 이용
    • 높은 일반화 성능
    • 경사 하강법(모델의 가중치와 절편을 조금씩 바꾸며 낮은 곳으로 내려간다)을 이용해 트리를 앙상블에 추가한다.
      • 분류: 로지스틱 손실 함수
      • 회귀: 평균 제곱 오차 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state=42)
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']))
# 0.8881086892152563 0.8720430147331015

# 학습률을 증가시키고 트리의 개수를 늘리면 성능 향상 가능
gb = GradientBoostingClassifier(n_estimators=500, learning_rate=0.2,random_state=42)
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']))
# 0.9464595437171814 0.8780082549788999

gb.fit(train_input, train_target)
print(gb.feature_importances_)
# [0.15872278 0.68010884 0.16116839]

❗ 그레이디언트 부스팅은 느림!

🔖 매개변수 subsample

  • subsample: 트리 훈련에 사용할 훈련 세트의 비율.
    • 기본: 1
    • 1보다 작으면: 훈련 세트의 일부 이용 => 확률적 경사 하강법, 미니배치 경사 하강법과 비슷

히스토그램 기반 그레이디언트 부스팅

  • 히스토그램 기반 그레이디언트 부스팅: 정형 데이터를 다루는 머신러닝 알고리즘 중에 가장 인기가 높은 알고리즘입니다.
    • 먼저 입력 특성을 256개의 구간으로 나눔
      • 최적의 분할을 빠르게 찾을 수 있다
      • 256개의 구간 중에서 하나를 떼어 놓고 누락된 값을 위해서 사용한다.(입력에 null값이 있어도 전처리할 필요x)
    • 트리의 개수를 지정하는데 n_estimators 대신 부스팅 반복 횟수를 지정하는 max_iter을 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target, return_train_score=True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
# 0.9321723946453317 0.8801241948619236

hgb.fit(train_input, train_target)
print(rf.feature_importances_)
# [0.23167441 0.50039841 0.26792718]

hgb.score(test_input, test_target)
# 0.8723076923076923

🔖 XGBoost

  • tree_method 매개변수를 ‘hist’로 지정하면 히스토그램 기반 그레이디언트 부스팅을 사용할 수 있습니다.
  • 사이킷런의 cross_validate() 함수와 함께 사용할 수도 있습니다.
1
2
3
4
5
from xgboost import XGBClassifier
xgb = XGBClassifier(tree_method='hist', random_state=42)
scores = cross_validate(xgb, train_input, train_target, return_train_score=True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
# 0.9558403027491312 0.8782000074035686

🔖 LightGBM

1
2
3
4
5
from lightgbm import LGBMClassifier
lgb = LGBMClassifier(random_state=42)
scores = cross_validate(lgb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
# 0.935828414851749 0.8801251203079884

정리

키워드

  • 앙상블 학습: 더 좋은 예측 결과를 만들기 위해 여러 개의 모델을 훈련하는 머신러닝 알고리즘
  • 랜덤 포레스트: 결정 트리 기반의 앙상블 학습 방법. 부트스트랩 샘플을 사용하고 랜덤하게 일부 특성을 선택하여 트리를 만듦
  • 엑스트라 트리: 결정 트리를 이용하여 앙상블 모델을 만들지만 부트스트랩 샘플을 사용하지 않습니다. 랜덤하게 노드를 분할해 과대적합을 감소시킵니다.
  • 그레이디언트 부스팅: 결정 트리를 연속적으로 추가하여 손실 함수를 최소화하는 앙상블 방법. 훈련 속도가 느림 => 개선한 것이 히스토그램 기반 그레이디언트 부스팅!

핵심 패키지와 함수

  • scikit-learn
    • RandomForestClassifier
      • n_estimators
      • criterion: gini or entropy
      • max_depth
        • 리프 노드가 순수하거나 min_samples_split보다 샘플 개수가 적을 때까지 성장
      • min_samples_split
      • max_features
      • bootstrap
      • oob_score
      • n_jobs
    • ExtraTreesClassifier
    • GradientBoostingClassifier
      • loss
      • learning_rate
      • n_estimators
      • subsample
      • max_depth
    • HistGradientBoostingClassifier
      • max_iter
      • max_bins: 입력 데이터를 나눌 구간의 개수. 기본값은 255이며 이보다 크게 저장x

❗ 새로 추가되는 매개변수만 추가했으니 안적혀 있는 매개변수 라도 해당 함수에 적용될 수도 있음.

This post is licensed under CC BY 4.0 by the author.

Trending Tags