앙상블 :  프랑스어로 '함께, 동시에, 한꺼번에, 협력하여' 등을 의미하는 부사

1. 앙상블 학습이란?

앙상블 학습(Ensemble Learning)은 여러 개의 분류기를 생성하고, 그 예측을 결합함으로써 보다 정확한 예측을 도출하는 기법을 말합니다.

강력한 하나의 모델을 사용하는대신 보다 약한 모델 여러개를 조합하여 더 정확한 예측에 도움을 주는 방식입니다.

현실세계로 예를 들면, 어려운 문제를 해결하는데 한 명의 전문가보다 여러명의 집단지성을 이용하여 문제를 해결하는 방식을 앙상블 기법이라 할 수 있습니다.

 

2. 앙상블 학습 유형

앙상블 학습은 일반적으로 보팅(Voting), 배깅(Bagging), 부스팅(Boosting) 세 가지의 유형으로 나눌 수 있습니다.

  • 보팅(Voting)
    • 여러 개의 분류기가 투표를 통해 최종 예측 결과를 결정하는 방식
    • 서로 다른 알고리즘을 여러 개 결합하여 사용
    • 보팅 방식
      • 하드 보팅(Hard Voting)
        • 다수의 분류기가 예측한 결과값을 최종 결과로 선정
      • 소프트 보팅(Soft Voting)
        • 모든 분류기가 예측한 레이블 값의 결정 확률 평균을 구한 뒤 가장 확률이 높은 레이블 값을 최종 결과로 선정

  • 배깅(Bootstrap AGGregatING, Bagging)
    • 데이터 샘플링(Bootstrap) 을 통해 모델을 학습시키고 결과를 집계(Aggregating) 하는 방법
    • 모두 같은 유형의 알고리즘 기반의 분류기를 사용
    • 데이터 분할 시 중복을 허용
    • Categorical Data : 다수결 투표 방식으로 결과 집계
    • Continuous Data : 평균값 집계
    • 과적합(Overfitting) 방지에 효과적
    • 대표적인 배깅 방식 : 랜덤 포레스트 알고리즘

 

  • 부스팅(Boosting)
    • 여러개의 분류기가 순차적으로 학습을 수행
    • 이전 분류기가 예측이 틀린 데이터에 대해서 올바르게 예측할 수 있도록 다음 분류기에게 가중치(weight)를 부여하면서 학습과 예측을 진행
    • 계속하여 분류기에게 가중치를 부스팅하며 학습을 진행하기에 부스팅 방식이라고 불림
    • 예측 성능이 뛰어나 앙상블 학습을 주도
    • 대표적인 부스팅 모듈 – XGBoost, LightGBM, SKlearn
    • 보통 부스팅 방식은 배깅에 비해 성능이 좋지만, 속도가 느리고 과적합이 발생할 가능성이 존재하므로 상황에 따라 적절하게 사용해야 함.

 

정형 데이터 / 비정형 데이터

여태까지 사용했던 데이터들 (도미/빙어/와인 등..)은 길이,무게,높이 등 csv파일에 가지런한 데이터로 되어있었다. 이런 데이터들을 정형 데이터라고 부른다.[어떠한 구조로 되어있다는 뜻] CSV / XLSX 등에 저장하기 쉽다. 반대로 비정형 데이터는 책과 같은 텍스트 데이터, 사진 , 음악, 영상 등이 있다.

 

정형 데이터들을 다루는 데 가장 뛰어난 성과를 내는 알고리즘이 앙상블 학습 이라고한다.

그렇다면 비정형 데이터는 어떤 알고리즘을 사용해야할까? (+어떻게 정리?) > 7장

 

랜덤 포레스트 알고리즘

랜덤 포레스트는 앙상블 학습의 대표격으로, 안정적 성능을 자랑한다.

좋은 이유 ==>  1. 배깅의 이점을 살리고, 2. 변수를 랜덤으로 선택하는 과정을 추가함으로 개별나무들의 상관성을 줄여 예                                측력이 좋기 때문

1 : 배깅 : 데이터 샘플링(Bootstrap) 을 통해 모델을 학습시키고 결과를 집계(Aggregating) 하는 방법

