🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

분류 모델 평가 완벽 가이드: Accuracy, Precision, Recall, F1 - 슬라이드 1/8
A

AI Generated

2025. 12. 6. · 11 Views

분류 모델 평가 완벽 가이드: Accuracy, Precision, Recall, F1

머신러닝 분류 모델의 성능을 평가하는 핵심 지표들을 알아봅니다. 정확도만으로는 부족한 이유와 각 지표의 의미, 그리고 상황에 맞는 지표 선택법을 배웁니다.


목차

  1. 혼동 행렬의 이해
  2. 정확도의 함정
  3. Precision 정밀도
  4. Recall 재현율
  5. Precision과 Recall의 트레이드오프
  6. F1 Score의 등장
  7. 실전 종합 평가

1. 혼동 행렬의 이해

김개발 씨는 스팸 메일 필터 모델을 만들었습니다. 테스트 결과 정확도가 98%라고 나왔는데, 선배가 고개를 저었습니다.

"정확도만 보면 안 돼요. 혼동 행렬부터 확인해봐요."

**혼동 행렬(Confusion Matrix)**은 분류 모델이 예측한 결과와 실제 정답을 비교하는 표입니다. 마치 시험 채점표처럼, 맞힌 것과 틀린 것을 한눈에 보여줍니다.

이 표를 이해하면 모델이 어디서 실수하는지 정확히 파악할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import confusion_matrix
import numpy as np

# 실제 값: 0은 정상 메일, 1은 스팸
y_true = [0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
# 모델 예측 값
y_pred = [0, 0, 0, 1, 0, 0, 1, 1, 1, 1]

# 혼동 행렬 생성
cm = confusion_matrix(y_true, y_pred)
print("혼동 행렬:")
print(cm)
# [[3, 1],   <- TN=3, FP=1 (정상 메일 중)
#  [2, 4]]   <- FN=2, TP=4 (스팸 메일 중)

김개발 씨는 입사 6개월 차 데이터 사이언티스트입니다. 첫 번째 머신러닝 프로젝트로 스팸 메일 필터를 개발했습니다.

모델 학습을 마치고 테스트 데이터로 평가해보니 정확도가 98%나 나왔습니다. 뿌듯한 마음으로 결과를 공유했는데, 선배 박시니어 씨가 의외의 반응을 보였습니다.

"정확도가 98%라고요? 근데 테스트 데이터에서 스팸 비율이 얼마였어요?" 김개발 씨가 확인해보니 전체 메일 중 스팸은 2%에 불과했습니다.

박시니어 씨가 고개를 끄덕였습니다. "그럼 모든 메일을 정상이라고 예측해도 정확도가 98%가 나오겠네요.

우리가 정말 알고 싶은 건 스팸을 얼마나 잘 잡아내는지 아닌가요?" 바로 이런 상황에서 혼동 행렬이 필요합니다. 혼동 행렬은 마치 학교 성적표와 같습니다.

단순히 "몇 점"이라고만 말하면 어떤 과목을 잘했고 못했는지 알 수 없습니다. 국어, 수학, 영어 각각의 점수를 봐야 진짜 실력을 알 수 있죠.

혼동 행렬도 마찬가지입니다. 전체 정확도라는 하나의 숫자 대신, 네 가지 경우로 나눠서 보여줍니다.

네 가지 경우를 살펴보겠습니다. 먼저 **True Positive(TP)**는 스팸을 스팸이라고 정확히 예측한 경우입니다.

**True Negative(TN)**는 정상 메일을 정상이라고 정확히 예측한 경우입니다. 여기까지는 모델이 맞힌 것들입니다.

문제는 틀린 경우입니다. **False Positive(FP)**는 정상 메일인데 스팸이라고 잘못 예측한 경우입니다.

중요한 업무 메일이 스팸함으로 들어가버리는 상황이죠. **False Negative(FN)**는 스팸인데 정상이라고 잘못 예측한 경우입니다.

악성 스팸이 받은 편지함에 버젓이 들어오는 상황입니다. 위 코드를 살펴보면, confusion_matrix 함수에 실제 값과 예측 값을 넣어줍니다.

결과로 2x2 행렬이 나오는데, 왼쪽 위부터 시계방향으로 TN, FP, TP, FN 순서입니다. 다만 sklearn에서는 행렬의 순서가 조금 다르니 주의가 필요합니다.

실무에서 이 행렬을 보면 모델의 성격을 파악할 수 있습니다. FP가 많으면 "너무 예민한" 모델이고, FN이 많으면 "너무 둔한" 모델입니다.

스팸 필터라면 FN이 많은 것보다 FP가 많은 게 사용자에게 더 불편할 수 있습니다. 반대로 암 진단 모델이라면 FN이 치명적이겠죠.

김개발 씨는 혼동 행렬을 확인하고 나서야 자신의 모델이 스팸을 거의 못 잡고 있다는 사실을 알았습니다. 정확도 98%라는 숫자에 속았던 것입니다.

이제 진짜 성능을 측정할 다른 지표들이 필요해졌습니다.

실전 팁

💡 - 혼동 행렬은 모든 분류 지표의 기초이므로 반드시 먼저 확인하세요

  • 클래스 불균형이 심할수록 혼동 행렬의 중요성이 커집니다

2. 정확도의 함정

김개발 씨는 신용카드 사기 탐지 모델을 만들었습니다. 정확도가 99.9%나 나와서 자신만만했는데, 실제로 배포해보니 사기 거래를 하나도 못 잡았습니다.

도대체 무슨 일이 벌어진 걸까요?

**정확도(Accuracy)**는 전체 예측 중 맞힌 비율입니다. 가장 직관적인 지표이지만, 클래스 불균형 상황에서는 심각한 함정이 있습니다.

마치 야구에서 타율만 보고 선수를 평가하면 안 되는 것처럼, 정확도만으로 모델을 판단하면 큰 실수를 할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import accuracy_score

# 사기 탐지 예시: 1000건 중 사기는 단 1건
y_true = [0] * 999 + [1]  # 999건 정상, 1건 사기
y_pred = [0] * 1000       # 모든 거래를 정상으로 예측

# 정확도 계산
accuracy = accuracy_score(y_true, y_pred)
print(f"정확도: {accuracy:.1%}")  # 99.9%

# 하지만 사기 탐지율은?
tp = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 1)
print(f"탐지한 사기 건수: {tp}건")  # 0건!

