KT AIVLE School/[TIL] AIVLE School 당일 복습

[TIL] [KT AIVLE School] KT 에이블스쿨 6기(DX 트랙) 7주차 3일. 머신러닝 - 지도학습(3). KNN 모델, Decision Tree 모델

guoyee94 2024. 10. 16. 18:16

 

박차를 가하고 있는 머신러닝 3일차.

 

점점 수학 비중이 늘어난다 괴롭다

 

일단 수업들으면서 이해한 만큼만 주절주절 적어 본다.

 

 

 

 


 

 

 

기본 알고리즘(2). KNN(K-Nearest Neighbor)

 

KNN( K-Nearest Neighbor, K 최근접 이웃)은 train 데이터를 산점도로 쫙 나타낸 다음에,

(따라서 fit() 과정은 그냥 산점도를 그리는 과정이다. = 연산이 단순하다.)

 

주어진 test 데이터를 이 산점도에 찍은 후

그 근처에 있는 점들(이웃, Nearest Neigbor) K개로부터 답을 구하는 모델이다.

 

Linear Regression과 달리 회귀와 분류 양쪽에 다 쓸 수 있다.

 

회귀이웃들의 평균, 분류이웃들의 최빈값을 답으로 뱉으면 되니까.

 

 

그런 이유로 KNN에서는 이웃의 수 K를 정하는 것이 가장 중요하다.

 

비교할 그룹을 설정하는 거니까.

 

K의 최대값학습 데이터 전체의 크기가 되겠지.

 

그러면 예측값은 전체의 평균(회귀모델) / 전체의 최빈값(분류모델)이 된다.

 

이는 모델이 지나치게 단순한 것이라고 볼 수 있다.

 

반대로 K의 최솟값은  K=1이다.

 

가장 가까운 이웃 하나의 값으로 답이 정해지는 모델인 셈.

 

이것은 학습 데이터가 극도로 예민하다는 뜻인데,

모델의 입장에서 이런 요소는 패턴 도출을 힘들게 하고,

모델이 지나치게 복잡한 것이라고 말할 수 있다.

 

따라서 우리는 파라미터 'n_neighbors='을 통해 이웃의 범위를 조정해야 한다.

 

<모델 복잡도와 과대적합/과소적합>
출처 : https://www.geeksforgeeks.org/underfitting-and-overfitting-in-machine-learning/

모델이 너무 단순하면, 당연히 현실의 복잡한 데이터를 예측하기 힘들다.

이를 과소적합(Underfitting)이라고 한다.

평균이나 최빈값도 일종의 모델인데, 이것들이 Underfitting된 모델들이라고 볼 수 있겠지.

데이터 분석가 입장에서는 그보다는 과대적합(Overfitting, 과적합)을 걱정해야 한다.

직관적으로는 모델에게 최대한 많은 데이터를 주면 더 좋은 학습을 할 것 같지만,
많은 데이터는 다르게 말하자면 높은 복잡성이라고 할 수 있다.
(데이터가 많으니까 당연히 복잡하다.)

이것이 오히려 로직을 꼬아 버림으로써
모델이 train 데이터에만 최적화되게 하는데,

이런 모델은 현실 데이터에 대해서는 성능이 떨어진다.

이것이 과대적합이다.

따라서 모델의 복잡성은, 과소적합도 과대적합도 유발하지 않도록 적절히 조정되어야 한다.

 

 

 

 


 

스케일링(Scaling)

 

앞에서 보았듯 KNN에서는 '가까운' 점들을 판단의 근거로 삼는다.

 

이때 가까움을 판별할 수 있는,

다시말해 거리를 측정하는 두 가지 방법은 크게 둘로 나뉜다.

 

 

유클리드 거리(Euclidean Distance)

피타고라스 정리를 기반으로 두 점 사이의 거리를 계산하는 방법이다.

 

맨해튼 거리(Manhattan Distance)

한 점에서 다른 점까지 수평/수직 축을 따라 격자식으로 이동할 때의 거리를 말한다.

 

