Proximal Policy Optimization (PPO)

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 답변 완료

개념

PPO clipped surrogate: flat roof, flat floor L^{CLIP}(theta) = E[ min( r A, clip(r, 1-eps, 1+eps) * A ) ] r = pi_theta(a|s) / pi_old(a|s) typical eps = 0.2 advantage A > 0 (good action) r L r = 1+eps r = 1 clip: flat roof gradient flows advantage A < 0 (bad action) r L r = 1-eps r = 1 clip: flat floor gradient flows if the ratio drifts past the clip in the "useful" direction, gradient goes to zero — no unbounded updates

중요도 비율(importance ratio).

r_t(θ) = π_θ(a_t | s_t) / π_{θ_old}(a_t | s_t)

새 정책과 데이터를 모은 정책 사이의 가능도 비(likelihood ratio)입니다. r_t = 1이면 변화가 없습니다. r_t = 2이면 새 정책이 a_t를 옛 정책보다 두 배 더 자주 선택한다는 뜻입니다.

클리핑된 대리 목적 함수(clipped surrogate).

L^{CLIP}(θ) = E_t [ min( r_t(θ) A_t, clip(r_t(θ), 1-ε, 1+ε) A_t ) ]

두 항이 있습니다.

  • 이득(advantage) A_t > 0이고 비율이 1 + ε를 넘어 커지려 하면, 클리핑(clip)이 기울기를 평평하게 만듭니다. 즉 좋은 행동(action)의 확률을 옛 확률보다 이상 더 밀어붙이지 말라는 뜻입니다.
  • 이득 A_t < 0이고 비율이 1 - ε 아래로 내려가려 하면(즉 클리핑이 줄여 주는 정도보다 더 나쁜 행동을 가능성 높게 만들려는 상황), 클리핑이 기울기를 막아 줍니다. 나쁜 행동을 보다 더 멀리 밀어붙이지 말라는 뜻입니다.

min은 반대 방향을 처리합니다. 비율이 유익한 방향으로 움직였다면 여전히 기울기를 받습니다. 손해를 주는 쪽에만 클리핑이 걸리지 않습니다.

전형적인 값은 ε = 0.2입니다. 목적 함수를 r_t의 함수로 그려 보면, 좋은 쪽에는 평평한 천장(flat roof)이, 나쁜 쪽에는 평평한 바닥(flat floor)이 있는 조각별 선형(piecewise-linear) 함수입니다.

전체 PPO 손실 함수(loss).

L(θ, φ) = L^{CLIP}(θ) - c_v · (V_φ(s_t) - V_t^{target})² + c_e · H(π_θ(·|s_t))

A2C와 같은 액터-크리틱(actor-critic) 구조입니다. 세 가지 계수(coefficient)는 보통 c_v = 0.5, c_e = 0.01, ε = 0.2를 사용합니다.