신용카드 회사에서 일하는 김개발 씨에게 새로운 프로젝트가 주어졌습니다. 사기 거래를 탐지하는 머신러닝 모델을 만드는 것이었습니다.

열심히 모델을 학습시키고 테스트해보니 정확도가 99.9%나 나왔습니다. "와, 거의 완벽한 모델이네!" 김개발 씨는 자신만만하게 모델을 배포했습니다.

그런데 한 달이 지나도 사기 거래 알림이 하나도 오지 않았습니다. 처음에는 사기가 없어서 그런 줄 알았는데, 나중에 보니 실제 사기 피해가 여러 건 발생했습니다.

모델이 전부 놓친 것입니다. 박시니어 씨가 상황을 설명해주었습니다.

"정확도의 함정에 빠졌네요. 전체 거래 중 사기 비율이 0.1%밖에 안 되잖아요.

그냥 모든 거래를 정상이라고 찍어도 99.9% 정확도가 나와요." 이것이 바로 **정확도 역설(Accuracy Paradox)**입니다. 비유를 들어보겠습니다.

어떤 의사가 있습니다. 이 의사는 모든 환자에게 "당신은 건강합니다"라고 말합니다.

전체 인구의 99%는 실제로 건강하니까, 이 의사의 진단 정확도는 99%입니다. 하지만 이 의사에게 진찰받고 싶은 사람이 있을까요?

정작 아픈 환자는 하나도 찾아내지 못하는 의사입니다. **정확도(Accuracy)**의 공식은 간단합니다.

(TP + TN) / (TP + TN + FP + FN), 즉 전체 예측 중 맞힌 것의 비율입니다. 직관적이고 이해하기 쉽습니다.

