<혼자 공부하는 머신러닝 + 딥러닝>의 'Ch.5 트리 알고리즘_1. 결정 트리'의 내용을 요약 및 정리한 내용입니다.
https://product.kyobobook.co.kr/detail/S000001810330
혼자 공부하는 머신러닝+딥러닝 | 박해선 - 교보문고
혼자 공부하는 머신러닝+딥러닝 | 혼자 해도 충분하다! 1:1 과외하듯 배우는 인공지능 자습서이 책은 수식과 이론으로 중무장한 머신러닝, 딥러닝 책에 지친 ‘독학하는 입문자’가 ‘꼭 필요한
product.kyobobook.co.kr
1. 결정 트리 알고리즘을 사용해 새로운 분류 문제를 다루어 봅니다.
2. 결정 트리가 머신러닝 문제를 어떻게 해결하는지 이해합니다.
1. 로지스틱 회귀로 와인 분류하기
이번에는 6,497개의 와인 샘플 데이터를 이용해 와인을 분류하겠습니다.
데이터의 주소는 다음과 같습니다 : https://bit.ly/wine_csv_data
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
와인 데이터셋을 판다스 데이터프레임으로 제대로 읽어 들였는지 head() 메서드로 처음 5개의 샘플을 확인해 보겠습니다.
여기서 의미하는 바는 아래와 같습니다.
alcohol : 알코올 도수 | sugar : 당도 | pH : pH | class : 0 이면 레드와인, 1이면 화이트 와인
즉, 레드 와인과 화이트 와인을 구분하는 이진 분류 문제이고, 화이트 와인이 양성 클래스이기에 전체 와인에서 화이트 와인은 골라내는 문제입니다.
이제 로지스틱 회귀 모델을 훈련을 해야합니다. 그 이전에 판다스 데이터프임의 메서드인 info(), describe()를 이용해 추가로 데이터를 간단히 파악하고 모델을 훈련시키겠습니다.
먼저 info()메서드는 데이터프레임의 각 열의 데이터 타입과 누락된 데이터가 있는지 확인하는데 유용합니다. 위의 결과를 보면 총 6,497개의 샘플이 있고 4개의 열 모두 float64로 실수이며, 모두 Non-Null Count가 6497개 이므로 누락된 값은 없습니다.
다음 describe() 메서드는 열에 대한 간략한 통계를 출력해줍니다. 평균 (mean), 표준편차(std), 최소(min), 최대(max)값 그리고 중간값(50%)과 1사분위수(25%), 3사분위수(75%)를 확인했습니다.
이를 통해 각각의 값의 스케일이 다르다는 것을 알 수 있습니다. 따라 각각의 특성을 표준화해줘야 합니다.
판다스 데이터프레임을 배열로 바꾸고 훈련 세트와 테스트 세트로 나눠주겠습니다.
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
wine 데이터프레임에서 처음 3개의 열을 넘파이 배열로 바꿔서 data 배열에 저장하고 마지막 class 열을 넘파이 배열로 바꿔서 target 배열에 저장했습니다.
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)
훈련 세트와 테스트 세트로 나눴습니다. 여기서 train_test_split()함수는 설정값을 지정하지 않으면 25%를 테스트 세트로 지정합니다. 그러나 샘플 개수가 충분히 많으므로 test_size=0.2 를 통해 20% 테스트 세트로 지정했습니다.
만들어진 훈련 세트와 테스트 세트의 크기를 확인하면 훈련 세트는 5,197개이고 테스트 세트는 1,300개로 잘 나눠진 것을 확인할 수 있습니다. 확인했으니 이제 StandardScaler 클래스를 사용해 훈련 세트와 테스트 세트를 전처리하겠습니다.
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
모든 준비가 끝났습니다. 표준점수로 변환된 train_scaled와 test_scaled 를 사용해 로지스틱 회귀 모델을 훈련하겠습니다.
위의 결과처럼 생각보다 점수가 높지 않습니다. 또한 훈련 세트와 테스트 세트의 점수가 모두 낮으니 모델이 다소 과소적합된 것을 알 수 있습니다.
설명하기 쉬운 모델과 어려운 모델
만약 이 모델을 설명할려면 어떻게 해야할까요?
이 모델을 설명하기 위해 로지스틱 회귀가 학습한 계수와 절편을 출력해보겠습니다. 위를 식으로 나타내면 아래와 같습니다.
$$ y = 0.51270274 x_{alcohol}+1.6733911x_{sugar}-0.68767781x_{pH}+1.81777902 $$
이러한 식은 직관적으로 어떤 의미인지 이해하기 어려울 뿐만 아니라 왜 저런 계수 값을 모델이 학습했는지 정확히 이해하기 어려우며, 설명하기도 어렵습니다.
여기서 y 값이 0보다 클 경우 화이트 와인이고, 작으면 레드 와인입니다.
그렇기에 알코올 도수와 당도가 높을수록 화이트 와인일 가능성이 높고, pH가 높을수록 레드 와인일 가능성이 높은 것 같습니다. 그러나 여기서 다항 특성을 추가한다면 설명하기가 더 어려워집니다.
이처럼 대부분 머신러닝 모델은 학습의 결과를 설명하기 어렵습니다. 그런데 어려운 설명은 종종 엔지니어를 신뢰하지 않는 결과로 불상사가 생기기도 합니다. 그렇다면 어떤 모델이 설명하기 쉬운 모델일까요?
2. 결정 트리
결정 트리 모델은 위의 모델과는 달리 이유를 설명하기 쉽습니다. 마치 스무고개처럼 계속해서 질문을 통해 정답을 찾아가는 형식입니다. 데이터를 잘 나눌 수 있는 질문을 찾는다면 계속 질문을 추가해서 분류 정확도를 높일 수 있습니다.
사이킷런은 결정 트리 알고리즘 또한 제공합니다. 따라 사이킷런의 DecisionTreeClassifier 클래스를 사용해 결정 트리 모델을 훈련하겠습니다. 사용법은 이전 방법과 동일합니다. fit()메서드를 호출해서 모델을 훈련한 다음 score() 메서드로 정확도를 평가해 보겠습니다.
훈련 세트에 대한 점수가 0.9969 정도로 엄청 높습니다. 이에 비해 테스트 세트의 성능은 그해 비해 조금 낮습니다. 과대적합된 모델입니다. plot_tree () 함수를 통해 이 모델을 이해하기 쉬운 트리 그림으로 표현해보겠습니다.
엄청난 트리가 만들어졌습니다. 이때 맨 위의 노드를 루트 노드라 부르고 맨 아래 끝에 달린 노드를 리프 노드라고 합니다.
너무 복잡하기 plot_tree()함수에서 트리의 깊이를 제한해서 출력하겠습니다. max_depth 매개변수를 1로 주면 루트 노드를 제외하고 하나의 노드를 더 확장하여 그립니다. 또한 filled 매개변수에서 클래스에 맞게 노드의 색을 칠할 수도 있으며, feature_names 매개변수에는 특성의 이름을 전달할수 있습니다. 이렇게 하면 노드가 어떤 특성으로 나뉘는지 좀 더 잘 이해할 수 있습니다. 이렇게 그려보겠습니다.
위의 그림보다 파악하기 좋아졌습니다. 기본적으로 그림이 담고 있는 정보는 다음과 같습니다.
print("""
=====================================================
|| 테스트 조건(sugar) ||
|| 불순도 (gini) ||
|| 총 샘플 수(samples) ||
|| 클래스별 샘플 수(value) ||
=====================================================
|| 조건 만족 : 왼쪽 || 조건 불만족 : 오른쪽 ||
=====================================================
""")
하나씩 한 번 살펴보겠습니다.
루트 노드는 당도(sugar)가 -0.239 이하인지 질문을 합니다. 만약 어떤 샘플의 당도가 -0.239와 같거나 작으면 왼쪽 가지로 갑니다. 그렇지 않으면 오른쪽 가지로 이동합니다. 루트 노드의 총 샘플 수 (samples)는 5,197개입니다. 이 중에서 음성 클래스 (레드 와인)는 1,258개이고, 양성 클래스(화이트와인)는 3,939개 입니다. 이 값이 value에 나타나 있습니다.
이어서 왼쪽 노드를 살펴보겠습니다. 이 노드는 당도가 더 낮은지를 물어봅니다. 당도가 -0.802와 같거나 낮다면 다시 왼쪽 가지로, 그렇지 않으면 오른쪽 가지로 이동합니다. 이 노드에서 음성 클래스와 양성 클래스의 샘플 개수는 각각 1,177개와 1,745개입니다. 루트 노드보다 양성 클래스, 즉 화이트 와인의 비율이 크게 줄어들었습니다. 그 이유는 오른쪽 노드를 보면 알 수 있습니다.
오른쪽 노드는 음성 클래스가 81개, 양성 클래스가 2,194개로 대부분의 화이트와인 샘플이 이 노드로 이동했습니다.
또한 노드의 바탕 색깔을 보면 루트 노드보다 오른쪽 노드가 더 진하고, 왼쪽 노드는 더 연합니다. plot_tree()함수에서 filled=True로 지정하면 이처럼 클래스마다 색깔을 부여하고, 어떤 클래스의 비율이 높아지면 점점 진한 색으로 표시합니다. 더욱 직관적입니다.
결정 트리에서 예측하는 방법은 간단합니다. 리프노드에서 가장 많은 클래스가 예측 클래스가 됩니다. 만약 이 결정 트리의 성장을 여기서 멈춘다면 왼쪽 노드에 도달한 샘플과 오른쪽 노드에 도달한 샘플은 모두 양성 클래스 개수가 많기에 양성 클래스로 예측하게 될 것입니다.
불순도
노드 상자 안에 있는 gini는 지니 불순도를 의미합니다. DecisionTreeClassfier 클래스의 critetion 매개변수의 기본값이 'gini'입니다. criterion 매개 변수의 용도는 노드에서 데이터를 분할할 기준을 정하는 것입니다.
루트 노드를 보겠습니다. 즉, 당도 -0.239를 기준으로 왼쪽과 오른쪽 노드로 나눠진 것이 바로 criterion 매개변수에 지정한 매개 변수를 통해 나눠진 것입니다.
이때의 기준은 'gini' 이기에 지니 불순도를 사용합니다. 지니 불순도를 어떻게 계산하는지 알아보겠습니다. 아주 간단합니다.지니 분순도는 클래스의 비율을 제곱해서 더한 다음 1에서 빼면 됩니다.
$$ 지니불순도 = 1 - (음성 클래스 비율 ^{2} + 양성 클래스 비율^{2})$$
이게 끝입니다. 다중 클래스 문제라면 클래스가 더 많겠지만 계산하는 방법은 동일합니다.
그럼 위의 루트 노드의 지니 불순도를 계산해 봅시다. 루트 노드는 총 5,197개의 샘플이 있고 그중에 1,258개가 음성 클래스, 3,939개가 양성 클래스 입니다. 따라서 다음과 같이 지니 불순도를 계산할 수 있습니다.
$$ 1 - ((1258/5197)^{2}+(3939/5197)^{2}) = 0.367$$
만약 100개의 샘플이 있는 어떤 노드의 두 클래스의 비율이 정확히 1/2씩이라면 지니 불순도는 0.5가 되어 최악이 됩니다.
$$1 - ((50/100)^{2}+(50/100)^{2}) = 0.5$$
노드에 하나의 클래스만 있다면 지니 불순도는 0이 되어 가장 작습니다. 이런 노드를 순수 노드라고도 부릅니다.
$$1 - ((0/100)^{2}+(100/100)^{2}) = 0$$
결정 트리 모델은 부모 노드와 자식 노드의 불순도 차이가 가능한 크도록 트리를 성장시킵니다.
부모 노드와 자식 노드의 불순도 차이를 계산하는 방법은 먼저 자식 노드의 불순도를 샘플 개수에 비례하여 모두 더하고, 그다음 부모 노드의 불순도에서 빼면 됩니다.
$$ 부모의 불순도 - ( 왼쪽 노드 샘플 수 / 부모의 샘플 수 ) * 왼쪽 노드 불순도 -$$
$$( 오른쪽 노드 샘플 수 / 부모의 샘플 수 ) * 오른쪽 노드 불순도 = $$
$$0.367 - (2922 / 5197) * 0.481 - ( 2275 / 5197 ) * 0.069 = 0.066 $$
이런 부모와 자식 노드 사이의 불순도 차이를 정보 이득이라고 부릅니다. 결정 트리의 노드를 어떻게 나누는지 이해했습니다. 이 알고리즘은 정보 이득이 최대가 되도록 데이터를 나눕니다. 이때 지니 불순도를 기준으로 사용합니다.
이 외 사이킷런에는 또 다른 불순도 기준이 있습니다.
DecisionTreeClassifier 클래스에서 criterion='entropy'를 지정하여 엔트로피 불순도를 사용할 수 있습니다. 엔트로피 불순도도 노드의 클래스 비율을 사용하지만 지니 불순도처럼 제곱이 아니라 밑이 2인 로그를 사용하여 곱합니다.
$$-음성 클래스 비율 * log_{2}(음성 클래스 비율) - 양성 클래스 비율 * log_{2}(양성 클래스 비율)$$
$$= -(1258 / 5197) * log_{2}(1258 / 5197) - (3939 / 5197) * log_{2}(3939 /5197) = 0.798$$
보통 기본값인 지니 불순도와 엔트로피 불순도가 만든 결과의 차이는 크지 않습니다. 그렇기에 기본 값인 지니 불순도를 계속 사용하겠습니다.
이제 결정 트리 알고리즘을 확실히 이해했습니다. 불순도 기준을 사용해 정보 이득이 최대가 되도록 노드를 분할합니다. 노드를 순수하게 나눌수록 정보 이득이 커집니다. 새로운 샘플에 대해 예측할 때에는 노드의 질문에 따라 트리를 이동합니다. 그리고 마지막에 도달한 노드의 클래스 비율을 보고 예측을 만듭니다.
그런데 앞의 트리는 제한 없이 자라났기 때문에 훈련 세트보다 테스트 세트에서 점수가 크게 낮았습니다. 이 문제를 해결해보겠습니다.
가지치기
열매를 잘 맺기 위해 과수원에서 가지치기를 하는 것처럼 결정 트리도 가지치기를 해야합니다. 그렇지 않으면 훈련 세트에는 아주 잘 맞겠지만 테스트 세트에서 점수는 그에 못 미치게 됩니다. 이를 두고 일반화가 잘 안 될 것 같다고 말합니다.
결정 트리에서 가지치기를 하는 가장 간단한 방법은 자라날 수 있는 트리의 최대 깊이를 지정하는 것입니다. DecisionTreeClassfier 클래스의 max_depth 매개변수를 3으로 지정하여 모델을 만들어 보겠습니다. 이렇게 하면 루트 노드 아래로 최대 3개의 노드까지만 성장할 수 있습니다.
훈련 세트의 성능은 낮아졌지만 테스트 세트의 성능은 거의 그대로 입니다. 다음 이 모델을 트리 그래프로 그려보겠습니다.
해당 그래프를 통해 샘플이 어떻게 나뉘는지를 확인할 수 있습니다.
루트 노드 다음에 있는 깊이 1의 노드는 모두 당도(sugar)를 기준으로 훈련 세트를 나눕니다.
깊이 2의 노드는 맨 왼쪽의 노드만 당도를 기준으로 나누고 왼쪽에서 두 번째 노드는 알코올 도수(alcohol)를 기준으로 나눕니다. 오른쪽의 두 노드는 pH를 사용합니다.
깊이 3에 있는 노드가 최종 노드인 리프 노드입니다. 왼쪽에서 세 번째에 있는 노드만 음성 클래스가 더 많습니다. 이 노드에 도착해야만 레드 와인으로 예측합니다.
그렇다면 루트 노드부터 이 노드까지 도달하려면 당도는 -0.239보다 작고 또 -0.802보다 커야 합니다. 그리고 알코올 도수는 0.454보다 작아야 합니다. 그래야만 세 번째 리프 노드에 도달하네요. 즉 당도가 -0.802보다 크고 -0.239보다 작은 와인 중에 알코올 도수가 0.454와 같거나 작은 것이 레드 와인입니다.
그럼 앞서 전처리하기 전의 훈련 세트(train_input)와 테스트 세트(test_test)로 결정 트리 모델을 다시 훈련해보겠습니다.
결과가 정확히 같습니다. 이번에는 트리를 그려 보겠습니다.
결과를 보면 같은 트리지만, 특성값을 표준 점수로 바꾸지 않은 터라 이해하기가 훨씬 쉽습니다. 당도가 1.625보다 크고 4.325보다 작은 와인 중에 알코올 도수가 11.025와 같거나 작은 것이 레드 와인 이군요. 그 외에는 모두 화이트 와인으로 예측했습니다.
마지막으로 결정 트리는 어떤 특성이 가장 유용한지 나타내는 특성 중요도도 역시 계산해 줍니다. 이 트리의 루트 노드와 깊이 1에서 당도를 사용했기 때문에 아마도 당도(sugar)가 가장 유용한 특성 중 하나일 것 같습니다. 특성 중요도는 결정 트리 모델의 feature_importances_ 속성에 저장되어 있습니다. 이 값을 출력해 확인해보겠습니다.
예상한대로 두 번째 특성인 당도가 0.87 정도로 특성 중요도가 가장 높습니다. 그다음 알코올 도수, pH순입니다. 이 값을 모두 더하면 1이됩니다. 특성 중요도는 각 노드의 정도 이득과 전체 샘플에 대한 비율을 곱한 후 특성별로 더하여 계산합니다. 이처럼 특성 중요도를 활용하면 결정 트리 모델의 특성 선택에 활용할 수 있습니다.
3. 이해하기 쉬운 결정 트리 모델
이번에는 와인을 분류했습니다. 먼저 알코올 도수, 당도, pH 데이터를 기준으로 화이트 와인은 골라내는 이진 분류 로지스틱 회귀 모델을 훈련했습니다. 하지만 이는 설명하기가 어려운 단점이 있었습니다.
따라 다음으로 결정 트리를 사용해 레드 와인과 화이트 와인을 분류하는 문제를 풀었습니다. 특성을 더 추가하지 않고도 결정 트리의 성능이 로지스틱 회귀 모델보다 더 좋았습니다. 게다가 결정 트리는 깊이가 너무 깊지 않다면 비교적 설명하기가 쉽습니다.
머신러닝 모델을 종졸 블랙박스와 같다고 말합니다. 실제로 모델의 계수나 절편이 왜 그렇게 학습되었는지 설명하기가 어렵습니다. 이에 비해 결정 트리는 비교적 비전문가에게도 설명하기 쉬운 모델을 만듭니다.
'IT > [혼공머신] 혼자 공부하는 머신러닝 + 딥러닝' 카테고리의 다른 글
[혼공머신] Ch.8 이미지를 위한 인공 신경망_1. 합성곱 신경망의 구성 요소 (0) | 2023.10.11 |
---|---|
[혼공머신] Ch.5 트리 알고리즘_3. 트리의 앙상블 (0) | 2023.09.10 |
[혼공머신] Ch.5 트리 알고리즘_2. 교차 검증과 그리드 서치 (0) | 2023.09.10 |
[혼공머신] Ch.2 데이터 다루기_2. 데이터 전처리 (0) | 2023.08.20 |
[혼공머신] Ch.2 데이터 다루기_1. 훈련 세트와 테스트 세트 (0) | 2023.08.20 |