BuNa_
IT Story
BuNa_
전체 방문자
오늘
어제
  • 분류 전체보기 (117)
    • CS (14)
      • 운영체제 (8)
      • 네트워크 (0)
      • Design Pattern (1)
      • OOP (4)
    • 대외활동 (24)
      • 우아한테크코스 (14)
      • DND 동아리 (4)
      • UMC 동아리 (5)
      • 해커톤 (1)
    • Android (29)
      • MVVM (2)
      • 스터디 (11)
      • Compose (3)
      • Unit Test (1)
    • Project (5)
      • 어따세워 (5)
      • DnD 과외 서비스 (0)
    • Programming (11)
      • Kotlin (4)
      • 파이썬 (7)
    • Git (1)
    • 인공지능 (22)
    • 백준 (8)
    • 기타 (3)
      • IntelliJ (1)
      • 일상 (0)

블로그 메뉴

  • 홈

공지사항

인기 글

태그

  • UMC
  • k-means++
  • MVVM
  • K-means
  • 우테코
  • 백준
  • 셀레니움
  • Android
  • ViewModel
  • 원시값 포장
  • Ai
  • 컴공선배
  • 어따세워
  • 우테코 프리코스
  • 다이나믹 프로그래밍
  • 파이썬
  • 안드로이드
  • 운영체제
  • RecyclerView
  • External fragmentation
  • 객체지향 생활체조
  • 인공지능
  • Baekjoon
  • 인공지능 분류
  • 외부 단편화
  • Compose
  • 우테코 5기
  • 우아한테크코스
  • 선형회귀
  • 딥러닝

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
BuNa_

IT Story

[인공지능][실습] 결정 트리(Decision Tree) 모델로 와인(Wine) 데이터셋을 분류하고 교차 검증(Cross Validation)과 그리드 서치(Grid Search)로 최적의 하이퍼 파라미터를 찾아보자!
인공지능

[인공지능][실습] 결정 트리(Decision Tree) 모델로 와인(Wine) 데이터셋을 분류하고 교차 검증(Cross Validation)과 그리드 서치(Grid Search)로 최적의 하이퍼 파라미터를 찾아보자!

2021. 5. 18. 20:13

 

 

결정 트리(Decistion Tree)와 가지치기(Pruning)에 대한 이론이 필요하신 분들은 아래 링크를 참조해주시기 바랍니다.

[인공지능][개념] 분류(Classification) - 결정 트리(Decisioin Tree)와 가지치기(Pruning) : https://itstory1592.tistory.com/12

 

 

 

결정 트리(Decision Tree) 모델을 사용하여 와인을 분류하는 모델을 만들어보자.

 

이번 글에서는 결정 트리(Decision Tree)와 가지치기(Pruning)를 사용하여 모델의 과대 적합(Overfitting)을 줄이면서 와인 데이터셋을 화이트 와인과 레드 와인으로 분류하는 모델을 구현해볼 예정이다.

더불어 교차 검증(Cross Validation)을 통해 테스트 데이터 한 묶음만 사용하는 것이 아닌, 훈련용 데이터의 모든 부분을 테스트 데이터로 활용하는 방법과, 그리드 서치(Grid Search)로 최적의 하이퍼 파라미터를 찾아보는 방법에 대해 알아보자.

 

 

이전 실습에서는 로지스틱 회귀(Logistic Regression) 모델을 이용하여 데이터를 분류하는 방법에 대해 배웠는데,

왜 결정 트리 (Decision Tree)를 알아야 할까?

이유를 알기 위해, 우선 로지스틱 회귀 모델로 와인 데이터셋을 학습시켜보자.

 

 

import pandas as pd

wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head()

 

우선 판다스(pandas) 라이브러리를 통해 와인 데이터셋을 불러오고,

맨 위에 위치한 와인 데이터 5개를 출력해보자.

 

 

head() 메소드'를 통해 불러온 상단부 와인 데이터셋 5개
'head() 메소드'를 통해 불러온 상단부 와인 데이터셋 5개

 

특징(feature)으로는 alcohol(도수), 당도(sugar), pH(산성)이 있다.

맨 오른쪽에 있는 class는 화이트 와인과 레드 와인을 숫자로 표현한 것인데,

이 데이터에서는 class가 0인 데이터가 화이트 와인, 1인 데이터를 레드 와인으로 표현하였다.

 

 

wine.info()

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

 

다음으론, info() 메소드를 호출하여 데이터의 구성을 더 구체적으로 확인해보고,

