홈강좌LAYA 결정 모델 24편 중 11편째
LAYA 지속 학습 3 — 새 판은 기준을 넘을 때만 올린다
새로 학습한 판을 운영에 올릴지 사람이 감으로 정하지 않는다. 같은 평가 데이터로 지금 판(current)과 새 판(candidate) 을 재고, 정해 둔 기준을 모두 넘을 때만 올린다. 이 편에서 그 판정을 코드로 만든다. 10편의 v2 는 통과했고, 이어 학습한 v2b 는 막혔다.
01 기준 셋
- 전체 정확도가 0.02 이상 오른다. 10편에서 본 대로 같은 학습도 판마다 몇 점씩 흔들린다. 그보다 작은 차이는 우연일 수 있으니, 바꾸는 수고를 들일 만큼 오를 때만 올린다.
- 어느 팀도 0.05 넘게 떨어지지 않는다. 전체 평균이 올라도 한 팀이 무너지면 그 팀의 고객은 서비스가 나빠진 것이다. 평균이 가리는 퇴보를 잡는다.
- ECE 가 0.25 이하다. 확신이 크게 어긋나면 9편의 "확신이 낮으면 사람에게" 가 듣지 않는다.
숫자는 이 강좌의 데이터에 맞춰 정한 것이다. 내 서비스에서는 "틀리면 얼마나 손해인가" 로 정한다(아래 실습).
02 판정 코드
"""지금 쓰는 판(current)과 새 판(candidate)을 같은 평가 세트로 재고, 기준을 넘을 때만 승격한다.
기준 — 셋 다 통과해야 한다:
1. 전체 정확도가 0.02 이상 오른다
2. 어느 팀도 정확도가 0.05 넘게 떨어지지 않는다(전체 평균에 가려진 퇴보를 잡는다)
3. ECE(확신과 실제의 차이)가 0.25 이하
결과는 data/registry.jsonl 에 한 줄씩 남긴다 — 무엇을 왜 올렸고 왜 막았는지.
"""
import json
import sys
import time
from datetime import date
import numpy as np
import laya
from laya.common import ece_score
current_path, candidate_path = sys.argv[1], sys.argv[2]
cases = [json.loads(line) for line in open("tickets-eval.jsonl", encoding="utf-8")]
def measure(path):
agent = laya.load(path)
correct, conf, per_team = [], [], {}
for case in cases:
answer = agent.predict(case["state"], case["questions"])["answers"]["team"]
ok = answer["choice"] == case["expected"]["team"]
correct.append(ok)
conf.append(answer["probabilities"][answer["choice"]])
per_team.setdefault(case["expected"]["team"], []).append(ok)
correct = np.array(correct, dtype=float)
return {
"accuracy": round(float(correct.mean()), 3),
"ece": round(float(ece_score(np.array(conf), correct, bins=10)), 3),
"teams": {t: round(sum(v) / len(v), 3) for t, v in sorted(per_team.items())},
}
cur, cand = measure(current_path), measure(candidate_path)
checks = {
"accuracy +0.02": cand["accuracy"] - cur["accuracy"] >= 0.02,
"no team -0.05": all(cand["teams"][t] - cur["teams"][t] >= -0.05 for t in cur["teams"]),
"ECE <= 0.25": cand["ece"] <= 0.25,
}
decision = "PROMOTE" if all(checks.values()) else "REJECT"
print(f"{'':<12}{'current':>10}{'candidate':>11}")
print(f"{'accuracy':<12}{cur['accuracy']:>10.3f}{cand['accuracy']:>11.3f}")
for team in cur["teams"]:
print(f" {team:<10}{cur['teams'][team]:>10.3f}{cand['teams'][team]:>11.3f}")
print(f"{'ECE':<12}{cur['ece']:>10.3f}{cand['ece']:>11.3f}")
for name, ok in checks.items():
print(f"[{'pass' if ok else 'FAIL'}] {name}")
print(f"decision: {decision} {candidate_path}")
record = {"date": str(date.today()), "current": current_path, "candidate": candidate_path,
"decision": decision, "checks": checks, "current_metrics": cur, "candidate_metrics": cand}
with open("data/registry.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")판정 결과는 통과·실패와 상관없이 data/registry.jsonl 에 한 줄씩 남는다. 언제, 무엇을, 무엇과 비교해, 왜 올리거나 막았는지가 기록으로 남아야 나중에 "이 판은 왜 올렸지?" 에 답할 수 있다.
03 v1 → v2: 통과
python promote.py checkpoints/v1 checkpoints/v2 current candidate
accuracy 0.750 0.815
account 0.800 0.889
billing 0.746 0.825
delivery 0.600 0.622
technical 0.851 0.915
ECE 0.207 0.136
[pass] accuracy +0.02
[pass] no team -0.05
[pass] ECE <= 0.25
decision: PROMOTE checkpoints/v2전체 0.750 → 0.815(+0.065), 네 팀 모두 같거나 올랐고, ECE 도 0.207 → 0.136 으로 좋아졌다. 셋 다 통과해 PROMOTE 다. 배송팀(0.600 → 0.622)은 거의 그대로지만 떨어지지 않았으니 막지 않는다.
04 v1 → v2b: 막힘
10편에서 v1 에 누적 데이터를 이어 학습한 v2b 를 같은 기준에 넣었다.
python promote.py checkpoints/v1 checkpoints/v2b current candidate
accuracy 0.750 0.735
account 0.800 0.800
billing 0.746 0.905
delivery 0.600 0.711
technical 0.851 0.468
ECE 0.207 0.237
[FAIL] accuracy +0.02
[FAIL] no team -0.05
[pass] ECE <= 0.25
decision: REJECT checkpoints/v2b결제팀(0.905)·배송팀(0.711)은 v1 보다 좋다. 하지만 기술팀이 0.851 → 0.468 로 무너졌고, 그 탓에 전체도 0.750 → 0.735 로 떨어졌다. 기준 둘이 실패해 REJECT 다. 결제팀 숫자만 보고 "좋아졌다" 고 올렸다면, 기술 문의의 절반이 결제팀으로 가는 판이 운영에 나갔을 것이다.
05 기록
tail -n 2 data/registry.jsonl한 줄에 날짜·지금 판·새 판·판정·기준별 통과 여부·두 판의 팀별 숫자가 들어 있다. 이 파일이 쌓이면 "언제부터 어느 팀이 좋아졌는지" 를 판 순서대로 볼 수 있다.
06 실습 — 내 서비스의 기준 정하기
| 물을 것 | 기준으로 |
|---|---|
| 틀리면 손해가 큰 팀(선택지)은? | 그 팀만 "떨어지지 않기" 를 더 엄하게(-0.02 등) |
| 판을 바꾸는 데 드는 수고는? | 수고가 크면 전체 상승 기준을 높게 |
| 사람에게 넘기는 규칙을 쓰나? | 쓰면 ECE 기준을 둔다 |
| 평가 데이터는 몇 건인가? | 200건이면 1건이 0.005 다. 기준이 너무 촘촘하면 우연에 흔들린다 |
판정을 통과한 판만 다음 편부터의 배포로 넘어간다. 그래도 평가 데이터는 실제 운영과 다를 수 있으니, 15편에서 실제 문의로 한 번 더 비교한 뒤 바꾼다.