2 : 변수를 랜덤 으로 선택

 

랜덤 포레스트는 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만들고, 각 트리의 예측을 사용해 최종 예측을 만듦.

 

  • 트리의 훈련 데이터를 랜덤하게 설정 : 부트스트랩 샘플 (노드 분할시 계속 전체 무작위로)

예측력이  좋은 이유 : 다른 알고리즘과 달리 변수들의 상관관계 확연히 줄어듦.개별 나무 분리할 때마다 계속해서 랜덤한 데이터가 뽑히므로!

 

와인 데이터셋으로 해보자

import numpy as np
import pandas as pd
from sklearn.model_selection 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)

 

cross_validate()으로 교차검증을 해보자!랜덤포레스트 기본 결정트리 개수 = 100개n_jobs = -1 (오래걸리니까?)

return_train_score = True : 검증점수 뿐만아니라 훈련 세트에 대한 점수도 같이 반환한다

(과대 /과소 파악하는데 쉬우므로)

from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = corss_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

훈련세트에 과대적합

                                                            도수  /   당도      /      pH

특성 중요도 < 단순 결정 트리에서는 0.123 /    0.868    /    0.00791  > 였다.!

rf.fit(train_input, train_target)
print(rf.feature_importances_)

[0.23167441 0.50039841 0.26792718]

 랜덤 포레스트 알고리즘에서의 특성 중요도는 당도가 줄어들고 도수 pH가 모두 증가

        >>>랜덤하게 선택하여 개별 나무들의 상관성이 줄어들었기 때문 ( 예측력 상승)<<<

 

 

OOB 샘플 (out of bag) 샘플

말그대로 사용되지 않고 남은 샘플을 의미,

이 샘플들을 이용해서 부트스트랩 샘플로 훈련한 트리를 평가할 수 있다!! ( 검증 세트의 역할과 비슷)

 

rf = RandomForestTreeClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score)

0.8934000384837406

 

 

엑스트라 트리

엑스트라 트리는 랜덤 포레스트와 비슷하게 동작한다.

차이점: 부트스트랩 샘플 사용 X , 결정트리를 만들 때 전체 훈련 세트를 사용한다. 대신 노드를 분할할 때 가장 좋은 분할을 찾는 것이 아니라 무작위로 분할한다. 기존의 결정 트리에서 splitter='random' 사용한것과 같.

 

성능은 낮아지지만 검증 세트의 점수를 높일 수 있다.(왜?) + 속도 빠르다 ( 기준이 랜덤) +

 

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_scores']), np.mean(scores['test_score']))

0.9974503966084433 0.8887848893166506

비슷하다 (특성 많지않아서)  특성 중요도를 살펴보자.

ef.fit(train_input, train_target)
print(ef.feature_importances_)

[0.20183568 0.52242907 0.27573525]

오 비슷하다.

 

 

다음은 랜덤포레스트 / 엑스트라 트리와는 다른 앙상블 학습을 알아보자.

 

그레이디언트 부스팅

그레디언트 부스팅(Gradient boosting)

  • 깊이가 얕은 결정 트리 ( 이전 트리의 오차를 보완해 나가는 방식)
  • 경사하강법을 사용
  • 분류- 로지스틱 손실 함수를 사용  /  회귀 - 평균 제곱 오차 함수를 사용

( 경사하강법 조금씩 이동 ) <=> ( 그레디언트 - 길이 얕은 트리)

https://www.youtube.com/watch?v=3CC4N4z3GJc

그레디언트 부스팅
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_scores']))

0.8881086892152563 0.8720430147331015

굿! 개수를  더 높여보자 (개수 500)

from sklearn.ensemble import GradientBoostingClassifier
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_scores']))

0.9464595437171814 0.8780082549788999

특성 중요도

gb.fit(train_input,train_target)
print(gb.feature_importances_)

[0.15872278 0.68010884 0.16116839]

 

 

+새로운 매개변수 subsample : 트리 훈련에 사용할 훈련 세트 비율을 정함 (default=1 [전체])