두 방법 다 가로축과 세로축 연산을 통해 거리를 계산하게 되는데,

양 축은 서로 다른 feature이므로 대체로 단위가 다르다.

 

여기에서 문제가 발생한다.

 

가령, 가로축이 '키'이고 세로축이 '시력'이라고 가정하자.

 

x축은 일반적으로 0.0~250.0의 범위를, y축은 0.0~2.0의 범위를 지닐 것이다.

 

x죽의 1은 총 거리에서 0.4%만큼을 차지하지만, y축은 50%의 거리를 차지한다.

 

이런 문제를 없애기 위해 필요한 것이 스케일링이다.

 

스케일링이란 일련의 식을 통해 각 feature들의 데이터 범위를 비슷하게 만드는 것이다.

 

기억하자. 거리 개념을 활용하려면 반드시 스케일링을 거쳐야 한다!

(따라서 모든 알고리즘에 스케일링을 할 필요는 없다.)

*단, 딥러닝 시에는 무조건 스케일링을 한다. 

 

스케일링 이전(좌)에는 각 데이터의 범위가 제각각이라 거리 비교가 안되지만, 이후(우)에는 데이터의 범위가 일정해졌다.

 

스케일링의 방법은 두가지가 있다.

 

정규화(Normalization) : 각 변수의 값이 0과 1사이 값이 된다.

표준화(Standardization) : 각 변수의 평균이 0, 표준편차가 1이 된다.

 

 

정규화 공식(좌)와 표준화 공식(우)

 

위 식을 활용해서 정규화를 진행할 때 주의할 점.

평가용 데이터에도 학습용 데이터를 기준으로 진행해야 한다.
학습 데이터와 평가용 데이터는 분포가 비슷하기야 하겠지만,

그렇다고 최댓값과 최솟값이 일치한다는 보장은 없기 떄문이다. 
정규화는 최댓값을 1, 최솟값을 0으로 잡고 수행하기에,
평가용 데이터에서는 다르게 처리될 수도 있다.

따라서 평가용 데이터에 정규화를 할 때도 최솟값-최댓값은 학습용 데이터가 기준이 되어야 한다.

 

그럼 공식을 활용해 데이터가 정규화되는 모습을 살펴보자.

 

데이터는 airquelity 데이터를 쓴다.

# train_test_split 이후
# 데이터 분포 확인
plt.figure(figsize=(10,3))
plt.boxplot(x_train, vert=False, labels=list(x_train))
plt.show()

컬럼마다 범위가 달라 거리 기반 비교가 불가능하다.

 

정규화를 위해 정규화 공식을 적용해 보자.

# 최댓값, 최솟값 구하기
x_max = x_train.max()
x_min = x_train.min()

# 정규화
x_train = (x_train - x_min) / (x_max - x_min)
x_test = (x_test - x_min) / (x_max - x_min)

# 데이터 분포 재확인
plt.figure(figsize=(10,3))
plt.boxplot(x_train, vert=False, labels=list(x))
plt.show()

 

좀 번잡하다.

 

이와 똑같은 식을 구현하려면, 

sklearn.preprocessing에서 스케일러 모델을 import 해서 사용하면 된다.

# 모듈 불러오기
from sklearn.preprocessing import MinMaxScaler

# 정규화
scaler = MinMaxScaler()
x_train = scaler.fit_transform(x_train) # fit = 학습 데이터의 최솟값, 최댓값 찾는 과정,
                                        # transform : 정규화 적용하기
x_test = scaler.transform(x_test) # 지금 scaler는 x_train의 최댓값, 최솟값을 배운 상태

 

이렇게 MinMaxScaler를 쓰면 정규화를 할 수 있다.

 

아까도 말했듯 주의할 점. test데이터도 train데이터를 기준으로 스케일링을 한다.

 

따라서 x_train에는 fit_transform()을,

x_test에는 transform을 쓴다.

 

