답이 셋뿐인 장난감 문제를 떠올려 보자. "대한민국의 수도는?"에 모델이 “서울입니다”(1점), “부산입니다”(0점), “서울이요”(0.6점) 가운데 하나로만 답한다. 평균 점수를 올리려고 샘플을 하나씩 뽑아 파라미터를 업데이트하면 학습 곡선이 요동친다. 점수마다 똑같이 5를 더해 주면 요동은 더 심해진다. 모든 답에 똑같이 얹은 점수가 어느 답이 나은지를 바꿀 리 없고, 평균을 올리는 방향도 그대로인데 왜 그럴까? 그리고 그 요동은 어떻게 줄이는가?
정확한 식이지만 언어 모델에서는 계산할 수 없다. 기댓값을 구하려면 가능한 모든 응답을 더해야 하는데, 어휘가 1만 개인 모델이 20토큰짜리 답만 쓴다고 해도 가짓수가 1000020=1080 이다. 관측 가능한 우주의 원자 수로 흔히 꼽는 값(약 1080)과 맞먹고, 실제 답은 이보다 훨씬 길다. 그러니 할 수 있는 일은 하나뿐이다: 몇 개를 뽑아서 평균을 낸다.
몬테카를로 추정: 기댓값 대신 샘플 평균
뽑은 몇 개의 평균이 정말 참 그래디언트를 대신할 수 있을까? 대신할 수 있다면, 업데이트 한 스텝은 실제로 어떻게 돌아가고, 무엇이 비싼가?
각 항의 기댓값이 ∇J이므로 평균의 기댓값도 ∇J다. 이처럼 여러 번 뽑아 평균내면 참값으로 수렴하는 추정을 편향 없는(unbiased) 추정이라고 한다. 미니배치 확률적 경사하강법(SGD)이 전체 데이터의 그래디언트 대신 배치 평균을 쓰는 것과 같은 원리다.
실제 학습 루프
REINFORCE 한 스텝은 여섯 단계다.
단계
하는 일
비용
① 생성
현재 모델로 프롬프트마다 응답을 뽑는다 (샘플링 온도 1: 모델이 준 확률 그대로 뽑는다)
모델을 돌려 답을 뽑는 계산(가장 느린 단계)
② 채점
각 응답에 보상 Ri를 매긴다 (검증기, 보상 모델, 사람)
채점기에 따라 다름
③ 로그확률
응답의 토큰 로그확률을 더해 logπθ(yi)를 구한다
순전파 1회
④ 대리 손실(그래디언트만 빌려 쓰는 손실)
L=−N1∑iRilogπθ(yi)
무시할 만함
⑤ 역전파·업데이트
∇L로 파라미터를 한 스텝 옮긴다
역전파 1회
⑥ 폐기
뽑았던 응답을 버리고 ①로 돌아간다
없음
샘플링 온도를 1로 두는 것은 샘플 평균 식이 yi∼πθ 를 조건으로 하기 때문이다. 샘플링 온도를 낮춰 뽑으면 샘플이 πθ 가 아닌 더 뾰족한 분포에서 나오고, 그 샘플의 평균은 더 이상 ∇J 를 겨누지 않는다. 코드로 쓰면 SFT(정답을 그대로 따라 쓰게 하는 지도 파인튜닝)에서 몇 줄 바뀔 뿐이다.
responses = policy.generate(prompts, do_sample=True) # ① 생성
rewards = score(prompts, responses) # ② 채점, shape [N]
logp = policy.logprob(prompts, responses).sum(dim=-1) # ③ 토큰 로그확률의 합, shape [N]
loss = -(rewards.detach() * logp).mean() # ④ 대리 손실
loss.backward(); optimizer.step(); optimizer.zero_grad() # ⑤
⑥이 중요하다. 로그 미분 트릭은 "지금의 πθ에서 뽑은 샘플"에만 성립하므로, 파라미터를 한 번 업데이트하면 방금 뽑은 응답은 남의 응답이 된다. 비싸게 생성한 샘플을 그래디언트 한 스텝에 쓰고 버리는 이 낭비가 REINFORCE의 큰 약점이다.
왜 이렇게 흔들리는가
장의 맨 앞에서 든 장난감 문제(「대한민국의 수도는?」에 답이 셋뿐인 모델)로 재 보자. 처음 소프트맥스 정책이 세 답 “서울입니다”(1점), “부산입니다”(0점), “서울이요”(0.6점)에 약 0.14, 0.61, 0.25의 확률을 준다. 이때 "서울입니다"의 로짓(소프트맥스를 거쳐 확률이 되기 전의 값)에 대한 참 그래디언트는 +0.098이다. 샘플 하나로 추정하면 뽑힌 답에 따라 값이 달라진다. 이 추정치의 표준편차를 계산해 보면:
설정
추정치 평균
표준편차
신호 대 잡음비
평균이 표준편차보다 커지려면 필요한 샘플 수
보상 그대로
0.098
0.307
0.32
10개
보상에 +5
0.098
2.024
0.05
430개
(신호 대 잡음비는 평균 ÷ 표준편차로, 추정치의 평균이 표준편차의 몇 배인지를 잰다. 마지막 열은 (표준편차/평균)2, 즉 N개를 평균내 표준편차가 1/N로 줄었을 때 평균과 같아지는 N이다.)
평균은 두 줄 모두 똑같고 달라지는 것은 표준편차(제곱하면 분산)여서, 보상에 5를 더했을 뿐인데 같은 정확도를 얻으려면 샘플이 43배 필요하다. 샘플 하나가 LLM의 응답 생성 한 번이라는 걸 생각하면, 분산은 곧 GPU 비용이다.
샘플 하나의 추정치가 실제로 어떤 값들인지 펼쳐 보면 왜 그런지 보인다.
왜 상수가 분산을 키우는가? 추정치는 R⋅∇logπ(y)다. ∇logπ는 어떤 답이 뽑혔느냐에 따라 방향이 크게 바뀌는 벡터이고, R은 그 벡터의 길이를 늘린다. 모든 답의 R이 5 근처라면, 매 샘플이 "뽑힌 답을 5배 세게 올려라"라고 외친다. 좋은 답이 뽑히든 나쁜 답이 뽑히든. 그림 아래쪽에서 "부산입니다"가 뽑힌 샘플이 "서울입니다"의 로짓을 −0.68만큼 내리라고 외치는 것이 그 외침이다. 이 외침들은 평균내면 상쇄되지만(확률의 합이 늘 1이므로 상수 보상의 기대 그래디언트는 0이다), 하나하나는 크다.
문제 1: 몇 명에게 물어야 하나
동아리 회장이 MT를 바다로 갈지 산으로 갈지 정하려고 회원에게 한 명씩 물어, 바다를 고른 답은 +1, 산을 고른 답은 −1로 적고 그 평균으로 판단한다. 회원 전체로는 60%가 바다파다. (가) 아무나 한 명에게 물었을 때 그 답의 평균과 표준편차는? (나) 몇 명에게 물어 평균을 내야 그 평균이 평균의 표준편차(한 명 답의 표준편차 ÷ √명수)만큼 커지는가? (다) 바다파가 55%라면?