subsample을 낮추면????   <<< 미니배치 경사 하강법과 비슷함 >>>

 

그레디언트 부스팅은 랜덤 포레스트보다 더 높은 성능! 하지만 속도 느리고 과적합 가능성 높다.

 

그레디언트 부스팅의 속도와 성능을 개선한 모델

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

정형 데이터를 다루는 머신러닝 알고리즘 중 가장 인기가 높은 알고리즘이래.

 

입력 특성을 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

과대적합 더 억제하면서 그레디언트부스팅보다 성능이 좋다.

 

여기서 특성중요도는 feature_importnaces_가 아니라 permutation_importance 라고 한다.

from sklearn.inspection import permutatin_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]

 

n_repeats는 랜덤하게 섞을 횟수 (default=5)

 

성능을 확인해보자.

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)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.8824322471423747 0.8726214185237284

 

 

 

 

여태까지 배웠던 최근접 이웃, 로지스틱 회귀, SGD, 트리 등 다양한 머신러닝 모델들이 있었지만,

모든 모델들은 결국 실전에 투입시키기 위해 사용한다. 따라서 성능을 높이기 위해 반복적으로 테스트 세트를 활용하여 점수를 높여가야하는데, 이는 목적과 다르게 모델이 테스트 세트에 더 적합해지는 불상사가 생길 수 있다. 

이를 해결하기 위해 검증 세트를 활용해보자.

 

검증 세트 (validation set)

테스트 세트를 마지막에 한 번 사용하기위해 훈련 세트를 또 나누어서 검증세트를 만들고 이 세트를 테스트 세트와 똑같이 사용할 수 있다.

 import pandas as pd
 wine = pd.read_csv("https://bit.ly/wine_csv_data")
 
 # class 열을 타겟으로 설정하고 나머지 열은 특성으로 저장 ( .to_numpy() )

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

# 이제 훈련 테스트 세트 나누기 
from sklearn.model_selection import test_train_split
train_input , test_input, train_target, test_target = test_train_split(data,target,test_size=0.2, random_state=42)

# 검증 세트 만들기
sub_input,val_input, sub_target,val_target = test_train_split(train_input,train_target,test_size=0.2, random_state=42)

print(sub_input.shape, val_input.shape)
(4157, 3) (1040, 3)  # <최종 훈련세트수 4157개, 검증세트 수 1040개>

이제 평가해보자.

from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier( random_state=42 )
df.fit(sub_input, sub_target)

print(dt.score(sub_input,sub_target))
print(dt.score(val_input,val_target))

0.9971133028626413
0.864423076923077

 

교차 검증

 

검증세트를 배워 보았는데, 확실히 성능 향상에 좋은 모델을 만들 수 있을것 같다!

하지만 훈련에 많은 데이터를 활용할수록 좋은데, 검증세트로 인해 훈련세트가 적어지는 단점이 있다.

그렇다고 검증세트를 적게하면 그만큼 점수가 불안정하다.

 

이럴 때 교차검증을 이용하면 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있다.

 

교차검증은 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복한다. 그 다음 이 점수를 평균하여 최종 검증 점수를 얻는다. 위 그림과 같이 3개로 나누는것을 3-폴드 교차 검증이라 한다.

보통 실전에서는 5-폴드나 10-폴드 교차검증을 주로 사용한다. 이렇게 하면 데이터의 8~90%까지 훈련에 사용할 수 있다.

 

사이킷런의 cross_validate() 교차 검증 함수를 사용하여 코드를 실행해보자.

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

