본문 바로가기
개발/Python

Threshold 조정에 따른 Precision과 Recall 결과

by 피로물든딸기 2025. 9. 8.
반응형

전체 링크

 

Confusion Matrix

  Positive (예측) Negative (예측)  
Positive (실제) TP FN TP + FN = 실제 양성인 데이터

TP / (TP + FN) = Recall
→ TPR
----------------------------------
FN
/ (TP + FN)
→ 제 2종 오류 β
Negative (실제) FP TN FP + TN = 실제 음성인 데이터

TN / (FP + TN) = Specificity
----------------------------------
FP
/ (FP + TN) = FPR
→ 제1종 오류 α
  TP + FP = 양성으로 예측한 데이터
TP / (TP + FP) = Precision
   

 

TP : 예측을 True / 실제도 True

FN : 예측을 False / 실제는 True

FP : 예측을 True / 실제는 False

TN : 예측을 False / 실제도 False

 

정밀도 - 예측이 Positive인 것 중 실제 Positive 비율

 

재현율 - 실제 Positive 중 올바르게 예측한 비율 (민감도, Sensitivity, TPR = 참 긍정률)

 

특이도 - 실제 Negative 중 올바르게 예측한 비율 (TNR)

 

거짓 긍정률 - 실제 Negative 중 잘못 Positive로 예측한 비율 (FPR)

 

 

제1종 오류 확률 α : 유의수준, False Positive의 가능성 

→ FP / (FP + TN)

FPR

1 - 특이도

→ 실제 Negative를 Positive로 잘못 예측한 비율

 

제2종 오류 확률 β : False Negative 가능성

→ FN / (TP + FN)

FNR

1 - 재현율

실제 Positive를 Negative로 잘못 예측한 비율

 

1 - α : TN / (FP + TN) → 특이도

1 - β : 검정력 (Power) = 올바르게 H0를 기각할 확률 →  TP / (TP + FN)Recall (민감도)

 

제1종 오류 → 기존 주장이 무너지는 것이므로 더 큰 문제가 발생할 수 있음.


아래 데이터에 대해 로지스틱 회귀 결과를 확인해 보자.

import pandas as pd
import numpy as np

np.random.seed(1234)

n_samples = 300
X1 = np.random.normal(0, 1, n_samples)
X2 = np.random.normal(1, 2, n_samples)

# 로지스틱 회귀를 위한 이진 타겟 생성
y = (X1 + X2 + np.random.normal(0, 1, n_samples) > 1).astype(int)

df = pd.DataFrame({
    "X1": X1,
    "X2": X2,
    "y": y
})

df

 

먼저 데이터를 분리한다.

from sklearn.model_selection import train_test_split

X = df[["X1", "X2"]]
y = df["y"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1234)

 

train으로 학습한 후, test 데이터에 대해 precisionrecall을 출력하면 다음과 같다.

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score

model = LogisticRegression(solver='liblinear')
model.fit(X_train, y_train)

y_true = y_test
y_pred = model.predict(X_test)

precision_score(y_true, y_pred), recall_score(y_true, y_pred)


로지스틱 회귀는 predict_proba로 각 클래스 별 확률을 구할 수 있다.

위의 결과 predict확률이 50% 이상인 경우 1로 분류하고, 그렇지 않으면 0으로 분류한 결과다.

 

predict_proba에서 클래스 1일 확률만 얻은 후, 0.5보다 큰 값을 1이라고 한 것과 결과가 같은 것을 알 수 있다.

y_true = y_test
y_proba = model.predict_proba(X_test)[:, 1] # 클래스 1일 확률

y_pred = (y_proba > 0.5) * 1 # 50% 이상인 경우만 클래스 1로 판단

precision_score(y_true, y_pred), recall_score(y_true, y_pred)

 

기준이되는 확률(= threshold)을 50%보다 더 높이거나 낮추면 precision과 recall의 값이 조정된다.

 

precision이 중요한 상황

- False Positive(FP, 음성을 양성으로 잘못 분류)를 최소화해야 하는 상황

- 양성이라고 판정할 때, 정말 맞아야 하는 상황

- 법적/사법적 시스템: 범죄자 탐지, 부정 행위 적발 → 무고한 사람을 잘못 잡으면 큰 문제.
- 추천 시스템: 고객에게 상품 추천 → 쓸데없는 걸 추천하면 사용자 불만이 커짐.
- 광고 클릭 예측: 클릭 안 할 사람에게 광고를 계속 보여주면 비용 낭비.
- 채용 자동화 시스템: 적합하지 않은 사람을 합격시키는 것보다, 적합한 사람을 걸러내는 게 낫다고 판단될 때.

