1. 어떤 문제를 풀 것인가? (이진 분류 vs 다중 분류)
노트북의 서두는 우리가 풀려는 문제의 종류를 정의하며 시작합니다.
- 다중 분류: 이미지를 주고 "고양이/개/새" 중 하나를 고르라고 하는 것처럼, 범주(정답)의 종류가 여러 개인 문제입니다.
- 이진 분류 (Binary): 특정 클래스인지 아닌지, 즉 정답이 딱 두 개로 나뉘는 문제입니다.
- 양성 (Positive, 1): 우리가 '찾으려는' 표적 대상입니다.
- 음성 (Negative, 0): 우리가 찾으려는 대상이 '아닌' 나머지 전부입니다.
예를 들어, 나중에 개발하실 한국 지역 관광 정보 웹사이트에서 수집한 후기 글이 '광고성 스팸'인지 '진짜 유저 리뷰'인지 자동으로 걸러내는 분류 AI를 만든다고 해봅시다. 이때 우리가 걸러내기 위해 추적하는 표적인 '스팸 메일(리뷰)'이 바로 양성(Positive, 1)이 되는 것입니다!
2. 정확도(Accuracy)의 끔찍한 함정
노트북에서는 분류 문제의 대표 평가지표인 정확도를 설명하면서, 이것이 왜 위험한지 증명하기 위해 MNIST(손글씨 숫자 0~9 이미지) 데이터를 불러옵니다.
import numpy as np
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score
# 1. 데이터 불러오기
digits = load_digits()
X = digits.data
y = digits.target
# 2. 강제로 '불균형 데이터' 만들기 (핵심!)
# 정답(y)이 숫자 9인 것만 1(양성)로 바꾸고, 0~8은 전부 0(음성)으로 만듭니다.
y = np.where(y==9, 1, 0)
이렇게 하면 전체 데이터 중 90%는 음성(0)이고, 10%만 양성(1)인 불균형 데이터(Imbalanced Data)가 됩니다. 여기서 충격적인 코드를 하나 작성합니다.
# 3. 깡통 모델 만들기
# 컴퓨터가 데이터 공부를 아예 안 하고, 그냥 몽땅 다 "0(음성)이야!" 라고 찍게 만듭니다.
y_hat = np.zeros_like(y)
# 4. 정확도 채점
print(accuracy_score(y, y_hat))
결과가 어떻게 나올까요? 무조건 0으로만 찍었는데도 전체 데이터 중 90%가 실제 0이었으므로, 정확도는 90%가 나옵니다. 이 모델을 관광 사이트 리뷰 필터링에 가져가면 "전부 정상 리뷰입니다!" 라고만 외치는 쓸모없는 AI인데 말이죠. 양성은 아예 맞추지 못함에도 불구하고 숫자에 속게 되는 것, 이것이 바로 노트북에서 강조하는 "불균형 데이터에서 정확도만 보면 안 되는 이유" 입니다.
3. 함정을 파훼하는 4가지 관점 (혼동 행렬과 친구들)
그래서 등장한 것이 실제 값(정답)과 예측한 것을 표로 만든 혼동 행렬(Confusion Matrix)입니다. 예측 결과가 몇 개나 맞고 틀렸는지를 4가지 방으로 나눕니다.
- TP (True Positive): 스팸(1)을 스팸(1)이라고 제대로 양성으로 잡음! (최고)
- TN (True Negative): 진짜 리뷰(0)를 진짜 리뷰(0)라고 제대로 음성으로 냅둠!
- FP (False Positive): 진짜 리뷰(0)인데 스팸(1)이라고 잘못 예측함 (위양성)
- FN (False Negative): 스팸(1)인데 진짜 리뷰(0)라고 놓침 (위음성)
이 4개의 숫자를 조합해서 모델의 진짜 성능을 다각도로 평가합니다.
🎯 양성(Positive) 예측력을 측정하는 지표
- 재현율 (Recall / Sensitivity): 실제 양성인 것 중에 모델이 양성으로 예측한 비율입니다.
- 예: 실제 스팸 메일 중에서 모델이 놓치지 않고 몇 %나 잡아냈는가?
- 정밀도 (Precision): 모델이 양성으로 예측한 것 중 실제 양성인 비율입니다.
- 예: 모델이 "이거 스팸이야!"라고 찍은 것 중에서 진짜 스팸의 비율은?
- F1 점수 (F1 Score): 정밀도와 재현율이 한쪽으로 치우치지 않고 둘 다 좋은지 판단하기 위해 사용하는 조화평균 점수입니다.
🛡️ 음성(Negative) 예측력을 측정하는 지표
- 특이도 (Specificity): 실제 음성인 것들 중 모델이 실수 없이 음성으로 맞게 예측한 비율입니다.
- 위양성률 (Fall-out): 실제 음성인데, 모델이 양성이라고 잘못 예측한(FP) 비율입니다. (낮을수록 좋습니다!)
Scikit-learn(sklearn.metrics)에는 이 복잡한 계산을 한 줄로 끝내주는 마법의 도구들이 다 들어있습니다.
from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score, classification_report
# [상황 가정]
# y_test: 실제 정답 (1=스팸 리뷰, 0=정상 리뷰)
# pred: 모델이 예측한 정답 ( model.predict(X_test) 로 나온 결과 )
# ---------------------------------------------------------
# 1. 혼동 행렬 (Confusion Matrix) 그리기
# ---------------------------------------------------------
cm = confusion_matrix(y_test, pred)
print("혼동 행렬:\n", cm)
# 출력 결과 예시:
# [[900 10] <-- [TN(정상 맞춤), FP(정상인데 스팸이라 우김)]
# [ 5 95]] <-- [FN(스팸인데 정상이라 놓침), TP(스팸 맞춤!)]
# ---------------------------------------------------------
# 2. 개별 평가지표 4대장 뽑아보기
# ---------------------------------------------------------
acc = accuracy_score(y_test, pred)
precision = precision_score(y_test, pred)
recall = recall_score(y_test, pred)
f1 = f1_score(y_test, pred)
print(f"정확도(Accuracy): {acc:.4f}")
print(f"정밀도(Precision): {precision:.4f}")
print(f"재현율(Recall): {recall:.4f}")
print(f"F1 점수(F1-Score): {f1:.4f}")
# ---------------------------------------------------------
# 3. [실무 꿀팁 ⭐] 종합 성적표 한 방에 출력하기
# ---------------------------------------------------------
# 실무자들은 위처럼 하나씩 치기 귀찮아서 이 코드 '한 줄'로 끝냅니다!
report = classification_report(y_test, pred)
print("종합 분류 리포트:\n", report)
👨🏫 과외 선생님의 코드 해설 포인트
① confusion_matrix 의 결과물 보는 법 위 코드를 실행하면 표가 아니라 [[900, 10], [5, 95]] 같은 숫자 배열이 툭 튀어나옵니다. 당황하지 마세요!
- 왼쪽 위(TN): 정답 0을 0으로 잘 맞춘 개수 (진짜 리뷰를 냅둠)
- 오른쪽 아래(TP): 정답 1을 1로 잘 맞춘 개수 (스팸 리뷰 잘 잡아냄!)
- 즉, 왼쪽 위에서 오른쪽 아래로 이어지는 대각선 숫자가 크면 클수록 일을 잘한 모델입니다.
② 언제 어떤 코드를 유심히 봐야 할까요?
- precision_score (정밀도): 억울한 피해자가 생기면 안 될 때 중요합니다. (예: 스팸 필터에서 멀쩡한 일반 유저의 정성스러운 리뷰를 스팸으로 걸러버리면 유저가 화나서 탈퇴하겠죠? 이때는 정밀도를 높여야 합니다.)
- recall_score (재현율): 범인을 단 한 명이라도 놓치면 큰일 날 때 중요합니다. (예: 암 진단 AI가 실제 암 환자를 정상으로 오해해서 돌려보내면 생명이 위험해집니다. 이때는 무조건 재현율을 봐야 합니다.)
- f1_score (F1 점수): "아, 둘 다 골고루 좋았으면 좋겠어!" 할 때 보는 점수입니다. 불균형 데이터(정상이 90%, 스팸이 10%인 상황)에서는 '정확도' 대신 F1 점수를 진짜 모델의 실력으로 인정해 줍니다.
③ classification_report (강력 추천!) 이 함수는 정말 효자입니다. 코드 한 줄만 치면 0(음성)을 기준으로 했을 때의 정밀도/재현율, 1(양성)을 기준으로 했을 때의 정밀도/재현율, 그리고 전체 데이터의 개수(support)까지 보기 좋은 표 형태로 쫙 뽑아줍니다. 복습하실 때 이 코드는 무조건 손에 익혀두세요!
⚖️ 4. 임계값(Threshold)의 비밀: 정밀도와 재현율의 시소게임
앞서 정밀도(Precision)와 재현율(Recall)을 배웠죠? 이 둘은 완벽한 시소 관계(Trade-off)입니다. 하나가 올라가면 하나가 떨어지죠. 그 비밀은 컴퓨터가 정답을 고르는 기준인 '임계값(Threshold)'에 있습니다.
컴퓨터는 사실 "이거 스팸이야!"라고 바로 답을 내지 않습니다. "이게 스팸일 확률은 80%야"라고 확률(Probability)을 뱉어냅니다.
보통은 이 확률이 50%를 넘으면 양성(스팸), 안 넘으면 음성(정상)이라고 판단합니다. 이때 기준이 되는 50%를 '임계값'이라고 부릅니다.
- 임계값을 10%로 확 낮춘다면? (경찰의 그물망 넓히기)
- "조금이라도 수상하면(확률 10% 이상) 다 잡아들여!"
- ➡️ 진짜 도둑을 놓칠 일은 거의 없어집니다. (재현율 상승 ⬆️)
- ➡️ 하지만 억울하게 잡혀 온 일반인도 엄청나게 많아지겠죠. (정밀도 하락 ⬇️)
- 임계값을 90%로 확 높인다면? (돌다리도 두들겨 보고 건너기)
- "확실한 증거(확률 90% 이상)가 있는 놈만 잡아!"
- ➡️ 잡혀 온 사람들은 100% 진짜 도둑일 겁니다. (정밀도 상승 ⬆️)
- ➡️ 하지만 교묘한 도둑들은 증거 부족으로 다 놓치게 됩니다. (재현율 하락 ⬇️)
이처럼 임계값을 0%부터 100%까지 이리저리 바꿔가면서 정밀도와 재현율이 어떻게 변하는지 점을 찍어 그린 선이 바로 PR Curve (Precision-Recall Curve)입니다. 그리고 이 곡선 아래의 면적을 구한 점수가 AP Score (Average Precision)입니다. (AP 점수가 1에 가까울수록 아주 훌륭한 모델입니다!)
컴퓨터에게 "무조건 0, 1로 대답하지 말고, 확률로 대답해!"라고 명령하는 predict_proba가 핵심입니다.
from sklearn.metrics import precision_recall_curve, average_precision_score
import numpy as np
# 1. 확률 뽑아내기 (predict 대신 predict_proba 사용!)
# predict_proba는 [음성(0)일 확률, 양성(1)일 확률] 이렇게 두 개를 줍니다.
# 우리는 스팸(양성)일 확률만 필요하니까 [:, 1] 을 써서 뒤쪽 숫자만 쏙 빼옵니다.
pred_proba = model.predict_proba(X_test)[:, 1]
# 2. 내 마음대로 임계값(커트라인) 조절해 보기
# "양성일 확률이 10%(0.1)만 넘어도 무조건 스팸(1)으로 잡아들여!" (경찰 그물망 넓히기)
custom_predict = (pred_proba > 0.1).astype(int)
# 3. AP 점수 (PR Curve의 종합 점수) 채점하기
# 확률값(pred_proba)을 그대로 넣어서 계산합니다. 1.0에 가까울수록 완벽한 모델!
ap_score = average_precision_score(y_test, pred_proba)
print("AP 점수:", ap_score)
📈 5. 분류 모델의 최종 종합 성적표: ROC Curve와 AUC
PR Curve와 비슷한데, 실무에서 더 자주 쓰이는 '분류 모델의 찐 종합 성적표'입니다.
- ROC Curve: 임계값을 바꿔가면서, 진짜 양성을 맞춘 비율(TPR, 재현율)과 가짜인데 양성이라고 우긴 비율(FPR, 위양성률)이 어떻게 변하는지 그린 곡선입니다.
- ROC-AUC Score: 이 ROC 곡선 아래의 면적(Area Under the Curve)을 뜻합니다.
- AUC = 1.0: 완벽한 모델 (신!)
- AUC = 0.5: 동전 던지기 수준의 모델 (찍는 것과 다름없음)
- 즉, AUC 점수는 "이 모델이 양성과 음성을 얼마나 기가 막히게 잘 구분해 내는가?"를 보여주는 가장 강력한 지표입니다.
분류 모델의 '찐' 종합 성적표인 AUC 점수를 구하는 코드입니다. 실무에서 모델 평가할 때 가장 많이 치게 될 코드입니다.
from sklearn.metrics import roc_curve, roc_auc_score
# 1. 분류 모델의 최종 종합 성적표(AUC 점수) 구하기
# 1.0이면 완벽한 신의 모델, 0.5면 동전 던지기(찍기) 수준입니다.
# 여기도 예측값(0, 1)이 아니라 확률값(pred_proba)을 넣어줘야 정확하게 계산됩니다!
auc_score = roc_auc_score(y_test, pred_proba)
print("ROC-AUC 점수:", auc_score)
# ---------------------------------------------
# (참고) 나중에 그래프(ROC Curve)를 그리고 싶을 때 재료를 뽑아주는 코드
# 반환값이 3개입니다. (가짜양성비율, 진짜양성비율, 그때의 임계값들)
fpr, tpr, thresholds = roc_curve(y_test, pred_proba)
📏 6. 회귀(Regression) 평가지표: 집값 예측의 채점 방식
지금까지는 "A냐 B냐"를 맞추는 '분류'였습니다. 하지만 "이 아파트 가격이 얼마일까?"처럼 연속된 숫자를 맞추는 머신러닝을 회귀(Regression)라고 부릅니다.
집값 예측에서는 "3억 1천만 원"이 정답인데 컴퓨터가 "3억 1천 1백만 원"이라고 예측했다고 해서 "틀렸어! 빵점!"이라고 할 순 없겠죠? 그래서 회귀에서는 '정확도(Accuracy)'를 쓰지 않고 '오차(Error, 정답과 얼마나 차이가 나는가?)'를 씁니다.
- MSE (Mean Squared Error - 평균 제곱 오차):
- 정답과 예측값의 차이(오차)를 구한 뒤, 그걸 제곱해서 다 더하고 평균을 냅니다.
- 왜 제곱을 할까요? 오차가 -5(낮게 예측), +5(높게 예측)일 때 그냥 더하면 0이 되어서 "오차가 없네?" 하고 착각할 수 있기 때문입니다. 게다가 틀린 정도가 클수록 가차 없이 엄청난 감점(페널티)을 주기 위해서입니다.
- RMSE (Root Mean Squared Error):
- MSE는 제곱을 해버려서 숫자가 너무 뻥튀기됩니다. (집값 오차를 제곱하면 단위가 '원'에서 '조' 단위로 커져버리죠.) 그래서 다시 원래 단위로 돌려놓기 위해 루트(Root, $\sqrt{}$)를 씌운 것입니다. (당연히 MSE와 RMSE는 숫자가 작을수록 좋은 모델입니다!)
- $R^2$ (R square - 결정계수):
- 오차(Error) 지표들의 단점은 "숫자가 5,000인데... 이게 좋은 건가 나쁜 건가?" 하고 감이 잘 안 온다는 겁니다.
- 그래서 "그냥 다 똑같이 평균값으로 찍었을 때보다 우리 모델이 얼마나 더 똑똑해?"를 0~1 사이의 점수로 환산한 지표입니다. (1에 가까울수록 정답을 완벽하게 설명하는 최고의 모델입니다!)
집값 예측처럼 '연속된 숫자'를 맞출 때 사용하는 채점 공식들입니다. 오차(Error)를 다루므로 여기서는 pred_proba(확률)가 아니라, 그냥 예측한 숫자(pred)를 그대로 씁니다.
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# y_test: 실제 아파트 가격 / pred: 모델이 예측한 가격 (예: model.predict(X_test))
# 1. MSE (평균 제곱 오차)
# 차이를 제곱해 버려서, 에러가 클수록 엄청난 페널티를 주지만 단위가 '원'에서 '조'단위로 뻥튀기됩니다.
mse = mean_squared_error(y_test, pred)
# 2. RMSE (루트 평균 제곱 오차)
# 뻥튀기된 MSE에 넘파이(np)의 루트(sqrt)를 씌워서, 다시 원래 돈 단위로 예쁘게 돌려놓습니다.
# (최근 사이킷런 버전에선 mean_squared_error 안에 squared=False 옵션을 넣어도 됩니다)
rmse = np.sqrt(mse)
# 3. R2 Score (결정계수)
# "그냥 다 평균값으로 찍은 바보 모델보다 우리 모델이 얼마나 똑똑해?"를 0~1 사이로 보여줍니다.
# 1.0에 가까울수록 집값을 기가 막히게 잘 맞추는 훌륭한 모델입니다!
r2 = r2_score(y_test, pred)
print(f"MSE: {mse:.2f}")
print(f"RMSE: {rmse:.2f}")
print(f"R2 Score: {r2:.4f}")
'두두 IT > 파이썬' 카테고리의 다른 글
| [머신런닝 4-1] 데이터 전처리 (0) | 2026.05.14 |
|---|---|
| [머신러닝 1] 머신러닝(ML)개요 (0) | 2026.05.12 |
| [머신러닝 3-2] 데이터셋 분할 & 교차 검증 (0) | 2026.05.12 |
| [머신러닝 3-1] 데이터셋 분리와 모델 검증 (0) | 2026.05.12 |
| [머신런닝 2] 머신러닝 분석 - Iris_분석 (0) | 2026.05.12 |