[53일차] 트리 알고리즘

2023. 3. 9. 16:24카테고리 없음

1. 결정트리

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

💻 데이터 컴온

import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')

wine.head()

class 0➡레드 와인, 1➡화이트 와인

 

📍 info() : 데이터프레임 전체를 돌아다니면서 해당 요약값을 만들어줌 (pandas)

wine.info()

>>> <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 6497 entries, 0 to 6496
    Data columns (total 4 columns):
     #   Column   Non-Null Count  Dtype  
    ---  ------   --------------  -----  
     0   alcohol  6497 non-null   float64
     1   sugar    6497 non-null   float64
     2   pH       6497 non-null   float64
     3   class    6497 non-null   float64
    dtypes: float64(4)
    memory usage: 203.2 KB

 

📍 describe() : 기초 통계 요약값을 출력해줌 ➡ 크기의 편차 확인 가능해짐 ➡ 표준화시킬건지 그냥 진행할건지 결정

wine.describe()

👨🏻‍💻 결정 트리는 사실 표준화 할 필요 없어!

 

💻 데이터 넘파이 배열로 바꾸고 훈련 세트와 테스트 세트로 분류

data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

from sklearn.model_selection import train_test_splittrain_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)

🔎 train_test_split() 함수는 설정값을 지정하지 않으면 25%를 테스트 세트로 지정됨

      20%만 테스트 세트로 사용하길 원해서 test_size를 0.2로 지정

 

💻 만들어진 훈련 세트와 테스트 세트의 크기 확인

print(train_input.shape, test_input.shape)
>>> (5197, 3) (1300, 3)

🔎 실제로 80%에 해당하는 5197개가 train_input에, 20%에 해당하는 1300개는 test_input에 속함을 확인할 수 있다.

 

💻 표준화 작업 : StandardScaler 클래스를 사용해 데이터 전처리

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

💻 표준화된 데이터로 로지스틱 회귀 모델 훈련

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

🔎 훈련 세트와 테스트 세트의 score이 모두 낮으니 과소적합 

 

💻 로지스틱 회귀가 학습한 계수와 절편 출력

print(lr.coef_, lr.intercept_)
>>> [[ 0.51270274  1.6733911  -0.68767781]] [1.81777902]

 

2. 결정 트리

📍 DecisionTreeClassifier : 사이킷런의 결정 트리 클래스

💻 표준화된 데이터 훈련 및 평가

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

🔎 훈련 데이터에 대한 score 높은데, 그에 비해 낮은 테스트 데이터의 score ➡ 과대적합

 

📍 plot_tree() 함수 : 결정 트리를 이해하기 쉬운 트리 그림으로 출력, 사이킷런이 제공해주는함수

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree

plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()

🔎 맨 위의 노드는 root node, 맨 아래의 노드는 leaf node 라고 부른다.

 

📍 max_depth : 루트 노드를 제외하고 몇 개의 노드를 보여줄지 지정

    filled : True로 설정 시, 노드 색칠

    feature_names : 특성의 이름 전달, 생략 시 임의의 변수값을 지정됨

plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar','pH'])
plt.show()

📌 지니 불순도 = 1 - (음성 클래스 비율² + 양성 클래스 비율²

 

💻 최대 3개의 노드까지만 가지치기

dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_scaled, train_target)

print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
>>> 0.8454877814123533
>>> 0.8415384615384616

🔎 훈련 세트의 score이 변화했지만, 테스트 세트의 성능은 그대로

 

💻 트리 그래프

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

💻 전처리하기 전 데이터로 훈련 및 평가

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()

🔎 표준화를 거치지 않았기 때문에, 기준값을 이해하기 편하다.

 

📍 feture_importances_ 속성 : 특정 중요도

print(dt.feature_importances_)
>>> [0.12345626 0.86862934 0.0079144 ]

🔎 2번째 특성인 당도가 제일 중요하고, 그 다음은 1번째 특성인 알코올, 마지막으로 pH값이 중요함을 확인할 수 있다.

 

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

