hyunsu blog

홈강좌LAYA 결정 모델 24편 중 8편째

LAYA 학습 결과 읽기 — 좋아질 때, 그대로일 때, 속을 때

목차 · 4
  1. 01v1 을 팀 질문만 따로 잰다
  2. 02결과 가능성 — 다섯 가지를 실제로 돌렸다
  3. 03정리 — 결과별로 읽는 법
  4. 04실습 — 내 데이터에서 해 볼 것

학습한 LAYA 를 평가 데이터로 다시 재면 팀 정확도가 0.55 에서 0.75 로 올랐다. 이 편은 그 결과를 읽고, 같은 방법으로 학습이 잘 안 되는 경우를 실제로 돌려 본다. 데이터가 적을 때, 라벨이 틀렸을 때, 끝단만 학습할 때, 너무 오래 학습할 때다. 학습 로그가 1.000 을 찍었는데 실제로는 0.6 대인 경우가 나오고, 같은 명령이 돌릴 때마다 0.1 씩 달라지는 것도 본다.

01 v1 을 팀 질문만 따로 잰다

7편의 표는 팀과 서두름을 합친 숫자였다. 5편의 evaluate.py 로 팀만 잰다.

bash
python evaluate.py checkpoints/v1
text
model: checkpoints/v1
accuracy: 0.750 (150/200)
  account   0.800 (36/45)
  billing   0.746 (47/63)
  delivery  0.600 (27/45)
  technical 0.851 (40/47)
mean confidence: 0.954
ECE: 0.207
most common mistakes: billing -> technical ×15, delivery -> technical ×10, account -> billing ×8
time: 40 ms per case
학습 전(5편)v1
정확도0.5500.750
account0.2220.800
billing0.3970.746
delivery0.6220.600
technical1.0000.851
평균 확신0.8460.954
ECE0.2970.207
그림 1.팀별 정확도 — 학습 전과 v1
0.00.20.40.60.81.0accountbillingdeliverytechnicalaccount · 학습 전: 0.222account · v1: 0.800billing · 학습 전: 0.397billing · v1: 0.746delivery · 학습 전: 0.622delivery · v1: 0.600technical · 학습 전: 1.000technical · v1: 0.8510.2220.8000.3970.7460.6220.6001.0000.8510.00.20.40.60.81.0accountbillingdeliverytechnicalaccount · 학습 전: 0.222account · v1: 0.800billing · 학습 전: 0.397billing · v1: 0.746delivery · 학습 전: 0.622delivery · v1: 0.600technical · 학습 전: 1.000technical · v1: 0.8510.2220.8000.3970.7460.6220.6001.0000.851
  • 학습 전
  • v1

평가 데이터 200건

숫자로 보기
항목학습 전v1
account0.2220.800
billing0.3970.746
delivery0.6220.600
technical1.0000.851
  • 학습 전에는 거의 모든 문의를 기술팀으로 보냈다(technical 1.000 은 실력이 아니라 쏠림이었다). v1 은 그 쏠림이 풀려 계정·결제를 제대로 나누기 시작했다.
  • 대신 기술팀은 0.851 로 내려갔고, 배송팀은 그대로다. 전체가 올라도 팀마다 오르내림이 다르다. 11편의 승격 기준에서 팀별 숫자를 따로 보는 이유다.
  • 평균 확신 0.954 인데 정확도는 0.750 이다. 여전히 지나치게 확신한다(ECE 0.207). 7편의 보정 경고 그대로다.

02 결과 가능성 — 다섯 가지를 실제로 돌렸다

같은 바탕 모델(multilingual)에서 조건 하나씩만 바꿔 학습하고, 같은 평가 데이터로 쟀다. 7편의 v1 과 달리 아래 넷은 --eval 없이 돌렸다. 그러면 학습 로그는 학습용에서 떼어 낸 보정용 몇십 건으로 "전후" 를 잰다.

1. 데이터가 너무 적다 — 40건

bash
laya-train --data first-40.jsonl --base multilingual --out checkpoints/few-40
python evaluate.py checkpoints/few-40

학습 로그의 전후 표는 이렇다.

text
=== Calibration Evidence (in-sample calibration slice, 8 items; not held-out) ===
Metric                 Before         After          Delta         
-----------------------------------------------------------------
Accuracy               0.7500         1.0000         +0.2500       
Loss                   0.9659         0.0924         -0.8735       
ECE                    0.2359         0.0834         -0.1525       
Brier (multi-class)    0.4789         0.0300         -0.4489       
Brier (top-1)          0.2394         0.0152         -0.2242       
Mean Confidence        0.9859         0.9166         -0.0693       

정확도 1.0000. 그런데 제목 줄이 스스로 말한다. 보정용 8건으로 잰 것이고(8 items), 학습 데이터와 같은 곳에서 나온 것이라 따로 떼어 둔 평가가 아니다(not held-out). 평가 데이터로 다시 재면 이렇다.