하지만 클래스 간 비율이 심하게 불균형할 때는 쓸모가 없어집니다. 위 코드에서 볼 수 있듯이, 1000건의 거래 중 사기는 단 1건입니다.

모델이 그냥 모든 거래를 정상이라고 예측했습니다. 정확도는 99.9%지만, 정작 우리가 찾고 싶었던 사기 거래는 하나도 못 찾았습니다.

그렇다면 정확도는 언제 유용할까요? 클래스 비율이 비슷할 때입니다.

예를 들어 개와 고양이 사진을 분류하는 모델에서 개 사진과 고양이 사진이 비슷하게 있다면, 정확도는 좋은 지표가 됩니다. 실무에서 정확도만 보고 모델을 평가하면 안 되는 대표적인 분야가 있습니다.

사기 탐지, 질병 진단, 결함 검출, 이상 탐지 등입니다. 이런 분야에서는 찾고자 하는 클래스(양성)가 전체의 극히 일부를 차지합니다.

김개발 씨는 뼈아픈 교훈을 얻었습니다. 정확도가 높다고 좋은 모델이 아닙니다.

우리가 정말 잘 맞혀야 하는 것을 잘 맞히는지 확인해야 합니다. 그래서 다음에 배울 Precision과 Recall이 필요한 것입니다.

실전 팁

💡 - 클래스 불균형이 심할수록 정확도를 의심하세요

  • 항상 혼동 행렬과 함께 다른 지표들도 확인하는 습관을 들이세요

3. Precision 정밀도

김개발 씨는 이메일 마케팅 팀에서 "이 고객은 제품을 살 것이다"라고 예측하는 모델을 만들었습니다. 모델이 "살 것이다"라고 예측한 고객에게만 마케팅 전화를 걸기로 했습니다.

그런데 전화했더니 대부분이 관심 없다고 끊어버렸습니다.

**Precision(정밀도)**은 모델이 양성이라고 예측한 것 중 실제로 양성인 비율입니다. 마치 낚시에서 던진 그물에 잡힌 것 중 원하는 물고기의 비율과 같습니다.

"모델이 맞다고 한 것을 얼마나 믿을 수 있는가"를 나타냅니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import precision_score

# 구매 예측 예시
y_true = [1, 0, 1, 1, 0, 0, 1, 0, 0, 0]  # 실제 구매 여부
y_pred = [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]  # 모델 예측

# Precision 계산: 양성 예측 중 실제 양성 비율
precision = precision_score(y_true, y_pred)
print(f"Precision: {precision:.2f}")  # 0.60

# 수동 계산으로 이해하기
tp = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 1)  # 3
fp = sum(1 for t, p in zip(y_true, y_pred) if t == 0 and p == 1)  # 2
print(f"TP: {tp}, FP: {fp}, Precision: {tp/(tp+fp):.2f}")

마케팅 팀에서 새로운 캠페인을 준비 중이었습니다. 예산이 한정되어 있어서, 제품을 살 가능성이 높은 고객에게만 마케팅 전화를 걸기로 했습니다.

김개발 씨가 만든 모델이 "이 고객은 살 것이다"라고 예측한 100명에게 전화를 걸었습니다. 결과는 참담했습니다.

100명 중 실제로 구매한 사람은 30명뿐이었습니다. 나머지 70명은 시간 낭비였습니다.

마케팅 팀장이 불만을 표했습니다. "모델이 '산다'고 한 사람 중에 실제로 사는 사람이 너무 적어요!" 바로 이때 필요한 지표가 **Precision(정밀도)**입니다.

비유를 들어보겠습니다. 금속 탐지기를 들고 해변에서 보물을 찾는다고 상상해보세요.

탐지기가 삐삐 소리를 내서 파보면, 진짜 금반지일 수도 있고 그냥 깡통일 수도 있습니다. Precision은 "탐지기가 울린 곳을 파봤을 때 진짜 보물이 나오는 비율"입니다.

공식으로 표현하면 **Precision = TP / (TP + FP)**입니다. 분자는 진짜 양성(True Positive)이고, 분모는 양성이라고 예측한 모든 것(TP + FP)입니다.

