Python

[Python] 머신러닝 (3)

orin602 2025. 3. 10. 15:25

결정트리(Decision Tree) : 데이터를 분류하거나 회귀 분석을 수행하는 머신러닝 알고리즘.

  • 노드(Node)와 가지(Branch)로 이루어진 트리 구조를 가지고, 특정 기준에 따라 데이터를 반복적으로 분할해서 예측을 수행한다.
    • 루트 노드(Root Node) : 처음 데이터를 나누는 시작점
    • 내부 노드(Internal Node) : 중간 의사결정을 담당하는 노드
    • 리프 노드(Leaf Node) : 최종적인 예측값을 제공하는 노드
  • 결정트리는 엔트로피와 지니 계수 같은 척도를 이용해서 데이터의 불순도를 측정하고, 가장 좋은 기준을 찾아서 데이터를 분할한다.
  • 알고리즘 작동 방식
    • 특성 선택 : 데이터를 가장 잘 나눌 수 있는 특성 선택
    • 분할 : 선택된 특성에 따라 데이터를 두 개 이상의 그룹으로 분할
    • 반복 : 리프 노드가 될 때까지 분할 과정 반복
    • 가지치기 : 트리가 너무 깊어지는 것을 방지하여 과적합을 방지.(선택 사항)
  • 장점
    • 해석이 쉽다
    • 데이터 정규화 필요 없음
    • 특성 선택을 자동으로 수행
  • 단점
    • 과적합 위험(트리가 길어질 경우)
    • 작은 변화에도 모델이 크게 변화할 수 있음
    • 선형 데이터에는 성능이 낮음

데이터 준비

# 와인 분류하기
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head()

타깃(class) : 레드와인 = 0, 화이트와인 = 1 [이진분류 문제]

 

# 입력값과 타겟값 분리
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) # 테스트 샘플 수 : 전체 데이터의 20%로 지정

# 크기 확인
print('훈련 세트 :', train_input.shape)
print('테스트 세트 :', test_input.shape)

 

표준화 전처리

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()	# 객체 생성
ss.fit(train_input, train_target)	# 학습(평균, 표준편차 계산)
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))

점수가 낮음 : 과소적합 모델

>> 규제변수 C값 조절

 

1. 결정트리 모델 훈련

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

훈련 세트에 대한 점수가 높음 : 과대적합 모델

 

2. 결정트리 출력

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize = (10, 7)) # 그래프 크기 조정 (가로 10인치, 세로 7인치)
plot_tree(dt) # 결정트리 모델 시각화
plt.show()

plt.figure(figsize = (10, 7))
plot_tree(dt, max_depth = 2, filled = True, feature_names = ['alcohol', 'sugar', 'pH'])
# max_depth = 2 : 트리 깊이는 최대 2까지 시각화
# filled = True : 노드 색상을 클래스에 따라 채우기
# feature_names = [...] : 특성 이름 지정
plt.show()
  • 너무 깊은 트리는 복잡해서 이해하기 어려울 수 있어서 일부만 시각화하는 것이 유용하다.
  • 노드 색이 진할수록 해당 클래스의 확신이 높음을 의미
  • 요약
    • max_depth=2를 설정하면 너무 깊지 않은 트리만 시각화할 수 있음
    • filled=True를 설정하면 클래스별로 색상이 다르게 표시됨
    • feature_names=['alcohol', 'sugar', 'pH']를 설정하면 트리에서 특성을 쉽게 이해할 수 있음

3. 가지 치기

# max_depth : 최대 3개 노드까지만 성장
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))

훈련 세트의 성능은 낮아짐, 테스트 성능은 그대로.

 

# 결정 트리 그래프 그리기
plt.figure(figsize = (20, 15))
plot_tree(dt, filled = True, feature_names = ['alcohol', 'sugar', 'pH'])
plt.show()

특성값의 스케일은 결정 트리 알고리즘에 아무런 영향을 미치지 않음.

>> 표준화 전처리를 할 필요가 없음! (장점)

# 전처리를 하기 전 훈련 세트로 다시 훈련
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))

결과는 표준화 전처리 한 것과 동일함.

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

같은 트리지만, 표준 점수로 바꾸지 않아서 훨씬 이해하기 쉬움.

당도가 1.625보다 크고, 4.325보다 작은 와인 중 알콜 도수가 11.025와 같거나 작음 = 레드 와인

그 외는 화이트 와인

 

결정트리에서 어떤 특성이 유용한지 특성 중요도 계산하기

feature_importances_ 속성을 통해 결정트리 모델이 각 특성을 얼마나 중요하게 여겼는지를 나타내는 배열을 반환

