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

20장. LLM 평가와 모니터링

드리프트를 재는 법

지난달과 이번 달의 주문표를 나란히 놓고 메뉴별 비율을 견주면, 손님이 바뀐 것을 숫자로 알 수 있습니다. 드리프트를 재는 일도 "평소"인 기준 기간과 지금을 견주는 일입니다.

기준 기간 정하기, 비교 기간 모으기, 분포의 거리 재기, 문턱과 견주기의 네 단계 흐름도

1번에서 평소로 볼 기간(이 장에서는 1~4주)을 정합니다. 2번에서 견줄 기간(주마다)의 질문을 모읍니다. 3번에서 두 분포가 얼마나 다른지 숫자로 잽니다. 4번에서 문턱을 넘으면 살펴봅니다. 지난달 주문표가 기준 기간, 이번 달 주문표가 비교 기간입니다.

재는 값

값무엇을 견주나읽는 법
PSI범주(가장 가까운 문서)의 비율흔히 0.1 아래면 안정, 0.1~0.25 살펴봄, 0.25 넘으면 큰 변화로 본다
KS 검정숫자(검색 점수)의 분포p 값이 작을수록(예: 0.01 아래) 두 분포가 다르다
처음 보는 질문 비율기준 기간의 질문과 뜻이 먼 질문가장 가까운 기준 질문과의 유사도가 문턱 아래인 비율
결과 지표모름, 싫어요 비율기준 기간보다 크게 오르면 품질 문제

PSI 계산

PSI 는 범주마다 (지금 비율 − 기준 비율) × ln(지금 비율 ÷ 기준 비율)을 더한 값입니다. 비율이 0 인 범주는 아주 작은 값으로 바꿔 나눗셈을 막습니다.

Python
import numpy as np


def psi(base, now, eps=1e-4):
    """base, now: {범주: 비율}. 두 분포의 PSI"""
    keys = sorted(set(base) | set(now))
    b = np.array([base.get(k, 0) for k in keys]) + eps
    n = np.array([now.get(k, 0) for k in keys]) + eps
    return float(((n - b) * np.log(n / b)).sum())


base = {"대출": 0.6, "열람실": 0.3, "주차": 0.1}
print(f"{psi(base, {'대출': 0.58, '열람실': 0.32, '주차': 0.1}):.3f}")                   # 거의 같다
print(f"{psi(base, {'대출': 0.4, '열람실': 0.2, '주차': 0.1, '스마트 도서관': 0.3}):.3f}")  # 새 주제가 30%

코드에 나오는 함수

코드쉬운 뜻
import numpy as npNumPy(Numerical Python, 숫자 계산 파이썬)를 np 라는 짧은 이름으로 불러온다
set(base) | set(now)두 사전의 범주를 합친다(합집합). 코드에서는 반각 세로줄 연산자다
sorted(...)범주를 가나다 차례로 늘어놓아 두 배열의 칸 차례를 맞춘다
base.get(k, 0)범주 k 의 비율을 꺼내고, 없으면 0 을 쓴다
np.array([...]) + eps목록을 배열로 바꾸고 칸마다 eps(epsilon, 아주 작은 수) 0.0001 을 더한다
np.log(n / b)칸마다 자연로그(ln)를 구한다. 지금 비율이 기준의 몇 배인지를 로그로 본 값이다
.sum(), float(...)칸의 값을 모두 더하고, NumPy 숫자를 보통 파이썬 숫자로 바꾼다

이 코드가 뜻하는 것

마트 매대별 판매 비율을 지난달과 이번 달로 견주는 일과 같습니다. 매대마다 "비율이 얼마나 바뀌었나"와 "몇 배가 되었나"를 곱해 변화 점수를 매기고, 점수를 모두 더합니다. 여기서 매대가 범주, 점수 합계가 PSI 입니다. 비유가 맞지 않는 곳이 하나 있습니다. 지난달에 없던 매대는 "몇 배"를 셀 수 없어서, 코드는 0 대신 0.0001 이 팔렸다고 치고 셉니다.

줄하는 일쉽게 말하면
keys = sorted(...)두 기간의 범주를 한 목록으로두 달의 매대 목록 합치기
b = ..., n = ...범주마다 기준 비율과 지금 비율매대별 판매 비율 두 줄
(n - b) * np.log(n / b)범주마다 변화 몫매대마다 변화 점수
.sum()몫을 모두 더한다점수 합계

첫 결과 0.002 는 0.1 아래라 안정입니다. 둘째 결과 2.524 는 0.25 를 크게 넘어 큰 변화입니다. 새 주제 하나가 30% 를 차지한 것만으로 이렇게 커집니다.

작게 줄여서 눈으로 보기

둘째 결과 2.524 가 어느 범주에서 왔는지 범주마다 나눠 봅니다.

Python
import numpy as np

keys = ["대출", "열람실", "주차", "스마트 도서관"]
b = np.array([0.6, 0.3, 0.1, 0.0]) + 1e-4     # 기준 비율
n = np.array([0.4, 0.2, 0.1, 0.3]) + 1e-4     # 지금 비율
terms = (n - b) * np.log(n / b)               # 범주마다의 몫
for k, t in zip(keys, terms):
    print(f"{k:8s} {t:.3f}")
print(f"합계 {terms.sum():.3f}")
출력
대출       0.081
열람실      0.041
주차       0.000
스마트 도서관  2.402
합계 2.524
결과읽는 법
대출 0.0810.6 에서 0.4 로 줄어 몫이 생겼다
열람실 0.0410.3 에서 0.2 로 줄었다
주차 0.000비율이 같으니 몫이 0 이다
스마트 도서관 2.402기준 비율을 0.0001 로 보니 0.3 은 3천 배쯤이다. 로그가 커서 합계의 대부분을 차지한다
합계 2.524본문 코드의 둘째 결과와 같다

PSI 가 크게 나오면 이렇게 범주마다 몫을 나눠 봅니다. 어느 주제가 늘었는지 바로 드러납니다. 기준 기간에 없던 범주의 몫은 eps 를 더 작게 잡을수록 더 커지므로, 숫자 크기보다 "새 범주가 생겼다"는 사실을 먼저 읽습니다.

주의할 점

  • 표본이 적은 주에는 숫자가 크게 흔들립니다. 하루보다 한 주 단위로 보고, 질문 수도 함께 적습니다.
  • PSI 의 문턱(0.1, 0.25)은 업계에서 흔히 쓰는 어림값입니다. 자기 서비스의 평소 흔들림을 보고 정합니다.
← 이전 절20장 · 모델 드리프트다음 절 →20장 · 모니터링 지표