다시 말해, 모델이 "양성이다!"라고 외친 것들 중에서 실제로 양성인 비율입니다. 위 코드를 보면, 10명의 고객 데이터가 있습니다.

모델은 5명을 "살 것이다"라고 예측했습니다(y_pred에서 1인 것들). 그 5명 중 실제로 산 사람(y_true도 1인 사람)은 3명입니다.

따라서 Precision은 3/5 = 0.60, 즉 60%입니다. Precision이 중요한 상황이 있습니다.

False Positive의 비용이 클 때입니다. 몇 가지 예를 들어보겠습니다.

스팸 메일 필터에서 FP는 중요한 메일을 스팸함으로 보내는 것입니다. 업무상 중요한 메일을 놓칠 수 있으니 심각한 문제입니다.

법원에서 유죄 판결의 FP는 무고한 사람을 감옥에 보내는 것입니다. 끔찍한 일이죠.

추천 시스템에서 FP는 관심 없는 상품을 추천하는 것입니다. 사용자 경험이 나빠집니다.

반면 Precision만 높이려고 하면 부작용이 있습니다. 모델이 아주 확실한 경우에만 양성으로 예측하게 됩니다.

결과적으로 많은 실제 양성을 놓치게 됩니다. "확실한 것만 말할게요, 대신 많이는 못 찾아요"라는 상태가 됩니다.

김개발 씨는 Precision을 개선하기 위해 모델의 임계값을 높였습니다. 이제 정말 확실한 경우에만 "살 것이다"라고 예측합니다.

Precision은 80%로 올랐지만, 예측하는 고객 수가 크게 줄었습니다. 이 trade-off를 어떻게 해결해야 할까요?

다음에 배울 Recall과 함께 고민해봐야 합니다.

실전 팁

💡 - False Positive의 비용이 클 때 Precision을 중점적으로 확인하세요

  • Precision만 높이면 예측을 너무 보수적으로 하게 되어 많은 양성을 놓칠 수 있습니다

4. Recall 재현율

병원에서 암 검진 AI를 도입했습니다. 이 AI의 Precision은 95%로 매우 높았습니다.

하지만 실제 암 환자 100명 중 30명만 "암입니다"라고 진단했고, 나머지 70명은 놓쳤습니다. 의료진은 경악했습니다.

**Recall(재현율)**은 실제 양성 중에서 모델이 양성으로 정확히 예측한 비율입니다. 마치 숨어있는 범인을 모두 찾아내는 것과 같습니다.

"진짜 양성을 얼마나 빠짐없이 찾아내는가"를 나타냅니다. 민감도(Sensitivity)라고도 불립니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import recall_score

# 암 진단 예시
y_true = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]  # 실제: 5명 암 환자
y_pred = [1, 1, 0, 0, 0, 0, 0, 0, 0, 0]  # 예측: 2명만 암으로 진단

# Recall 계산: 실제 양성 중 예측한 비율
recall = recall_score(y_true, y_pred)
print(f"Recall: {recall:.2f}")  # 0.40

# 수동 계산으로 이해하기
tp = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 1)  # 2
fn = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 0)  # 3
print(f"TP: {tp}, FN: {fn}, Recall: {tp/(tp+fn):.2f}")

대학병원에서 AI 기반 암 검진 시스템을 도입하기로 했습니다. 여러 모델을 비교 평가하던 중, 가장 Precision이 높은 모델 A를 선택했습니다.

모델 A가 "암입니다"라고 진단하면 95%가 실제로 암이었습니다. 매우 신뢰할 수 있는 진단이었죠.

하지만 실제로 배포하고 나서 문제가 드러났습니다. 실제 암 환자 100명 중 30명만 발견했습니다.

나머지 70명은 "정상입니다"라는 진단을 받고 집으로 돌아갔습니다. 이 70명은 암이 더 진행된 후에야 발견되어 치료 시기를 놓쳤습니다.

이 비극적인 상황에서 의료진이 깨달은 것이 **Recall(재현율)**의 중요성입니다. 비유를 들어보겠습니다.

경찰이 범인 10명을 잡아야 하는 상황입니다. 경찰 A는 신중해서 확실한 증거가 있는 3명만 체포했고, 3명 모두 진범이었습니다(Precision 100%).

