logo

의사결정 나무를 이용한 분류

질문으로 사례를 나누고 잎의 클래스 비율로 불량 여부를 예측하는 분류나무

분류나무의 예측 원리

  • 의사결정 나무(decision tree): 입력 변수에 대한 질문으로 사례를 반복해서 나누는 모델
  • 회귀나무: 잎에 모인 훈련 사례의 타깃 평균으로 숫자 예측
  • 분류나무: 잎에 모인 훈련 사례의 클래스 비율로 확률 추정. 가장 비율이 높은 클래스로 분류
  • 예: 한 잎의 훈련 사례 100개 중 불량 부품이 65개면 불량 확률 추정값은 0.65

질문을 고르는 기준

  • 후보 질문: 상대 습도가 특정 값 이하인지, 표면 오염 지수가 특정 값 이하인지 등
  • 지니 불순도(Gini impurity): 한 집단에 서로 다른 클래스가 얼마나 섞였는지 나타내는 값
  • 분할 뒤 두 집단의 크기를 반영한 평균 불순도가 가장 많이 줄어드는 질문 선택
G=1−k=0∑1​pk2​
  • pk​: 해당 집단에서 클래스 k의 비율
  • 한 클래스만 있는 집단: G=0
  • 두 클래스가 절반씩 있는 집단: G=0.5

실습 데이터: 전자 부품 불량 판정

  • 전자 부품 10,000개의 보관 조건과 표면 상태로 검사에서 불량 판정을 받을지 예측하는 데이터
  • 비슷한 보관 조건과 표면 상태에서도 정상과 불량이 섞인 구성
  • 입력 변수
    • exposure_hours: 습한 환경 노출 시간(0~12시간)
    • humidity: 상대 습도(20~100%)
    • contamination_index: 표면 오염 지수(0~100, 높을수록 오염이 심함)
  • 타깃 defective: 0=정상, 1=불량
  • 양성(positive) 클래스: 찾아내려는 결과인 불량(1).
    • 여기서 positive는 긍정적이라는 뜻이 아님

실습 준비

🤖 AI 프롬프트
electronic_parts.xlsx를 불러와서 의사결정 나무로 분류하는 코드를 작성해줘.
타깃은 defective이며 0은 정상, 1은 불량이야. 나머지 변수는 입력 변수로 사용해.
학습·검증·테스트 데이터를 60%·20%·20%로 나눠 줘.
깊이 1~12의 나무를 각각 학습하고 검증 데이터로 가장 정확도가 높은 깊이를선택해줘.
테스트 데이터로 선택된 나무의 최종 테스트 정확도를 확인해 줘.
각 단계에 초보자를 위한 한글 주석을 달아줘.
import pandas as pd

df = pd.read_excel("electronic_parts.xlsx")
df.head()
실행 결과
   exposure_hours  humidity  contamination_index  defective
0             4.1      93.8                 25.3          0
1             4.8      26.2                 17.0          0
2             2.2      71.4                 27.6          0
3             5.4      83.7                 27.5          0
4             7.5      45.0                 95.1          1
df["defective"].value_counts().sort_index()
실행 결과
defective
0    6783
1    3217
Name: count, dtype: int64
  • 불량 3,217개, 정상 6,783개. 정상 부품이 더 많은 구성

학습·검증·테스트 데이터 분할

  • 학습 6,000개: 나무 모델 학습
  • 검증 2,000개: 나무의 깊이 선택
  • 테스트 2,000개: 깊이를 선택한 모델의 최종 평가
  • 네 분류 실습에서 같은 분할과 양성 클래스 사용
from sklearn.model_selection import train_test_split

features = ["exposure_hours", "humidity", "contamination_index"]
X = df[features]
y = df["defective"]