표준화도 같은 방법으로 가능한데,

StandardScaler를 쓰면 된다.

# 모듈 불러오기
from sklearn.preprocessing import StandardScaler

# 정규화
scaler = StandardScaler()
x_train = scaler.fit_transform(x_train) # fit = 학습 데이터의 최솟값, 최댓값 찾는 과정,
                                        # transform : 정규화 적용하기
x_test = scaler.transform(x_test) # 지금 scaler는 x_train의 최댓값, 최솟값을 배운 상태

 

이런 과정을 거쳐 스케일링이 된 자료를 시각화해 보자.

 

<정규화 결과(MinMaxScaler)>

 

정규화한 결과, 각 컬럼의 최댓값 - 최솟값이 1 - 0으로 설정되며 각 컬럼의 거리 영향력이 같아졌다.

 

여기서 Wind 컬럼을 보면, 이상치가 최댓값, 최솟값으로 설정되는 바람에

이상치를 제외한 일반적인 값들은 실질적으로 1과 0이 아닌 값들이 최댓값-최솟값이 되었다.

 

따라서, MinMaxScaler는 이상치의 영향을 받는다.

 

 

 

<표준화 결과(StandardScaler)>

 

반면 StandardScaler는 아래처럼 이상치의 영향 없이 균일한 스케일링이 이루어진다.

 

 

 

 

 

 

 

 

기본 알고리즘(3). Decision Tree

Decision Tree(의사결정나무)는 위처럼 특정 의사결정 규칙을 나무처럼 뻗어나가는 알고리즘이다.

 

이는 몇가지 특징을 가지고 있는데,

데이터 분석가로서 알아야 하는 가장 중요한 점은 깊이 제한의 필요성이다.

 

의사결정 나무는 한 계층 내려갈때마다 질문을 하고, 이에 따라 True/False를 구분하며

가지를 쌓아 나간다.

 

하지만 첫 질문이 모든 분류를 가능케 할 정도로 날카로웠다면?

한 계층만 뻗고도 Decision Tree는 완벽한 답을 내는 것이다.

 

반면에 질문을 많이 했다면(=깊이가 깊다면) 그만큼 모델이 복잡하다는 것이다.

 

따라서 의사결정나무가 깊이 진행될수록 과적합의 위험성이 커진다.

 

따라서 우리는 마치 정원사가 가지를 쳐내듯이

트리가 최대로 뻗어나갈 수 있는 정도(최대 깊이)를 제한해 주어야 한다.

 

파라미터 'max_depth='를 통해 모델이 최대로 갈 수 있는 깊이를 정할 수 있다.

 

이를 위해 가장 중요한 것은, 먼저 하는 질문들이 의미있는 것이어야겠지.

<Decision Tree의 다른 특징들>
- 스케일링 등 전처리 영향도가 작음
- 분석과정을 관측가능한 화이트박스 모델
- 훈련 데이터에 대한 제약 사항이 거의 없는 유연한 모델

 


Decision Tree의 용어.

 

Decision Tree가 뱉어내는 반환값은 어떻게 결정될까?

 

분류문제의 반환값은 리프 노드 샘플들의 최빈값이고,

회귀문제의 반환값은 리프 노드 샘플들의 평균이다.

 

그렇다면 성능 평가의 지표는 뭘까?

 

주로 쓰는 비용함수(평가 지표)는 다음과 같다.

분류문제 불순도
회귀문제 MSE

 

MSE는 들어 봤지만... 불순도는 처음 듣는다.

 

아 또 정리해야되네. 흑흑

<불순도>
Decision Tree를 활용한 분류 문제가 어떻게 흘러가는지 생각해 보자.

Root 노드에서 질문을 던지면, y_train의 데이터는 둘로 갈라진다.
이때, 당연히 깔끔하게 딱딱 갈라지지는 않겠지. 변수들은 아직 섞여 있을 것이다.
이 '섞인 정도'를 불순도라고 한다.