값이 높을수록 해당 특성이 모델의 예측에 중요한 영향을 미침을 의미함.

알콜 중요도 : 0.1234(12.3%)

당도 중요도 : 0.8686(86.9%) 가장 중요

pH 중요도 : 0.0079(0.79%) 영향 없음

# 특성 이름과 중요도 가져오기
feature_names = ['alcohol', 'sugar', 'pH']
importances = dt.feature_importances_

# 시각화
plt.figure(figsize=(8, 5))
plt.barh(feature_names, importances, color='royalblue')
plt.xlabel("Feature Importance")
plt.ylabel("Features")
plt.title("Decision Tree Feature Importances")
plt.show()


교차 검증과 그리드 서치

- 검증 세트

 

훈련 세트에서 모델을 훈련하고 검증 세트로 모델 평가하기

(훈련 세트 : 60%, 테스트 세트 : 20%, 검증 세트 : 20%)

테스트 세트를 최종 점수 평가

 

1. 검증 세트 만들기

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)
# 훈련 세트를 훈련 세트(sub_)와 검증 세트(val_)로 나누기
sub_input, val_input, sub_target, val_target = train_test_split(
    data, target, test_size = 0.2, random_state = 42)

 

훈련 세트로 학습 후 평가하기

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

>>> 과대적합 ; 매개변수를 바꿔서 좋은 모델을 찾아야 함.

 

2. 교차 검증

- 많은 데이터를 훈련에 사용할수록 좋은 모델이 만들어진다.

교차 검증을 통해 안정적인 검증 점수를 얻고, 더 많은 데이터를 훈련에 사용하기

교차 검증 = 검증 세트를 떼어내는 과정을 여러번 반복

# 교차 검증 함수 : cross_validate()
# 파라미터로 평가할 모델 객체를 전달하고, 검증 세트를 떼어내지 않고 훈련 세트를 전체 전달.
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
  • fit_time : 각 폴드에서 모델을 훈련하는데 걸린 시간(초)
  • score_time : 각 폴드에서 모델을 평가하는데 걸린 시간(초)
  • test_score : 각 폴드에서의 검증 정확도(점수)

 

test_score 5개의 점수를 평균하면 최종 점수.

 

교차 검증시 분할기 사용하기

# 분할기(splitter) : 교차 검증을 할 때 훈련 세트를 섞어줌.
# StratifiedKFold() : 분할기 클래스
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv = StratifiedKFold())
print('평균 검증 정확도 :', np.mean(scores['test_score']))
  • StratifiedKFold() : 층화 교차 검증을 수행하는 방법
    • 일반적인 KFold 교차 검증은 데이터를 단순하게 k개로 나누지만, StratifiedKFold는 클래스 비율을 유지하면서 나눈다.
    • 여기서는 train_target에 속한 각 클래스의 비율을 동일하게 유지하면서 데이터를 나눈다.
  • cv = StratifiedKFold() 를 설정해서 층화된 방식으로 데이터를 나눔.

# 훈련 세트를 섞은 후 10-폴드 교차 검증
splitter = StratifiedKFold(n_splits = 10, shuffle = True, random_state = 42)
scores = cross_validate(dt, train_input, train_target, cv = splitter)
print('평균 검증 정확도 :', np.mean(scores['test_score']))
  • n_splits = 10 : 폴드 수를 10으로 설정(기본값 5)
  • shuffle = True : 데이터 섞기(기본은 섞지 않음)
    • 훈련 세트를 먼저 랜덤하게 섞고, 그 후에 폴드를 나눠서 데이터 분배가 더욱 균등해질 수 있음.

10-폴드 교차 검증을 사용해서 데이터를 섞어 더 균일하게 나눠서 정확도가 조금 향상됨.

 

 

그리드 서치

  • 기본 개념
    • 사용자가 여러 개의 하이퍼파라미터(모델이 학습하는 과정에서 사용자가 직접 설정해야 하는 값) 후보 값을 설정.
    • GridSearchCV가 모든 조합을 대상으로 교차 검증을 수행해서 최적의 조합을 찾음.
    • 최적의 하이퍼파라미터를 자동으로 설정해서 모델 성능을 최적화할 수 있음.
  •  장점
    • 최적의 하이퍼파라미터를 자동으로 탐색해서 성능을 극대화할 수 있음.
    • 교차 검증을  포함해서 안정적인 성능 평가가 가능.
    • 다양한 머신러닝 모델에서 사용이 가능
  • 단점
    • 모든 조합을 시도하기 때문에 하이퍼파라미터가 많으면 시간이 오래 걸림.
    • 데이터가 많거나 복잡한 모델일 경우 계산 비용이 커짐.