경찰 B는 적극적으로 10명을 체포했고, 그 중 8명이 진범이었습니다(Precision 80%). 어떤 경찰이 더 잘한 걸까요?

Precision 관점에서는 경찰 A가 낫지만, 사회 안전 관점에서는 경찰 B가 낫습니다. 경찰 A는 7명의 범인을 놓쳤고, 경찰 B는 2명만 놓쳤으니까요.

Recall은 "범인을 얼마나 빠짐없이 잡았는가"를 측정합니다. 공식으로 표현하면 **Recall = TP / (TP + FN)**입니다. 분자는 진짜 양성을 맞힌 것(True Positive)이고, 분모는 실제 양성 전체(TP + FN)입니다.

FN은 실제론 양성인데 음성으로 잘못 예측한 것, 즉 "놓친 것"입니다. 위 코드에서 실제 암 환자는 5명입니다.

모델은 그 중 2명만 암으로 진단했습니다. 따라서 Recall은 2/5 = 0.40, 즉 40%입니다.

암 환자의 60%를 놓친 셈입니다. Recall이 특히 중요한 상황이 있습니다.

False Negative의 비용이 클 때입니다. 암 진단에서 FN은 암 환자를 정상이라고 진단하는 것입니다.

환자의 생명이 위험해집니다. 사기 탐지에서 FN은 사기 거래를 정상이라고 판단하는 것입니다.

금전적 피해가 발생합니다. 제조업 결함 검출에서 FN은 불량품을 정상이라고 판단하는 것입니다.

리콜 사태로 이어질 수 있습니다. 하지만 Recall만 높이려고 하면 역시 문제가 생깁니다.

모델이 조금만 의심되면 전부 양성으로 예측하게 됩니다. 결과적으로 정상인 것도 양성으로 예측하는 경우가 많아집니다.

"일단 다 잡고 봅시다, 대신 오해도 많아요"라는 상태가 됩니다. 의료진은 깨달았습니다.

암 검진에서는 Precision보다 Recall이 더 중요합니다. "암인데 정상이라고 하는 것"보다 "정상인데 암 의심이라고 하는 것"이 그나마 낫습니다.

후자는 추가 검사를 하면 되지만, 전자는 돌이킬 수 없는 결과를 낳기 때문입니다.

실전 팁

💡 - 놓치면 심각한 결과가 생기는 경우(의료, 안전, 보안) Recall을 중시하세요

  • Recall만 높이면 오탐(False Positive)이 많아지는 trade-off가 있습니다

5. Precision과 Recall의 트레이드오프

김개발 씨는 모델의 임계값을 조정하면서 이상한 현상을 발견했습니다. Precision을 올리면 Recall이 떨어지고, Recall을 올리면 Precision이 떨어집니다.

마치 시소 같았습니다. 왜 이런 일이 일어나는 걸까요?

Precision과 Recall은 트레이드오프(trade-off) 관계에 있습니다. 하나를 높이면 다른 하나가 낮아지는 경향이 있습니다.

마치 그물코의 크기와 같습니다. 그물코를 촘촘히 하면 원하는 물고기를 놓치진 않지만 잡동사니도 많이 잡히고, 그물코를 크게 하면 원하는 물고기만 잡지만 많이 놓칩니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import precision_recall_curve
import numpy as np

# 모델의 예측 확률과 실제 레이블
y_true = [0, 0, 1, 1, 1, 0, 1, 0, 1, 1]
y_scores = [0.1, 0.2, 0.35, 0.4, 0.5, 0.55, 0.7, 0.75, 0.85, 0.95]

# 다양한 임계값에서의 Precision, Recall 계산
precisions, recalls, thresholds = precision_recall_curve(y_true, y_scores)

# 임계값별 결과 출력
for i in range(0, len(thresholds), 2):
    print(f"임계값 {thresholds[i]:.2f}: "
          f"Precision={precisions[i]:.2f}, Recall={recalls[i]:.2f}")