모델 훈련을 위해, 데이터 구성을 '입력 데이터(input data)'와 '타겟 데이터(target)'로 나누어 준다.

 

 

info() 메소드로 와인(Wine) 데이터셋의 구성을 살펴본 이미지
info() 메소드로 와인(Wine) 데이터셋의 구성을 살펴본 이미지

 

 총 6497개의 데이터가 존재하며, null 값이 없어 따로 처리할 필요는 없을 것 같다.

컬럼(특징)은 위에서 봤던 것처럼 alcohol, sugar, pH가 존재한다.

 

 

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)

 

이제 train_test_split() 메소드를 통해 전체 데이터를 훈련용 데이터셋과 테스트용 데이터셋으로 나누고,

실제로 데이터가 잘 나누어졌는지 출력해보자.

 

 

Train (훈련) / Test (테스트) 데이터셋의 형태(shape)
Train (훈련) / Test (테스트) 데이터셋의 형태

 

3개의 컬럼(Column)으로 이루어진 데이터가 대략 8 : 2 비율로 데이터가 적절히 나누어진 모습을 확인할 수 있다.

 

 

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))
print(lr.score(test_scaled, test_target))
print('\n')
print(lr.coef_, lr.intercept_)

 

데이터 처리가 끝났다면 본격적으로 로지스틱 회귀를 사용하기 위해 사이킷런(sklearn)의 LogisticRegression 클래스를 임포틀해준다.

로지스틱 회귀에서는 각각의 특징(alcohol, sugar, pH)들의 데이터 스케일이 다르므로, StandardScaler()를 통해 데이터를 표준화해주어야 한다.

이러한 과정을 데이터 전처리(Data Scaling)라고 부른다.

 

이 과정을 마쳤다면, 로지스틱 회귀 모델을 선언하여 fit() 메소드를 통해 훈련용 데이터셋을 훈련시킨다.

그다음에는, 과대(Overfitting), 과소적합(Underfitting) 여부를 확인하기 위해 훈련용 데이터셋과 테스트용 데이터셋의 점수를 출력시켜보고,

각 특징들 중에서 어떤 특징이 모델에서 중요한 역할을 하는지 확인해보기 위해, 훈련시킨 모델의 coef_와 intercept_에 접근하여 가중치와 절편을 분석해보자.

 

 

각 데이터셋의 점수와 특성(feature)의 가중치 및 절편
각 데이터셋의 점수와 특성(feature)의 가중치 및 절편

 

훈련용 데이터셋과 테스트용 데이터셋에 대해 모두 78% 정도의 정확도가 출력되었다.

이전에 비해 그리 높은 정확도는 아닌 듯하다.

그 밑에는 각각 alcohol, sugar, pH 순으로 가중치와 절편이 출력되었는데,

알코올(alcohol)과 당도(sugar)와 가중치가 양수인 것으로 보아, 알코올과 당도가 클수록 양성 클래스(화이트 와인)일 가능성이 높아지고, 맨 오른쪽에 있는 pH가 클수록 음성 클래스(레드 와인)일 가능성이 높을 것이라고 예상할 수 있다.

해당 가중치와 절편으로 만든 Z 방정식의 값이 0보다 크면 양성 클래스, 0보다 작으면 음성 클래스로 분류된다.

 

 

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

 

로지스틱 회귀 모델로 학습이 마쳤다면, 이제 오늘 본문의 주인공인 결정 트리(Decision Tree) 모델을 훈련시켜보자. 

결정 트리도 이전과 다를 바 없이 DecisionTreeClassifer로 인스턴스를 생성해주고 fit() 메소드로 훈련용 데이터셋을 학습시키면 된다.

다른 점으로는, 결정 트리는 로지스틱 회귀와 달리 데이터 전처리(Data Scaling) 과정이 필요 없다는 점이다.

결정 트리는 개별적인 특성을 기준으로 데이터에게 질문을 던져 분류하기 때문이다.

 

학습을 마친 후 훈련 데이터와 테스트 데이터의 점수를 확인해보면..?!

 

 


 

 

 

결정 트리(Decision Tree) 모델로 훈련시킨 점수 결과
결정 트리(Decision Tree) 모델로 훈련시킨 점수 결과

 

로지스틱 회귀 모델에 비해 정확도가 확연히 높아졌음을 알 수 있다.

하지만, 훈련 데이터에 대한 점수가 상대적으로 너무 높기 때문에 이 모델은 훈련 데이터에 과대적합(Overfitting) 되었다고 할 수 있다.