- 결정트리(Decision Tree) 알고리즘에서 최적의 max_depth, min_impurity_decrease 등의

매개변수 값을 바꿔가면서 최적의 모델을 찾아줌.

from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease' :[0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}
gs = GridSearchCV(DecisionTreeClassifier(random_state = 42), params, n_jobs = -1)
  • params : 탐색할 하이퍼파라미터 설정(min_impurity_decrease 값 목록)
  • gs : 그리드 서치 객체 생성
  • DecisionTreeClassifier(random_state = 42) : 결정 트리 모델 사용
  • n_jobs = -1 : 모든 CPU 코어를 사용해서 속도 향상

그리드 서치 학습

# min_impurity_decrease(최소 불순도) 값을 바꿔가며 5번 실행
# cv(교차 검증 폴드 기본값 : 5)
# 검증 수행 횟수 = 5x5=25개 모델 훈련
gs.fit(train_input, train_target)

# 검증 점수가 가장 높음 모델을 찾아 자동으로 다시 모델 훈련
dt = gs.best_estimator_ # 25개의 모델 중 가장 점수가 높은 모델 선택
print('훈련 데이터 정확도 :', dt.score(train_input, train_target))
  • gs.best_estimator_는 그리드 서치(GridSearchCV)가 찾은 최적의 결정 트리 모델
  • GridSearchCV는 min_impurity_decrease 값을 조합하여 총 5개의 값 × 5 폴드 교차 검증 = 25개 모델을 학습
  • 가장 검증 정확도(test_score)가 높은 모델을 best_estimator_로 반환

 

교차 검증 평균 점수 확인

# 교차 검증의 평균 점수는 cv_results_ 속성의 mean_test_score 키에 저장됨
print('평균 점수 :', gs.cv_results_['mean_test_score'])

# 넘파이의 argmax() : 가장 큰 값의 인덱스를 찾음
best_index = np.argmax(gs.cv_results_['mean_test_score'])
print('최적의 하이퍼파라미터 :', gs.cv_results_['params'][best_index])

# max_depth(트리 깊이 제한), min_samples_split(노드를 나누기 위한 최소 샘플 수)
params = {'min_impurity_decrease' : np.arange(0.0001, 0.001, 0.0001), # 9개
          'max_depth' : range(5, 20, 1), # 15개
          'min_samples_split' : range(2, 100, 10)} # 10개

수행 교차 검증 횟수 : 9 x 15 x 10 = 1350회

5-폴드 교차 검증 수행 >> 5 x 1350 = 6750회

gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)   # n_jobs=-1 : 사용할 CPU 코어의 수
gs.fit(train_input, train_target)

# 최상의 매개변수 조합
print(gs.best_params_)

# 최상의 교차 검증 점수
print(np.max(gs.cv_results_['mean_test_score']))

 

랜덤 서치

# 너무 많은 매개변수 조건이 있을 때 최적의 모델을 서치하는 수행 시간이 오래걸림.
# >>> 랜덤 서치 사용
# 랜덤 서치에서는 매개변수의 값을 전달하는 것이 아닌 샘플링하는 확률분포 객체를 전달함.
from scipy.stats import uniform, randint
# uniform : 실수값, randint : 정수값
rgen = randint(0, 10) # 0이상, 10미만의 정수 생성
rgen.rvs(10) # 10개의 난수 샘플 생성

# 1000개를 샘플링해서 각 숫자의 갯수 세어보기
np.unique(rgen.rvs(1000), return_counts = True)

  • array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) : 출력된 고유한 숫자들(0~9)
  • array([89, 115, 86, 108, 112, 107, 98, 82, 90, 113]) : 각 숫자가 등장한 갯
ugen = uniform(0, 1) # 0~1 사이의 10개 실수
ugen.rvs(10)

 

# 랜덤 서치에서 randint, uniform 클래스 객체를 넘겨주고 총 몇 번 샘플링해서
# 최적의 매개변수를 찾기
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)} # 리프노드가 되기 위한 최소 샘플의 갯수.
# n_iter : 100 >> 매개변수 범위에서 총 100번 샘플링
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)

 

# 최적의 모델 가져와서 점수 확인
dt = gs.best_estimator_
print('최적화 검증 점수 :', dt.score(test_input, test_target))


 

'Python' 카테고리의 다른 글

[Python] 비지도 학습  (0) 2025.03.13
[Python] 트리 앙상블  (0) 2025.03.11
[Python] 머신러닝 (2)  (0) 2025.03.08
[Python] 머신러닝 (1)  (0) 2025.03.05
[Python] 클래스(Class)  (0) 2025.03.04