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

Policy gradient och REINFORCE

Kunna härleda policy gradient-satsen och implementera REINFORCE.

Förkunskaper

Intuition

Q-learning lär sig värden och härleder policyn av dem. Policy gradient hoppar över mellanledet och optimerar policyn direkt.

Policyn πθ(a∣s)\pi_\theta(a \mid s) är ett neuralt nät som ger sannolikheter över handlingar. Vi vill justera θ\theta så att bra utfall blir mer sannolika.

Idén i en mening:

Gick det bra — gör mer av det du gjorde. Gick det dåligt — gör mindre.

Matematiskt: multiplicera log-sannolikheten för varje handling med hur bra episoden blev, och gå uppför gradienten.

Varför bry sig, när Q-learning finns?

VärdebaseratPolicy gradient
Kontinuerliga handlingarsvårt (max över oändligt många)naturligt
Stokastisk policynejja — behövs i spel med bluff
Konvergenskan oscillera med funktionsapproximationstabilare garantier
Sampeleffektivitetbättre (off-policy, replay)sämre (on-policy)

Den sista raden är priset: REINFORCE slänger all data efter varje uppdatering.

Härledning

Målet är förväntad avkastning över banor τ\tau:

J(θ)=Eτ∼πθ[R(τ)]=∫pθ(τ)R(τ) dτJ(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}[R(\tau)] = \int p_\theta(\tau) R(\tau)\, d\tau

Problemet: gradienten hamnar på sannolikhetsfördelningen, som vi inte kan derivera igenom direkt.

Log-derivatatricket löser det. Eftersom ∇θpθ=pθ∇θlog⁡pθ\nabla_\theta p_\theta = p_\theta \nabla_\theta \log p_\theta:

∇θJ=∫∇θpθ(τ)R(τ) dτ=∫pθ(τ) ∇θlog⁡pθ(τ) R(τ) dτ=Eτ ⁣[∇θlog⁡pθ(τ) R(τ)]\nabla_\theta J = \int \nabla_\theta p_\theta(\tau) R(\tau)\, d\tau = \int p_\theta(\tau)\, \nabla_\theta \log p_\theta(\tau)\, R(\tau)\, d\tau = \mathbb{E}_\tau\!\left[\nabla_\theta \log p_\theta(\tau)\, R(\tau)\right]

Nu är det ett väntevärde igen — och väntevärden kan skattas med stickprov.

Nästa steg: banans sannolikhet är

pθ(τ)=ρ(s0)∏tP(st+1∣st,at) πθ(at∣st)p_\theta(\tau) = \rho(s_0)\prod_t P(s_{t+1} \mid s_t, a_t)\, \pi_\theta(a_t \mid s_t)

Tar vi logaritmen blir det en summa, och allt som inte beror på θ\theta försvinner vid derivering — inklusive miljöns dynamik PP. Det är hela poängen: vi behöver ingen modell av världen.

∇θJ=E[∑t∇θlog⁡πθ(at∣st) R(τ)]\nabla_\theta J = \mathbb{E}\left[\sum_t \nabla_\theta \log \pi_\theta(a_t \mid s_t)\, R(\tau)\right]

Två förbättringar som inte ändrar väntevärdet men sänker variansen kraftigt:

  1. Kausalitet. En handling kan inte påverka det som redan hänt. Byt R(τ)R(\tau) mot avkastningen framåt från steg tt: Gt=∑k≥tγk−trkG_t = \sum_{k \geq t}\gamma^{k-t} r_k.
  2. Baslinje. För vilken funktion b(s)b(s) som helst gäller E[∇θlog⁡πθ(a∣s) b(s)]=0\mathbb{E}[\nabla_\theta \log \pi_\theta(a\mid s)\, b(s)] = 0, eftersom ∑a∇θπθ(a∣s)=∇θ1=0\sum_a \nabla_\theta \pi_\theta(a \mid s) = \nabla_\theta 1 = 0. Alltså kan vi dra bort b(s)b(s) gratis.

Med b(s)=V(s)b(s) = V(s) blir Gt−V(st)G_t - V(s_t) fördelen AtA_t — och då är vi framme vid actor–critic.

∇θJ=E[∑t∇θlog⁡πθ(at∣st) At]\boxed{\nabla_\theta J = \mathbb{E}\left[\sum_t \nabla_\theta \log \pi_\theta(a_t \mid s_t)\, A_t\right]}

Varför variansen är problemet: skattningen bygger på hela episoders slumpmässiga utfall. Utan baslinje kan gradienten peka åt olika håll mellan två körningar med samma policy. Baslinjen tar bort den delen av signalen som är gemensam för alla handlingar i ett tillstånd — och det är den delen som bara är brus.

Kod

import torch, torch.nn as nn

class Policy(nn.Module):
    def __init__(self, obs, handlingar, dolt=128):
        super().__init__()
        self.f = nn.Sequential(nn.Linear(obs, dolt), nn.Tanh(), nn.Linear(dolt, handlingar))

    def forward(self, s):
        return torch.distributions.Categorical(logits=self.f(s))

def returer(belonningar, gamma=0.99):
    ut, G = [], 0.0
    for r in reversed(belonningar):
        G = r + gamma * G
        ut.append(G)
    return list(reversed(ut))

def reinforce(miljo, policy, opt, episoder=1000, gamma=0.99):
    for _ in range(episoder):
        s, klar = miljo.reset()[0], False
        logp, belon = [], []
        while not klar:
            d = policy(torch.as_tensor(s, dtype=torch.float32))
            a = d.sample()
            logp.append(d.log_prob(a))
            s, r, term, trunk, _ = miljo.step(int(a))
            belon.append(r); klar = term or trunk

        G = torch.tensor(returer(belon, gamma))
        G = (G - G.mean()) / (G.std() + 1e-8)          # baslinje: sänker variansen kraftigt
        förlust = -(torch.stack(logp) * G).sum()       # minustecknet: vi maximerar J
        opt.zero_grad(); förlust.backward()
        nn.utils.clip_grad_norm_(policy.parameters(), 1.0)
        opt.step()

Tre saker som brukar gå fel:

SymtomOrsak
Policyn blir snabbt deterministisk och slutar förbättrasentropin kollapsar — lägg till en entropibonus
Ingen inlärning allsglömt minustecknet, eller normaliserat bort all signal
Väldigt skakig inlärningnormalisering över en enda kort episod; batcha flera

Normaliseringen på rad 24 är standard men inte oskyldig: gör du den över en enda episod jämför du bara handlingar inom den episoden, och information om att hela episoden var dålig går förlorad.

Behärskning innebär

  • Härleder policy gradient-satsen med log-derivatatricket
  • Implementerar REINFORCE med baslinje
  • Förklarar variansproblemet och motmedlen

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

Källor

Alla källor och licenser