LLM 엔지니어링20장 · LLM 평가와 모니터링15 / 22← 이전목차다음 →MetaContents

20장. LLM 평가와 모니터링

실습 2 — 뜻으로 채점하기

실습 1의 답을 참조 답과 견줘 e5 임베딩의 코사인 유사도로 채점합니다. 규칙과 판정이 갈린 답을 보고, 숫자 하나가 틀린 문장의 유사도도 재어 봅니다. 실습 1을 먼저 실행해야 합니다.

1단계. 준비

Python
import json
from pathlib import Path

import numpy as np
import pandas as pd
from sentence_transformers import SentenceTransformer

answers = pd.read_csv("out_lib_answers.csv")
evalset = {e["번호"]: e for e in map(json.loads, Path("library_eval.jsonl").read_text(encoding="utf-8").splitlines())}
emb = SentenceTransformer("intfloat/multilingual-e5-small", device="cpu")


def sim(a, b):
    """두 문장의 코사인 유사도. 둘 다 같은 접두어(query: )로 임베딩한다"""
    v = emb.encode(["query: " + a, "query: " + b], normalize_embeddings=True)
    return float(v[0] @ v[1])


print(len(answers), "개 답 · 예:", f"{sim('대출 기간은 14일입니다.', '14일 동안 빌릴 수 있어요.'):.3f}")
출력
44 개 답 · 예: 0.934

2단계. 참조 답과의 유사도 재기

Python
answers["유사도"] = [sim(a, evalset[n]["정답"]) for a, n in zip(answers["답"], answers["번호"])]
# 결과: 판과 종류마다 유사도의 평균과 가장 낮은 값
print(answers.groupby(["판", "종류"])["유사도"].agg(["mean", "min"]).round(3))
출력
        mean    min
판  종류
v2 답   0.946  0.890
   모름  0.868  0.851
v3 답   0.944  0.835
   모름  0.927  0.927

3단계. 문턱 정하고 규칙과 견주기

Python
import re

# 실습 1과 같은 규칙(열쇠, 형식, 길이)을 다시 매긴다
def rule_ok(a, key):
    return any(k in a for k in key.split("|")) and re.search(r"[A-Za-z]{3,}", a) is None and 0 < len(a) <= 120


answers["규칙"] = [rule_ok(a, evalset[n]["열쇠"]) for a, n in zip(answers["답"], answers["번호"])]
THRESHOLD = 0.9                                # 문턱: 답이 있는 질문의 맞는 답들이 대부분 넘는 값으로 잡았다
answers["뜻"] = answers["유사도"] >= THRESHOLD
print(pd.crosstab(answers["규칙"], answers["뜻"], rownames=["규칙"], colnames=["뜻"]))
# 두 방법의 판정이 갈린 답
diff = answers[answers["규칙"] != answers["뜻"]]
print(diff[["번호", "판", "규칙", "뜻", "유사도", "답"]].round(3).to_string(index=False))
answers[["번호", "판", "유사도", "규칙", "뜻"]].to_csv("out_lib_scores.csv", index=False, encoding="utf-8-sig")
출력
뜻      False  True
규칙
False      5      4
True       1     34
 번호  판    규칙     뜻   유사도                                                                                 답
 18 v2  True False 0.890 연체가 발생하면, 가장 늦게 반납된 책을 기준으로 대출이 정지됩니다. 정지된 대출이 발생하면, 해당 책을 반납하고 정지된 대출을 해지해야 합니다.
 19 v3 False  True 0.927                                                                      안내 자료에 없습니다.
 20 v3 False  True 0.927                                                                      안내 자료에 없습니다.
 21 v3 False  True 0.927                                                                      안내 자료에 없습니다.
 22 v3 False  True 0.927                                                                      안내 자료에 없습니다.

4단계. 숫자 하나가 틀린 문장

Python
ref = "한 사람이 7권까지 빌릴 수 있습니다."
for cand in ["한 사람이 7권까지 빌릴 수 있습니다.", "한 사람이 5권까지 빌릴 수 있습니다.",
             "책은 일곱 권까지 대출됩니다.", "도서관은 매주 월요일에 쉽니다."]:
    # 결과: 숫자만 틀린 문장도 문턱을 넘는지 본다
    s = sim(ref, cand)
    print(f"{s:.3f} {'≥' if s >= THRESHOLD else '<'} {THRESHOLD}  {cand}")
출력
1.000 ≥ 0.9  한 사람이 7권까지 빌릴 수 있습니다.
0.967 ≥ 0.9  한 사람이 5권까지 빌릴 수 있습니다.
0.910 ≥ 0.9  책은 일곱 권까지 대출됩니다.
0.863 < 0.9  도서관은 매주 월요일에 쉽니다.

