Hoppa till innehållet
AI-grafen
F· AI engineeringreinforcement-learning· ca 90 min· volatil — kontrolleras ofta· verifierad 2026-09-20

Actor–critic och PPO

Kunna förklara advantage, clipping och varför PPO används i RLHF.

Förkunskaper

Intuition

Actor–critic kombinerar de två familjerna:

  • Actor — policyn πθ(a∣s)\pi_\theta(a\mid s), den som handlar.
  • Critic — värdefunktionen Vϕ(s)V_\phi(s), den som bedömer hur bra läget är.

Critic används som baslinje, så att actorn får veta inte bara «det gick bra» utan «det gick bättre än väntat». Det är fördelen:

At=Gt−Vϕ(st)A_t = G_t - V_\phi(s_t)

Skillnaden är stor i praktiken. «Du fick 100 poäng» säger lite om läget alltid ger 100. «Du fick 20 mer än förväntat» är en användbar signal.

PPO:s bidrag är att lösa ett annat problem: hur stort steg får man ta? Ett för stort policysteg kan förstöra en fungerande policy, och till skillnad från övervakad inlärning kommer den inte tillbaka — nästa omgång data samlas in av den trasiga policyn.

PPO:s svar är att klippa bort incitamentet att flytta sig för långt. Inget hårt tak, ingen kluster av KL-beräkningar — bara en min med ett klippt alternativ.

Formellt

GAE (generalized advantage estimation) interpolerar mellan låg bias och låg varians:

δt=rt+γV(st+1)−V(st),AtGAE=∑l≥0(γλ)lδt+l\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t), \qquad A_t^{\text{GAE}} = \sum_{l \geq 0} (\gamma\lambda)^l \delta_{t+l}

λ=0\lambda = 0 ger ren TD (låg varians, hög bias), λ=1\lambda = 1 ger Monte Carlo (hög varians, låg bias). Typiskt λ=0,95\lambda = 0{,}95.

PPO:s klippta mål. Med kvoten rt(θ)=πθ(at∣st)πθgammal(at∣st)r_t(\theta) = \dfrac{\pi_\theta(a_t\mid s_t)}{\pi_{\theta_{\text{gammal}}}(a_t\mid s_t)}:

LCLIP=Et[min⁡(rtAt,  clip(rt,1−ϵ,1+ϵ) At)]L^{\text{CLIP}} = \mathbb{E}_t\left[\min\left(r_t A_t,\; \mathrm{clip}(r_t, 1-\epsilon, 1+\epsilon)\, A_t\right)\right]

Asymmetrin är hela poängen, och den missas ofta. Med ϵ=0,2\epsilon = 0{,}2:

AtA_trtr_tKlippt termmin väljerEffekt
+11,51,21,2uppsidan kapas — sluta öka sannolikheten
+10,50,80,5ingen kapning — får återhämta sig fritt
−11,5−1,2−1,5ingen kapning — får straffas fullt ut
−10,5−0,8−0,8nedsidan kapas — sluta minska sannolikheten

Med andra ord: min klipper bara i den riktning som skulle göra uppdateringen större. Att gå tillbaka mot den gamla policyn är alltid tillåtet.

Hela förlusten har tre termer:

L=−LCLIP+c1(Vϕ−G)2⏟critic−c2H[πθ]⏟entropiL = -L^{\text{CLIP}} + c_1 \underbrace{(V_\phi - G)^2}_{\text{critic}} - c_2 \underbrace{H[\pi_\theta]}_{\text{entropi}}

Entropibonusen motverkar att policyn kollapsar till determinism för tidigt.

Varför PPO i RLHF? Fyra skäl, i ordning:

  1. Robust mot hyperparametrar — viktigt när varje körning kostar GPU-timmar och man inte kan söka brett.
  2. Flera epoker per datainsamling — kvoten gör det säkert att återanvända data, vilket är avgörande när «miljön» är dyr mänsklig eller modellerad återkoppling.
  3. Inget andra ordningens krångel — TRPO kräver konjugerade gradienter och Fisher-matrisprodukter; PPO är några rader.
  4. KL-straffet mot referensmodellen passar in naturligt som en extra term — man vill att modellen ska förbättras utan att glida bort från språkmodellen den startade från.

DPO och andra direkta metoder har på senare år tagit över mycket av RLHF-arbetet just för att de slipper hela RL-loopen — men PPO är fortfarande referensimplementationen att förstå först.

Kod

import torch

def gae(belonningar, varden, klara, gamma=0.99, lam=0.95):
    """varden har längd T+1 (det sista är V(s_T))."""
    T = len(belonningar)
    A = torch.zeros(T)
    sista = 0.0
    for t in reversed(range(T)):
        icke_terminal = 1.0 - klara[t]
        delta = belonningar[t] + gamma * varden[t + 1] * icke_terminal - varden[t]
        sista = delta + gamma * lam * icke_terminal * sista
        A[t] = sista
    return A, A + varden[:T]                     # (fördelar, värdemål)

def ppo_uppdatering(policy, critic, opt, batch, epoker=4, eps=0.2, c1=0.5, c2=0.01):
    s, a, gamla_logp, A, V_mal = batch
    A = (A - A.mean()) / (A.std() + 1e-8)
    for _ in range(epoker):                       # flera epoker på SAMMA data — det PPO möjliggör
        d = policy(s)
        logp = d.log_prob(a)
        kvot = torch.exp(logp - gamla_logp)
        oklippt = kvot * A
        klippt = torch.clamp(kvot, 1 - eps, 1 + eps) * A
        pol_forlust = -torch.min(oklippt, klippt).mean()
        v_forlust = ((critic(s).squeeze(-1) - V_mal) ** 2).mean()
        entropi = d.entropy().mean()
        forlust = pol_forlust + c1 * v_forlust - c2 * entropi
        opt.zero_grad(); forlust.backward()
        torch.nn.utils.clip_grad_norm_(
            list(policy.parameters()) + list(critic.parameters()), 0.5)
        opt.step()

        if (logp - gamla_logp).pow(2).mean() > 0.03:   # approx KL — bryt om vi drivit för långt
            break

Tre diagnostiska storheter att logga, och vad de betyder:

StorhetSund nivåVad avvikelsen betyder
approx-KL per uppdatering0,005–0,02för hög: för många epoker eller för hög lr
andel klippta samplen0,1–0,3nära 0: klippningen gör inget; nära 1: stegen är för stora
policyns entropiavtar långsamtbrant fall: kollaps, höj c2

Kombinationen «låg klippandel, hög KL» betyder nästan alltid att värdefunktionen är trasig snarare än policyn.

Behärskning innebär

  • Förklarar actor–critic och fördelsskattning
  • Härleder och tolkar PPO:s klippta mål
  • Vet varför PPO valdes för RLHF

Logga in för att göra övningarna och bygga upp din behärskning.

Källor

Alla källor och licenser