의사결정 나무를 이용한 분류
질문으로 사례를 나누고 잎의 클래스 비율로 불량 여부를 예측하는 분류나무
분류나무의 예측 원리
- 의사결정 나무(decision tree): 입력 변수에 대한 질문으로 사례를 반복해서 나누는 모델
- 회귀나무: 잎에 모인 훈련 사례의 타깃 평균으로 숫자 예측
- 분류나무: 잎에 모인 훈련 사례의 클래스 비율로 확률 추정. 가장 비율이 높은 클래스로 분류
- 예: 한 잎의 훈련 사례 100개 중 불량 부품이 65개면 불량 확률 추정값은 0.65
질문을 고르는 기준
- 후보 질문: 상대 습도가 특정 값 이하인지, 표면 오염 지수가 특정 값 이하인지 등
- 지니 불순도(Gini impurity): 한 집단에 서로 다른 클래스가 얼마나 섞였는지 나타내는 값
- 분할 뒤 두 집단의 크기를 반영한 평균 불순도가 가장 많이 줄어드는 질문 선택
- 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%. 불량 부품을 얼마나 찾아냈는지는 정확도만으로 알 수 없으므로 혼동행렬과 함께 평가
퀴즈
분류나무의 잎에서 불량 확률은 무엇을 기준으로 추정하나요?
퀴즈를 풀려면 대화형 기능을 불러와야 합니다.