Decision Tree는 이렇게 층을 늘려 가며 불순도를 차츰 낮춰 간다.
따라서 좋은 질문이란? 불순도를 가장 많이 낮춰 준 질문.

특정 노드의 불순도가 낮다는 것은
해당 노드의 요소들이 특정 클래스에 편중되어있다는 뜻
(= 분류가 잘 됐다는 뜻)


이런 불순도를 수치화할 수 있는 지표로는 두 가지가 있다.

1. 지니 불순도(Gini Impurity)

모델의 기본 불순도 지표는 지니 불순도이다.
지니 불순도는 최솟값으로 0을, 최댓값으로는 '1/클래스의 수'을 갖는다.

예를 들어 3개의 클래스가 0.33씩 나뉘어 있다면?
가장 불순하다는 뜻이지.



2. 엔트로피(Entrophy)

엔트로피도 지니 불순도처럼 혼합된 정도를 나타내는 지표다.
엔트로피는 0~1 사이의 값을 가진다. 

데이터 수에 따라서 변동되는 수치가 아니라 순수하게 불순도만을 수치화한 것이므로,
정보 이득 개념을 다룰 때는 엔트로피를 쓴다.

정보 이득(Information Gain)이란 쉽게 말해
어떤 질문이 엔트로피를 줄여 준 정도를 뜻한다.

따라서 정보 이득이 높다는 것은 효과적인 질문,
즉 target의 변별에 중대한 역할을 하는 질문이라는 뜻이다.

질문이란 것은 결국 '특정 feature에 속하는지 아닌지'의 형태이므로,
Decision Tree는 feature의 중요도를 정보 이득이라는 지표로 라벨링하는 것과 같다.

 

 

 


 

 

 

이런 이유로 Decision Tree를 이용한 분석에서는 두 가지를 추가로 표현해 줄 수 있다.

 

  • 화이트박스 모델이라는 점 : 트리 자체를 그려서 표현

graphviz를 다운받아 환경변수로 선언해야 가능하다.

 

참고 : 

https://aeir.tistory.com/entry/graphviz-%EA%B0%84%EB%8B%A8-%EC%84%A4%EC%B9%98-%EB%B0%A9%EB%B2%95-%EC%9C%88%EB%8F%84%EC%9A%B010-1

 

파이썬 환경에서 머신러닝이 끝난 후, model에 대해

# 트리 시각화
# 시각화 모듈 불러오기
from sklearn.tree import export_graphviz
from IPython.display import Image

# 이미지 파일 만들기
export_graphviz(model,                          # 모델 이름
                out_file='tree.dot',            # 파일 이름
                feature_names=list(x),          # Feature 이름
                class_names=['no', 'yes'],      # Target Class 이름
                rounded=True,                   # 둥근 테두리
                precision=2,                    # 불순도 소숫점 자리수
                max_depth=5,                    # 출력할 트리의 깊이
                filled=True)                    # 박스 내부 채우기

# 파일 변환
!dot tree.dot -Tpng -otree.png -Gdpi=300

# 이미지 파일 표시
Image(filename='tree.png')

 

이런 식을 통해 트리 자체를 출력할 수 있다.

 

각각의 노드에는 질문, 지니 불순도, 전체 샘플 수, 각 클래스에 해당하는 샘플 수, 판단 결과가 적혀 있다.

 


 

  • feature마다 정보 이득이라는 수치가 할당된다는 점 : 변수 중요도 시각화 

Decision Tree 모델은 정보 이득을 바탕으로 변수의 중요도를 반환하는

.feature_importance 라는 속성을 가진다.

 

이를 barh 형태로 시각화하면 된다.

# 변수 중요도 시각화
# 데이터프레임 만들기
df = pd.DataFrame()
df['feature'] = list(x)
df['importance'] = model.feature_importances_
df.sort_values(by='importance', ascending=True, inplace=True)

# 시각화
plt.figure(figsize=(5, 5))
plt.barh(df['feature'], df['importance'])
plt.show()