1. 검증 세트

💻데이터 읽은 후 타깃 데이터 지정

import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')

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)

 

💻 훈련 세트와 검증 세트 생성

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)

 

💻 훈련 세트와 검증 세트 훈련 및 평가

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

 

2. 교차 검증 cross validation

📌 보통 5-폴드 교차 검증이나 10-폴드 교차 검증 많이 사용

📍 cross_validate() : 교차 검증 함수. 결과값이 딕셔너리 형태로 만들어짐

from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)

print(scores)
>>> {'fit_time': array([0.00698662, 0.00614285, 0.00613952, 0.00655127, 0.00845551]), 
     'score_time': array([0.00071287, 0.00071335, 0.00070405, 0.00131798, 0.00080347]), 
     'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}

🔎 처음 2개의 키는 각각 모델을 훈련하는 시간과 검증하는 시간으로 각 키마다 5개의 숫자가 담겨 있다.

 

💻 test_score 키에 담긴 5개의 점수를 평균하여 얻는 교차 검증의 최종 점수

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

 

📍 분할기 splitter : 교차 검증을 할 때, 훈련 세트를 섞기 위해 지정하는 것

from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score']))
>>> 0.855300214703487

🔎 분류 모델인 경우 StratifiedKFold, 회귀 모델인 경우에는 KFold 사용

 

💻 훈련 세트를 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
# 이렇게 한 번에 할 수 있어욥
dt1 = DecisionTreeClassifier()

splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
scores = cross_validate(dt1, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
>>> 0.8587672298799467

🔎 훈련을 거치지 않은 데이터를 넣어도 됨!

 

3. 하이퍼파라미터 튜닝

📌 하이퍼파라미터 : 머신러닝 모델이 학습하는 파라미터

 

📍 GridSearchCV : 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행하는 사이킷런의 클래스

💻 탐색할 매개변수와 탐색할 값의 리스트 딕셔너리로 만들기

from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}

🔎 params는 딕셔너리 형태여야 함.

     키는 특성이어햐고, 문자열로 표기해야 함.

     값은 리스트 형태여야한다.

 

💻 그리드 서치 객체 생성

gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)

🔎 n_jobs 매개변수는 병령 실행에 사용할 CPU 코어 수를 지정. -1로 기본값을 지정하면, 시스템이 있는 모든 코어를 사용함

 

💻 훈련

gs.fit(train_input, train_target)

 

📍 best_estimator_ : 최적화된 훈련 모델이 저장되어 있음 (결정트리모델)

dt = gs.best_estimator_
print(dt.score(train_input, train_target))
>>> 0.9615162593804117

 

📍 best_params_ : 최적의 매개변수 저장

print(gs.best_params_)
>>> {'min_impurity_decrease': 0.0001}

 

📍 mean_test_score : 5번의 교차 검증으로 얻은 점수

print(gs.cv_results_['mean_test_score'])
>>> [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]

 

📍 argmax : 가장 큰 값의 인덱스 추출 (넘파이꺼)

best_index = np.argmax(gs.cv_results_['mean_test_score'])

print(gs.cv_results_['params'][best_index])
>>> {'min_impurity_decrease': 0.0001}

📌과정 정리

  • 모델 선택
  • 탐색할 매개변수 지정
  • 훈련 세트에서 그리드 서치 수행 후 최상의 평균 검증 점수가 나오는 매개변수 조합 찾기
    ➡ 이 조합은 그리드 서치 객체에 저장됨
  • 그리드 서치는 최상의 매개변수에서 (교차 검증에 사용한 훈련 세트가 아니라) 전체 훈련 세트를 사용해 최종 모델 훈련 ➡ 이 모델도 그리드 서치 객체에 저장

💻 복잡한 매개변수 조합 탐색

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)}

 

💻 그리드 서치 진행

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