김개발 씨는 스팸 필터 모델의 성능을 조정하고 있었습니다. 모델은 각 이메일에 대해 "스팸일 확률"을 0부터 1 사이의 숫자로 출력합니다.

문제는 어떤 확률 이상일 때 스팸으로 분류할지, 즉 **임계값(threshold)**을 정하는 것이었습니다. 임계값을 0.3으로 낮추니까 스팸을 거의 다 잡았습니다(Recall 높음).

하지만 정상 메일도 많이 스팸함에 들어갔습니다(Precision 낮음). 임계값을 0.9로 높이니까 스팸이라고 분류한 것은 거의 다 진짜 스팸이었습니다(Precision 높음).

하지만 많은 스팸이 받은 편지함에 들어왔습니다(Recall 낮음). 이것이 바로 Precision-Recall 트레이드오프입니다.

비유를 들어보겠습니다. 공항 보안 검색대를 생각해보세요.

검색 기준을 매우 엄격하게 하면 위험물을 가진 사람을 거의 다 잡습니다(Recall 높음). 하지만 무고한 승객도 많이 잡아서 검사합니다(Precision 낮음).

비행기가 계속 지연됩니다. 반대로 검색 기준을 느슨하게 하면 통과가 빨라집니다(Precision 높음).

하지만 위험물을 놓칠 수 있습니다(Recall 낮음). 왜 이런 현상이 일어날까요?

모델이 출력하는 확률값의 분포를 생각해보면 이해가 됩니다. 실제 양성 샘플들은 대체로 높은 확률값을, 실제 음성 샘플들은 대체로 낮은 확률값을 받습니다.

하지만 완벽하게 분리되지는 않고, 중간 영역에서 겹칩니다. 임계값을 이 겹치는 영역에 설정해야 하는데, 어디에 설정하느냐에 따라 결과가 달라집니다.

임계값을 낮추면 더 많은 양성을 잡지만(Recall 증가), 음성도 같이 잡힙니다(Precision 감소). 임계값을 높이면 확실한 양성만 잡지만(Precision 증가), 불확실한 양성은 놓칩니다(Recall 감소).

위 코드에서 precision_recall_curve 함수는 다양한 임계값에서의 Precision과 Recall을 계산해줍니다. 결과를 그래프로 그리면 왼쪽 위에서 오른쪽 아래로 내려가는 곡선이 나타납니다.

이것이 PR 곡선입니다. 그렇다면 어떤 임계값을 선택해야 할까요?

정답은 없습니다. 비즈니스 상황에 따라 다릅니다.

스팸 메일 필터라면? 중요한 메일을 놓치면 안 되니까 Precision을 조금 높게 유지합니다.

암 검진이라면? 환자를 놓치면 안 되니까 Recall을 높게 유지합니다.

추천 시스템이라면? 둘 다 어느 정도 필요하니까 균형점을 찾습니다.

김개발 씨는 마케팅 팀과 논의했습니다. 전화 비용을 고려하면 Precision이 중요하지만, 놓치는 고객도 아깝습니다.

결국 Precision 70%, Recall 60% 정도의 균형점을 선택했습니다. 하지만 이 두 숫자를 하나로 표현할 수는 없을까요?

그래서 F1 Score가 등장합니다.

실전 팁

💡 - 임계값 조정으로 Precision과 Recall의 균형을 맞출 수 있습니다

  • PR 곡선을 그려보면 모델의 전반적인 성능을 파악할 수 있습니다

6. F1 Score의 등장

김개발 씨는 두 개의 모델을 비교해야 했습니다. 모델 A는 Precision 90%, Recall 50%이고, 모델 B는 Precision 70%, Recall 70%입니다.

어떤 모델이 더 좋은 걸까요? 한 숫자로 비교할 수 없을까요?

F1 Score는 Precision과 Recall의 조화 평균입니다. 두 지표를 하나의 숫자로 종합하여, 둘 다 높아야 높은 점수가 나옵니다.

마치 학교에서 국어와 수학의 평균 점수로 전체 성적을 매기는 것과 비슷합니다. 단, 산술 평균이 아닌 조화 평균이라 한쪽이 낮으면 점수가 크게 떨어집니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import f1_score, precision_score, recall_score

