15. 실전사례 — 현장의 목소리, JoyCaption의 DPO 실전기

마진 기반 필터링: 양 끝을 골라 쌍을 만든다

좋은 쌍의 조건을 알아도, 모델이 스스로 뽑은 응답을 아무렇게나 짝지으면 그런 쌍이 얼마나 나올까? JoyCaption의 첫 시도가 바로 이 물음에 걸려 넘어졌다.

첫 번째 시도의 실패 — 약한 예제의 함정

첫 시도에서는 프롬프트마다 응답을 두 개 뽑아 심사 모델에게 어느 쪽이 나은지 고르게 했다. 이렇게 약 1만 쌍을 만들어 DPO를 돌렸다. 학습이 되는지는 보상 정확도로 봤다. DPO에는 보상 모델이 따로 없지만, 학습 중인 모델이 레퍼런스보다 그 응답의 확률을 얼마나 올렸는지가 보상 구실을 한다. 이것을 암묵적 보상이라 부른다.

r^(x,y)=β[log⁡πθ(y∣x)−log⁡πref(y∣x)]\textcolor{#d9670b}{\hat r}(\textcolor{#0093b8}{x}, \textcolor{#1c9c60}{y}) = \textcolor{#827717}{\beta}\big[\log\textcolor{#1565c0}{\pi_\theta}(\textcolor{#1c9c60}{y} \mid \textcolor{#0093b8}{x}) - \log\textcolor{#6f6f78}{\pi_\text{ref}}(\textcolor{#1c9c60}{y} \mid \textcolor{#0093b8}{x})\big]
r^암묵적 보상: 레퍼런스보다 로그확률을 얼마나 올렸나βKL 강도 (JoyCaption은 0.1)πθ학습 중인 정책πref레퍼런스: 학습 전 모델을 얼려 둔 것x, y프롬프트와 응답 \small\begin{array}{ll} \textcolor{#d9670b}{\hat r} & \text{암묵적 보상: 레퍼런스보다 로그확률을 얼마나 올렸나} \\ \textcolor{#827717}{\beta} & \text{KL 강도 (JoyCaption은 0.1)} \\ \textcolor{#1565c0}{\pi_\theta} & \text{학습 중인 정책} \\ \textcolor{#6f6f78}{\pi_\text{ref}} & \text{레퍼런스: 학습 전 모델을 얼려 둔 것} \\ \textcolor{#0093b8}{x},\ \textcolor{#1c9c60}{y} & \text{프롬프트와 응답} \end{array}

보상 정확도는 선호 응답 yw\textcolor{#e000a5}{y_w} 의 암묵적 보상이 비선호 응답 yl\textcolor{#e000a5}{y_l} 보다 높은 쌍의 비율이다(JoyCaption 학습 코드가 기록하는 rewards/accuracies). 학습을 시작할 때는 πθ=πref\textcolor{#1565c0}{\pi_\theta} = \textcolor{#6f6f78}{\pi_\text{ref}} 라 모든 보상이 0이고, 모델이 두 응답을 가르는 법을 배울수록 오른다. 아무렇게나 찍으면 50%다.

첫 시도의 보상 정확도는 하이퍼파라미터를 아무리 바꿔도 약 60%에서 더 오르지 않았다. 찍는 것보다 조금 나은 수준이다. 데이터셋을 직접 열어 보니 대부분의 쌍이 비슷비슷했다. 모델이 이미 잘하는 영역에서는 좋은 답 두 개가, 못하는 영역에서는 비슷하게 나쁜 답 두 개가 나왔다. 같은 쌍을 다른 심사 모델로 다시 판정해 보니 심사 모델끼리 일치하는 비율이 낮았다. 두 응답의 질이 비슷하다는 뜻이다. 게다가 심사 모델들이 순서에 합의하더라도 차이가 미미하면 모델이 그 쌍에서 배울 것도 적다. 제작자의 표현으로 모델은 「갈 방향을 잡지 못하고 이리저리 흔들리기만」 했다. 마진을 벌려야 DPO가 배운다는 것이 현장에서 그대로 드러난 것이다.

해결: 점수를 매기고 양 끝을 묶는다

개발자는 심사 방식을 바꿨다. 둘 중 어느 쪽이 나은지만 고르게 하던 것을, 두 응답 각각에 1–10점도 매기게 했다. 이제 점수가 가까운 쌍을 버리고 마진이 넓은 쌍만 남길 수 있다. 그러자 새 문제가 생겼다. 대부분의 쌍이 마진이 좁아 버려졌고, 쓸 만한 쌍 하나를 얻으려고 심사 모델을 여러 번 불러야 해서 비용이 커졌다.

그래서 값싼 첫 거름을 앞에 두었다. 한 프롬프트에 응답을 10개 뽑아 심사 모델에게 한 번에 순위를 매기게 하고, 맨 위와 맨 아래의 두 응답을 쌍 후보로 삼았다. 이 후보만 원래의 두 응답 심사에 보내 점수와 마진을 확인했다. 10개 중 양 끝을 고르면 마진이 저절로 커지고, 예제 1 같은 (8점 vs 7점) 쌍은 애초에 후보가 되지 않는다.

무작위로 고른 두 개 마진 1점 12345678910 맨 위와 맨 아래 마진 6점 12345678910

위 그림은 한 프롬프트의 응답 10개에 점수를 지어 붙인 예다. 아무 둘이나 고르면 붙어 있는 두 점이 걸리기 쉽고, 양 끝을 고르면 늘 가장 먼 두 점이 걸린다. 아래 위젯은 프롬프트 400개로 같은 일을 되풀이해 마진의 분포를 비교한다.

이 과정에서 그는 헛발질도 따로 캤다. 같은 구절을 되풀이하는 응답을 골라내는 규칙 기반 검출기를 만들어, 반복 루프에 빠진 응답과 멀쩡한 응답이 짝지어진 쌍을 먼저 골랐다. 헛발질을 없애는 것이 강화학습을 쓴 주된 목적이었기 때문이다. 이렇게 다시 만든 데이터셋으로 두 번째 시도를 하자 보상 정확도는 평가용 데이터에서 76%까지 올랐다.

결과는 다음과 같았다. 결함률은 헛발질(반복 루프나 쓸모없는 출력)의 비율이고, 선호율과 품질은 학습에 쓰지 않은 프롬프트로 앞 모델과 새 모델의 응답을 심사 모델에게 견주게 해서 쟀다.

지표 DPO 전 1라운드 뒤 (1만 1천 쌍) 2라운드 뒤 (2만 5천 쌍)
결함률 (헛발질 출력 비율) 9.6% 3.0% 1.5%
앞 모델 대비 선호율 — 2 : 1 우세 다시 2 : 1 우세
심사 모델이 매긴 평균 품질 (0~1) 0.46 0.56 0.67
SD 프롬프트 유형 결함률 37% 5%
문제 3 — 아무나 둘, 가장 큰 사람과 가장 작은 사람

어느 학년 학생들의 키가 평균 170cm, 표준편차 6cm인 정규분포를 따른다고 하자. (가) 아무나 두 명을 골랐을 때, 큰 학생의 키에서 작은 학생의 키를 뺀 차이는 평균 몇 cm인가? (나) 열 명을 골랐을 때 가장 큰 학생과 가장 작은 학생의 키 차이는 평균 몇 cm인가?

김민준 (평상)
김민준
(가)는 0 아니에요? 두 학생이 같은 분포에서 나왔으니까 평균끼리 빼면 0이요.
선생님 (질문)
선생님
민준 학생, 문제는 "큰 학생 키 − 작은 학생 키"예요. 그럼 음수가 나올 수 있나요?
김민준 (당황)
김민준
아… 큰 쪽에서 작은 쪽을 빼니까 항상 양수네요. 차이의 평균이 아니라 차이의 절댓값의 평균이었어요.
이서연 (평상)
이서연
그건 공식이 있어. 독립인 두 정규분포의 차는 표준편차가 626\sqrt{2} 인 정규분포고, 그 절댓값의 평균은 2×6/π≈6.82 \times 6 / \sqrt{\pi} \approx 6.8cm.
김민준 (평상)
김민준
(나)는 공식을 몰라서 40만 번 돌렸어. 열 명 중 최대 − 최소의 평균이 18.5cm쯤 나와.
선생님 (평상)
선생님
공식과 시뮬레이션이 같이 있으니 좋네요. 2.7배 차이예요.
김민준 (평상)
김민준
팀플 점수 차이를 물었는데 "평균적으로 다들 비슷해요"라고 답한 셈이었네요. 누가 더 잘했는지 따지는 순간 차이는 음수가 안 되는데.

정리 "큰 쪽 − 작은 쪽"은 늘 0 이상이므로 평균은 절댓값의 평균이다. (가) 2σ/π=2×6/π≈6.82\sigma/\sqrt{\pi} = 2 \times 6/\sqrt{\pi} \approx 6.8cm. (나) 약 18.5cm(시뮬레이션). 아무 둘이 아니라 양 끝을 고르면 차이가 약 2.7배가 된다.

문제 4 — 양 끝을 고르면 얼마나 벌어지나

응답 점수가 표준편차 1.5인 정규분포를 따른다고 하자. (가) 무작위로 두 응답을 골라 만든 쌍의 평균 마진과 (나) 10개 중 최상위·최하위로 만든 쌍의 평균 마진을 구하시오. (다) 심사 비용을 아끼려고 프롬프트마다 후보를 5개만 뽑으면, 양 끝 쌍의 평균 마진도 (나)의 절반이 되는가?

김민준 (자신만만)
김민준
아까 키 문제랑 같네요. 표준편차만 6에서 1.5로 줄었으니 전부 4로 나누면 돼요. (가)는 2×1.5/π≈1.692 \times 1.5/\sqrt{\pi} \approx 1.69점, (나)는 18.5/4≈4.6218.5/4 \approx 4.62점.
선생님 (평상)
선생님
좋아요. (다)는요?
김민준 (평상)
김민준
후보를 절반으로 줄였으니 마진도 절반이겠죠. 2.3점쯤이요.
선생님 (질문)
선생님
민준 학생, 후보가 2개일 때 양 끝 쌍의 마진은 얼마죠? 그리고 2개에서 10개로 다섯 배 늘렸을 때 마진은 몇 배가 됐죠?
김민준 (난처함)
김민준
후보가 2개면 양 끝이 곧 무작위 쌍이라 (가)의 1.69점이고… 다섯 배 늘렸는데 마진은 2.7배밖에 안 늘었네요. 후보 수에 비례하지 않아요.
이서연 (생각)
이서연
맨 끝 값은 후보가 늘수록 점점 덜 늘어나. 정규분포는 꼬리로 갈수록 얇아서, 이미 끝에 가까운 값을 넘어서는 응답이 잘 안 나오니까. 표준정규분포 nn개의 최댓값은 대략 2log⁡n\sqrt{2\log n} 처럼 아주 느리게 자란다고 배웠어.
김민준 (평상)
김민준
돌려 봤어요. 5개면 평균 마진이 3.49점이에요. 절반이 아니라 4분의 3쯤 남네요. 반대로 20개로 늘려도 5.60점이라 21%밖에 안 늘고요.
선생님 (평상)
선생님
그래요. 그래서 "몇 개를 뽑을까"는 마진과 심사 비용을 맞바꾸는 선택이에요.
김민준 (평상)
김민준
조원을 다섯 명에서 열 명으로 늘린다고 최고점과 최저점 차이가 두 배가 되지는 않는 거네요.

정리 (가) 2σ/π≈1.692\sigma/\sqrt{\pi} \approx 1.69점. (나) 약 4.62점(시뮬레이션). 후보를 많이 뽑아 양 끝을 고르는 것만으로 마진이 약 2.7배가 된다. (다) 아니다. 5개면 약 3.49점으로 4분의 3쯤 남는다(20개면 약 5.60점). 양 끝 마진은 후보 수에 비례하지 않고 아주 느리게 자란다. 위젯에서 N을 2, 5, 10으로 바꿔 확인해 보라.

문제 5 — (킬러) 심사 모델도 틀린다

응답의 진짜 품질을 q∼N(0,1)q \sim \mathcal{N}(0, 1), 심사 모델이 매긴 점수를 s=q+ns = q + n, 심사 오차를 n∼N(0,1)n \sim \mathcal{N}(0, 1) 이라 하자(글자는 품질 quality, 점수 score, 오차 noise의 머리글자다). JoyCaption처럼 10개 중 ss 가 가장 높은 것과 낮은 것으로 쌍을 만든다. (가) 쌍의 관측 마진(ss 의 차)과 진짜 마진(qq 의 차)의 평균을 비교하시오. (나) 진짜 품질이 뒤집힌 쌍(qw<qlq_w < q_l), 즉 라벨이 틀린 쌍의 비율을 무작위 쌍과 비교하시오. (다) 이 결과는 마진 필터링과, 라벨이 틀렸을 확률 ε을 손실에 섞는 cDPO에 대해 무엇을 말하는가?

민준이 40만 번 시뮬레이션한 결과를 띄운다.
김민준 (자신만만)
김민준
(가) 관측 마진 평균은 4.35예요. 심사 점수로 골랐으니 진짜 마진도 4.35쯤이겠죠.
선생님 (질문)
선생님
그런데 시뮬레이션 결과표에 진짜 마진도 있네요. 얼마죠?
김민준 (놀람)
김민준
2.18… 딱 절반이에요. 왜 반이나 줄지?
이서연 (평상)
이서연
점수가 높은 걸 고르면 품질이 높은 것도 뽑히지만, 오차가 운 좋게 큰 것도 같이 뽑혀. qq 와 nn 의 분산이 같으니까, 관측된 ss 중에서 평균적으로 절반은 품질이고 절반은 오차야. E[q∣s]=s/2\mathbb{E}[q \mid s] = s/2.
김민준 (평상)
김민준
그러니까 제일 높게 채점된 응답은 "진짜로 좋은 것 + 운 좋게 후하게 채점된 것"이구나.
선생님 (평상)
선생님
맞아요. 이걸 승자의 저주라고 해요. 그럼 (나). 서연 학생, 라벨이 틀린 쌍은 어느 쪽이 많을까요?
이서연 (평상)
이서연
양 끝을 고를 때 오차가 극단인 것도 같이 골랐으니까, 라벨이 틀린 쌍도 극단 쌍 쪽에 더 많을 것 같아요. 노이즈를 일부러 모은 셈이니까요.
선생님 (질문)
선생님
결과표의 뒤집힌 비율을 볼까요?
김민준 (평상)
김민준
무작위 쌍은 25%, 양 끝 쌍은 2.8%예요.
이서연 (생각)
이서연
반대네요. 오차를 모으긴 했지만, 품질 차이도 같이 커졌어요. 진짜 마진이 2.18이나 되면 오차가 그걸 뒤집을 만큼 크기가 어렵고요. 무작위 쌍은 진짜 마진이 0.8밖에 안 돼서, 오차 하나로 쉽게 뒤집히고요.
이서연 (깨달음)
이서연
"오차가 섞였다"와 "라벨이 틀렸다"는 다른 문제였어요. 마진의 크기는 부풀려졌지만 부호는 오히려 더 믿을 만해졌네요.
선생님 (짚어주기)
선생님
그런데 "절반"은 어디서 왔죠? 심사 모델이 훨씬 정확해서 오차의 표준편차가 1이 아니라 0.5라면요?
이서연 (생각)
이서연
절반은 qq 와 nn 의 분산이 둘 다 1이라서 나온 거예요. 일반적으로는 E[q∣s]=Var⁡qVar⁡q+Var⁡n s\mathbb{E}[q \mid s] = \frac{\operatorname{Var} q}{\operatorname{Var} q + \operatorname{Var} n}\, s 니까, 오차 표준편차가 0.5면 1/(1+0.25)=0.81/(1 + 0.25) = 0.8. 점수의 80%가 품질이에요.
김민준 (평상)
김민준
돌려 보면 관측 마진 3.44, 진짜 마진 2.75로 정말 0.8배예요. 뒤집힌 비율은 무작위 쌍 14.7%, 양 끝 쌍 0.09%로 둘 다 줄고요.
선생님 (평상)
선생님
그래요. 얼마나 깎아 읽을지는 심사 오차가 품질의 흩어짐에 비해 얼마나 큰지가 정해요. 민준 학생은 크기를, 서연 학생은 부호를 잘못 짚었어요. 그럼 (다)는요?
김민준 (평상)
김민준
마진 필터링은 라벨 노이즈를 줄여 주니까 좋아요. 대신 "관측 마진 9점"이라고 진짜 9점만큼 차이 나는 건 아니에요.
이서연 (평상)
이서연
그리고 뒤집힌 비율이 0은 아니니까, cDPO처럼 "라벨이 틀렸을 확률 ε"을 두는 게 여전히 의미가 있어요. 무작위 쌍이면 ε이 25% 근처지만, 양 끝 쌍이면 3% 정도로 잡을 수 있겠네요.
이서연 (평상)
이서연
통계 수업에서 배운 평균으로의 회귀랑 같아요. 첫 시험에서 1등 한 학생의 두 번째 점수는 평균적으로 조금 내려가죠. 실력이 준 게 아니라 첫 점수에 운이 섞여 있었으니까요.
김민준 (평상)
김민준
나는 조교님이 "제일 잘 쓴 보고서랑 제일 못 쓴 보고서는 채점이 틀려도 순서는 안 바뀐다, 헷갈리는 건 중간이다"라고 했던 게 이제 이해돼.

정리 (가) 관측 마진 약 4.35, 진짜 마진 약 2.18 — 오차와 품질의 분산이 같으면 선택된 점수의 절반은 운이다(승자의 저주, E[q∣s]=s/2\mathbb{E}[q \mid s] = s/2). 깎는 몫은 일반적으로 Var⁡q/(Var⁡q+Var⁡n)\operatorname{Var} q / (\operatorname{Var} q + \operatorname{Var} n) 이고, 오차 표준편차가 0.5면 점수의 80%가 품질이다(관측 3.44, 진짜 2.75). (나) 라벨이 뒤집힌 쌍: 무작위 쌍 약 25%, 양 끝 쌍 약 2.8%. 오차가 섞였어도 품질 차이가 더 크게 벌어져 부호는 더 믿을 만해진다. (다) 마진 필터링은 라벨 노이즈를 크게 줄이지만 마진의 크기는 과대평가한다. 남은 노이즈는 cDPO의 ε으로 다룰 수 있고, ε을 쌍을 고른 방식에 맞춰 정해야 한다.