text
model: checkpoints/few-40
accuracy: 0.625 (125/200)
  account   0.400 (18/45)
  billing   0.952 (60/63)
  delivery  0.644 (29/45)
  technical 0.383 (18/47)
mean confidence: 0.904
ECE: 0.285
most common mistakes: technical -> billing ×17, account -> delivery ×14, account -> billing ×9
time: 40 ms per case

실제는 0.625. 결제팀은 0.952 로 잘 맞히지만 기술팀은 0.383 이다. 40건 가운데 결제 문의가 많았던 쪽으로 쏠렸다. 적은 데이터는 "어느 팀이 많았나" 를 배운다.

2. 라벨이 틀렸다 — 150건 중 30% 를 아무 팀으로

한 건마다 30% 확률로 정답 팀을 아무 팀으로 바꿨다. 원래 팀이 다시 뽑히기도 하니, 실제로 틀린 라벨이 된 것은 41건(27%)이다(출력 첫 줄).

bash
python make_noisy.py
laya-train --data noisy-150.jsonl --base multilingual --out checkpoints/noisy-150
python evaluate.py checkpoints/noisy-150
text
labels changed: 41/150
text
epoch 1/4 mean loss 1.0897
epoch 2/4 mean loss 0.4150
epoch 3/4 mean loss 0.3039
epoch 4/4 mean loss 0.4188

손실이 1.0897 에서 0.3~0.4 대에서 더 내려가지 않는다. 서로 어긋나는 정답을 동시에 맞힐 수는 없기 때문이다. 손실이 일찍 멈추면 라벨부터 의심한다.

text
model: checkpoints/noisy-150
accuracy: 0.545 (109/200)
  account   0.400 (18/45)
  billing   0.619 (39/63)
  delivery  0.556 (25/45)
  technical 0.574 (27/47)
mean confidence: 0.529
ECE: 0.063
most common mistakes: billing -> technical ×18, technical -> account ×18, delivery -> technical ×17
time: 37 ms per case

정확도 0.545, 학습 전(0.550)과 같다. 흥미로운 것은 평균 확신이 0.529 로 떨어지고 ECE 가 0.063 으로 이 편에서 가장 좋다는 점이다. 모델이 "잘 모르겠다" 를 정직하게 말하게 됐지만, 쓸모는 없다. 보정 지표만 좋아졌다고 좋은 모델은 아니다.

3. 끝단만 학습 — --freeze-encoder

문장을 읽는 본체(인코더)는 그대로 두고 맨 끝의 판단 부분만 학습한다. 가볍고 빠르지만 효과가 작다고 공식 문서가 적고 있다.

bash
laya-train --data first-150.jsonl --base multilingual --freeze-encoder --out checkpoints/frozen-150
text
epoch 1/4 mean loss 0.7263
epoch 2/4 mean loss 0.6337
epoch 3/4 mean loss 0.6762
epoch 4/4 mean loss 0.6723
text
model: checkpoints/frozen-150
accuracy: 0.560 (112/200)
  account   0.244 (11/45)
  billing   0.413 (26/63)
  delivery  0.622 (28/45)
  technical 1.000 (47/47)
mean confidence: 0.727
ECE: 0.187
most common mistakes: billing -> technical ×33, account -> technical ×23, delivery -> technical ×17
time: 39 ms per case

손실이 0.6~0.7 에서 거의 움직이지 않았고, 정확도 0.560·팀별 숫자·틀린 방향(기술팀 쏠림)까지 학습 전과 거의 같다. "결제창 오류는 기술팀" 같은 규칙은 문장을 다르게 읽어야 배울 수 있는데, 읽는 부분을 얼려 두었기 때문이다. 메모리가 아주 작은 기계가 아니라면 맥북에서는 전체 학습이 낫다.

4. 오래 학습 — 12에폭

v1 은 4에폭이었다. 세 배로 늘려 본다.

bash
laya-train --data first-150.jsonl --base multilingual --epochs 12 --out checkpoints/long-150
python evaluate.py checkpoints/long-150
text
epoch 1/12 mean loss 0.3569
epoch 2/12 mean loss -0.0588
epoch 3/12 mean loss 0.0361
epoch 4/12 mean loss -0.0092
epoch 5/12 mean loss 0.0095
epoch 6/12 mean loss 0.0000
epoch 7/12 mean loss 0.0000
epoch 8/12 mean loss 0.0000
epoch 9/12 mean loss 0.0000
epoch 10/12 mean loss 0.0000
epoch 11/12 mean loss 0.0000
epoch 12/12 mean loss 0.0000
그림 2.12에폭 학습 손실
mean loss−0.10.00.10.20.30.41234567891011121: 0.35692: −0.05883: 0.03614: −0.00925: 0.00956: 0.00007: 0.00008: 0.00009: 0.000010: 0.000011: 0.000012: 0.00000.35690.0000mean loss−0.10.00.10.20.30.413579111: 0.35692: −0.05883: 0.03614: −0.00925: 0.00956: 0.00007: 0.00008: 0.00009: 0.000010: 0.000011: 0.000012: 0.00000.35690.0000

