왜 불확실성이 문제가 되는가

LLM은 모르는 질문에도 자신 있는 문장을 만들어낸다. 이 '자신감'은 토큰 확률과 사실 정확도가 분리되어 있기 때문에 생기는 구조적 현상이다. 고객 응대, 의료·법률 요약, RAG 기반 검색처럼 오답 비용이 큰 도메인에서는 "틀린 답을 하느니 모른다고 답하는" 거절(abstention) 전략이 필요하다. 핵심은 모델의 불확실성을 측정 가능한 신호로 바꾸고, 임계값을 넘으면 응답을 보류하는 파이프라인을 만드는 것이다.

불확실성 신호를 어떻게 얻는가

실무에서 자주 쓰는 신호는 세 가지다. 첫째, 토큰 로그확률(logprobs) 기반 신뢰도. 둘째, 같은 질문을 여러 번 샘플링해 답변이 흩어지는 정도를 보는 self-consistency. 셋째, 별도 검증 모델이나 프롬프트로 근거 유무를 판단하는 verifier 방식이다. 세 신호는 상호 보완적이다.

방법비용지연강점
Logprob 신뢰도낮음없음단일 호출로 계산
Self-consistency높음높음추론형 질문에 강함
Verifier 모델중간중간근거·환각 판별

Logprob 기반 신뢰도 추정

API가 토큰 로그확률을 반환하면, 응답 토큰의 평균 확률을 신뢰도 대리값으로 쓸 수 있다. 낮으면 모델이 흔들린 것이다.

import math
from anthropic import Anthropic

client = Anthropic()

def mean_token_prob(logprobs):
    # logprobs: 각 토큰의 log 확률 리스트
    probs = [math.exp(lp) for lp in logprobs]
    return sum(probs) / len(probs)

# 임계값 아래면 거절
CONFIDENCE_THRESHOLD = 0.55

def answer_or_abstain(logprobs, text):
    conf = mean_token_prob(logprobs)
    if conf < CONFIDENCE_THRESHOLD:
        return "확실하지 않아 답변을 보류합니다."
    return text

주의: logprob은 문법적 유창함까지 반영하므로 사실성과 완전히 일치하지 않는다. 단독 지표로 쓰지 말고 보조 신호로 결합하라.

Self-consistency로 산포 측정

temperature를 올려 N번 샘플링한 뒤 답이 갈리면 불확실한 것으로 본다. 계산·분류처럼 정답이 이산적인 문제에 특히 잘 맞는다.

from collections import Counter

def self_consistency(model_call, question, n=5):
    answers = [model_call(question, temperature=0.8) for _ in range(n)]
    counts = Counter(a.strip() for a in answers)
    top, freq = counts.most_common(1)[0]
    agreement = freq / n          # 0.0 ~ 1.0
    if agreement < 0.6:
        return None, agreement    # 합의 부족 → 거절
    return top, agreement

N이 커질수록 안정적이지만 비용이 선형으로 증가한다. 실시간 경로에는 N=3~5, 배치 검증에는 더 큰 값을 쓴다.

거절 정책과 임계값 튜닝

임계값은 감으로 정하지 말고 라벨링된 검증셋으로 정한다. 정답/오답이 표시된 샘플에서 신뢰도 분포를 뽑아, 원하는 정밀도(precision)를 만족하는 최소 임계값을 고른다. 아래는 검증 로그를 SQL로 분석하는 예다.

-- 신뢰도 구간별 정확도와 거절 시 남는 응답 비율
SELECT
  round(confidence, 1) AS conf_bucket,
  count(*)                         AS n,
  avg(is_correct::int)             AS accuracy,
  sum(count(*)) OVER (ORDER BY round(confidence,1) DESC)
    / (SELECT count(*) FROM eval_logs) AS coverage
FROM eval_logs
GROUP BY 1
ORDER BY 1 DESC;

이 쿼리로 "임계값을 올릴수록 정확도는 오르지만 답하는 비율(coverage)은 준다"는 트레이드오프를 눈으로 확인하고, 도메인 허용 오답률에 맞춰 지점을 고른다.

운영 시 주의점

  • 보정(calibration) 확인: 모델·프롬프트를 바꾸면 신뢰도 분포가 이동한다. 배포 후에도 주기적으로 검증셋에 재측정하라.
  • 거절도 실패다: 무조건 거절하면 안전하지만 무용하다. coverage와 accuracy를 함께 대시보드에 두고 관리한다.
  • 거절 메시지 설계: "모른다"로 끝내지 말고 재질문 유도나 사람 상담 연결 같은 대안 경로를 제공한다.
  • RAG 결합: 검색 근거가 비었을 때 강제 거절하는 규칙을 신뢰도 신호와 별개로 두면 환각을 크게 줄일 수 있다.

결국 불확실성 추정은 단일 기법이 아니라, 신호 수집 → 임계값 튜닝 → 모니터링으로 이어지는 파이프라인이다. 값싼 logprob으로 1차 필터링하고, 애매한 구간만 self-consistency나 verifier로 넘기는 계단식 구조가 비용과 품질의 현실적 균형점이다.