hyunsu blog

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

LAYA 지속 학습 1 — 운영에서 사람이 고친 판단을 학습 데이터로 모은다

목차 · 7
  1. 01지속 학습의 고리
  2. 02운영에서 들어오는 문의
  3. 03흉내 내는 코드
  4. 04느슨한 규칙 — 문턱 0.90, 표본 10%
  5. 05엄격한 규칙 — 문턱 0.99, 표본 30%
  6. 06왜 문턱이 듣지 않나
  7. 07실습 — 내 서비스에 고리를 놓을 때

지속 학습의 재료는 운영에서 사람이 고친 판단이다. 이 편은 v1 이 새 문의 300건을 처리하는 3주를 흉내 낸다. 확신이 낮은 것은 사람이 직접 분류하고, 높은 것은 자동으로 보내되 일부를 사람이 다시 본다. 사람이 본 것은 모두 정답이 붙으니 다음 판의 학습 데이터가 된다. 결과는 생각보다 나빴다. v1 은 확신 0.99 이상에서도 9.4% 를 틀렸다.

01 지속 학습의 고리

  1. v1 이 문의를 판단한다.
  2. 확신이 높으면 자동 처리하고, 그중 일부를 사람이 다시 본다(표본 검사).
  3. 확신이 낮으면 사람이 직접 분류한다(검토 큐).
  4. 사람이 본 것은 모두 정답이 붙어 학습 데이터(feedback)로 쌓인다.
  5. 이전 데이터와 합쳐 v2 를 학습한다(10편).
  6. 승격 판정을 통과하면 배포한다(11~15편). 그리고 다시 1번으로.

사람이 보는 곳은 둘이다.

  • 검토 큐 — 확신이 문턱보다 낮은 판단. 모델이 자신 없어 하니 사람이 정한다.
  • 표본 검사 — 확신이 높아 자동 처리한 것 가운데 일부를 무작위로 다시 본다. 모델이 자신 있게 틀리는 것은 이렇게만 찾을 수 있다.

02 운영에서 들어오는 문의

6편에서 만든 incoming-300.jsonl 은 처음 150건과 겹치지 않는 문의 300건이다. 70% 는 학습 때 없던 새 표현(ops 문장 틀)이고 30% 는 익숙한 표현이다. 처음 몇 건은 이렇다.

문의정답
노트북 거치대 반품하려는데 박스를 버렸어요 빨리 확인 부탁드려요.delivery
무선 이어폰 사고 나서 가격이 내렸는데 차액 돌려주나요?billing
로그인 버튼이 화면 밖으로 밀려나 있어요 어떻게 해야 하나요?technical
쿠폰 입력 칸이 눌리지 않아요 어떻게 해야 하나요?technical

"로그인" 이 들어 있어도 화면이 깨진 것이니 기술팀, "쿠폰" 이 들어 있어도 입력 칸이 안 눌리니 기술팀이다. 처음 데이터에 없던 갈림길이다.

03 흉내 내는 코드

python
"""운영 3주를 흉내 낸다 — v1 이 새 문의 300건을 판단하고, 사람이 고친 것을 학습 데이터로 모은다.

규칙: 확신이 THRESHOLD 미만이면 사람이 직접 분류한다(검토 큐).
      그 이상은 자동 처리하되, 그중 AUDIT_RATE 만큼을 사람이 뽑아 다시 본다(표본 검사).
사람이 본 건은 모두 정답이 붙으므로 학습 데이터로 쌓는다. 정답은 데이터에 미리 들어 있는 expected 로 흉내 낸다.
"""
import json
import random
import sys

import laya

THRESHOLD = float(sys.argv[1])  # 이 확신 미만은 사람이 직접 분류
AUDIT_RATE = float(sys.argv[2])  # 자동 처리분에서 사람이 다시 보는 비율
OUT = sys.argv[3]

agent = laya.load("checkpoints/v1")
rng = random.Random(3)
cases = [json.loads(line) for line in open("incoming-300.jsonl", encoding="utf-8")]

auto, auto_wrong, review, review_wrong, audited, audit_found = 0, 0, 0, 0, 0, 0
feedback = []
for case in cases:
    answer = agent.predict(case["state"], case["questions"])["answers"]["team"]
    confidence = answer["probabilities"][answer["choice"]]
    wrong = answer["choice"] != case["expected"]["team"]
    if confidence < THRESHOLD:
        review += 1
        review_wrong += wrong
        feedback.append(case)
    else:
        auto += 1
        auto_wrong += wrong
        if rng.random() < AUDIT_RATE:
            audited += 1
            audit_found += wrong
            feedback.append(case)

with open(OUT, "w", encoding="utf-8") as f:
    for case in feedback:
        f.write(json.dumps(case, ensure_ascii=False) + "\n")

print(f"incoming: {len(cases)}")
print(f"auto (confidence >= {THRESHOLD}): {auto}, wrong {auto_wrong} ({auto_wrong / max(auto, 1):.1%})")
print(f"human review (confidence < {THRESHOLD}): {review}, v1 was wrong on {review_wrong}")
print(f"audit sample of auto: {audited}, mistakes found {audit_found}")
print(f"human work: {review + audited} of {len(cases)} ({(review + audited) / len(cases):.0%})")
print(f"feedback rows saved: {len(feedback)} -> {OUT}")