한번 해당 모델에 대한 모양을 그림으로 나타내어 확인해보자.

 

 

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

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

 

맷플롯립(matplotlib)과 사이킷런(sklearn)에서 결정 트리를 그림으로 표현하기 위해 필요한 라이브러리를 임포트해준다.

plt.figure()의 매개변수로 사용된 figsize는 차트의 그림 사이즈를 조정하는 변수이다.

(이 예제에서는 값을 (10, 7)로 설정하였다.)

plot_tree() 메소드는 매개변수로 전달받은 결정 트리 모델을 이미지로 표현해주는 역할을 한다.

 

 

최대 깊이(max_depth)를 설정하지 않아 가지가 수 없이 생긴 결정 트리(Decision Tree)
최대 깊이(max_depth)를 설정하지 않아 가지가 수 없이 생긴 결정 트리(Decision Tree)

 

show() 메소드를 호출하면 위와 같은 그림이 출력된다.

그림을 보면 알다시피, 결정 트리에 너무 많은 가지(branch)가 존재하는데,

이런 부분이 '과대 적합(Overfitting)의 요인'이 될 수 있다.

 

따라서, 우리는 가지치기(Pruning)라 불리는 기술을 통해 결정 트리의 깊이(depth)를 적절히 조절해줄 필요가 있다.

그전에, 이렇게 복잡한 트리 이미지가 실제로는 어떻게 생겼는지 살펴보자.

 

 

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

 

위에서 봤던 트리(tree) 이미지를 조금 더 보기 쉽게, max_depth를 1로 설정하여 깊이(depth)를 1까지만 그려보겠다.

매개변수 filled를 True로 설정해주면 클래스의 비율에 따라 노드(Node) 색상을 다르게 칠해준다.

(클래스 비율이 높을수록 Node의 색이 진해지며, 비율이 낮을수록 옅어진다.)

 

 

깊이(Depth)를 1로 설정하여 시각화한 결정 트리
깊이(Depth)를 1로 설정하여 시각화한 결정 트리

 

오른쪽 자식 노드 (Right Child Node)를 확인해보면 양성 클래스(레드 와인)의 개수가 훨씬 많기 때문에,

다른 노드들에 비해 진한 색상을 띠고 있음을 확인해볼 수 있다.

 

 

이제, 가지치기를 사용하여 이 복잡하고 과대적합한 결정 트리를 최적화시켜주자.

 

 

가지치기(Pruning)


 

 

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

 

가지치기(Pruning)라고 해서 그리 어려울 것이 없다.

기존 DecisionTreeClassifier에 max_depth를 3으로 설정해주어, 최대 뻗을 수 있는 가지의 깊이를 3으로 제한해주면 가지치기 작업이 끝이 난다.

 

 

최대 깊이(max_depth)를 3으로 설정하여 훈련시킨 모델
최대 깊이(max_depth)를 3으로 설정하여 훈련시킨 모델

 

그럼 이렇게 84%의 정확도를 가지면서 과대적합까지 해결된 모습을 확인할 수 있다.

이 결정 트리도 그림으로 나타내 보자.

 

 

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

최대 깊이(max_depth)가 3인 모델을 시각화한 이미지
최대 깊이(max_depth)가 3인 모델을 시각화한 이미지

 

위 코드로 인해, 깊이(Depth)가 3인 결정 트리가 출력되었다.

확실히 보기도 좋으면서, 과대적합까지 해결할 수 있어 1석 2조이다.

시각화를 해본김에 중요도까지도 수치로 확인해보자.

 

 

#특성의 중요도 출력
print('  alcohol      sugar       pH')
print(dt.feature_importances_)

 

coef_와 intercept_와 다르게 feature_importances_를 출력하면 각 특성(feature)의 중요도를 알아볼 수 있다.

 

 

특성의 중요도 (importance of features)
특성의 중요도 (importance of features)

 

coef_에 접근했을 때, 당도(sugar)의 가중치가 높게 나왔듯이, 중요도(importance) 또한 당도에서 확연히 높은 수치를 보여주고 있다.

 

 

dt = DecisionTreeClassifier(criterion='gini', min_impurity_decrease=0.0005, random_state=42)
dt.fit(train_input, train_target)

print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))

 

과대적합(overfitting)은 매개변수 max_depth를 조정하는 것 외에도, min_impurity_decrease를 조정하여 해결할 수도 있다.

매개변수 min_inpurity_decrease는 최소 불순도를 조절하여 트리를 구성하는데, 이 모델에는 0.0005라는 수치를 줘보겠다.

 

 

