같은 모델을 다시 채점하자 0.21%가 달라졌습니다: 자기개선 고리의 관문을 세우며 잰 채점 잡음의 바닥
TL;DR 모델이 스스로 나아지는 고리를 만들면 가장 먼저 필요한 것은 더 똑똑한 생성기가 아니라 "이 후보가 정말 부모보다 낫다"를 가려내는 관문입니다. 저희는 그 관문을 만들면서 먼저 채점 자체가 얼마나 흔들리는지부터 쟀습니다. 같은 모델을 같은 문항으로 두 번 채점했더니 필드의 0.21%에서 답이 달라졌고, 서버를 거치지 않고 한 프로세스에서 같은 순서로 다시 채점하자 0.00%가
TL;DR
모델이 스스로 나아지는 고리를 만들면 가장 먼저 필요한 것은 더 똑똑한 생성기가 아니라 "이 후보가 정말 부모보다 낫다"를 가려내는 관문입니다. 저희는 그 관문을 만들면서 먼저 채점 자체가 얼마나 흔들리는지부터 쟀습니다. 같은 모델을 같은 문항으로 두 번 채점했더니 필드의 0.21%에서 답이 달라졌고, 서버를 거치지 않고 한 프로세스에서 같은 순서로 다시 채점하자 0.00%가 되었습니다. 흔들림의 정체는 모델이 아니라 서버가 요청을 묶는 방식이었습니다.
이 바닥값 위에서 관문은 세 후보를 모두 맞게 판정했습니다. 아무것도 바꾸지 않은 재채점은 0.00%p로 거부, 가중치를 무작위로 살짝 흔든 대조군은 -0.11%p로 거부, 실제로 나아진 후보 v14는 +2.44%p로 들임이었습니다. 정답률 1%p 개선을 가려내려면 답이 갈리는 비율이 7.87%일 때 약 4,900필드가 필요하다는 계산도 함께 정리합니다.
이 글의 모든 수치는 저희가 직접 잰 값이고, 제3자가 검증한 결과가 아닙니다. 판독형(정답과 같은지 대조하는) 채점에서 잰 값이라 생성형 채점에는 그대로 옮길 수 없습니다.
자기개선 고리에서 관문은 왜 가장 중요한가요?
재귀적 자기개선(RSI)을 실제로 돌리려면 한 바퀴마다 같은 질문에 답해야 합니다. 새로 만든 후보가 부모보다 나은가, 그래서 부모 자리를 물려줘도 되는가. 후보를 만드는 쪽은 아무리 정교해도 가끔 틀립니다. 그러니 틀린 후보가 부모를 대체하지 못하게 막는 장치가 관문입니다. 관문이 약하면 고리는 점수의 잡음을 개선으로 착각해 계속 누적하고, 몇 바퀴 뒤에는 원래보다 나빠진 모델이 "개선된 모델"이라는 이름으로 남습니다.
그래서 관문을 설계할 때 제일 먼저 정할 것은 임계값이 아니라 잡음의 크기입니다. 같은 모델을 다시 재서 점수가 0.2% 흔들린다면, 0.2% 이하의 개선은 관문이 볼 수 없는 영역입니다. 이 글은 저희가 그 바닥을 어떻게 쟀는지, 그리고 그 위에서 관문이 어떻게 동작했는지를 숫자로 기록한 것입니다. 앞서 쓴 재귀적 자가개선(RSI)이란 무엇인가가 개념 설명이었다면, 이 글은 그 안에서 가장 지루하지만 가장 중요한 부품에 대한 이야기입니다.
같은 모델을 두 번 채점하면 얼마나 달라지나요?
실험의 바탕은 DI 0.3 공개 채점입니다. 보기를 예측해서 정답과 같은지 대조하는 방식이라 채점기가 모델이 아니라는 점이 중요합니다. 정답 대조는 정의상 채점 오류가 없으니 흔들림이 있다면 전부 추론 쪽에서 나온 것입니다. 기준 모델은 공개된 Darwin-27B-ZTC이고, 10월 8일에 한 번 돌린 원 실행과 11월 2일부터 3일 새벽까지 다시 돌린 재실행을 견주었습니다.
재실행은 140,620행을 한 줄도 빠짐없이, 오류 0건으로 마쳤습니다. 종합 점수는 원 실행 62.63, 재실행 62.60으로 0.03점 차이였고, 영역별로도 점수가 거의 붙어 있었습니다.
| 영역 | 두 실행의 점수 | 차이(절댓값) |
|---|---|---|
| knowledge | 0.4459 / 0.4472 | 0.0013 |
| language | 0.6851 / 0.6846 | 0.0005 |
| retrieval | 0.6093 / 0.6090 | 0.0003 |
| tools | 0.8661 / 0.8669 | 0.0008 |
| arts | 0.5331 / 0.5334 | 0.0003 |
종합 점수가 거의 같다는 것은 좋은 소식이지만 관문에는 충분한 정보가 아닙니다. 평균이 같아도 필드마다 맞고 틀림이 뒤바뀌었을 수 있기 때문입니다. 그래서 두 실행에서 겹치는 같은 166,129필드를 하나씩 대조했습니다. 결과가 달라진 필드는 0.21%(95% 구간 0.19~0.23)였고, 답이 그대로인 필드는 99.79%였습니다. 정답률 차이는 +0.024%p로, 방향이 있다고 말할 수 없는 수준이었습니다.
0.21%는 작지만 0은 아닙니다. 같은 가중치, 같은 문항, 같은 채점 규칙인데 왜 달라질까요?
흔들림은 모델이 아니라 서버에서 나왔습니다
원인을 좁히는 방법은 간단했습니다. 서버를 뺐습니다. 첫 번째 재실행은 추론 서버에 요청을 보내는 공식 http 경로였고, 서버는 동시에 들어온 요청을 그때그때 묶어 한꺼번에 계산합니다. 묶음 구성이 매번 다르면 보통 부동소수점 덧셈 순서가 달라지고, 확률이 거의 같은 두 보기 사이에서 결과가 뒤집힐 수 있습니다. 문항이 길수록, 보기 점수가 가까울수록 이런 뒤집힘이 잘 일어납니다.
그래서 관문용 채점은 서버를 띄우지 않고, 공식 채점 코드의 모델 클래스를 한 프로세스 안에서 직접 부르도록 했습니다. 이렇게 만든 하네스가 공식 경로와 같은 결과를 내는지부터 확인했습니다. 부모의 판정이 10월 8일 공식 http 실행의 판정과 99.66% 같았고, 저희가 미리 정해 둔 기준을 넘었습니다. 그다음 같은 부모를 같은 순서, 같은 묶음으로 한 번 더 채점했습니다. 바뀐 필드는 0개였습니다.
정리하면 이렇습니다.
| 조건 | 결과가 바뀐 필드 |
|---|---|
| 공식 http 서버 경로, 두 번의 실행 사이 | 0.21% |
| 한 프로세스, 같은 순서와 묶음으로 재채점 | 0.00% |
바닥값 0.21%는 모델의 불확실성이 아니라 요청 묶음이 매번 다른 데서 오는 배치 잡음이었다는 뜻입니다. 이 결론은 관문 설계에 바로 영향을 줍니다. 부모와 후보를 같은 프로세스에서 같은 묶음 순서로 채점해야 잡음이 가장 작습니다. 후보만 서버로 재고 부모는 예전 기록을 가져다 쓰면, 개선이 없어도 0.2% 안팎의 가짜 차이가 생길 수 있습니다.
참고로 이 바닥값은 판독형 채점에서 잰 것입니다. 가중치 원소 0.005%만 달라도 점수가 1.4%p 움직였다는 앞선 글에서 다룬 것처럼 생성형 채점은 잡음이 훨씬 큽니다. 같은 방법으로 생성형 바닥값을 따로 재야 하며, 0.21%를 거기에 가져다 쓰면 안 됩니다.
관문은 어떻게 판정하고, 세 후보는 어떻게 나왔나요?
관문의 규칙은 돌리기 전에 고정했습니다. 사후에 규칙을 손보면 결과를 보고 규칙을 맞추게 되기 때문입니다. 후보가 부모보다 같은 필드끼리 짝지어 견준 정답률 차가 0보다 크고, 단측 정확 McNemar 검정의 p가 0.05 미만이고, 행 단위 부트스트랩 2,000회로 얻은 95% 구간의 하한이 0보다 커야 들입니다. 셋 중 하나라도 어기면 거부합니다. 관문에 쓴 문항은 관문용으로 나눠 둔 절반에서 무작위로 고른 2,216행, 4,460필드이고, 나머지 절반은 나중에 확인용으로 따로 보관했습니다.
세 후보는 일부러 정답을 알고 있는 것들로 골랐습니다. 하나는 아무것도 바꾸지 않은 부모 재채점, 하나는 부모 가중치를 무작위로 살짝 흔든 대조군, 하나는 실제로 나아진 것으로 알려진 공개 모델 Darwin-27B-RSI-Decision v14입니다. 관문이 정답을 맞히는지 보려는 시험입니다.
| 후보 | 정답률 차 | 95% 구간 | 올라감 / 내려감 필드 | 단측 p | 판정 |
|---|---|---|---|---|---|
| 같은 부모 재채점 | 0.00%p | [0.00, 0.00] | 0 / 0 | 1.0 | 거부 |
| 무작위로 흔든 대조군 | -0.11%p | [-0.30, +0.07] | 6 / 11 | 0.93 | 거부 |
| 실제 후보 v14 | +2.44%p | [+1.52, +3.50] | 230 / 121 | 3.1e-9 | 들임 |
세 판정이 모두 맞았습니다. p 값은 표의 필드 수에서 직접 다시 계산해 보고서 값과 같은 것을 확인했습니다. 230개가 올라가고 121개가 내려간 v14는 351개 중 230개 이상이 올라갈 확률이 0.5라는 가정에서 3.1e-9이고, 6개와 11개로 갈린 대조군은 0.93이었습니다.
눈여겨볼 점이 둘 있습니다. 첫째, 무작위 대조군은 필드의 0.38%를 바꾸고 정답률을 0.11%p 떨어뜨렸는데, 4,460필드 관문으로는 "나빠졌다"가 통계적으로 확정되지 않았습니다. 그래도 들임 규칙이 개선만 들이고 확정되지 않은 변화는 전부 거부하므로 안전한 방향은 지켜졌습니다. 둘째, v14의 관문 측정 차이(+2.44%p)가 따로 보관해 둔 나머지 절반에서 잰 실제 차이(+3.14%p)보다 작았습니다. 관문 표본에서 생기는 오차이고 구간 안에 들어 있습니다. 그래서 "얼마나 나아졌는가"는 관문 값이 아니라 보관해 둔 확인용 절반의 값으로 보고해야 합니다. 관문은 들일지 말지를 정하는 도구이지, 개선폭을 재는 자가 아닙니다.
정답률 1%p 개선을 가려내려면 문항이 몇 개 필요한가요?
관문 표본의 크기는 감으로 정할 수 없습니다. 같은 필드끼리 짝지은 McNemar 검정에서 필요한 필드 수는 개선폭과 부모와 후보의 답이 갈리는 필드 비율이 정합니다. 개선폭을 delta, 갈리는 비율을 d라고 하면 단측 5%, 검정력 80%에서 필요한 수는 근사적으로 아래 식입니다.
import math
from scipy.stats import norm
def need_n(delta, d, alpha=0.05, power=0.8):
za, zb = norm.ppf(1 - alpha), norm.ppf(power)
return ((za * math.sqrt(d) + zb * math.sqrt(d - delta ** 2)) / delta) ** 2
print(round(need_n(0.01, 0.0787))) # 4864
v14에서 실측한 갈림 비율은 (230+121)/4,460 = 7.87%였습니다. 이 값을 넣으면 1%p를 가려내는 데 약 4,864필드가 필요합니다. 아래 그림은 갈림 비율에 따라 이 수가 어떻게 변하는지 보여 줍니다. 후보가 부모와 많이 다를수록, 곧 변화가 클수록 같은 개선폭을 잡으려면 더 많은 문항이 필요하다는 점이 직관과 반대로 보일 수 있지만, 갈리는 필드가 많을수록 그중 우연히 한쪽으로 쏠리는 몫도 커지기 때문입니다.
이 그림은 위 식으로 계산한 값이며 실측이 아닙니다. 식이 근사라서 몬테카를로로도 확인했습니다. 갈림 비율 7.87%, 개선폭 1%p를 가정하고 4,000번 모의했을 때, 관문이 쓴 4,460필드에서 개선을 잡아낼 확률은 약 75%, 4,900필드에서는 약 79%였습니다. 개선폭이 0.5%p로 줄면 4,460필드에서의 검정력은 약 30%로 떨어집니다. 저희 관문은 1%p 정도의 개선은 대체로 잡고, 0.5%p 이하는 놓칠 가능성이 크다는 뜻이고, 이 한계를 숨기지 않고 장부에 적어 두는 것이 중요합니다.
관문 코드는 길지 않습니다. 아래는 같은 필드끼리 짝지은 0/1 배열 두 개와 행 번호만 받아 판정을 돌려주는 최소 형태입니다.
import json, time
import numpy as np
from scipy.stats import binom
def gate(parent_ok, cand_ok, row_id, seed=0, boot=2000):
p, c = np.asarray(parent_ok), np.asarray(cand_ok)
up, down = int(((c == 1) & (p == 0)).sum()), int(((c == 0) & (p == 1)).sum())
diff = float(c.mean() - p.mean())
p_one = float(binom.sf(up - 1, up + down, 0.5)) if up + down else 1.0
_, inv = np.unique(row_id, return_inverse=True)
d_arr = np.bincount(inv, weights=c - p)
n_arr = np.bincount(inv).astype(float)
rng = np.random.default_rng(seed)
idx = rng.integers(0, len(d_arr), size=(boot, len(d_arr)))
boots = d_arr[idx].sum(axis=1) / n_arr[idx].sum(axis=1)
lo, hi = np.percentile(boots, [2.5, 97.5])
admit = diff > 0 and p_one < 0.05 and lo > 0
return {"diff": diff, "ci": [float(lo), float(hi)], "up": up, "down": down,
"p_one_sided": p_one, "verdict": "들임" if admit else "거부", "ts": time.time()}
def log(ledger_path, candidate_id, parent_id, result):
with open(ledger_path, "a", encoding="utf-8") as f:
f.write(json.dumps({"candidate": candidate_id, "parent": parent_id, **result}, ensure_ascii=False) + "\n")
행 단위로 부트스트랩하는 이유는 같은 행에 속한 필드끼리 상관이 있기 때문입니다. 필드 단위로 다시 뽑으면 구간이 실제보다 좁게 나옵니다. 이 코드를 필드 수만 맞춘 가상의 데이터(부모 정답률 약 62%, 230개 상승과 121개 하락을 일부러 심은 후보)에 돌려 보면 재채점 대조는 "거부", 심은 후보는 "들임"으로 나옵니다. 실제 모델 결과가 아니라 코드가 의도대로 동작하는지 보는 설명용 모의입니다. 장부에는 판정마다 한 줄씩 남기고, 후보 식별값, 부모, 정답률 두 개, 차이, 구간, 두 칸의 McNemar 수, p, 판정, 사유, 시각을 넣습니다. 나중에 "왜 이 후보가 들어왔나"를 한 줄로 되짚을 수 있어야 하기 때문입니다.
고리가 후보를 직접 만들기 시작하면 어떻게 되나요?
첫 바퀴에서는 후보를 사람이 넣었습니다. 다음 단계로 후보를 고리가 스스로 만들게 붙여 10라운드를 돌렸습니다. 저희 1팀 기록으로는 후보 10개가 전부 거부되었고, 부모 대비 정답률 차는 -1.61%p에서 -0.09%p 사이였습니다. 그중 5개는 구간 상한까지 0보다 낮았습니다. 즉 이번 생성기가 만든 후보는 부모보다 나아지지 못했고, 관문은 부모를 그대로 유지했습니다.
이 결과는 실패가 아니라 관문이 일하는 모습입니다. 좋아 보이는 후보가 아니라 나아진 후보만 통과시키는 장치가 있기 때문에 "후보 10개가 모두 부모보다 못했다"는 사실을 바로 알 수 있습니다. 관문이 없었다면 마지막 후보가 최신 모델이라는 이유로 자리를 이어받았을 것입니다. 이 후속 결과는 아직 사람이 감독하는 모드에서 얻은 것이고, 무인으로 돌리는 것은 별도의 결재 대상으로 남겨 두었습니다.
자주 묻는 질문
Q. 0.21%는 무시해도 되는 값인가요?
종합 점수를 비교할 때는 작아 보이지만, 관문처럼 필드 단위로 "올라갔나 내려갔나"를 세는 곳에서는 의미가 있습니다. 0.21%는 4,460필드에서 약 9개가 이유 없이 바뀐다는 뜻이고, 개선폭 1%p(약 45개의 순증가)와 견주면 무시할 크기가 아닙니다. 그래서 부모와 후보를 같은 프로세스에서 채점해 이 잡음을 0으로 만드는 쪽을 택했습니다.
Q. 서버를 빼면 결과가 달라지지 않나요?
달라질 수 있어서 먼저 확인했습니다. 하네스의 판정이 공식 http 실행과 99.66% 같았습니다. 나머지 0.34%는 위에서 본 배치 잡음의 범위로 보고 있습니다.
Q. 무작위로 흔든 대조군은 왜 필요한가요?
관문이 "아무 변화나 개선으로 착각하지 않는지"를 보는 시험입니다. 아무것도 바꾸지 않은 대조군은 잡음이 0일 때를, 무작위로 흔든 대조군은 작지만 실재하는 손상이 있을 때를 보여 줍니다. 두 경우 모두 거부되어야 합니다.
Q. 관문의 개선폭을 그대로 보고하면 안 되나요?
권하지 않습니다. 들인 후보의 관문 측정값(+2.44%p)은 선택 과정에 쓰인 표본에서 나온 값이라 실제보다 크거나 작을 수 있습니다. 개선폭은 관문에 쓰지 않은 보관 표본에서 따로 재서 보고하는 것이 맞고, 이번에는 +3.14%p였습니다.
Q. 이 결과를 다른 모델과 다른 채점에도 쓸 수 있나요?
방법은 쓸 수 있지만 숫자는 아닙니다. 0.21%는 이 모델, 이 판독형 채점, 이 서버 설정에서 잰 바닥입니다. 생성형 채점이나 다른 서버에서는 바닥을 새로 재야 합니다.
한계와 아직 모르는 것
이 글의 수치는 전부 저희 팀의 자체 측정이고 외부 기관이 독립적으로 확인한 것이 아닙니다. 바닥값 0.21%는 두 실행이 겹치는 166,129필드로 잰 것이며, 재실행이 끝난 140,620행 전체로 같은 비교를 다시 한 값은 아닙니다. 필요 문항 수 계산은 근사식과 몬테카를로로 확인한 설명용 수치입니다. 고리가 후보를 스스로 만든 10라운드의 결과는 한 번의 시도이고, 생성기를 바꾸면 달라질 수 있습니다.
Further reading
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.