REINFORCE

Gradien kebijakan Monte Carlo, diturunkan secara ringan.

Daftar isi

REINFORCE adalah policy gradient Monte Carlo yang paling sederhana, yaitu memperbarui parameter kebijakan setelah sebuah episode selesai, dengan bobot berupa return kumulatif sepanjang trajectory. Topik ini menurunkan rumus gradien secara ringan dari objective log-likelihood yang diharapkan, lalu mengimplementasikannya di CartPole-v1 dengan jaringan kecil selama 200 episode. Karena kebijakan acak awal gagal dalam sekitar 20 sampai 30 langkah, reward rata-rata tidak melonjak dramatis, dan keluaran yang dicetak mencerminkan keadaan tersebut.

Objektif log-likelihood dibobot return#

Tujuan policy gradient adalah memaksimalkan expected return dari kebijakan:

J(θ)=Eτ∼πθ[∑t=0Tγtrt+1], J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}\left[\sum_{t=0}^{T} \gamma^t r_{t+1}\right],

dengan τ\tau adalah trajectory (s0,a0,r1,s1,a1,… )(s_0, a_0, r_1, s_1, a_1, \dots) yang dihasilkan dengan mengikuti πθ\pi_\theta. Karena return bergantung pada kebijakan melalui jalur yang panjang, optimasi langsung sulit. Jalan keluarnya adalah memanfaatkan fakta bahwa gradien expected return terhadap θ\theta dapat ditulis ulang sebagai ekspektasi atas log-probability aksi yang dibobot return.

Penurunan ringan#

Probabilitas sebuah trajectory di bawah kebijakan πθ\pi_\theta adalah

Pθ(τ)=p(s0)∏t=0Tπθ(at∣st) p(st+1∣st,at). P_\theta(\tau) = p(s_0)\prod_{t=0}^{T} \pi_\theta(a_t\mid s_t)\, p(s_{t+1}\mid s_t, a_t).

Gradien logaritma terhadap θ\theta hanya menyentuh faktor kebijakan karena dinamika lingkungan tidak bergantung pada θ\theta:

∇θlog⁡Pθ(τ)=∑t=0T∇θlog⁡πθ(at∣st). \nabla_\theta \log P_\theta(\tau) = \sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t\mid s_t).

Dengan trik identitas log-derivative ∇θJ(θ)=Eτ[∇θlog⁡Pθ(τ) G(τ)]\nabla_\theta J(\theta) = \mathbb{E}_{\tau}\big[\nabla_\theta \log P_\theta(\tau)\, G(\tau)\big], dan menyederhanakan jumlah reward menjadi return Gt=∑k=tTγk−trk+1G_t = \sum_{k=t}^{T} \gamma^{k-t} r_{k+1}, diperoleh estimator gradien:

∇θJ(θ)=E[∑t=0T∇θlog⁡πθ(at∣st) Gt]. \nabla_\theta J(\theta) = \mathbb{E}\left[\sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t\mid s_t)\, G_t\right].

Estimator ini disebut REINFORCE, atau Monte Carlo policy gradient. Setiap langkah dalam trajectory menyumbang sebesar gradien log-probability aksinya dikalikan return mulai dari langkah itu. Karena ekspektasi di atas tidak dapat dihitung secara analitik, ia diperkirakan dengan rata-rata atas beberapa trajectory sampel, dan θ\theta diperbarui dengan gradient ascent.

Implementasi REINFORCE di CartPole#

Berkas reinforce.py berikut menjalankan REINFORCE penuh pada CartPole-v1 selama 200 episode, dengan discount γ=0,99\gamma = 0{,}99, optimizer Adam learning rate 1e-2, dan jaringan satu lapis tersembunyi berukuran 32. Setiap episode dikumpulkan utuh, return dihitung secara mundur dengan diskon, lalu loss adalah jumlah −log⁡πθ(at∣st)⋅Gt-\log \pi_\theta(a_t\mid s_t) \cdot G_t yang diminimalkan oleh Adam.

python reinforce.py
import warnings; warnings.filterwarnings("ignore")
import numpy as np, torch, random
np.random.seed(0); torch.manual_seed(0); random.seed(0)
import gymnasium as gym

GAMMA = 0.99
LR = 1e-2
N_EPISODES = 200


class Policy(torch.nn.Module):
    def __init__(self, obs_dim, n_aksi, hidden=32):
        super().__init__()
        self.net = torch.nn.Sequential(
            torch.nn.Linear(obs_dim, hidden),
            torch.nn.Tanh(),
            torch.nn.Linear(hidden, n_aksi),
        )

    def forward(self, x):
        return self.net(x)


env = gym.make("CartPole-v1")
obs, info = env.reset(seed=0)
env.action_space.seed(0)
env.observation_space.seed(0)