X_train_valid, X_test, y_train_valid, y_test = train_test_split(
    X, y,
    test_size=0.2,     # 전체의 20%를 최종 테스트용으로 보관
    random_state=42,   # 네 실습에서 같은 사례로 분할
    stratify=y,        # 정상과 불량의 비율 유지
)
X_train, X_valid, y_train, y_valid = train_test_split(
    X_train_valid, y_train_valid,
    test_size=0.25,    # 남은 80%의 25%: 전체의 20%를 검증용으로 보관
    random_state=42,
    stratify=y_train_valid,
)

분류나무 학습

  • DecisionTreeClassifier: 클래스 예측용 나무
  • max_depth: 뿌리에서 잎까지의 질문 횟수 제한
  • min_samples_leaf: 한 잎에 남길 최소 훈련 사례 수
  • 깊이를 제한하지 않으면 훈련 사례를 지나치게 세분화할 수 있으므로 얕은 나무로 시작
  • 변수의 크기보다 각 변수 안의 분할 기준을 사용하므로 이 실습에서는 표준화 생략
  • 먼저 깊이 4인 나무를 학습. 이 값이 적절한지는 이어지는 검증 데이터 비교에서 확인
from sklearn.tree import DecisionTreeClassifier

model = DecisionTreeClassifier(
    max_depth=4,          # 질문을 최대 네 단계로 제한
    min_samples_leaf=50,  # 작은 잎으로 과도하게 나뉘는 현상 제한
    random_state=42,
)
model.fit(X_train, y_train)
실행 결과
DecisionTreeClassifier(max_depth=4, min_samples_leaf=50, random_state=42)

검증 데이터로 나무의 깊이 결정

  • max_depth: 학습 전에 정하는 설정값인 하이퍼파라미터(hyperparameter)
  • 깊이 1~12의 나무를 각각 같은 훈련 데이터로 학습하고, 훈련·검증 정확도 비교
  • min_samples_leaf=50과 random_state=42는 고정해 깊이의 차이를 비교
  • score(X, y): 분류나무에서는 주어진 데이터의 정확도 반환
  • 깊이 선택 기준: 검증 정확도가 가장 높은 값. 훈련 정확도는 과대적합 양상을 확인하는 참고 지표
depths = range(1, 13)  # 1부터 12까지 비교; 끝값 13은 포함하지 않음
rows = []
models = {}  # 선택한 깊이의 학습된 나무를 다시 사용

for depth in depths:
    candidate = DecisionTreeClassifier(
        max_depth=depth,
        min_samples_leaf=50,
        random_state=42,
    )
    candidate.fit(X_train, y_train)
    models[depth] = candidate
    rows.append({
        "max_depth": depth,
        "train_accuracy": candidate.score(X_train, y_train),
        "valid_accuracy": candidate.score(X_valid, y_valid),
    })

depth_scores = pd.DataFrame(rows).set_index("max_depth")
depth_scores
실행 결과
           train_accuracy  valid_accuracy
max_depth                                
1                0.678333          0.6780
2                0.700167          0.6860
3                0.721833          0.7235
4                0.735000          0.7230
5                0.745833          0.7355
6                0.753167          0.7095
7                0.756667          0.7165
8                0.756667          0.7180
9                0.759000          0.7185
10               0.759500          0.7170
11               0.759500          0.7170
12               0.759500          0.7170
  • 깊이 5에서 검증 정확도 73.55%로 가장 높은 결과
  • 깊이 6부터는 훈련 정확도가 높아져도 검증 정확도는 깊이 5보다 낮은 상태. 훈련 성능만으로 깊이를 고르면 과대적합 가능
  • 비교한 깊이 1~12와 고정한 다른 설정 안에서의 선택. 모든 데이터에 깊이 5가 적절하다는 의미는 아님

선택한 나무 사용

  • idxmax(): 가장 큰 값이 있는 인덱스 반환. 같은 최대값이 여러 개이면 먼저 나온 작은 깊이 선택
  • 검증 정확도가 가장 높은 깊이의 나무를 model로 지정. 이후 질문 확인과 예측에 이 나무 사용
  • 테스트 데이터는 깊이 선택이 끝난 뒤 예측 결과 확인과 최종 평가에 사용
