결정트리(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 |