문턱(THRESHOLD)·표본 비율(AUDIT_RATE)·저장할 파일을 인자로 받는다. 사람이 정답을 고르는 일은 데이터에 들어 있는 정답(expected)으로 흉내 냈다.

04 느슨한 규칙 — 문턱 0.90, 표본 10%

bash
python collect.py 0.90 0.10 data/feedback-loose.jsonl
text
incoming: 300
auto (confidence >= 0.9): 294, wrong 33 (11.2%)
human review (confidence < 0.9): 6, v1 was wrong on 1
audit sample of auto: 30, mistakes found 5
human work: 36 of 300 (12%)
feedback rows saved: 36 -> data/feedback-loose.jsonl

300건 가운데 294건을 0.90 이상으로 확신해 자동 처리했는데, 그중 33건(11.2%)이 틀렸다. 사람에게 넘어온 것은 6건뿐이다. 표본 검사 30건에서 틀린 것 5건을 찾았다. 사람 손은 12% 만 들었지만, 틀린 33건 가운데 사람이 찾은 것은 6건(검토 1 + 표본 5)이다. 나머지 27건은 틀린 채로 고객에게 갔다.

05 엄격한 규칙 — 문턱 0.99, 표본 30%

bash
python collect.py 0.99 0.30 data/feedback.jsonl
text
incoming: 300
auto (confidence >= 0.99): 286, wrong 27 (9.4%)
human review (confidence < 0.99): 14, v1 was wrong on 7
audit sample of auto: 76, mistakes found 11
human work: 90 of 300 (30%)
feedback rows saved: 90 -> data/feedback.jsonl
그림 1.느슨한 규칙과 엄격한 규칙
(%)051015202530자동 처리 중 오답사람이 본 비율자동 처리 중 오답 · 느슨한(0.90·10%): 11.2%자동 처리 중 오답 · 엄격한(0.99·30%): 9.4%사람이 본 비율 · 느슨한(0.90·10%): 12.0%사람이 본 비율 · 엄격한(0.99·30%): 30.0%11.2%9.4%12.0%30.0%0102030(%)자동 처리 중 오답사람이 본 비율자동 처리 중 오답 · 느슨한(0.90·10%): 11.2%자동 처리 중 오답 · 엄격한(0.99·30%): 9.4%사람이 본 비율 · 느슨한(0.90·10%): 12.0%사람이 본 비율 · 엄격한(0.99·30%): 30.0%11.2%9.4%12.0%30.0%
  • 느슨한(0.90·10%)
  • 엄격한(0.99·30%)

운영 문의 300건. 문턱을 올려도 자동 처리의 오답률은 거의 그대로다

숫자로 보기
항목느슨한(0.90·10%) (%)엄격한(0.99·30%) (%)
자동 처리 중 오답11.29.4
사람이 본 비율12.030.0

문턱을 0.99 로 올려도 사람에게 넘어온 것은 14건이다. 0.99 이상으로 확신한 286건 가운데 27건(9.4%)이 틀렸다. 표본을 30% 로 늘려 11건을 더 찾았다. 사람 손은 30%, 모인 정답은 90건이다.

06 왜 문턱이 듣지 않나

1편에서 결정 모델의 값어치는 믿을 수 있는 확신에 있다고 했다. 7편의 보정 경고가 그 문제를 미리 알렸다. 보정 데이터가 14건뿐이라 v1 의 확신은 보정되지 않았고, 실제로 v1 은 틀릴 때도 0.99 를 말한다. 확신을 믿을 수 없으면 문턱은 거의 아무것도 걸러 내지 못한다.

그래서 운영 초기에는 다음처럼 한다.

  1. 표본 검사를 넉넉히 한다(20~30%). 확신이 아직 거짓말을 하니 무작위로 보는 것이 가장 정직한 측정이다.
  2. 표본에서 찾은 틀린 비율을 실제 오류율로 기록한다. 이 숫자가 줄어드는지가 지속 학습이 잘 되는지의 첫 지표다.
  3. 모인 정답이 늘어 보정이 제대로 되면(학습 로그에 보정 경고가 사라지면) 그때 문턱에 기대기 시작한다.

공식 모델 카드도 배포된 체크포인트는 지나치게 확신하니 자기 데이터로 다시 보정하라고 적고 있다. 내 데이터로 학습한 판도 데이터가 적으면 마찬가지다.

07 실습 — 내 서비스에 고리를 놓을 때

  • 판단 결과를 남길 때 문장·선택·확신·판 이름을 함께 저장한다. 나중에 어느 판이 무엇을 틀렸는지 다시 볼 수 있다.
  • 사람이 고친 정답은 같은 형식(6편의 JSONL)으로 바로 쌓는다. 학습 데이터로 옮길 때 다시 손댈 일이 없다.
  • 고친 것뿐 아니라 표본 검사에서 맞았다고 확인한 것도 저장한다. 위의 90건 대부분이 그렇다. 맞았다고 확인된 새 표현도 다음 판에는 좋은 재료다.

다음 편에서 이 90건으로 v2 를 만든다. 이어서 학습하는 방법 셋을 실제로 비교하는데, 가장 자연스러워 보이는 방법이 오히려 점수를 떨어뜨린다.