문턱 0.9 에서 v3 의 모름 답 넷(0.927)은 맞음, v2 의 모름 답(평균 0.868)은 모두 틀림이 되어 실습 1의 규칙과 판정이 갈렸습니다. 이번에는 뜻 쪽이 실제 답을 더 잘 읽었습니다. v3 가 4번에서 거절한 답은 유사도 0.835 로 문턱 아래라, 규칙과 뜻이 함께 잡았습니다. 하지만 4단계에서 보듯 "5권"으로 틀린 문장(0.967)이 "일곱 권"으로 바르게 쓴 문장(0.910)보다 참조 답에 더 가깝게 나왔습니다. 유사도만으로는 숫자가 틀린 답을 걸러 낼 수 없습니다.

이 실습이 뜻하는 것

이 실습은 답안을 "모범 답안과 뜻이 얼마나 비슷한가"로 채점하는 선생님과 같습니다. 비슷한 정도를 재는 자를 준비하고(1단계), 답마다 모범 답안과 비슷한 정도를 0~1 사이 점수로 재고(2단계), 0.9 를 넘으면 맞음으로 정해 실습 1의 채점표와 견준 뒤(3단계), 숫자 하나만 바꾼 문장으로 이 자의 약점을 확인합니다(4단계). 여기서 자가 e5 임베딩의 코사인 유사도, 모범 답안이 참조 답, 합격선 0.9 가 문턱(THRESHOLD)입니다. 비유와 다른 점은, 사람 선생님은 숫자 하나 틀린 것을 바로 알아보지만 이 자는 문장 전체의 뜻을 보므로 숫자 차이를 잘 놓친다는 것입니다.

단계출력뜻
1. 준비44 개 답 · 예: 0.934실습 1의 답 44개를 읽었습니다. "14일입니다"와 "14일 동안 빌릴 수 있어요"는 말이 달라도 0.934 로 아주 비슷하다고 나왔습니다
2. 답 있는 질문v2 평균 0.946, v3 평균 0.9441에 가까울수록 참조 답과 뜻이 비슷합니다. 두 판이 거의 같습니다
2. 모름 질문v2 평균 0.868, v3 0.927v3 의 "안내 자료에 없습니다"가 참조 답에 더 가깝습니다. v2 는 지어낸 말이 섞여 멀어졌습니다
2. 가장 낮은 값v3 답의 min 0.835v3 가 답이 있는 4번을 거절한 답입니다. 가장 낮은 값이 문제 있는 답을 가리킵니다
3. 두 방법 견주기같음 5 + 34, 갈림 4 + 144개 가운데 39개(약 0.886)는 규칙과 뜻의 판정이 같았고, 5개가 갈렸습니다
3. 갈린 답19~22번 v3 0.927, 18번 v2 0.890v3 의 바른 거절 넷은 뜻으로만 맞음이 되었습니다. 18번은 규칙은 통과했지만 뜻은 문턱 바로 아래였습니다
4. 숫자 하나"5권" 0.967, "일곱 권" 0.910숫자가 틀린 문장이 바르게 바꿔 쓴 문장보다 더 비슷하다고 나왔습니다. 주제가 다른 휴관일 문장(0.863)만 문턱 아래였습니다

한 줄로 줄이면, 뜻 채점은 규칙이 놓친 "바른 거절"을 알아보지만 숫자가 틀린 답은 걸러 내지 못한다는 것을 보인 실습입니다. 문턱 0.9 는 이 묶음의 결과를 보고 잡은 값이라, 다른 모델이나 질문에서는 다시 정해야 합니다. 숫자까지 견주는 채점은 실습 3의 LLM 채점자에서 시험합니다.

코드 설명

  • sim() 은 두 문장을 같은 접두어로 임베딩한 뒤 곱해 코사인 유사도를 냅니다. 정규화한 벡터라 곱이 곧 코사인입니다.
  • groupby(...).agg(["mean", "min"]) 로 판과 종류마다의 평균과 가장 낮은 값을 봅니다. 가장 낮은 값이 문제 있는 답을 가리킵니다.
  • 문턱 THRESHOLD 는 이 묶음의 결과를 보고 잡은 값입니다. 다른 모델이나 묶음에서는 다시 정해야 합니다.
  • pd.crosstab(규칙, 뜻) 은 두 방법의 판정이 같은지 갈리는지를 네 칸으로 셉니다.
  • 4단계는 의미 유사도의 약점, 곧 숫자 하나의 차이를 잘 못 본다는 것을 확인합니다. 유사도는 out_lib_scores.csv 로 저장해 실습 4가 씁니다.
← 이전 절20장 · 실습 1 — 답 만들고 규칙으로 채점하기다음 절 →20장 · 실습 3 — LLM 채점자 만들고 사람과 맞춰 보기