20장. LLM 평가와 모니터링
뜻으로 채점하기
받아쓰기는 글자가 같아야 맞지만, 들은 이야기를 요약하는 숙제는 뜻이 같으면 맞습니다. 의미 유사도는 두 문장을 임베딩으로 바꿔 뜻이 얼마나 가까운지 숫자로 잽니다.
1번에서 참조 답과 챗봇의 답을 같은 임베딩 모델로 벡터로 바꿉니다(7장). 2번에서 두 벡터의 코사인 유사도를 잽니다. 3번에서 정해 둔 문턱보다 높으면 맞음으로 봅니다. 요약 숙제의 "뜻이 같다"가 높은 유사도입니다. 비유가 맞지 않는 곳도 있습니다. 임베딩은 숫자 하나가 다른 두 문장도 아주 가깝다고 봅니다.
세 채점 방법 견주기
| 규칙 | 의미 유사도 | LLM 채점자 | |
|---|---|---|---|
| 빠르기 | 가장 빠르다 | 빠르다 | 느리다(답마다 LLM 호출) |
| 같은 뜻 다른 표현 | 놓친다 | 잡는다 | 잡는다 |
| 숫자 하나 틀림 | 열쇠가 숫자면 잡는다 | 잘 놓친다 | 기준표에 따라 잡는다 |
| 다시 채점하면 | 같다 | 같다 | 조금 달라질 수 있다 |
숫자 하나가 다른 두 문장
아래는 글자 조각(TF-IDF)으로 잰 유사도입니다. 실습 2에서는 e5 임베딩으로 같은 일을 잽니다.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
ref = "한 사람이 7권까지 빌릴 수 있습니다."
cands = ["한 사람이 5권까지 빌릴 수 있습니다.", "책은 일곱 권까지 대출됩니다.", "주차는 30분까지 무료입니다."]
vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 3)).fit([ref] + cands)
sims = cosine_similarity(vec.transform([ref]), vec.transform(cands))[0]
# 결과: 숫자만 틀린 문장이 뜻이 같은 문장보다 더 가깝게 나온다
for c, s in zip(cands, sims):
print(f"{s:.3f} {c}")코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
TfidfVectorizer | 글을 TF-IDF 숫자 벡터로 바꾸는 도구다. TF-IDF 는 Term Frequency–Inverse Document Frequency(낱말 빈도–역문서 빈도)의 머리글자로, 한 글에 자주 나오고 다른 글에는 드문 조각에 큰 무게를 준다 |
analyzer="char_wb" | character word boundary(낱말 경계 안의 글자)의 줄임말이다. 낱말 단위가 아니라 낱말 안의 글자 조각으로 자른다 |
ngram_range=(2, 3) | n-gram(n 개짜리 조각)의 길이 범위다. 2글자 조각과 3글자 조각을 쓴다 |
.fit([ref] + cands) | 네 문장을 보고 어떤 조각이 있는지와 조각마다의 무게를 배운다 |
vec.transform([...]) | 배운 조각표로 문장을 벡터로 바꾼다 |
cosine_similarity(A, B) | 두 벡터 묶음의 코사인 유사도 표를 만든다. 1에 가까울수록 닮았다 |
[0] | 표의 첫 줄, 곧 참조 답과 세 후보의 유사도 셋을 꺼낸다 |
이 코드가 뜻하는 것
두 사람의 마트 영수증을 나란히 놓고 같은 물건이 얼마나 겹치는지 보는 일과 같습니다. 겹치는 물건이 많을수록 두 장바구니가 닮았다고 봅니다. 여기서 영수증이 문장, 물건 하나하나가 글자 조각입니다. TF-IDF 는 누구나 사는 흔한 물건에는 무게를 낮게 줍니다. 비유와 다른 점도 있습니다. 사람은 "일곱"과 "7"이 같은 뜻인 줄 알지만, 이 방법은 뜻을 모르고 글자 조각만 견줍니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
vec = TfidfVectorizer(...).fit(...) | 네 문장의 글자 조각표를 만든다 | 물건 목록 만들기 |
vec.transform([ref]), vec.transform(cands) | 문장마다 조각의 무게를 벡터로 만든다 | 영수증마다 물건 체크 |
cosine_similarity(...)[0] | 참조 답과 후보 셋의 닮은 정도를 잰다 | 영수증 겹침 정도 |
결과에서 숫자만 틀린 "5권" 문장이 0.817 로 가장 가깝고, 뜻이 같은 "일곱 권" 문장은 0.133 입니다. 겹치는 글자 조각이 많을수록 높게 나오기 때문입니다. 유사도가 높다고 사실이 맞다는 뜻은 아닙니다.
작게 줄여서 눈으로 보기
char_wb 가 글을 어떤 조각으로 자르는지 직접 봅니다.
from sklearn.feature_extraction.text import TfidfVectorizer
vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 3))
analyze = vec.build_analyzer() # 글을 조각으로 자르는 함수만 꺼낸다
print(analyze("7권까지"))
print(analyze("5권까지"))[' 7', '7권', '권까', '까지', '지 ', ' 7권', '7권까', '권까지', '까지 ']
[' 5', '5권', '권까', '까지', '지 ', ' 5권', '5권까', '권까지', '까지 ']| 결과 | 읽는 법 |
|---|---|
' 7', '7권', '권까', '까지', '지 ' | 2글자 조각이다. 낱말 앞뒤에 빈칸을 붙여 자르므로 ' 7' 처럼 빈칸이 든 조각이 생긴다 |
' 7권', '7권까', '권까지', '까지 ' | 3글자 조각이다 |
| 두 줄 견주기 | 9조각 가운데 숫자가 든 넷(' 7', '7권', ' 7권', '7권까')만 다르고 다섯은 같다 |
build_analyzer() 는 벡터라이저가 글을 자를 때 쓰는 함수를 꺼내 줍니다. "7권까지" 와 "5권까지" 는 조각 다섯을 나눠 가집니다. 숫자 하나가 틀려도 유사도가 높게 나오는 까닭이 여기 있습니다.
주의할 점
- 유사도의 문턱은 모델마다 다릅니다. 사람이 판정한 몇십 개로 문턱을 정합니다.
- 숫자와 날짜가 핵심인 서비스는 유사도만 믿지 않습니다. 열쇠 규칙이나 LLM 채점자와 함께 씁니다.