pi = Policy(4, 2, hidden=32)
opt = torch.optim.Adam(pi.parameters(), lr=LR)

episode_rewards = []
grad_norms = []
sample_logprob = None

for ep in range(N_EPISODES):
    obs, _ = env.reset()
    log_probs = []
    rewards = []
    done = False
    while not done:
        x = torch.tensor(obs, dtype=torch.float32).unsqueeze(0)
        logits = pi(x)
        dist = torch.distributions.Categorical(logits=logits)
        a = dist.sample()
        lp = dist.log_prob(a)
        obs, r, term, trunc, _ = env.step(int(a.item()))
        log_probs.append(lp)
        rewards.append(r)
        done = term or trunc
    G = 0.0
    returns = []
    for r in reversed(rewards):
        G = r + GAMMA * G
        returns.insert(0, G)
    returns_t = torch.tensor(returns, dtype=torch.float32)
    log_probs_t = torch.stack(log_probs)
    loss = -(log_probs_t * returns_t).sum()
    opt.zero_grad()
    loss.backward()
    total_gn = 0.0
    for p in pi.parameters():
        if p.grad is not None:
            total_gn += float(p.grad.pow(2).sum())
    grad_norms.append(total_gn ** 0.5)
    if ep == 0:
        sample_logprob = float(log_probs_t[0].item())
    opt.step()
    episode_rewards.append(sum(rewards))

last20 = float(np.mean(episode_rewards[-20:]))
avg_gn = float(np.mean(grad_norms))
print(f"episodes               = {N_EPISODES}")
print(f"avg_reward 20 terakhir = {last20:.4f}")
print(f"contoh log-prob(ep0)   = {sample_logprob:.4f}")
print(f"norm gradien rata-rata = {avg_gn:.4f}")
env.close()
text Keluaran
episodes               = 200
avg_reward 20 terakhir = 38.2000
contoh log-prob(ep0)   = -0.8126
norm gradien rata-rata = 5793.0995

Membaca keluaran#

Reward rata-rata 20 episode terakhir mencapai 38.2, naik dari garis dasar acak sekitar 20 langkah, namun masih jauh dari batas atas 500 langkah CartPole-v1. Kenaikan ini wajar: 200 episode dengan learning rate 1e-2 tidak cukup untuk membuat kebijakan konvergen, dan REINFORCE murni memiliki variance besar sehingga pembaruan tunggal sering goyah. Angka 38.2 mencerminkan kebijakan yang mulai memilih aksi sedikit lebih baik daripada acak, bukan kebijakan terlatih.

Contoh log-probability dari episode pertama adalah −0,8126-0{,}8126, yang setara dengan probabilitas sekitar e−0,8126≈0,44e^{-0{,}8126} \approx 0{,}44. Nilai ini mendekati log⁡0,5\log 0{,}5 karena kebijakan awal mendekati seragam. Norma gradien rata-rata 5793,09955793{,}0995 sangat besar, dan inilah gejala inti masalah REINFORCE: karena return kumulatif GtG_t tidak dinormalisasi, magnitudo gradien bergeser bersama skala reward, sehingga optimizer perlu learning rate kecil agar stabil. Reduksi variance pada topik selanjutnya menangani masalah ini dengan rewards-to-go dan baseline.

Kesalahan umum#

Mengganti return dengan reward sesaat. Penyebabnya, hanya reward langkah tt yang dikalikan dengan log-probability, sehingga kontribusi aksi terhadap reward masa depan hilang. Akibatnya, kebijakan hanya belajar dari konsekuensi langsung. Perbaikannya, pakai return GtG_t yang menjumlahkan reward sejak langkah tt, sebagaimana diperlihatkan di atas.

Mengabaikan diskon pada return panjang. Penyebabnya, γ\gamma dianggap opsional pada episode pendek. Akibatnya, return dari episode yang kebetulan panjang mendominasi gradien dan menimbulkan variance besar. Perbaikannya, tetapkan γ\gamma kurang dari satu, misalnya 0,99, agar kontribusi reward jauh meredup secara eksponensial.

Ringkasan#

  • REINFORCE memperbarui θ\theta dengan estimator ∇θJ(θ)=E[∑t∇θlog⁡πθ(at∣st) Gt]\nabla_\theta J(\theta)=\mathbb{E}[\sum_t \nabla_\theta \log \pi_\theta(a_t\mid s_t)\, G_t], diturunkan dari trik log-derivative atas probabilitas trajectory.
  • Pada CartPole-v1 selama 200 episode, reward rata-rata 20 episode terakhir mencapai 38.2, jauh di bawah batas 500, dengan norma gradien rata-rata 5793.0995 yang menandakan variance besar.
  • Return kumulatif tanpa normalisasi menyebabkan magnitudo gradien bergeser bersama skala reward, sehingga learning rate harus kecil agar pelatihan stabil.

Referensi#