본문 바로가기
혼공학습단 머신러닝+딥러닝

[4주차] 트리, 교차검증, 하이퍼파라미터 튜닝, 앙상블

by 알래스카코코넛 2024. 1. 24.
반응형

 벌써 혼공학습단도 4주차에 접어들어 반이 지났네요. 머신러닝 지식도 새록새록 잘 돌아오고 있습니다. 저번주 좀 쉬었다고 컨디션도 나아져서 튼튼코코넛으로 바뀌었습니다. 좀 빠릿해진 기분으로 4주차 정리 시작합니다!

Ch.5 트리 알고리즘

5-1. 결정 트리

프로그래밍을 하시는 분이면 결정 트리(Decision Tree)는 다들 단간히 아실거라 믿습니다. 

결정트리 생긴거. 나무 같쥬?

일상 생활에서도 결정 트리는 무의식중에 많이 사용합니다. 실제 합리적인 의사 결정에 자주 쓰이기도 할 만큼, 이 알고리즘을 무에서 유도 창조하는 머신러닝 엔지니어들이 사용하지 않을 리가 없습니다. 이번 챕터는 정의가 어렵지는 않으므로  가볍게 몇 가지 용어만 짚고 가고, 제가 2023년 2학기(3학년 시기) 진행한 간단한 프로젝트를 예시로 같이 보겠습니다. 

먼저 용어 정리:

결정 트리 주어진 질문에 예/아니오로 답하면서 정답을 찾는 알고리즘
노드 훈련 데이터의 특성에 대한 테스트를 표현, 대체로 한 노드당 2개의 가지를 가지고 있다.
불순도  노드가 얼마나 순수한지/순수하지 않은지 알 수 있는 척도. 지니 불순도와 엔트로피 불순도가 있다.
정보 이득 (부모 노드 불순도) - (자식 노드 불순도)로, 정보 이득이 클수록 최적화된 결정 트리로 본다.
가지치기 결정 트리가 지나치게 가지를 많이 뻗어 과대적합되는 것을 방지하기 위한 방법
특성 중요도 특성이 불순도를 감소시키는데 얼마나 기여하였는지를 보여주는 척도 

아! 정말 하나도 뭔지 모르겠다!! 하는건 없는 것 같습니다. 그럼 프로젝트를 보면서 위 용어들이 어떻게 사용되었는지, 결정 트리 알고리즘이 실제 프로젝트에서는 어떻게 적용되는지 알아보겠습니다. 사실 데이터만 바뀌고, 코드는 혼공머신 책이랑 거의 유사하니까 교재만 참고해도 간단한 머신러닝 응용 프로젝트를 할 수 있다는 자신감이 생깁니다. 

프로젝트 소개: 한국정보사회지능원의 AI-hub에 비상업적 용도로 오픈된 누수감지 데이터를 활용하여 누수의 종류를 예측하는 모델을 제작하였습니다. 

 

AI-Hub

[영상이미지] 음식 이미지 및 영양정보 텍스트 #음식종류 # 음식 양 # 칼로리 # 한식 # 이미지 조회수 31,578 관심등록 117 다운수 5,663

www.aihub.or.kr

#leaktype(누수 종류) 예측하는 결정트리를 만들어보자
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

sink = pd.read_csv('/content/1.누수감지데이터-통합(leaks-all).csv')
sink

sink 데이터

# 교재(혼자공부하는 머신러닝+딥러닝) p.278 코드를 참조하여 코드를 구성하였습니다.
data = sink.iloc[:, 6:].to_numpy()
target = sink['leaktype'].to_numpy() #결과로 내고자 하는 값 - 누수 종류
#print(data)
#print(target)
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size = 0.25, random_state = 30)
print(train_input.shape, test_input.shape)
#(58653, 533) (19551, 533)

from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

#참고용 로지스틱 회귀
ss = StandardScaler()
ss.fit(train_input)

#정규화
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target)) #0.6526690876851994
print(lr.score(test_scaled, test_target)) #0.6371541097642064
#점수가 좋지 못하므로 결정트리를 제작하여 분류해본다

먼저 결정 트리를 이용하기 전, 로지스틱 회귀를 이용하여 분류한 결과 훈련 세트의 점수는 0.6526이고, 테스트 세트는 0.6371로 좋지 못한 점수가 도출되었습니다. 따라서 결정 트리 모델을 통하여 성능을 개선해보겠습니다. 

from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier()
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) #훈련 세트의 점수 출력 -> 1.0이 나왔다..
print(dt.score(test_scaled, test_target)) #테스트 세트의 점수 출력 -> 0.824.... 과대적합 발생