best_depth = depth_scores["valid_accuracy"].idxmax()
best_depth
실행 결과
5
model = models[best_depth]
model
실행 결과
DecisionTreeClassifier(max_depth=5, min_samples_leaf=50, random_state=42)

학습한 질문 확인

  • export_text(): 학습한 분할 조건을 텍스트로 확인
  • 아래는 앞의 두 질문 단계만 표시. truncated는 뒤의 질문을 생략했다는 뜻
from sklearn.tree import export_text

print(export_text(model, feature_names=features, max_depth=1))
실행 결과
|--- humidity <= 56.85
|   |--- contamination_index <= 69.55
|   |   |--- truncated branch of depth 4
|   |--- contamination_index >  69.55
|   |   |--- truncated branch of depth 4
|--- humidity >  56.85
|   |--- contamination_index <= 38.95
|   |   |--- truncated branch of depth 4
|   |--- contamination_index >  38.95
|   |   |--- truncated branch of depth 4
  • 첫 질문: 상대 습도가 56.85% 이하인지 확인
  • 상대 습도가 낮은 가지에서는 표면 오염 지수 69.55, 높은 가지에서는 38.95를 기준으로 다시 분할
  • 표시된 질문 뒤에도 분할을 이어가고 최종 잎에서 클래스 비율로 예측

클래스와 확률 예측

  • predict(): 최종 클래스 반환
  • predict_proba(): 클래스별 확률 반환. 같은 잎에 도착한 사례는 같은 확률
  • 확률 열의 순서는 classes_로 확인
model.classes_
실행 결과
array([0, 1])
  • 첫 열은 정상(0), 두 번째 열은 불량(1) 확률
  • 테스트 데이터 X_test의 앞 5개 사례로 입력값과 예측 결과 확인
X_test.head()
실행 결과
      exposure_hours  humidity  contamination_index
7317            11.3      22.5                 32.7
491             10.2      36.2                 72.3
49               9.0      77.1                 71.6
9223            11.9      86.2                 46.5
2367            10.5      82.2                 81.0
y_proba = model.predict_proba(X_test)
y_proba[:5]  # 앞의 5개 사례에 대한 클래스별 확률
실행 결과
array([[0.77358491, 0.22641509],
       [0.54676259, 0.45323741],
       [0.27659574, 0.72340426],
       [0.25423729, 0.74576271],
       [0.27659574, 0.72340426]])
y_pred = model.predict(X_test)
y_pred[:5]  # 앞의 5개 사례에 대한 예측 클래스
실행 결과
array([0, 0, 1, 1, 1])
  • 확률과 클래스는 X_test.head()에 표시된 행 순서에 대응
  • 잎의 불량 부품 비율이 더 높은 사례를 불량으로 분류

테스트 정확도

  • 검증 데이터에서 선택한 깊이 5의 나무를 테스트 데이터 2,000개로 최종 평가
  • 테스트 결과를 보고 다시 깊이를 고르지 않음
from sklearn.metrics import accuracy_score

accuracy_score(y_test, y_pred)
실행 결과
0.7315
  • 정확도 73.15%. 불량 부품을 얼마나 찾아냈는지는 정확도만으로 알 수 없으므로 혼동행렬과 함께 평가

퀴즈

문제 1 / 6맞음: 0힌트: 0틀림: 0채점중: 0남음: 6

분류나무의 잎에서 불량 확률은 무엇을 기준으로 추정하나요?

  • ○그 잎에 모인 훈련 사례 중 불량 부품의 비율
  • ○그 잎에 모인 사례의 표면 오염 지수 평균
  • ○전체 테스트 데이터의 불량 부품 비율
  • ○그 잎까지 거친 질문 수의 비율

퀴즈를 풀려면 대화형 기능을 불러와야 합니다.

Previous
학습·검증·테스트 데이터 분할