threshold를 높여서 더 확실한 경우에만 양성으로 판단한다.

 

recall이 중요한 상황

- False Negative(FN, 양성을 음성으로 잘못 분류)를 최소화해야 하는 상황

- 놓치면 안 되는 양성을 최대한 잡아내는 게 중요한 상황

- 의료 진단: 암, 전염병, 희귀병 같은 경우 → 환자를 놓치면 치명적임. (위양성은 추가 검사로 걸러낼 수 있음)
- 보안/위험 탐지: 폭발물 탐지, 스팸/피싱 탐지, 불법 거래 탐지 → 위험한 걸 놓치면 큰 피해.
- 재난 경보 시스템: 지진, 화재, 침수 등 위험 감지 → 놓치면 사람 목숨에 직결.

→ threshold를 낮춰서 양성일 가능성이 조금이라도 있으면 양성으로 판단한다.

→ 모두 Positive로 예측하면 FN이 0이 된다.

 

실제로 threshold를 0부터 0.9까지 바꿔보면 precision은 증가하고, recall은 감소하는 것을 알 수 있다.

for i in range(0, 10):
    y_pred = (y_proba > (i / 10)) * 1

    a = precision_score(y_true, y_pred)
    b = recall_score(y_true, y_pred)
    
    print(f"threshold : {i / 10}", f"precision : {a:.6f}", f"recall : {b:.6f}")

 

Threshold를 높이면 → FP 감소 → Precision ↑, TP도 줄어들 수 있음 → Recall ↓

Threshold를 낮추면 → TP ↑ → Recall ↑, FP도 늘어날 수 있음 → Precision ↓


위의 결과를 바탕으로 캐글에서 각 점수를 높이는 전략을 각각 세울 수 있다.

 

Precision (정밀도)

- 모델이 Positive라고 예측한 것 중 실제로 맞은 비율

 

  • Threshold 높이기 : 0.7, 0.8 같은 임계값 적용 → 확실히 Positive일 때만 Positive로 분류
  • Feature selection : 잡음(노이즈) 피처 제거 → FP 줄이기
  • 이상치 제거 : 잘못된 라벨, 애매한 데이터 정리
  • 앙상블에서 precision 높은 모델 가중치↑
  • 데이터 불균형 문제 해결 : FP 줄이는 방향으로 학습 (예: hard negative mining)

 

Recall (재현율)

- 실제 정답(Positive) 중에서 모델이 맞게 찾아낸 비율

 

  • Threshold 낮추기 : 0.5보다 작은 임계값을 적용하면 더 많은 데이터를 Positive로 분류 → FN 줄고 Recall ↑
  • 클래스 가중치 조정 : class_weight="balanced" 같은 옵션 사용 → 소수 클래스 잘 잡음
  • 데이터 증강/오버샘플링 : SMOTE 같은 기법으로 소수 클래스 샘플 늘리기
  • 언더샘플링 : 다수 클래스 줄여서 소수 클래스 놓치지 않게 하기
  • 앙상블에서 소수 클래스 민감 모델 가중치↑ : recall이 높은 모델을 여러 개 합쳐서 boost

 

F1 Score (조화평균)

- Precision과 Recall의 균형

 

  • Threshold 튜닝 (Precision-Recall trade-off 최적점 찾기)
    → PR Curve에서 F1이 최대가 되는 지점 선택
  • 데이터 증강 + Feature engineering : Recall과 Precision을 동시에 개선
  • 앙상블 기법 (Voting/Stacking) : 서로 다른 모델의 장점을 결합
  • Cross-validation 기반 hyperparameter tuning : overfitting 방지 + 균형 성능 유지

 

Macro F1 Score (클래스 불균형 고려)

- 각 클래스별 F1 Score를 계산한 후 평균낸 값 (클래스별 가중치 동일)

 

  • 클래스 불균형 해소 : 소수 클래스 oversampling, 데이터 증강
  • 클래스 가중치 부여 : class_weight 조정
  • 다중 클래스 분류기 튜닝 : 각 클래스에서 고르게 성능 나오게 만들기
  • One-vs-Rest 학습 : 각 클래스에 대해 개별적으로 최적화
  • Threshold per class : 클래스별 decision threshold 조정
반응형

'개발 > Python' 카테고리의 다른 글

군집화 비교  (0) 2025.09.08
MAE / MAPE / MSE / RMSE / MSLE / R2  (0) 2025.09.08
다항회귀모형 (PolynomialFeatures)  (1) 2025.08.21
F-검정 통계량 문제 예시  (0) 2025.08.21
포아송 분포 문제 예시  (0) 2025.08.21

댓글