A2C는 각 rollout을 한 번 업데이트(update)한 뒤 버립니다. PPO는 정책 기울기(policy gradient)를 클리핑된 중요도 비율(clipped importance ratio)로 감싸, 정책(policy)이 폭주하지 않으면서 같은 데이터에 10 epoch 이상을 수행할 수 있게 합니다. Schulman et al. (2017)의 방법이며, 2026년에도 여전히 기본 정책 기울기 알고리즘(policy-gradient algorithm)입니다.
유형: Build
언어: Python
선수 지식: Phase 9 · 06 (REINFORCE), Phase 9 · 07 (Actor-Critic)
예상 시간: 약 75분
문제
A2C(Lesson 07)는 on-policy 방식입니다. 기울기 E_{π_θ}[A · ∇ log π_θ]는 현재 정책 π_θ에서 표본 추출(sampling)된 데이터를 요구합니다. 한 번 업데이트하면 π_θ가 바뀌고, 방금 사용한 데이터는 이제 off-policy가 됩니다. 그 데이터를 다시 사용하면 기울기가 편향(biased)됩니다.
롤아웃(rollout)은 비쌉니다. Atari에서 환경(env) 8개 × 128 step의 롤아웃 하나는 1024개의 전이(transition)이고, 환경 시간(environment time)으로 십여 초가 필요합니다. 한 번의 기울기 단계 뒤에 이를 버리는 것은 큰 낭비입니다.
신뢰 영역 정책 최적화(Trust Region Policy Optimization; TRPO, Schulman 2015)가 첫 번째 해결책이었습니다. 각 업데이트를 제한해 옛 정책과 새 정책 사이의 KL 발산(KL divergence)이 δ 아래에 머물게 합니다. 이론적으로는 깔끔하지만, 업데이트마다 켤레 기울기(conjugate gradient) 풀이가 필요합니다. 2026년에는 거의 아무도 TRPO를 돌리지 않습니다.
PPO(Schulman et al. 2017)는 딱딱한 신뢰 영역 제약(trust-region constraint)을 단순한 클리핑된 목적 함수(clipped objective)로 바꿉니다. 코드 한 줄이 추가될 뿐입니다. 롤아웃 하나에 10 epoch를 돌릴 수 있습니다. 켤레 기울기가 필요 없습니다. 충분히 좋은 이론적 보장도 갖춥니다. 9년이 지난 지금도 MuJoCo부터 RLHF까지 기본 정책 기울기 알고리즘으로 쓰입니다.
사전 테스트
2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요
1.A2C는 각 롤아웃(rollout)을 정확히 한 번의 경사 갱신(gradient update)에만 사용하고 버립니다. 이것이 왜 낭비이며, 데이터를 여러 번 재사용하는 것을 가로막는 것은 무엇인가요?
2.PPO의 클리핑된 대리 목적 함수(clipped surrogate)는 중요도 비율(importance ratio) r_t(theta) = pi_theta(a|s) / pi_old(a|s)를 사용합니다. 특정 (s,a)에서 r_t = 2.0이 의미하는 것은 무엇인가요?
0/2 답변 완료
개념
중요도 비율(importance ratio).
r_t(θ) = π_θ(a_t | s_t) / π_{θ_old}(a_t | s_t)
새 정책과 데이터를 모은 정책 사이의 가능도 비(likelihood ratio)입니다. r_t = 1이면 변화가 없습니다. r_t = 2이면 새 정책이 a_t를 옛 정책보다 두 배 더 자주 선택한다는 뜻입니다.
L^{CLIP} + 가치 손실(value loss) + 엔트로피(entropy)를 적용합니다.
기울기 단계(gradient step)를 한 번 밟습니다.
롤아웃을 버리고 1번으로 돌아갑니다.
K = 10, 미니배치 64는 표준적인 하이퍼파라미터 조합입니다. PPO는 견고(robust)합니다. 정확한 수치는 대체로 ±50% 범위 안에서는 크게 중요하지 않습니다.
KL 페널티(KL-penalty) 변형. 원래 논문은 적응형 KL 페널티(adaptive KL penalty)를 쓰는 대안도 제안했습니다. L = L^{PG} - β · KL(π_θ || π_old)이며, 관측된 KL에 따라 β를 조정합니다. 클리핑 방식이 지배적이 되었지만, KL 변형은 RLHF에서 살아남았습니다. 참조 정책(reference policy)에 대한 KL은 어차피 항상 두고 싶은 제약(constraint)이기 때문입니다.
직접 만들기
Step 1: 롤아웃 시점에 log π_old(a | s) 저장하기
for step inrange(T):
probs = softmax(logits(theta, state_features(s)))
a = sample(probs, rng)
s_next, r, done = env.step(s, a)
buffer.append({
"s": s, "a": a, "r": r, "done": done,
"v_old": value(w, state_features(s)),
"log_pi_old": log(probs[a] + 1e-12),
})
s = s_next
스냅샷은 롤아웃 시점에 한 번만 찍습니다. 업데이트 epoch 동안에는 바뀌지 않습니다.
Step 2: GAE 이득 계산하기 (Lesson 07)
A2C와 동일합니다. 배치 전체에서 정규화(normalize)합니다.
Step 3: 클리핑된 대리 목적 함수로 업데이트하기
for _ inrange(K_EPOCHS):
for mb in minibatches(buffer, size=64):
for rec in mb:
x = state_features(rec["s"])
probs = softmax(logits(theta, x))
logp = log(probs[rec["a"]] + 1e-12)
ratio = exp(logp - rec["log_pi_old"])
adv = rec["advantage"]
surrogate = min(
ratio * adv,
clamp(ratio, 1 - EPS, 1 + EPS) * adv,
)
# -surrogate로 역전파, value loss는 더하고 entropy는 빼서 반영
grad_logpi = onehot(rec["a"]) - probs
if (adv > 0and ratio >= 1 + EPS) or (adv < 0and ratio <= 1 - EPS):
pg_grad = 0.0# clippedelse:
pg_grad = ratio * adv
for i inrange(N_ACTIONS):
for j inrange(N_FEAT):
theta[i][j] += LR * pg_grad * grad_logpi[i] * x[j]
"클리핑되면 기울기를 0으로 만든다"는 패턴이 PPO의 핵심입니다. 새 정책이 이미 유익한 방향으로 너무 멀리 흘러갔다면(drift) 업데이트를 멈춥니다.
Step 4: 가치(value)와 엔트로피(entropy)
A2C와 동일하게 크리틱 타깃(critic target)에 표준적인 평균 제곱 오차(MSE)를 더하고, 액터(actor)에는 엔트로피 보너스(entropy bonus)를 더합니다.
Step 5: 진단(diagnostics)
업데이트마다 다음 세 가지를 살펴봐야 합니다.
평균 KL(Mean KL)E[log π_old - log π_θ]. [0, 0.02] 범위에 머무는 것이 좋습니다. 0.1을 넘어서면 K_EPOCHS 또는 LR을 줄입니다.
클립 비율(Clip fraction) — 비율이 [1-ε, 1+ε] 바깥에 있는 표본의 비율입니다. ~0.1-0.3 정도가 좋습니다. ~0이면 클리핑이 전혀 걸리지 않는다는 뜻이므로 LR 또는 K_EPOCHS를 올립니다. ~0.5+이면 롤아웃에 과적합(overfitting) 중이므로 낮춥니다.
PPO의 손실 함수 형태, 즉 클리핑된 대리 함수 + 가치 + 엔트로피는 DPO, GRPO를 비롯한 거의 모든 RLHF 파이프라인의 뼈대(scaffolding)입니다.
산출물 만들기
outputs/skill-ppo-trainer.md로 저장합니다.
---
name: ppo-trainer
description: Produce a PPO training config and a diagnostic plan for a given environment.
version: 1.0.0
phase: 9
lesson: 8
tags: [rl, ppo, policy-gradient]
---
Given an environment and training budget, output:
1. Rollout size. `N` envs × `T` steps.
2. Update schedule. `K` epochs, minibatch size, LR schedule.
3. Surrogate params. `ε` (clip), `c_v`, `c_e`, advantage normalization on.
4. Advantage. GAE(`λ`) with explicit `γ` and `λ`.
5. Diagnostics plan. KL, clip fraction, explained variance thresholds with alerts.
Refuse `K > 30` or `ε > 0.3` (unsafe trust region). Refuse any PPO run without advantage normalization or KL/clip monitoring. Flag clip fraction sustained above 0.4 as drift.
연습문제
쉬움.ε=0.2, K=4로 4×4 GridWorld에서 PPO를 실행합니다. 환경 step 수를 맞춘 상태에서 A2C(롤아웃당 1 epoch)와 표본 효율(sample efficiency)을 비교합니다.
중간.K ∈ {1, 4, 10, 30}을 훑어 봅니다(sweep). 환경 step 수에 대한 반환값(return vs env steps)을 그리고, 업데이트별 평균 KL을 추적합니다. 이 과제에서는 어느 K에서 KL이 폭발하나요?
어려움. 클리핑된 대리 함수를 적응형 KL 페널티(adaptive KL penalty)로 바꿉니다. KL > 2·target이면 β를 두 배로, KL < target/2이면 절반으로 조정합니다. 최종 반환값, 안정성, 클리핑이 없다는 점을 함께 비교합니다.
핵심 용어
용어
흔한 설명
실제 의미
중요도 비율(Importance ratio)
"r_t(θ)"
`π_θ(a
클리핑된 대리 함수(Clipped surrogate)
"PPO의 핵심 트릭"
min(r·A, clip(r, 1-ε, 1+ε)·A)이다. 유익한 쪽에서 클립을 넘으면 기울기가 평평해진다.
신뢰 영역(Trust region)
"TRPO / PPO의 의도"
각 업데이트의 KL을 제한해 단조 향상(monotone improvement)을 보장하려는 생각이다.
KL 페널티(KL penalty)
"Soft trust region"
PPO의 대안: `L - β · KL(π_θ
클립 비율(Clip fraction)
"얼마나 자주 클리핑이 걸리는가"
진단 지표다. 0.1-0.3이 좋고, 이 바깥이면 튜닝이 잘못된 것이다.
다중 epoch 학습(Multi-epoch training)
"데이터 재사용"
각 롤아웃에 대해 K epoch를 수행한다. 분산(variance) 비용을 표본 효율과 맞바꾼다.
거의 on-policy(On-policy-ish)
"대체로 on-policy"
PPO는 명목상 on-policy지만, K>1 epoch는 약간 off-policy인 데이터를 안전하게 사용한다.