모델의 정확도가 95%여도 거짓말을 할 수 있습니다: Calibration, Reliability Diagram, ECE, 그리고 해결 방법
요약
모델의 정확도가 높더라도 예측 확신도가 실제 확률과 일치하지 않는 미교정(Miscalibration) 문제를 다룹니다. Reliability Diagram과 ECE를 통해 이를 측정하고, Platt scaling 및 Isotonic regression을 이용한 해결 방법을 제시합니다.
핵심 포인트
- 정확도(Accuracy)와 교정(Calibration)은 서로 다른 개념임
- Reliability Diagram을 통해 모델의 과잉/과소 확신을 시각화 가능
- ECE(Expected Calibration Error)로 교정 오차를 수치화함
- Platt scaling과 Isotonic regression을 통한 사후 교정 가능
분류기(Classifier)는 정확할 수 있지만, 자신의 확신도(Confidence)에 대해서는 여전히 거짓말을 할 수 있습니다. 잘 훈련된 모델이 "이것이 스팸일 확률이 90%입니다"라고 말할 때, 우리는 모델이 100번 중 90번은 맞기를 바랍니다. 하지만 현대적인 모델들(Boosted trees, Deep nets)은 과잉 확신(Over-confident)하는 것으로 유명합니다. 즉, 모델이 0.9라고 표시한 사례들 중 실제 정답이 양성(Positive)인 경우는 70%에 불과할 수도 있습니다. 정확도(Accuracy)가 "라벨이 맞는가?"를 묻는다면, Calibration(교정)은 "확률(Probability)이 맞는가?"라는 더 어려운 질문을 던집니다. 저는 실제 샘플을 빈(Bin)으로 나누고, 실시간으로 Calibration Error(교정 오차)를 계산하며, 라이브러리 없이 처음부터 두 가지 해결 방법을 적용하는 데모를 제작했습니다. 그 결과는 다음과 같습니다.
Reliability Diagram은 거짓말을 폭로합니다
미교정(Miscalibration)을 드러내는 도구는 Reliability Diagram(신뢰도 도표)입니다. 예측값을 [0, 1] 사이의 빈(Bin)으로 정렬하고, 각 빈에 대해 평균 예측 확률(conf)을 실제 양성 비율(acc)과 비교하여 그래프로 나타냅니다:
def reliability(p, y, n_bins=12):
edges = np.linspace(0, 1, n_bins + 1)
idx = np.clip(np.digitize(p, edges) - 1, 0, n_bins - 1)
...
완벽한 Calibration은 45도 대각선을 따라 움직입니다. 과잉 확신하는 모델은 대각선
아래로 처지며(0.9라고 예측한 값이 실제로는 그만큼 못 미침), 과소 확신하는 모델은 대각선
위로 올라갑니다. 데모의 Sharpness(선명도) 노브를 조절하면 두 상태 사이를 오가며 곡선이 어느 방향으로 휘는지 관찰할 수 있습니다. 이때 정확도는 완전히 변하지 않는데, 이는 오직
확신도에 붙은 숫자들만 움직이기 때문입니다.
ECE: 전체 곡선을 하나의 숫자로 표현하기
Expected Calibration Error (ECE, 기대 교정 오차)는 그 그림을 하나의 값으로 압축합니다. 이는 각 빈과 대각선 사이의 수직 간격을 크기 가중 평균(Size-weighted average)한 값입니다:
def ece(p, y, n_bins=12):
edges = np.linspace(0, 1, n_bins + 1)
idx = np.clip(np.digitize(p, edges) - 1, 0, n_bins - 1)
...
데모가 강조하는 핵심 포인트는 다음과 같습니다: 95% 정확도를 가진 모델이라도 확신도가 거짓말을 한다면 여전히 거대한 ECE를 가질 수 있습니다. 정확도와 Calibration은 서로 다른 축입니다.
두 가지 해결 방법, 모두 단조(Monotone)적임
Calibration(교정)은 사후 처리(post-processing) 과정입니다. 즉, 분류기(classifier)는 그대로 두고, 분류기의 점수(scores)로부터 holdout(홀드아웃) 데이터셋에 기반한 정직한 확률(honest probabilities)로 변환하는 단조(monotone) 사상(map)을 학습하는 것입니다. 두 가지 고전적인 선택지는 다음과 같습니다:
Platt scaling은 log-loss를 최소화함으로써 2-파라미터 시그모이드(sigmoid) 함수 σ(A·logit(p) + B)를 적합(fit)시킵니다. 매끄럽고(smooth) 분산이 낮으며, 왜곡이 실제로 시그모이드 형태를 띠고 데이터가 부족할 때 이상적입니다. 두 개의 노브(knob)만으로는 과적합(overfit)이 크게 일어나지 않기 때문입니다.
Isotonic regression은 Pool-Adjacent-Violators 알고리즘을 통해 어떠한 비감소(non-decreasing) 계단 함수(step function)라도 적합시킵니다. 점수 순으로 정렬한 다음, 값이 오른쪽 이웃보다 큰 인접 블록들을 반복적으로 하나의 가중 평균 블록으로 병합합니다. 그 결과는 자유로운 형태의 단조 계단(monotone staircase)이 됩니다. 이는 Platt scaling이 해결하지 못하는 기묘한 왜곡들을 교정하지만, 더 많은 데이터를 필요로 하며 적합 데이터셋(fit set)이 작을 경우 들쭉날쭉해지는 경향이 있습니다. 데모에서는 두 사상(map)을 나란히 그리고 ECE를 비교합니다. fit-set 슬라이더를 줄이면 Isotonic regression은 뾰족하게 튀는 반면, Platt scaling은 안정적으로 유지되는 것을 볼 수 있습니다.
두 방법 모두 단조(monotone)적이기 때문에 순위(ranking)를 변경하지 않습니다. 따라서 AUC와 (거의 동일한) 정확도(accuracy)는 영향을 받지 않습니다. 오직 확률(probabilities)만이 신뢰할 수 있게 변할 뿐입니다.
정직한 프로토콜 (The honest protocol)
Calibration 결과를 조용히 망치는 실수: 분류기가 이미 본 데이터로 교정기(calibrator)를 학습시키지 마세요. 또한, 교정에 사용한 동일한 데이터로 ECE를 보고하지 마세요. 두 경우 모두 정보가 누출(leak)되어 오차를 과소평가하게 됩니다. 별도의 calibration holdout을 분리하여 그곳에서 학습시키고, 별도의 테스트 세트(test set)에서 ECE를 측정해야 합니다:
cal = fit_platt(p_cal, y_cal) # holdout 데이터로 학습
print("raw ECE:", ece(p_test, y_test))
print("cal ECE:", ece(cal(p_test), y_test)) # 급격히 감소해야 함
실무에서는 scikit-learn의 CalibratedClassifierCV를 사용하세요 (method="sigmoid"는 Platt, "isotonic"은 Isotonic을 의미하며, holdout 기능이 내장되어 있습니다). 딥러닝 네트워크(deep nets)의 경우 표준적인 거의 무료에 가까운 해결책은 temperature scaling입니다. 이는 로짓(logits)을 학습된 하나의 스칼라(scalar) T로 나누는 방식으로, B=0인 Platt scaling의 특수한 사례입니다. 이는 과잉 확신하는(overconfident) 로짓을 완화하면서도 argmax는 유지하므로 (정확도는 고정된 채로) 확률을 교정합니다.
확률이 의사결정을 이끄는 모든 경우 — 의료적 위험 (medical risk), 사기 탐지 임계값 (fraud threshold), 기대 가치 베팅 (expected-value bet) — 에 있어서, 교정 (calibration)은 그 숫자가 말하는 바를 실제로 의미하게 만드는 핵심 요소입니다.
슬라이더를 움직이며 곡선이 직선화되고 ECE가 떨어지는 것을 확인해 보세요:
https://dev48v.infy.uk/ml/day44-calibration.html
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기