🔎 앞에서 설정한 매개변수의 간격을 더 좁거나 넓은 간격으로 시도할 수 있는 방법은 없을까?

 

4. 랜덤 서치

📌 매개변수의 값이 수치일 때의 단점

  • 값의 범위나 간격을 미리 정하기 어려움
  • 너무 많은 매개 변수 조건이 있어서 그리드 서치 수행 시간이 긺

 📌 랜덤 서치
매개변수 값의 목록을 전달하는 것이 아니라 매개변수를 샘플링 할 수 있는 확률 분포 객체 전달

 

💻 싸이파이의 확률 분포 클래스 임포트

from scipy.stats import uniform, randint

🔎 uniform은 실수값을 뽑고, randint는 정숫값을 뽑음

 

💻 0에서 10 사이의 범위를 갖는 randint 객체를 만들기
➡ 객체로 생성되기 때문에 print로 볼 수 없음
.rvs()로 호출해야 함

rgen = randint(0, 10)
rgen.rvs(10) # 10개 뽑아쥬
>>> array([6, 1, 0, 1, 2, 4, 7, 0, 3, 8])

 

💻 1000개 샘플링 후, 각 숫자 개수 세기

np.unique(rgen.rvs(1000), return_counts=True)
>>> (array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
     array([ 90, 111, 103,  80, 104,  94,  89, 110, 103, 116]))

🔎 return_counts= 를 통해 각 숫자의 개수를 셌다.

 

💻 0~1 사이에서 10개의 실수 추출

ugen = uniform(0,1)
ugen.rvs(10)
>>> array([0.07479982, 0.53667649, 0.27615805, 0.03354079, 0.48866032,
           0.74964592, 0.53307528, 0.98201321, 0.81466177, 0.87383583])

 

💻 탐색할 매개변수 딕셔너리 생성

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)

 

💻 최적의 매개변수 조합 출력

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

 

💻 최고의 교차 검증 score

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

 

💻 테스트 세트의 성능 확인

dt = gs.best_estimator_
print(dt.score(test_input, test_target))
>>> 0.86

 

3. 트리의 앙상블

1. 정형 데이터와 비정형 데이터

📌 정형 데이터 : 형식이 정해져 있다. 표 같은 것

      비정형 데이터 : 데이터베이스나 엑셀 같이 정형화 되어 있지 않은 것

👨🏻‍💻 몽고DB, 마리아 DB는 마음만 먹으면 호딱 배울 수 있어

📌 앙상블 학습 : 정형 데이터를 다루는 데 가장 뛰어난 성과를 내는 알고리즘

👨🏻‍💻 딥러닝은 비정형 데이터를 다루는 데 도가 터 있어

 

2. 랜덤 포레스트

📌 랜덤 포레스트 : 결정 트리의 예측을 통해 최종 예측을 만든다. (숲 안의 트리)

📍 RandomForestClassifier : 기본저긍로 전체 특성 개수의 제곱근만큼의 특성 선택

 

💻 판다스로 와인 데이터셋을 불러오고, 훈련 세트와 테스트 세트 나누기

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
wine = pd.read_csv('https://bit.ly/wine_csv_data')
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)

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]

 

🍯 누군가에겐 재밌는 기능

RandomForestClassifier은 자체적으로 모델 평가하는 점수를 얻을 수 있다. 

랜덤 포레스트는 훈련 세트에서 중복을 허용하여, 부트 스트랩 샘플을 만들어 결정 트리를 훈련하는데..!

이 때 부트 스트랩 샘플에 포함되지 않고 남는 샘플, OOB 샘플이 있다.

이 남는 샘플을 사용하여 부트스트랩 샘플를 검증 세트처럼, 부트스트랩 샘플로 훈련한 결정 트리를 평가하는 데 사용할 수 있다!

rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_)
>>> 0.8934000384837406

 

3. 엑스트라 트리