최대 불순도를 0.0005로 설정한 모델의 점수
최대 불순도를 0.0005로 설정한 모델의 점수

 

기존보다 과대적합이 해결됨과 동시에, max_depth를 조절했을 때보다 정확도가 약간 높아졌지만,

min_impurity_decrease를 조절했을 때는 과대적합이 완전히 해결되지는 않았다.

 

 

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

 

이유를 살펴보기 위해, 트리(tree)를 시각화하여 확인해보자.

 

 

최대 깊이를 설정하지 않고 최소 불순도만 설정한 결정 트리의 시각화 이미지
'최대 깊이를 설정하지 않고 최소 불순도만 설정한 결정 트리의 시각화 이미지'

 

최소 불순도(min_impurity)만 조절하다 보니 가지(branch)의 깊이가 꽤나 깊어졌기 때문인 듯하다..

그럼 최소 불순도와 최대 깊이를 동시에 적절히 조절해보면 되지 않을까?

 

하지만 '적절히' 라는 단어만큼 애매하고 어려운 것이 없다.

과연 몇으로 설정해야 적절한 값이 될 수 있을까?

 

머신러닝에서는 모델이 아닌 사람이 직접 지정해줘야 하는 매개변수를 하이퍼 파라미터(Hyper Parameter) 라고 부른다.

이 글에서 등장한 min_impurity_decrease와 max_depth 또한 마찬가지로 하이퍼 파라미터라고 할 수 있다.

사이킷런(sklearn)에는, 여러 값들 중에 어떤 값이 하이퍼 파라미터에 가장 적합한지 골라주는 'AutoML'클래스가 있다.

바로 'GridSearchCV'이다.

 

GridSearchCV는 교차 검증(Cross Validation)과 동시에 적절한 하이퍼 파라미터(Hyper Parameter) 값을 골라주는 유용한 클래스이다.

 

 

잠깐! 교차검증(Cross Validation)이란?

 

교차 검증(Cross Validation)은 전체 데이터의 1/k을 k번만큼 테스트 데이터로 사용하여 교차적으로 모델을 검증하는 방법이다.

 

 

5-Fold Cross Validation (5-폴드 교차 검증)
5-Fold Cross Validation (5-폴드 교차 검증)

 

위 그림처럼, 전체 데이터의 1/5을 순차적으로 테스트용 데이터로 사용하는 방식이다.

만약 그림처럼 5 분할하여 사용하는 경우에는 5-폴드 교차 검증(5-Fold Cross Validation)이라고 부르며,

보통, 5-폴드 교차 검증 또는 10-폴드 교차 검증을 많이 사용한다.

 

교차 검증을 하는 이유는, 모델을 테스트함에 있어, 테스트용 데이터셋만 사용하게 되면, 모델이 테스트 데이터셋에 'Overfit'하는 경우가 발생하기 때문이다.

또는 훈련 데이터가 부족할 경우, 전체 데이터의 1/k을 k번만큼 반복하여 테스트 데이터로 사용하면 모든 데이터를 테스트에 사용할 수 있다는 장점이 있다.

 

교차 검증의 장단점에 대해 보기 쉽게 정리해 놓았다.

 

 

교차 검증 장점 : 

1. 모든 데이터 셋을 평가에 활용할 수 있다.

  - 평가에 사용되는 데이터 편중을 막을 수 있다. (특정 평가 데이터 셋에 overfit 되는 것을 방지할 수 있다.)

  - 평가 결과에 따라 좀 더 일반화된 모델을 만들 수 있다. 

2. 모든 데이터 셋을 훈련에 활용할 수 있다.

  - 정확도를 향상시킬 수 있다.

  - 데이터 부족으로 인한 과소 적합(underfitting)을 방지할 수 있다.

교차 검증 단점 :

1. Iteration 횟수가 많기 때문에 모델 훈련/평가 시간이 오래 걸린다.

 

 

from sklearn.model_selection import GridSearchCV

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

# min_impurity_decrease : 최소 불순도
# min_impurity_split : 나무 성장을 멈추기 위한 임계치
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)

print(gs.best_params_)

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

 

params라는 딕셔너리 변수의 Key값으로는 우리가 설정할 하이퍼 파라미터 명칭을 입력하고,

Value로는 하이퍼 파라미터의 범위를 설정해준다.

 

GridSearchCV 클래스의 매개변수로는 우리가 사용할 모델과 위에서 선언한 params을 매개변수로 입력해주면 된다.