6에폭부터 0 — 학습 데이터를 다 외웠다

숫자로 보기
항목mean loss
10.3569
2-0.0588
30.0361
4-0.0092
50.0095
60.0000
70.0000
80.0000
90.0000
100.0000
110.0000
120.0000

6에폭부터 손실이 0.0000 이다(중간의 음수는 7편에서 본 RLCD 의 특성이다). 학습 데이터를 완전히 외웠다. 학습 로그의 전후 표도 정확도 1.0000·ECE 0.0000 으로 완벽해 보인다.

text
Accuracy               0.8000         1.0000         +0.2000       
Loss                   0.7643         0.0000         -0.7643       
ECE                    0.2474         0.0000         -0.2474       

평가 데이터로 재면 이렇다.

text
model: checkpoints/long-150
accuracy: 0.785 (157/200)
  account   0.756 (34/45)
  billing   0.952 (60/63)
  delivery  0.622 (28/45)
  technical 0.745 (35/47)
mean confidence: 0.978
ECE: 0.193
most common mistakes: technical -> account ×9, delivery -> technical ×9, delivery -> account ×8
time: 37 ms per case

0.785 로 4에폭의 v1(0.750)보다 오히려 높다. 평균 확신은 0.978 로 v1(0.954)보다 더 확신하지만, ECE 는 0.193 으로 v1(0.207)과 비슷하다. 그럼 12에폭이 더 좋은 걸까.

5. 한 번 잰 숫자는 흔들린다 — 같은 학습을 세 번씩

이 글을 쓰며 처음 12에폭을 돌렸을 때는 0.660 이 나왔다. "오래 학습해 나빠졌다" 고 쓸 뻔했는데, 같은 명령을 다시 돌리니 위의 0.785 였다. 맥 GPU(MPS)의 학습은 매번 완전히 같은 결과를 보장하지 않고, 150건뿐인 학습에서는 그 작은 차이가 정확도 몇 점으로 커진다. 그래서 4에폭과 12에폭을 씨앗(seed)만 바꿔 세 번씩 학습했다.

python
"""4에폭과 12에폭을 씨앗만 바꿔 세 번씩 학습해 비교한다 — 한 번 돌린 숫자로 '과적합' 을 말할 수 있나."""
import json
import shutil
import statistics
import subprocess

import laya

cases = [json.loads(line) for line in open("tickets-eval.jsonl", encoding="utf-8")]


def team_accuracy(path):
    agent = laya.load(path)
    return sum(agent.predict(c["state"], c["questions"])["answers"]["team"]["choice"] == c["expected"]["team"]
               for c in cases) / len(cases)


print(f"{'epochs':<10}{'seed 1':>8}{'seed 2':>8}{'seed 3':>8}{'mean':>8}{'range':>8}")
for epochs in [4, 12]:
    scores = []
    for seed in [1, 2, 3]:
        out = f"checkpoints/epochs-{epochs}-{seed}"
        subprocess.run(["laya-train", "--data", "first-150.jsonl", "--base", "multilingual", "--epochs", str(epochs),
                        "--seed", str(seed), "--out", out], check=True, capture_output=True)
        scores.append(team_accuracy(out))
        shutil.rmtree(out)
    cells = "".join(f"{s:>8.3f}" for s in scores)
    print(f"{epochs:<10}{cells}{statistics.mean(scores):>8.3f}{max(scores) - min(scores):>8.3f}", flush=True)
bash
python repeat_epochs.py

칸은 차례로 에폭, 씨앗 1·2·3 의 팀 정확도, 평균, 폭(가장 높은 것 - 가장 낮은 것)이다.

text
epochs      seed 1  seed 2  seed 3    mean   range
4            0.800   0.790   0.705   0.765   0.095
12           0.765   0.730   0.845   0.780   0.115
그림 3.같은 학습 세 번 — 에폭별 팀 정확도
0.600.650.700.750.800.850.904에폭12에폭4에폭: 0.8004에폭: 0.7904에폭: 0.70512에폭: 0.76512에폭: 0.73012에폭: 0.8454에폭: 0.76512에폭: 0.780평균 0.765평균 0.7800.60.70.80.94에폭12에폭4에폭: 0.8004에폭: 0.7904에폭: 0.70512에폭: 0.76512에폭: 0.73012에폭: 0.8454에폭: 0.76512에폭: 0.780평균 0.765평균 0.780
  • 씨앗 1·2·3
  • 평균
  • 최소~최대

