두 개의 평가 점수기(eval scorers)가 틀린 답변을 맞다고 판정한 이유: 올바른 내용을 언급했기 때문
요약
본 글은 AI 모델 평가 과정에서 두 개의 범용 스코어러(scorer)가 실제 정답을 언급하지 않은 오답을 '올바른 답변'으로 잘못 판정하는 사례를 분석합니다. 이는 부분 문자열 포함 여부나 접미사 일치 여부를 테스트하는 방식의 한계점을 보여줍니다. 개발자는 이 문제를 해결하기 위해 루브릭 기반의 평가 도구를 개발했으며, 형식 거부(format rejection)가 더 비용 효율적일 수 있음을 발견했습니다.
핵심 포인트
- 범용 스코어러는 부분 문자열/접미사 확인에 의존하여 오답을 통과시킬 위험이 있습니다.
- 정확한 평가를 위해서는 루브릭 기반의 엄격하고 결정론적인 스코어러가 필요합니다.
- 형식 거부(format rejection) 방식은 부분 문자열 누락보다 더 비용 효율적일 수 있다는 점을 발견했습니다.
원문은 erikhill.dev에 처음 게시되었습니다. 아래의 수치들은 해당 스코어러가 가져온 리포지토리와 대조하여 확인된 것입니다.
두 개의 평가 스코어러가 정답을 언급했기 때문에 오답을 통과시켰다
답변은 "아니요. 지문이 '예'를 뒷받침하지 않습니다." 였지만, 목표는 '예(Yes)'였습니다. Inspect AI에 포함된 세 가지 결정론적 스코어러 중 두 개가 이 답변을 올바르다고 표시했습니다.
저는 일주일 동안 만들고 있던 도구에 세 번째 외부 루브릭을 추가하던 일요일 밤에 이를 발견했습니다. 이 도구는 하나의 임무를 가지고 있습니다: 루브릭을 받고, 결함이 알려진 자료를 받아, 해당 루브릭이 좋은 작업과 나쁜 작업을 구별할 수 있는지 보고하는 것입니다. 그때쯤 되자 이미 promptfoo와 OpenAI evals에서 각각 한 가지의 발견 사항을 생성했습니다. Inspect는 제어(control) 역할을 해야 했기 때문에, 그 BoolQ 태스크는 양 끝단에 고정된 스코어러를 사용하며 속이기 어렵습니다.
속이기 어렵다는 것이 바로 이 발견이었습니다. 전체 표는 다음과 같습니다. 10개의 실제 BoolQ 검증 행과 각 행당 7개의 완성본(completion) 중, 의도상으로는 5개가 정확하고 2개가 잘못된 경우입니다:
scorer detects false alarms separation
pattern, as shipped 20/20 20/50 +0.60
includes() 10/20 0/50 +0.50
...
출시된 스코어러인 pattern(r"\A[\W_]*(Yes|No)[\W_]*\Z")는 잘못된 것을 아무것도 받아들이지 않습니다. 이 스코어러의 40% 오탐률은 프롬프트가 요구하는 "다른 것은 포함하지 마십시오"라는 지침을 어긴 모든 정확한 답변이 거부될 때 발생합니다. 예를 들어, "네, 왜냐하면 지문이 그렇게 말하기 때문입니다."와 같은 답변이나 "지문이 그것을 뒷받침합니다. 예(Yes)."도 마찬가지입니다. 이것은 문서화된 선택 사항입니다. 이 스코어러는 이유를 invalid_response_format으로 기록하고 모델에게 비용을 청구합니다.
나머지 두 개는 라이브러리의 범용 평가 점수기(general-purpose scorers)이며, 대부분의 사용자 지정 작업에서 사용됩니다. includes()는 문자열을 모두 포함하고 target in completion을 테스트하므로, 'Yes'를 언급하는 'No'도 타겟을 포함합니다. match()는 공백을 제거하고 대소문자를 무시하며 구두점을 제거한 후 기본 위치에서 completion.endswith(target)을 테스트하므로, 우연히 단어로 끝나는 문장도 통과합니다. 아무도 이 두 가지 중 어느 것도 선택하지 않았습니다. 이것들은 부분 문자열 확인(substring check)과 접미사 확인(suffix check)을 수행하는 것입니다.
그러자 도구가 제가 예상치 못한 일을 했습니다. 기본 커버리지 정책은 엄격하여 오탐지(false alarms)가 없어야 하며, 체크포인트는 최소 절반의 결함 샘플을 포착해야 합니다. 이 정책 하에서 includes()는 결함 클래스를 커버하는 점수기로 간주되고, 배송된 점수기는 그렇지 않습니다. 정책은 말한 대로 정확하게 작동했습니다. 또한 제가 계속해서 배우고 있는 핵심 사항을 지적했습니다. 형식 거부(format rejection)가 부분 문자열 누락(substring hole)보다 더 비용이 많이 드는지는 결정의 문제이며, 오직 한 개의 열만 출력하는 도구가 그 결정을 대신 내려주었습니다.
나머지 두 가지 도구 (각 1줄)
promptfoo 자체의 항공사 예제에는 에이전트가 제3자를 거부해야 하는 테스트에 `not-contains
이 도구는 deadcheck라고 불립니다. 이 도구는 코드 형태의 루브릭(rubric)을 입력받습니다 (체크포인트는 술어(predicate)이기 때문에 파일 형식으로 전송할 수 없습니다). 그리고 구성된 정답(ground truth)을 가진 표본 세트와 함께, 각 결함이 의견에 의해 라벨링되는 것이 아니라 구성에 의해 알려진 깨끗한 기본 데이터(clean base)에 하나의 주입(injection)을 추가하여 사용합니다. 이 도구는 각 체크포인트별로 세 가지 숫자를 보고합니다: 분리도(separation), 이는 탐지율(detection rate)에서 오탐률(false-alarm rate)을 뺀 값입니다; 커버리지(coverage)는 루브릭이 주장하는 것이 아니라 표본이 실제로 담고 있는 것을 기준으로 측정됩니다; 그리고 잔여분(residual)은 아무것도 포착하지 못한 클래스로, 각주가 아닌 열로 출력됩니다.
그 모든 것 이전에, 이 도구는 결정론적 동전 던지기(deterministic coin flip)를 포함하여 자료 전체에 걸쳐 일련의 순진한 탐색(naive probes)을 실행하고 바닥 수준(floor)을 출력합니다. 어떤 클래스에서 동전 던지기를 능가할 수 없는 루브릭은 그 클래스를 측정했다고 할 수 없습니다.
제가 가장 신뢰하는 부분은 저를 당황하게 했던 부분입니다. 시드 표본 팩에는 클래스당 네 개의 표본이 들어 있습니다. 파워 체크(power check)는 탐지율을 Wilson 구간으로 변환하며, 4개 중 3개에서 [0.21, 0.94]를 보고하는데, 이는 기준선을 넘지 못합니다. 따라서 이 도구 자체의 예시 루브릭에 대한 인증서에는 '파워 부족(UNDERPOWERED)'이라고 명시되어 있으며, 이를 확인하는 CI 단계는 비차단적(non-blocking)으로 지정되고 그 이유가 라벨링됩니다. 그리고 해결책은 클래스당 16개의 표본을 수동으로 작성하는 것입니다. 저는 술어의 키워드 목록을 넓히기보다는 빨간색으로 남겨두었습니다. 왜냐하면 그것이 이 도구가 이번 주에 이미 두 번 포착했던 과적합(overfit)이었기 때문입니다.
두 번째 당황스러움은 더 저렴했습니다. 패키지에서 배송된 첫 번째 휠(wheel)에는 어댑터가 전혀 없었습니다. 편집 가능한 설치(editable install)는 이를 문제없이 가져왔기 때문에 모든 테스트는 녹색이었지만, 사용자가 설치할 아티팩트(artifact)는 이 게시물 전체의 주제인 모듈이 빠져 있었습니다. 이제 CI 단계가 클린 venv에 휠을 설치하고 체크아웃 외부에서 어댑터를 가져와서 임포트하는 기능이 생겼으며, 이전 패키지 목록에서는 빨간색으로 표시되었고 새로운 목록에서는 녹색으로 표시되었습니다.
도메인 샘플 패키지(Domain specimen packs)는 공개되지 않습니다. 정답 데이터(Ground truth)는 수작업으로 제작되며, 병렬화가 불가능하고 구축하는 데 실제 시간이 소요되는 부분입니다. 도구(tool), 분류 체계(taxonomy), 그리고 시드 패키지(seed pack)는 공개되어 있습니다.
깨뜨리기 (Break it)
주장은 이 것이 루브릭이 실패할 수 있음을 인증한다는 것입니다. 이를 신뢰하는 유일한 정직한 방법은 그것이 거짓인 것을 인증하도록 만드는 것입니다. 제가 시도해 볼 순서대로 네 가지 목표가 있으며, 자세한 내용은 리포지토리의 BREAKING.md에 있습니다:
- 레지스트리가 수락할 수 있는 인증서를 위조합니다. 이 인증서는 모든 증거 파일(evidence file)을 재해시하고 모든 헤드라인을 재계산하며, 이 계열의 마지막 프로토콜은 외부 감사관에 의해 4바이트로 위조되었습니다.
- 아무것도 감지하지 않고 커버리지를 점수화합니다: 검토자가 이를 읽었을 때 눈가림이라고 부를 만한 시드 패키지에서 보정 기준선(calibration floor)을 통과시키는 술어(predicate).
- 결함을 유지하는 기계적 재작성으로 커버된 체크포인트(checkpoint)를 회피합니다. 의역 번호(paraphrase number)는 이미 공개되었으니, 공격 스위트가 0%라고 부르는 것을 찾아보세요.
- 어댑터가 상류 모델을 잘못 읽는다는 것을 보여줍니다. 하나의 반례(counterexample)는 그것이 지지하는 발견을 무효화하며, 이것이 목록에 있는 이유입니다.
재현 가능한 스크립트는 이슈에서 발견되는 것입니다. 그리고 그 발견은 이름이 붙은 테스트를 받습니다.
저는 LLM 시스템을 위한 결정론적 평가 및 검증 도구(deterministic evaluation and verification tooling)를 구축하고 있으며, AI 평가 또는 QA 엔지니어링 분야의 첫 정규직 역할을 찾고 있습니다. 미국 동부 원격 근무 또는 Charleston SC입니다. https://erikhill.dev
도구, 세 개의 어댑터 및 고정값(fixture): https://github.com/egnaro9/deadcheck
위에 제시된 모든 그림은 출판 전에 리포지토리와 대조되었습니다. 이것을 언급할 가치가 있는지에 대한 판단은 저의 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기