우리는 결정 트리(Decision Tree) 모델을 그리드 서치 및 교차 검증할 것이기 때문에, DecisionTreeClassifier 모델을 사용한다.

 

모델의 훈련을 마친 후에는 최적의 파라미터를 best_params_에 접근하여 알아볼 수 있으며,

이 파라미터를 사용하여 훈련된 모델을 best_estimator_에 접근하여 얻을 수 있다.

 

 

그리드 서치(GridSearch)를 사용하여 구한 하이퍼 파라미터와 해당 파라미터를 사용한 모델의 점수
그리드 서치(GridSearch)를 사용하여 구한 하이퍼 파라미터와 해당 파라미터를 사용한 모델의 점수

 

max_depth는 14 / min_impurity_decrease는 0.0004, min_samples_split은 12가 가장 적절하다고 말해주고 있다.

테스트셋에 대한 점수는 0.86 정도로 꽤 높은 편이다.

 

 

 

오늘은 결정 트리(Decision Tree)와 가지치기(Pruning)를 통해 분류 모델을 훈련시켜 보았다.

결정 트리는 로지스틱 회귀 모델과 달리, 시각화했을 때 모델이 분류하는 기준을 눈으로 확인할 수 있었으며,

독립적인 특징(Independent feature)을 기준으로 연속된 질문을 던져 최종적으로 클래스를 분류하는 방식을 사용하였다.

이러한 방식 덕분에 데이터 전처리(Data Scaling) 또한 생략할 수 있다는 장점이 있었다.

(또한, 경우에 따라 다르겠지만 해당 예제에서는 로지스틱 회귀에 비해 결정 트리에서 점수가 더 높게 나왔다.)

 

그 외에도 하이퍼 파라미터로 어떤 값을 입력해야 할지 모르겠다면, 그리드 서치(Grid Search)를 사용하여 적절한 값을 찾아보아야 한다.

모든 훈련 데이터를 테스트 데이터셋으로 사용하고 싶으면, 교차 검증(Cross Validation)을 통해 1/n만큼의 데이터씩 테스트 데이터로 돌려가며 사용하면 된다는 것을 알 수 있었다.

 

 

 

전체 소스 코드 : 

https://colab.research.google.com/drive/1rOVockfQZpNuL8fqR73mVZzSqgmkPqo7

 

 

(이해가 다소 힘들거나, 틀린 부분이 있다면 댓글 부탁드리겠습니다! 😊)

💖댓글과 공감은 큰 힘이 됩니다!💖

저작자표시 비영리 변경금지 (새창열림)

'인공지능' 카테고리의 다른 글

[인공지능][개념&실습] 트리의 앙상블(Ensemble)[2] - 엑스트라 트리(Random Forest)와 (+ 히스토그램 기반) 그레이디언트 부스팅  (2) 2021.05.20
[인공지능][개념&실습] 트리의 앙상블(Ensemble)[1] - 랜덤 포레스트(Random Forest)  (3) 2021.05.19
[인공지능][개념] 분류(Classification) - 결정 트리(Decisioin Tree)와 가지치기(Pruning)  (0) 2021.05.17
[인공지능][실습] 로지스틱 회귀(Logistic Regression) - 붓꽃 데이터(Iris Data)를 사용하여 3종류의 꽃을 분류해보자  (1) 2021.05.16
[인공지능][실습] 로지스틱 회귀(Logistic Regression) - 나는 과연 타이타닉 침몰에서 살아남을 수 있었을까?  (0) 2021.05.14
    '인공지능' 카테고리의 다른 글
    • [인공지능][개념&실습] 트리의 앙상블(Ensemble)[2] - 엑스트라 트리(Random Forest)와 (+ 히스토그램 기반) 그레이디언트 부스팅
    • [인공지능][개념&실습] 트리의 앙상블(Ensemble)[1] - 랜덤 포레스트(Random Forest)
    • [인공지능][개념] 분류(Classification) - 결정 트리(Decisioin Tree)와 가지치기(Pruning)
    • [인공지능][실습] 로지스틱 회귀(Logistic Regression) - 붓꽃 데이터(Iris Data)를 사용하여 3종류의 꽃을 분류해보자
    BuNa_
    BuNa_
    안드로이드 개발자를 향해 달리고 있는 공대생입니다! 🧑 Android, Kotlin, Java, Python 등 학습하고 있는 내용과 프로젝트를 주로 업로드하고 있습니다. 지적과 조언은 언제나 환영입니다!😊 github : https://github.com/tmdgh1592

    티스토리툴바