{'fit_time': array([0.01978898, 0.01216435, 0.01753545, 0.01066256, 0.03117847]), 'score_time': array([0.00559211, 0.00121975, 0.00127912, 0.00102139, 0.00114012]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}

cross_validate() 는 fit_time , score_time, test_score 각각의 키를 가진 딕셔너리를 반환한다.

fit_time:          모델을 훈련하는 시간

score_time:    모델을 검증하는 시간

test_score:     교차 검증 폴드의 점수들

 

교차 검증의 최종점수는 교차 검증 폴드의 점수들 (test_score) 의 평균이다.

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

0.855300214703487

※ 교차 검증은 데이터를 섞을 필요가 없다. ※

※ 교차 검증을 수행하면 모델에서 얻을 수 있는 최상의 검증 점수를 얻을 수 있다. ※

 

훈련 세트를 섞으려면 분할기를 지정해야 한다.

cross_validate()에서 사용하는 분할기- 회귀일 때는 KFold / 분류일 때는 StrarifiedKFold

여기에서는 분류이므로

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

0.855300214703487

위 두개는 결과가 같다 == cross_validate 할 때는 따로 섞지 않아도 된다! 

 

만약 훈련 세트를 섞고 10-폴드 교차 검증을 하고 싶으면

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

0.8574181117533719

 

하이퍼파라미터 튜닝

모델파라미터      :  모델이 학습하는 파라미터 

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

 

사이킷런과 같은 머신러닝 라이브러리를 사용할 때 하이퍼파라미터는 모두 클래스/메소드의 매개변수로 표현됨

< 하이퍼파라미터 : 머신러닝 클래스/함수의 파라미터(변수)>

 

머신러닝 모델에서 최적의 파라미터를 찾는다고 가정해보자. DecisionTreeClassifier을 예를들면,

최적의 파라미터들을 찾는다고 할 때, 먼저 최적의 max_depth를 찾고, 고정한 후 최적의 min_samples_split를 찾고.. 이런 방법으로 하이퍼파라미터 튜닝을 할 수 있을까?

 

불행하게도 이런식으로 찾을 수 없다. max_depth의 최적값은 min_samples_split 변수의 값이 바뀌면 따라 바뀐다.즉, 변수들을 동시에 바꿔가며 최적의 값을 찾아야 한다.  ( 변수의 값이 많을 수록 매우 복잡해진다.)

 

이렇게 동시에 변수들의 값을 바꿔가며 최적의 값을 찾아주는 기능이 있다.

그리드 서치 (Grid Search)   (하이퍼파라미터 튜닝)

그리드 서치는 위에 설명한 최적값을 교차 검증을 통해 찾아내 준다!! 

따라서 cross_validate()를 따로 호출할 필요가 없다.

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)    (그리드 서치를 통해 25개의 모델을 훈련시킨다)

dt = gs.best_estimator_             # 24개 모델 중 최고의 모델! 이 모델로 학습시키면된다 (dt)
print( dt.score(train_input, train_target))
0.9615162593804117

위 코드를 실행하면 그리드 서치는

prarams 변수 (5개)를 바꿔가며 5번(default=5) 실행한다. 따라서 총 25가지의 경우의 수가 나온다.

그 중 최적의 하이퍼파라미터를 찾으면 best_estimator_를 통해서 그 모델로 학습시키면 될 것 같다.

또한 최적의 매개변수는 best_params_에 저장되어 있다.

 # best_ estimator_

 # best_params_

 

print(dt.best_params_)

{'min_impurity_decrease': 0.0001}

  여기서는 0.0001 ( 첫 번째 변수) 변수가 최적이다.

 

각 매개변수에서 수행한 교차 검증의 평균 점수또한 cv_results_속성의 mean_test_score에 저장되어 있다.

 

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

# gs.cv_results_['mean_test_score']  : 평균 점수 나열

변수 개수가 많거나 찾기 힘들때는 

np.argmax() 메소드를 통해 가장 큰 값의 인덱스를 추출할 수 있다.

 

best_index = np.index[np.argmax(gs.cv_results_['mean_test_score']]  # 인덱스 번호 반환
print(gs.cv_results_['params'][best_index])                         # 키 값을 통해 params value 출력

{'min_impurity_decrease': 0.0001}

정리하자면

  1. 탐색할 매개변수를 지정 ex) max_depth, min_impurity_decrease
  2. 훈련 세트에서 그리드 서치를 하여 최상의 평균 검증 점수가 나오는 매개변수 조합을 찾음.
  3.  최상의 매개변수에서 전체 훈련 세트를 사용해 최종 모델을 훈련

 