평균 차이 0.015 가 같은 에폭 안의 폭(0.095·0.115)보다 작다

숫자로 보기
항목측정값평균
4에폭0.800, 0.790, 0.7050.765
12에폭0.765, 0.730, 0.8450.780
  • 같은 4에폭이 0.705 에서 0.800 까지 나왔다. 7편의 v1(0.750)도 이 범위 안의 한 번이다.
  • 평균은 4에폭 0.765, 12에폭 0.780 으로 차이가 0.015 다. 같은 에폭 안의 폭(0.095·0.115)보다 훨씬 작다. 이 데이터에서는 12에폭이 낫다고도, 나쁘다고도 말할 수 없다. 확실한 것은 학습 시간이 세 배라는 것뿐이다.

학습 데이터를 외워 새 데이터에서 나빠지는 것을 과적합(overfitting) 이라 한다. 데이터가 더 적거나 에폭이 훨씬 길면 실제로 생긴다. 하지만 손실 0 과 학습 로그의 1.000 은 과적합의 신호일 뿐이고, 판단은 여러 번 재서 평균이 떨어질 때 한다. 한 번 잰 0.660 이나 0.785 로 결론을 냈다면 둘 다 틀렸다. 이 시리즈는 에폭을 기본값 4 로 두고, 판을 비교할 때는 여러 번 재서 평균과 폭을 본다(10편).

그림 4.결과 가능성 — 평가 데이터 정확도
0.00.20.40.60.81.0학습 전v1 (150건·4에폭)데이터 40건라벨 27% 틀림끝단만 학습12에폭 (한 번)학습 전: 0.550v1 (150건·4에폭): 0.750데이터 40건: 0.625라벨 27% 틀림: 0.545끝단만 학습: 0.56012에폭 (한 번): 0.7850.5500.7500.6250.5450.5600.7850.00.20.40.60.81.0학습 전v1 (150건·4에폭)데이터 40건라벨 27% 틀림끝단만 학습12에폭 (한 번)학습 전: 0.550v1 (150건·4에폭): 0.750데이터 40건: 0.625라벨 27% 틀림: 0.545끝단만 학습: 0.56012에폭 (한 번): 0.7850.5500.7500.6250.5450.5600.785

같은 평가 데이터 200건. 한 번씩 돌린 숫자라 ±0.1 쯤 흔들린다(5절)

숫자로 보기
항목정확도
학습 전0.550
v1 (150건·4에폭)0.750
데이터 40건0.625
라벨 27% 틀림0.545
끝단만 학습0.560
12에폭 (한 번)0.785

03 정리 — 결과별로 읽는 법

결과로그에서 보이는 것평가에서 보이는 것다음 수
좋아짐(v1)손실이 크게 준다정확도가 오르고 팀 쏠림이 풀린다지속 학습으로 간다(9편)
데이터 부족보정 건수 경고, 학습 로그 1.000많은 팀으로 쏠린다적은 팀의 데이터를 더 모은다
라벨 오류손실이 일찍 멈춘다학습 전과 같고 확신만 낮다라벨을 다시 본다
끝단만 학습손실이 거의 그대로학습 전과 같다전체 학습으로
오래 학습손실 0, 학습 로그 1.000한 번은 높게, 한 번은 낮게 나온다세 번 재서 평균으로 본다
결과 흔들림같은 명령, 다른 손실같은 설정이 0.1 씩 달라진다씨앗을 바꿔 여러 번 잰다

모든 경우에 학습 로그의 숫자는 평가 데이터의 숫자와 달랐다. 학습 로그의 전후 표는 학습용에서 떼어 낸 수십 건으로 잰 것이라 믿을 수 없다. 학습용과 겹치지 않는, 고정된 평가 데이터로 재는 것이 유일한 판단 근거다. 이 평가 데이터는 다음 편부터 판을 바꿀 때마다 같은 것을 쓴다.

04 실습 — 내 데이터에서 해 볼 것

  1. 평가 데이터를 먼저 떼어 두고, 학습할 때마다 --eval 을 붙인다.
  2. 데이터 양을 반으로 줄여 한 번 더 학습해 본다. 정확도가 크게 떨어지면 데이터를 더 모을수록 좋아질 여지가 크다는 뜻이다.
  3. 에폭을 2·4·8 로 바꿔 평가 정확도가 가장 높은 곳을 찾는다. 설정마다 씨앗을 바꿔 세 번씩 재고, 평균의 차이가 폭보다 클 때만 "낫다" 고 본다.

v1 은 첫 판으로 쓸 만하지만 여전히 지나치게 확신한다. 다음 편에서 v1 을 운영에 넣고, 사람이 고친 판단을 모아 다음 판의 재료로 쓴다.