📌 엑스트라 트리

  • 결정 트리가 제공하는 대부분의 매개변수 지원
  • 전체 특성 중, 일부 특성을 랜덤하게 선택하여 노드 분할하는 데 사용
  • 랜덤 포레스트와의 차이점
    • 부트스트랩 샘플을 사용하지 않음
      ➡ 각 결정 트리를 만들 때 전체 훈련 세트 사용
      ➡ 성능은 낮아지겠지만, 많은 트리를 앙상블 하기 때문에 과대적합 막고 검증 세트의 점수 높일 수 있음
  • ExtraTreesClassfier : 사이킷런에서 제공하는 엑스트라 트리
    ExtraTreesRegressor 클래스 : 엑스트라 트리의 회귀 버전
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

🔎 랜덤 포레스트와 비슷한 결과.

      보통 엑스트라 트리가 무작위성이 좀 더 크기 때문에 랜덤 포레스트보다 더 많은 결정 트리를 훈련해야 한다.

      하지만 랜덤하게 노드를 분할하기 때문에 빠른 계산 속도가 엑스트라 트리의 장점

 

💻 엑스트라 트리의 중요도

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

🔎 순서는 [알코올 도수, 당도, pH].
       엑스트라 트리도 결정 트리보다 당도에 대한 의존성이 작음

 

4. 그레이디언트 부스팅

📌 그레이디언트 부스팅 

  • 깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블
  • 깊이가 얕은 결정 트리 사용
    • 과대적합에 강함
    • 일반적으로 높은 일반화 성능 기대 가능

💻 GradientBoostring Classifier를 사용해 와인 데이터셋의 교차 검증 점수 확인

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

 

💻 학습률 500으로 증가

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

🔎 결정 트리 개수를 5배나 늘렸는데도, 과대적합을 작 억제한다. 

 

💻 특성 중요도 확인

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

🔎 랜덤 포레스트보다 일부 특성(당도)에 더 집중

 

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

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

  • 정형 데이터를 다루는 머신러닝 알고리즘 중에 가장 인기가 높은 알고리즘
  • 입력 특성을 256개 구간으로 나눔
    ➡ 노드를 분할할 때 최적의 분할을 매우 빠르게 찾을 수 있음
  • 256개의 구간 중에서 하나를 떼어 놓고 누락된 값을 위해서 사용
    ➡ 입력에 누락된 특성이 있더라도 이를 따로 전처리할 필요 없음

💻 적용

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

🔎 과대 적합을 잘 억제하면서 그레이디언트 부스팅보다 조금 더 높은 성능 제공..
      성능의 판단 기준은 훈련 데이터와 테스트 데이터 평가 결과의 차인 듯?

 

📍 permutation_importance() 함수 : 히스토그램 기반 그레이디언트 부스팅의 특성 중요도 계산

from sklearn.inspection import permutation_importance

hgb.fit(train_input, train_target)
result = permutation_importance(hgb, train_input, train_target, n_repeats=10,
                                random_state=42, n_jobs=-1)
print(result.importances_mean)
>>> [0.08876275 0.23438522 0.08027708]

 

💻 테스트 세트에서 특성 중요도

result = permutation_importance(hgb, test_input, test_target, n_repeats=10,
                                random_state=42, n_jobs=-1)
print(result.importances_mean)
>>> [0.05969231 0.20238462 0.049     ]

 

📍 HistGradientBoostingClassifier : 테스트 세트에서의 성능 확인

hgb.score(test_input, test_target)
>>> 0.8723076923076923

 

📍 XGBoost
: 그레이디언트 부스팅 알고리즘을 구현한 라이브러리 

from xgboost import XGBClassifier

xgb = XGBClassifier(tree_method='hist', random_state=42)	# 히스토그램기반으로 설정 가능
scores = cross_validate(xgb, train_input, train_target, return_train_score=True, n_jobs=-1)

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

 

📍 LightGBM
: 그레이디언트 부스팅 알고리즘을 구현한 라이브러리

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.9338079582727165 0.8789710890649293