# 두 모델 비교
y_true = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]

# 모델 A: 보수적 (높은 Precision, 낮은 Recall)
y_pred_a = [1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
# 모델 B: 균형잡힌
y_pred_b = [1, 1, 1, 1, 0, 1, 0, 0, 0, 0]

for name, y_pred in [("모델 A", y_pred_a), ("모델 B", y_pred_b)]:
    p = precision_score(y_true, y_pred)
    r = recall_score(y_true, y_pred)
    f1 = f1_score(y_true, y_pred)
    print(f"{name}: Precision={p:.2f}, Recall={r:.2f}, F1={f1:.2f}")

회의실에서 김개발 씨와 동료들이 모델 선택을 두고 토론 중이었습니다. 테이블 위에는 두 모델의 성능표가 놓여 있었습니다.

"모델 A가 Precision이 높으니까 더 좋은 거 아니에요?" "아니, Recall이 50%밖에 안 되잖아요. 절반을 놓치는 건데요." "그럼 모델 B요?

Precision이 70%밖에 안 되는데..." 논쟁이 끝나지 않았습니다. 박시니어 씨가 한마디 했습니다.

"F1 Score로 비교해보세요." F1 Score는 Precision과 Recall을 하나의 숫자로 종합하는 지표입니다. 공식은 다음과 같습니다.

F1 = 2 * (Precision * Recall) / (Precision + Recall) 왜 단순 평균이 아니라 조화 평균을 사용할까요? 비유를 들어보겠습니다.

서울에서 부산까지 가는데, 갈 때는 시속 100km, 올 때는 시속 50km로 달렸습니다. 평균 속도가 75km/h일까요?

아닙니다. 전체 거리를 전체 시간으로 나눠야 합니다.

실제 평균 속도는 약 67km/h입니다. 이것이 조화 평균입니다.

조화 평균의 특징은 낮은 쪽에 민감하다는 것입니다. Precision이 90%고 Recall이 10%면, 산술 평균은 50%지만 F1은 18%에 불과합니다.

둘 다 높아야 F1이 높아집니다. 위 코드의 결과를 보겠습니다.

모델 A는 Precision 1.00, Recall 0.40으로 F1이 0.57입니다. 모델 B는 Precision 0.80, Recall 0.80으로 F1이 0.80입니다.

F1 Score로 보면 모델 B가 더 좋은 모델입니다. F1 Score가 특히 유용한 상황이 있습니다.

첫째, Precision과 Recall 중 하나를 희생할 수 없을 때입니다. 둘 다 어느 정도 높아야 하는 상황이죠.

둘째, 여러 모델을 비교할 때 하나의 지표로 순위를 매겨야 할 때입니다. 셋째, 클래스 불균형 상황에서 정확도 대신 사용할 때입니다.

하지만 F1 Score에도 한계가 있습니다. Precision과 Recall을 동등하게 취급하기 때문에, 둘 중 하나가 더 중요한 상황에서는 적합하지 않습니다.

암 진단처럼 Recall이 훨씬 중요한 경우, F1이 높아도 Recall이 낮으면 문제가 됩니다. 이런 경우를 위해 F-beta Score가 있습니다.

beta 값을 조정해서 Recall에 더 가중치를 주거나(beta > 1), Precision에 더 가중치를 줄 수 있습니다(beta < 1). F1은 beta가 1인 특수한 경우입니다.

김개발 씨는 결국 모델 B를 선택했습니다. F1 Score가 더 높았고, 비즈니스 요구사항에도 Precision과 Recall이 비슷하게 중요했기 때문입니다.

하지만 모든 상황에서 F1이 정답은 아닙니다. 상황에 맞는 지표를 선택하는 것이 중요합니다.

실전 팁

💡 - F1 Score는 Precision과 Recall이 비슷하게 중요할 때 사용하세요

  • 한쪽이 더 중요하면 F-beta Score나 해당 지표를 직접 확인하세요

7. 실전 종합 평가

드디어 김개발 씨의 모델이 실제 서비스에 배포될 날이 다가왔습니다. 최종 점검을 위해 모든 지표를 종합적으로 확인해야 합니다.

sklearn의 classification_report 하나면 모든 것을 한눈에 볼 수 있습니다.

classification_report는 분류 모델의 성능을 종합적으로 보여주는 함수입니다. Precision, Recall, F1 Score를 클래스별로 보여주고, 전체 평균까지 제공합니다.

마치 건강검진 결과표처럼, 모델의 상태를 한눈에 파악할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import classification_report, confusion_matrix
import numpy as np

# 실제 서비스 데이터 시뮬레이션
np.random.seed(42)
y_true = np.array([0]*900 + [1]*100)  # 불균형 데이터
y_pred = np.array([0]*850 + [1]*50 + [0]*20 + [1]*80)

# 혼동 행렬
print("=== 혼동 행렬 ===")
print(confusion_matrix(y_true, y_pred))

# 종합 리포트
print("\n=== Classification Report ===")
print(classification_report(y_true, y_pred,
                            target_names=['정상', '이상']))

배포 전날, 김개발 씨는 긴장된 마음으로 최종 점검을 하고 있었습니다. 박시니어 씨가 다가와 물었습니다.

"지표들 다 확인해봤어요?" 김개발 씨는 고개를 끄덕였습니다. "네, 하나씩 다 계산해봤는데요, 한눈에 보기가 어려워서..." 박시니어 씨가 웃으며 말했습니다.

"classification_report 써봤어요? 한 줄이면 다 나와요." classification_report는 sklearn이 제공하는 종합 리포트 함수입니다.

Precision, Recall, F1을 클래스별로 보여주고, 여러 종류의 평균도 함께 계산해줍니다. 위 코드의 결과를 해석해보겠습니다.

먼저 혼동 행렬을 보면 정상 900건 중 850건을 맞혔고(TN), 50건을 이상이라고 잘못 예측했습니다(FP). 이상 100건 중 80건을 맞혔고(TP), 20건을 정상이라고 잘못 예측했습니다(FN).

classification_report는 이를 바탕으로 각종 지표를 계산합니다. 정상 클래스의 Precision, Recall, F1과 이상 클래스의 Precision, Recall, F1이 각각 표시됩니다.

리포트 하단에는 세 가지 평균이 있습니다. macro avg는 클래스별 지표를 단순 평균한 것입니다.

클래스 크기에 관계없이 각 클래스를 동등하게 취급합니다. weighted avg는 클래스별 샘플 수로 가중 평균한 것입니다.

샘플이 많은 클래스가 더 큰 영향을 미칩니다. support는 각 클래스의 실제 샘플 수입니다.

이 숫자를 보면 클래스 불균형이 얼마나 심한지 알 수 있습니다. 위 예시에서는 정상이 900건, 이상이 100건으로 9:1 불균형입니다.

실무에서 리포트를 읽는 요령이 있습니다. 먼저 support를 확인해서 클래스 불균형 정도를 파악합니다.

그 다음 관심 있는 클래스(보통 소수 클래스)의 Precision, Recall, F1을 중점적으로 봅니다. 마지막으로 macro avg와 weighted avg의 차이를 확인합니다.

차이가 크면 클래스별 성능 편차가 크다는 의미입니다. 김개발 씨는 리포트를 확인하고 안도의 한숨을 쉬었습니다.

이상 탐지 클래스의 Recall이 80%로, 대부분의 이상을 잡아내고 있었습니다. Precision도 61%로 나쁘지 않았습니다.

완벽하진 않지만, 비즈니스 요구사항을 충족하는 수준이었습니다. 박시니어 씨가 마지막 조언을 했습니다.

"배포 후에도 지표를 계속 모니터링해야 해요. 실제 데이터 분포가 바뀌면 성능도 달라질 수 있거든요."

실전 팁

💡 - classification_report의 support 열로 클래스 불균형을 파악하세요

  • macro avg와 weighted avg의 차이가 크면 클래스별 성능 편차가 크다는 신호입니다
  • 배포 후에도 정기적으로 지표를 모니터링하는 것이 중요합니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#MachineLearning#Classification#Metrics#sklearn#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.