학습 루프(training loop).

  1. N개의 병렬 환경(parallel env)에서 각각 T step을 모아 N × T개의 전이를 수집합니다.
  2. 이득(GAE)을 계산하고 상수로 고정(freeze)합니다.
  3. 현재 π_θ의 스냅샷(snapshot)으로 π_{θ_old}를 고정합니다.
  4. K epoch 동안 (s, a, A, V_target, log π_old(a|s)) 미니배치(minibatch)마다 다음을 수행합니다.
    • r_t(θ) = exp(log π_θ(a|s) - log π_old(a|s))를 계산합니다.
    • L^{CLIP} + 가치 손실(value loss) + 엔트로피(entropy)를 적용합니다.
    • 기울기 단계(gradient step)를 한 번 밟습니다.
  5. 롤아웃을 버리고 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 in range(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 _ in range(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 > 0 and ratio >= 1 + EPS) or (adv < 0 and ratio <= 1 - EPS):
                pg_grad = 0.0  # clipped
            else:
                pg_grad = ratio * adv
            for i in range(N_ACTIONS):
                for j in range(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) 중이므로 낮춥니다.
  • 설명된 분산(Explained variance) 1 - Var(V_target - V_pred) / Var(V_target). 크리틱 품질 지표(critic quality metric)입니다. 크리틱이 학습되면서 1에 가까워져야 합니다.

자주 빠지는 함정

  • 클립 계수(clip coefficient)가 잘못 설정됨. ε = 0.2가 사실상의 표준입니다. 0.1은 업데이트를 너무 소심하게 만들고, 0.3+은 불안정성(instability)을 부릅니다.
  • epoch가 너무 많음. K > 20은 정책이 π_old에서 너무 멀리 흘러가기 때문에 자주 불안정해집니다. 특히 큰 신경망(network)에서는 epoch를 제한합니다.
  • 보상 정규화(reward normalization) 누락. 보상의 스케일이 크면 클립 범위(clip range)를 잠식합니다. 이득을 계산하기 전에 보상을 누적 표준편차(running std)로 정규화합니다.
  • 이득 정규화(advantage normalization) 누락. 배치별로 평균 0, 표준편차 1로 맞추는 정규화가 표준입니다. 빼먹으면 대부분의 벤치마크에서 PPO가 망가집니다.
  • 학습률 감쇠(learning rate decay) 없음. PPO는 0까지 선형 학습률 감쇠(linear LR decay to zero)에서 이득을 봅니다. 상수 학습률(constant LR)은 자주 더 나쁩니다.
  • 중요도 비율 계산 오류. 수치 안정성을 위해 항상 exp(log_new - log_old)를 사용합니다. new / old를 직접 쓰지 않습니다.
  • 기울기 부호(sign)가 뒤바뀜. 대리 목적 함수를 최대화한다는 것은 -L^{CLIP}최소화한다는 뜻입니다. 부호가 뒤집히는 것이 가장 흔한 PPO 버그입니다.

사용해보기

PPO는 2026년에 놀라울 만큼 많은 분야에서 기본 강화학습(RL) 알고리즘으로 쓰입니다.

사용 사례PPO 변형
MuJoCo / 로보틱스 제어가우시안 정책(Gaussian policy)과 GAE(0.95)를 쓰는 PPO
Atari / 이산 행동 게임범주형 정책(categorical policy)과 128-step 롤링 롤아웃(rolling rollout)을 쓰는 PPO
LLM을 위한 RLHF참조 모델(reference model)에 대한 KL penalty를 두고, 응답(response) 끝에서 보상 모델(RM)의 보상을 사용하는 PPO
대규모 게임 에이전트(agent)IMPALA + PPO (AlphaStar, OpenAI Five)
Reasoning LLMsGRPO (Lesson 12) — 크리틱(critic)이 없는 PPO 변형
선호 데이터(preference-only data)만 있는 경우DPO — PPO+KL을 닫힌 형식(closed-form)으로 접은 형태이며, 온라인 표본 추출(online sampling)이 없음

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.

연습문제

  1. 쉬움. ε=0.2, K=4로 4×4 GridWorld에서 PPO를 실행합니다. 환경 step 수를 맞춘 상태에서 A2C(롤아웃당 1 epoch)와 표본 효율(sample efficiency)을 비교합니다.
  2. 중간. K ∈ {1, 4, 10, 30}을 훑어 봅니다(sweep). 환경 step 수에 대한 반환값(return vs env steps)을 그리고, 업데이트별 평균 KL을 추적합니다. 이 과제에서는 어느 K에서 KL이 폭발하나요?
  3. 어려움. 클리핑된 대리 함수를 적응형 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인 데이터를 안전하게 사용한다.
PPO-KL"또 다른 PPO"KL 페널티 변형. RLHF에서는 참조 모델에 대한 KL이 이미 제약이라서 사용된다.

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

ppo-trainer

Produce a PPO training config and a diagnostic plan for a given environment.

Skill

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.PPO의 클립 비율(clip fraction, 비율이 [1-epsilon, 1+epsilon] 밖에 있는 샘플의 비율)을 모니터링하는데 학습 내내 0.5 이상을 유지합니다. 이것이 의미하는 바와 어떻게 해야 하나요?

2.PPO는 비율을 pi_new / pi_old를 직접 나누는 대신 exp(log_pi_new - log_pi_old)로 계산합니다. 이것이 수치적으로 왜 중요한가요?

3.LLM을 위한 RLHF에서 클리핑된 대리 함수(clipped surrogate) 대신 KL 페널티(KL penalty) 변형의 PPO가 사용됩니다. 이 설정에서 KL 변형이 선호되는 이유는 무엇인가요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다