더 복잡하게 해보자.       <<min_impurity_decrease = 노드를 분할하기 위한 불순도 감소 최소량을 지정>>

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)
          }
          
  # 총 경우의 수 (?) = 9 x 15 x 10 x 5(default =5) = 6,750 개

최적의 매개변수 조합을 확인해보자.

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

DecisionTreeClassifier(max_depth=14, min_impurity_decrease=0.0004,
                       min_samples_split=12, random_state=42)

max_depth= 14, min_impurity=0.0004, min_samples_split= 12 일 때가 최적이다!

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

 

랜덤 서치

위 코딩처럼 매개 변수처럼 범위나 간격을 일일히 설정해주기 힘들 수 있다. 변수가 많거나 할 수 없는 환경일 때 !

따라서 랜덤 서치를 이용할 수 있다. by using 싸이파이

 

랜덤 서치에는 매개변수 값의 목록이 아닌 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달한다.

싸이파이에서 2개의 확률 분포 클래스를 임포트해보자.

from scipy.stats import uniform, randint

randint = 정수 / uniform = 실수

scipy.stats 에 있는 uniform과 randint 클래스는 모두 주어진 범위에서 고르게 값을 뽑는데, 이를 

"균등 분포에서 샘플링한다"라고 한다.

 

 

rgen = randint(0,10)
rgen.rvs(10)

array([6, 9, 4, 9, 5, 1, 0, 3, 6, 1])

데이터 10개 중에서 10개를 뽑아서 균일하게 뽑혔는지 잘 모른다.

rgen = randint(0,10)
a = rgen.rvs(1000)
np.unique(a, return_counts = True)

(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
 array([ 98, 107, 107,  93,  88,  95,  98, 102, 106, 106]))

대략적으로 골고루 뽑히는것을 확인할 수 있다.

일종의 난수 발생기와 유사하다고 볼 수 있다.

 

이제 균등 분포에서 샘플링을 해보자!

 

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

샘플링 횟수는 사이킷런의 랜덤 서치 클래스인 RandomizedSearchCV의 n_iter = 에 지정 가능

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}

위 params에서 정의된 매개변수 범위에서 총 100번을 샘플링하여 교차 검증을 수행하고 최적의 매개변수 조합을 찾는다.

 

앞서 그리드 서치보다 훨씬 교차 검증 수를 줄이면서 넓은 영역을 효과적으로 탐색할 수 있다.

 

최고의 교차 검증 점수도 확인해보자.

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

0.8695428296438884

최적의 모델은 이미 전체 훈련세트로 훈련되어 best_estimator_ 속성에 저장되어 있다. 

이 모델을 최종 모델로 결정하고 이제 테스트 세트로 성능을 확인해보자!!!!

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

0.86

 

'머신러닝 & 딥러닝 기초 > 5장 - 트리 알고리즘' 카테고리의 다른 글

5 - 3 : 트리의 앙상블  (0) 2023.01.17
5 - 1 결정 트리  (1) 2023.01.09

로지스틱 회귀 모델을 적용하여 이진 분류를 해보자.

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

 

wine.head()

 

데이터를 보면 첫 번째 열부터 차례대로 도수, 당도, pH를 나타내며, 마지막 열인 class는 타깃값이 0이면 레드와인, 1이면 화이트 와인이라고 한다.

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


alcohol	sugar	pH	class
count	6497.000000	6497.000000	6497.000000	6497.000000
mean	10.491801	5.443235	3.218501	0.753886
std	1.192712	4.757804	0.160787	0.430779
min	8.000000	0.600000	2.720000	0.000000
25%	9.500000	1.800000	3.110000	1.000000
50%	10.300000	3.000000	3.210000	1.000000
75%	11.300000	8.100000	3.320000	1.000000
max	14.900000	65.800000	4.010000	1.000000

describe()를 통해 평균 / 표준편차 / 최솟값/ 쿼터값 / 최대값을 볼 수 있습니다.

 

이제 데이터 전처리 ( 표준화 ) 를 해보자.

 

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)

훈련 세트와 테스트 세트의 크기를 확인해 보자.

print(train_input.shape, test_input.shape)