훈련 세트의 점수는 1.0, 테스트 세트의 점수는 0.824로 크게 나쁘지는 않으나, 성능이 로지스틱 회귀보다는 확실히 좋아졌습니다. 이제 결정트리가 어떻게 생겼는지 한 번 보겠습니다. 

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize = (15,7))

#feature이 될 이름들 dataframe에서 가져오기
lst = sink.columns[sink.columns.get_loc('0HZ'):sink.columns.get_loc('MAX19') + 1].to_list()
#print(lst)
plot_tree(dt, max_depth = 2, filled = True, feature_names = lst)

plt.show()

상당히 깔끔하고 알아보기 쉽습니다. 이렇게 시각화가 잘 되어서 보기 좋은 게 결정트리의 큰 장점입니다. 이번에는 가지치기를 해보겠습니다. 

#가지치기
dt = DecisionTreeClassifier(max_depth = 3, random_state = 28)
dt.fit(train_scaled, train_target)

print(dt.score(train_scaled, train_target))   #점수가 매우 좋지 않다.
print(dt.score(test_scaled, test_target))
#0.5697406782261777
#0.5713262748708506

#숫자 너무 재밌다!

max_dept를 3으로 설정해서 트리의 최대 깊이가 3이 되면 어떨지 설정했더니, 훈련 세트 점수가 0.5697, 테스트 세트 점수가 0.5713으로 너무 낮은 성능이 도출되었습니다. 이러면 이제 하이퍼파라미터 설정이 잘못 되었구나!하고 실패 보고서에 max_dept= 3을 기록해준 뒤 다른 숫자를 넣어보면 좋은 반성문이 됩니다.

이 코드 전문은 https://colab.research.google.com/drive/1v9InW6eo1fEq1dg2oTXfai7ZBDScJP6B?usp=sharing 에서 확인할 수 있습니다. 이제는 다시 교재의 코드로 돌아오겠습니다. 

 

w7-2.ipynb

Colaboratory notebook

colab.research.google.com

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

지금까지 데이터를 테스트 세트/훈련 세트로 나누어서 작동시켰습니다. 그런데 테스트 세트의 점수에 집중하다 보니, 결국은 테스트 세트에 맞는 모델이 개발됩니다. 무슨 의미인지 좀 헷갈리죠? 예시를 함께 봅시다. 수능 준비를 하기 위해, 수능특강을 열심히 보고 있었습니다. 그런데 수능 특강만 파고, 또 예상 모의고사도 수능 특강을 바탕으로 만들어서 풀고.... 이러다 보면, 결국 수능 특강을 푸는 머리로는 적합하지만 다른 문제집을 풀기에는 부적합한 머리가 됩니다. 테스트 세트와 훈련 세트도 같습니다. 우리가 궁극적으로 잘해야 하는 것은 수능(실전)입니다. 수능 특강(테스트 세트)에 초점을 맞춰서는 안된다는 의미입니다. 그러면 어떻게 하는게 좋을까요? 해결책은 쉽습니다. 바로 수능완성처럼 다른 문제집도 풀어서 좋은 성능을 내면 됩니다. 이렇게, 테스트 세트 외에 성능을 평가하기 위해 만드는 또 다른 세트를 검증 세트(validation set)이라고 합니다. 

검증 세트를 만드는 방법은 쉽습니다. 우리의 영원한 친구 sklearn을 활용해줍시다.

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)

sub_input, val_input, sub_target, val_target이라는 변수가 새로 생겼습니다. sub_input과 sub_target은 훈련 세트, val_input과 val__target은 검증 세트입니다. 특히, test_size = 0.2로 지정해서 train_input의 약 20%를 검증 세트로 지정하였습니다. 이제 다시 모델의 성능을 검증세트를 이용해서 알아보겠습니다.

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

성능이 괜찮네요. 이제 검증세트에 대해서도 어느 정도 정리가 되었습니다. 

이전 주차에서 하이퍼파라미터에 대해 미션까지 하면서 배웠습니다. 혹시 기억이 안나시는 분들을 위해서 살짝 짚고 가자면, 하이퍼파라미터는 모델이 학습할 수 없어서 사용자가 지정해야만 하는 파라미터였습니다. 모델이 초콜릿을 만드는 공장이라고 치면 어떤 종류의 초콜릿을 만들지, 어떤 모양으로 만들지 등이 하이퍼파라미터라고 비유할 수 있습니다. 실제 코드에서는 클래스 혹은 매서드의 매개변수로 조정할 수 있습니다. 

이번에는 이런 하이퍼파라미터를 조정하는 하이퍼파라미터 튜닝을 정리하겠습니다. 물론 for문을 돌려도 되지만, 우리의 친구이자 구세주 sklearn은 하이퍼파라미터 튜닝에 필요한 도구와 시각화 방법까지도 미리 마련해두었습니다. 네, 다시 돌아온 sklearn 찬양 타임입니다. 

