পাঠ ২১ · ৩২-এর মধ্যে · মডিউল ৩

PPO — Proximal Policy Optimization

PPO — the workhorse of modern RL
৮ মিনিট পড়া উচ্চ · Advanced PyTorch

এই পাঠে যা শিখবেন

  • Clipped surrogate objective — derivation ও intuition
  • PPO-Clip vs PPO-Penalty (KL adaptive)
  • Full PyTorch implementation
  • RLHF-এ PPO-এর role

১ · TRPO থেকে PPO-এর পথ

TRPO theory ভাল কিন্তু implementation ৮০০+ lines. Schulman ভাবলেন — KL constraint-এর সরল alternative কী?

Idea: importance ratio-কে clip করো — extreme value reach না করুক। তাহলে — implicit "trust region".

২ · Importance ratio

$$r_t(\theta) = \frac{\pi_\theta(a_t | s_t)}{\pi_{\theta_{old}}(a_t | s_t)}$$

$r_t = 1$ — policy unchanged. $r_t > 1$ — likely under new policy.

৩ · Clipped surrogate objective

$$L^{CLIP}(\theta) = \mathbb{E}_t \left[ \min\left( r_t(\theta) A_t, \, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right]$$

$\epsilon$ — clip parameter. typical $\epsilon = 0.2$।

৪ · Why this works — case analysis

Case A — $A_t > 0$ (good action):

  • $r_t < 1+\epsilon$ — full gradient flow.
  • $r_t > 1+\epsilon$ — clip stops gradient. policy বেশি probability assign করতে চাইলেও — না।

Case B — $A_t < 0$ (bad action):

  • $r_t > 1-\epsilon$ — full gradient flow (probability decrease).
  • $r_t < 1-\epsilon$ — clip stops gradient. policy বেশি কমাতে চাইলেও — না।

Effect: ratio bounded in $[1-\epsilon, 1+\epsilon]$। policy old-এর কাছাকাছি — implicit trust region.

PPO Clipped Objective A_t > 0 (action ভাল) r_t L 1−ε 1+ε 1 r·A (unclipped) PPO objective A_t < 0 (action খারাপ) r_t L 1−ε 1+ε A > 0 ও r > 1+ε: clip stops further increase. A < 0 ও r < 1−ε: clip stops further decrease. Policy old-এর কাছাকাছি থাকে — implicit trust region. এই simple clip — TRPO-এর elaborate KL constraint-এর substitute. PPO-এর genius.
PPO clipping — extreme ratio gradient block. policy gradual update — collapse prevent.

৫ · PPO loss function — full

$$L_{total} = L^{CLIP} - c_1 \mathcal{L}^{VF} + c_2 \mathcal{H}[\pi]$$

Three terms:

  • $L^{CLIP}$ — clipped surrogate (maximize).
  • $\mathcal{L}^{VF} = (V_\phi(s) - V_{target})^2$ — critic MSE (minimize).
  • $\mathcal{H}[\pi]$ — entropy bonus (maximize).

Default: $c_1 = 0.5, c_2 = 0.01$।

৬ · Multiple SGD epochs

TRPO single update. PPO — same data 4-10 epochs use. sample efficient.

  1. Rollout — collect $T$ steps × $N$ envs.
  2. Compute GAE advantages + value targets.
  3. For $K$ epochs:
    • Shuffle batch.
    • Mini-batch SGD on $L^{CLIP} - c_1 \mathcal{L}^{VF} + c_2 \mathcal{H}$।
  4. Discard data, repeat.

৭ · Full PyTorch PPO

Python · PPO
import gym
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical
import numpy as np

env = gym.make("CartPole-v1")
state_dim, n_actions = 4, 2
gamma, lam, eps_clip = 0.99, 0.95, 0.2
n_epochs, mb_size = 4, 64

class ActorCritic(nn.Module):
    def __init__(self):
        super().__init__()
        self.shared = nn.Sequential(nn.Linear(state_dim, 64), nn.Tanh(), nn.Linear(64, 64), nn.Tanh())
        self.actor = nn.Linear(64, n_actions)
        self.critic = nn.Linear(64, 1)
    def forward(self, s):
        f = self.shared(s)
        return Categorical(logits=self.actor(f)), self.critic(f).squeeze(-1)

model = ActorCritic()
opt = optim.Adam(model.parameters(), lr=3e-4)

def collect(T=2048):
    states, actions, log_probs, rewards, dones, values = [], [], [], [], [], []
    state, _ = env.reset()
    for _ in range(T):
        s_t = torch.FloatTensor(state)
        with torch.no_grad():
            dist, V = model(s_t)
            a = dist.sample()
            lp = dist.log_prob(a)
        next_state, r, term, trunc, _ = env.step(a.item())
        states.append(state); actions.append(a.item()); log_probs.append(lp.item())
        rewards.append(r); dones.append(float(term or trunc)); values.append(V.item())
        state = next_state
        if term or trunc: state, _ = env.reset()
    return states, actions, log_probs, rewards, dones, values

def compute_gae(rewards, values, dones, V_last):
    A, advs = 0, []
    V_next = V_last
    for t in reversed(range(len(rewards))):
        delta = rewards[t] + gamma * V_next * (1 - dones[t]) - values[t]
        A = delta + gamma * lam * (1 - dones[t]) * A
        advs.insert(0, A)
        V_next = values[t]
    return advs

for it in range(50):
    states, actions, old_lp, rewards, dones, values = collect(2048)
    with torch.no_grad():
        _, V_last = model(torch.FloatTensor(env.reset()[0]))
    advs = compute_gae(rewards, values, dones, V_last.item())
    returns = [a + v for a, v in zip(advs, values)]

    states_t = torch.FloatTensor(np.array(states))
    actions_t = torch.LongTensor(actions)
    old_lp_t = torch.FloatTensor(old_lp)
    advs_t = torch.FloatTensor(advs)
    advs_t = (advs_t - advs_t.mean()) / (advs_t.std() + 1e-8)
    returns_t = torch.FloatTensor(returns)

    # PPO multiple epochs
    for _ in range(n_epochs):
        idx = np.random.permutation(len(states))
        for i in range(0, len(idx), mb_size):
            mb = idx[i:i+mb_size]
            dist, V = model(states_t[mb])
            new_lp = dist.log_prob(actions_t[mb])
            ratio = torch.exp(new_lp - old_lp_t[mb])
            s1 = ratio * advs_t[mb]
            s2 = torch.clamp(ratio, 1-eps_clip, 1+eps_clip) * advs_t[mb]
            actor_loss = -torch.min(s1, s2).mean()
            critic_loss = (returns_t[mb] - V).pow(2).mean()
            entropy = dist.entropy().mean()
            loss = actor_loss + 0.5*critic_loss - 0.01*entropy
            opt.zero_grad(); loss.backward(); opt.step()

    if it % 5 == 0:
        print(f"Iter {it:3d} | rollout reward = {sum(rewards)/sum(dones):.1f}")

    
১০০ lines-এর কাছাকাছি — TRPO-এর ৮০০+ lines-এর তুলনায়। CartPole-এ ১০-২০ iteration-এ solve.

৮ · PPO-এর famous applications

  • OpenAI Five (Dota 2): ২০১৯-এ professional team-কে হারালো।
  • AlphaStar (StarCraft II): Grandmaster level.
  • Robot manipulation: OpenAI dexterous hand.
  • RLHF (ChatGPT): human preference থেকে LLM align.
  • Game AI: almost every recent RL game agent.

৯ · PPO best practices

  • $\epsilon = 0.2$ — most tasks.
  • 4-10 epochs per batch.
  • 2048-32768 timesteps per rollout.
  • Adam, learning rate 3e-4.
  • Advantage normalization.
  • Gradient clip (max norm 0.5).
  • Linear learning rate decay.
  • Multiple seeds — RL high variance.

Cleanrl, Stable-Baselines3, RL Games — production implementations.

ভাবনার প্রশ্ন

প্র ০১PPO-এর "min" operator gradient-এর কী effect?

$L^{CLIP} = \min(r A, \text{clip}(r) A)$।

$A > 0$ case:

  • If $r < 1+\epsilon$: min = $rA$ (unclipped) — gradient flow.
  • If $r \ge 1+\epsilon$: min = $\text{clip}(r) A = (1+\epsilon)A$ (constant) — gradient zero.
  • Effect: probability বাড়াতে চাইলেও — clip-এ stop.

$A < 0$ case:

  • If $r > 1-\epsilon$: min = $rA$ — gradient flow (probability decrease).
  • If $r \le 1-\epsilon$: min = $(1-\epsilon)A$ (constant) — gradient zero.
  • Effect: probability কমাতে চাইলেও — clip stop.

Asymmetry: clip এক direction-এ block, অন্য direction-এ allow. "trust region in spirit" — bad actions আরও বেশি bad করার চেষ্টা ও সুযোগ।

মূল উপলব্ধি: min ensures pessimistic clip — "policy collapse" risk eliminate. কিন্তু good direction-এ enough flexibility.

প্র ০২RLHF-এ PPO use — কেন আদর্শ choice?

ChatGPT-এর alignment pipeline-এ PPO crucial. কারণ:

(১) Per-token RL:

  • Generated text-এর প্রতি token = action.
  • Reward model থেকে scalar reward (sequence-level).
  • PPO trajectory-style framework natural.

(২) KL constraint critical:

  • Base SFT model থেকে drift এড়ানো — nuisance text generation prevent.
  • PPO-এ extra KL term (RM reward + KL penalty).

(৩) Multiple epochs:

  • Generation expensive (large LLM rollout).
  • Same data বহুবার update — sample efficient.

(৪) Stability:

  • Clip prevents catastrophic update.
  • Large model-এ small step critical.

RLHF objective:

$L = L^{CLIP}(\theta) - \beta \cdot D_{KL}(\pi_\theta \| \pi_{SFT})$

Alternative — DPO (পাঠ ২৯): RL-পথ এড়িয়ে directly preference থেকে। সরল, কিন্তু কম flexible.

প্র ০৩PPO hyperparameters — কোনটি critical?

PPO অনেকগুলো knob-এর সাথে আসে। sensitivity ranking:

Most critical:

  • $\epsilon$ (clip): 0.1-0.3. 0.2 default ভাল most tasks.
  • Learning rate: 1e-4 to 3e-4. smaller for LLMs.
  • Rollout length: 2048 typical Atari, 256 small games.
  • Number of epochs: 4-10. বেশি — overfit, কম — underfit.

Moderately important:

  • $\gamma$: 0.99 default.
  • $\lambda$ (GAE): 0.95 default.
  • Mini-batch size: 64-256.

Less critical (default usually OK):

  • Entropy coef: 0.01.
  • Critic coef: 0.5.
  • Grad clip: 0.5.

Engelmann et al. (২০২২) "37 Implementation Details":

  • Subtle implementation tricks (advantage normalize, value clipping, ortho init) impact significant.
  • Code-level details often paper-এ undocumented.

Recommendation: production-grade PPO use Stable-Baselines3 বা cleanrl. reimplement করার চেয়ে।

প্র ০৪PPO-এর "implementation details" কেন original paper থেকে miss?

Engstrom et al. (2020) paper "Implementation Matters" famously showed — PPO performance largely from implementation tricks, not algorithm itself.

Key under-specified details:

  • Reward normalization/clipping.
  • Observation normalization (running mean/std).
  • Value function clipping.
  • Orthogonal weight initialization.
  • Tanh activation (not ReLU).
  • Adam epsilon (1e-5 not default 1e-8).
  • Linear learning rate decay.
  • Advantage normalization scope.

Impact: ablation দেখা গেছে — এই tricks remove করলে PPO ভাঙে। algorithmic core (clipping) actually small contribution.

Why paper miss:

  • Researchers code optimize over time.
  • Submission focus on novel idea, not engineering.
  • Code release-এর সংস্কৃতি sparse ছিল 2017-এ।

আজকের best practice:

  • Code release mandatory many venues-এ।
  • Reproducibility checklist.
  • cleanrl-এর single-file implementation gold standard.

অনুশীলন

  1. Clip output: $r=1.5$, $A=2$, $\epsilon=0.2$। PPO objective contribution?

    $rA = 3$। $\text{clip}(1.5, 0.8, 1.2) \cdot 2 = 1.2 \cdot 2 = 2.4$। min = 2.4.

    ($A > 0$, $r > 1+\epsilon$ — clip activates).

  2. Negative case: $r=0.5$, $A=-1$, $\epsilon=0.2$। PPO?

    $rA = -0.5$। $\text{clip}(0.5, 0.8, 1.2) \cdot (-1) = 0.8 \cdot (-1) = -0.8$। min = -0.8.

    ($A < 0$, $r < 1-\epsilon$ — clip activates, gradient block).

  3. Code modify: উপরের PPO-এ early stopping by KL divergence যোগ করুন (KL > 0.02 হলে stop).
    kl = (old_lp_t[mb] - new_lp).mean().item()
    if kl > 0.02:
        break  # stop epoch — too far

    OpenAI implementation এই trick — extra safety.

আরও পড়ুন

পূর্ববর্তী পাঠ
পাঠ ২০ · TRPO