(5197, 3) (1300, 3)

 이제 표준화를 해보자.

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

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() + 학습원리 설명하기 어려움

--결정 트리 이용

 

결정 트리 모델

 

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

점수가 높아졌다. !

 

이 트리를 시각화해보자. by using plot tree()

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

노드의 길이가 너무 길어서 제한해보자 by using max_depth = 1 하면 하나의 노드를 더 그림

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

 

그림이 담고 있는 정보는 조건 (sugar), 불순도(gini), 샘플 수, 클래스 별 샘플 수로 이루어져 있다.

루트 노드는 당도가 -0.239이하인지...yes <---   /   No --->

불순도

  • 해당 범주 안에 서로 다른 데이터가 얼마나 섞여 있는지를 뜻함.

gini는 지니 불순도를 의미하는데,

<<<<<클래스개수에 따른 케이스들의 불순한 정도를 나타내는 척도라고 생각하면 될 것 같다.

지니불순도가 필요한 이유는 의사결정을 하는데 있어서 최적의 분류를 위한 결정을 계속해서 맞이하는데 이 결정에 사용되기 때문이다. 이 변수로 인해서 분류를 거쳤을 때 지니불순도가 얼마나 되는가? 를 생각할 수 있게된다. 또다른 불순도지표로는 엔트로피가 있다.>>>>>>

DecisionTreeClassifier 클래스의 criterion 매개변수의 기본값이 gini이다.

 

지니 불순도(gini) = 1 - ( 음성 클래스 비율^2 + 양성 클래스 비율^2 )

결정 트리 모델은 부모와 자식 노드의 불순도 차이가 커지도록 트리를 성장시킨다.

 

(자식 노드의 불순도를 샘플 개수에 비례하여 모두 더하고 그 이후 부모 노드의 불순도에서 뺀다.)

 

=부모 gini - (왼쪽 샘플 수 / 부모 샘플 수) x 왼쪽 gini - (오른쪽 샘플 수 / 부모 샘플 수) x 오른쪽 gini

 

= 0.367 - [(2922/5197) x 0.481] - [(2275/5197) x 0.069] = 0.066

 

부모와 자식 노드간의 불순도 차이를 정보 이득이라고 한다.

따라서 정보 이득이 최대가 되도록 데이터를 나누고, 그 기준으로 불순도 (gini)를 사용한다.

 

한편, 사이킷런에서는 또 다른 불순도 기준이 존재한다. ( 엔트로피 불순도)

 

DeicisionTreeClassifier 클래스에서 criterion='entropy' 

엔트로피 불순도 기준은 아래에 log 2를 붙여서 사용한다.

 

가지치기

끝도 없이 자라나는 트리가 생길 수 있기 때문에 가지치기는 필수적이다.

가장 간단한 방법은 아까 했던 것처럼  max_depth = 으로 지정할 수 있다.

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

 

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

위 그림을 보면 깊이 0~3 중 깊이 1은 당도를 기준, 2는 당도,도수,pH 순으로 기준을 사용했다.

최종 노드를 보면 왼쪽에서 세번째 노드만 음성클래스가 더 많은것을 볼 수 있다. 

따라서 이 노드 (주황)에 도착해야만 레드와인으로 분류하는것 같다.

즉, 당도는 -0.802~-0.239 사이, 도수는 0.454보다 작아야 레드와인으로 분류한다.

 

그런데 당도가 음수라는것을 어떻게 해석해야할까.( 문제는 전처리)

 

결정 트리는 표준화 전처리 과정을 하지 않아도 된다!!!

 

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

트리를 그려보자 (no 전처리)

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

이제 가시적으로 해석하기 더 쉽다. 

마지막으로, 결정 트리에는 어떤 특성이 가장 유용한지 나타내는 특성 중요도를 계산해 줍니다. 이 트리의 루트 노드와 깊이1에서 당도를 사용했기 때문에 당도가 가장 유용한 특성일 것 같다.

메서드는 DecisionTreeClassifier.feature_importances_

print(dt.feature_importances_)

[0.12345626 0.86862934 0.0079144 ]

 

+ Recent posts