참고로 짤 오른쪽 부분은 제가 만들었습니다

sklearn의 패키지 중 그리드서치(Grid Search)를 이용하면 하이퍼파라미터 튜닝을 쉽게 할 수 있습니다. 게다가 앞서 언급했던 교차 검증도 해주네요. 1+1이라니 혜자가 아닐 수 없습니다. 

from sklearn.model_selection import GridSearchCV

#0.0001에서 0.0005까지 증가하는 5개의 값을 하이퍼파라미터로 적용시켜보겠다. 
params = {'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}

#그리드 서치 객체 gs 생성 
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)

#검증 점수가 가장 높은 모델을 dt에 저장
dt = gs.best_estimator_
print(dt.score(train_input, train_target))
#0.9615162593804117

print(gs.best_params_)
#{'min_impurity_decrease': 0.0001} - 0.0001에서 가장 좋은 값이 도출되었다. 

print(gs.cv_results_['mean_test_score'])  #하이퍼파라미터별 성능 출력 
#[0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]

코드로도 알기 쉽고 직관적입니다. 

5-3. 트리의 앙상블 

앙상블이 뭘까요? 함께, 동시에라는 뜻으로 둘 이상의 연주자나 단원 등이 같이 무용이나 음악 등을 하는 것을 말합니다. 앙스타 아닙니다. 굳이 따지자면 같이 하니까 맞긴 한데... 머신러닝에서도 비슷한 느낌입니다. 앙상블의 머신러닝적 정의는 여러 가설의 집합을 선택하고, 그 예측 결과를 결합하는 것을 말합니다. 대표적인 예시로 랜덤 포레스트, 엑스트라 트리, 그레디언트 부스팅 등이 있으며, 특히 그레디언트 부스팅에는 XGBoost와 LightGBM이 있습니다. 이 부분은 하나하나 깊게 짚으면 별개로 글을 하나씩 써야 할 정도의 분량이라 이번에는 가볍게 짚고 넘어가겠습니다. 이 중 랜덤포레스트가 가장 메인 앙상블 기법이니, 시간이 없거나 영 어려운 분이면 랜덤 포레스트를 중점적으로 보고, 나머지는 발전된 트리 계열 알고리즘이구나~만 아셔도 무방합니다.

랜덤 포레스트:

1. 투표(voting) 기반 분류기: soft voting과 hard voting이 있습니다. soft voting은 간접 투표, hard voting은 직접 투표방식이라고 생각하면 쉽습니다. 

2. Bagging: 부트스트랩 샘플을 사용하며, voting과는 달리 서로 다른 알고리즘 모델을 사용합니다. 

3. Boosting: 가중치를 사용하는 알고리즘입니다. 

더보기

추가적으로 알고싶으신 분들을 위한 발전된 트리 계열 알고리즘 간략한 정리:

 

1. Extra trees: 무작위로 노드를 분할하여 Bias와 Variance가 낮습니다. 암기를 하시겠다면 드라마에서 엑스트라는 원래 무작위로 뽑힌다~로 암기하시면 됩니다.

2. Gradient Boosting: Gradient Descent를 사용하여 얕은 트리를 연속적으로 증가시키는 알고리즘입니다.

3. XG Boost: Data Sorting을 데이터를 받을 때 해서 cost가 절약되는 효과가 있습니다.

4. LightGBM: 대용량 데이터 처리에 용이합니다. 

정의를 알았으니 진짜 마지막으로 코드를 알아보...려 했으나 이게 선택미션이네요. 1+1인 기분으로 앙상블 기법 코드를 살펴보겠습니다.

참고로 맨 위 결정 트리 파트에서 코드 마지막 부분에 히스토그램 기반 그레디언트 부스팅 코드로 결정 트리보다 더 좋은 성능을 보인 부분이 있으니까 참고하면 좋습니다. 혹시 귀찮은 여러분을 위해 이 코드도 바로 밑에 올려둡니다. 친절한 주인장. 

#Histogram-based Gradient Boosting
from sklearn.model_selection import cross_validate
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier

hgb = HistGradientBoostingClassifier(random_state = 28) #hgb 객체 생성
scores = cross_validate(hgb, train_input, train_target, return_train_score = True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))  #scores의 평균값 출력 - 성능이 결정트리일 때보다 훨씬 좋아졌다!
#0.9610335377796726 0.9025454870232201

순서가 좀 이상하긴 하나 마지막으로 기본 미션을 하고 이번주도 마무리입니다. 

기본미션

자 이렇게 4주차도 끝났습니다. 어느새 2/3이 완료네요! 

반응형