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

REINFORCE অ্যালগরিদম

REINFORCE — Monte Carlo policy gradient (Williams, 1992)
৭ মিনিট পড়া উচ্চ · Advanced PyTorch

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

  • REINFORCE algorithm — pseudocode + intuition
  • Causality trick — only future rewards matter
  • Baseline subtraction — variance reduction
  • CartPole-এ scratch থেকে full PyTorch implementation

১ · Algorithm-এর pseudocode

Initialize policy $\pi_\theta$ with random $\theta$।

For episode $= 1, 2, \ldots$:

  1. Generate trajectory $\tau = (s_0, a_0, r_1, s_1, \ldots, s_T)$ following $\pi_\theta$।
  2. For each $t = 0, \ldots, T-1$:
    • $G_t = \sum_{k=t}^{T-1} \gamma^{k-t} r_{k+1}$।
    • $\theta \leftarrow \theta + \alpha \nabla_\theta \log \pi_\theta(a_t | s_t) \cdot G_t$।

২ · Causality trick

Original gradient:

$$\nabla J = \mathbb{E}\left[ \left(\sum_t \nabla \log \pi(a_t|s_t)\right) \left(\sum_t r_t\right) \right]$$

Insight: $a_t$ পরের reward-এ প্রভাব ফেলে, আগের reward-এ না। তাই pre-time-$t$ rewards drop করা যায় (causality):

$$\nabla J = \mathbb{E}\left[ \sum_t \nabla \log \pi(a_t|s_t) \cdot G_t \right]$$

Variance reduction without bias। REINFORCE এই form-এ।

৩ · Baseline subtraction

$$\nabla J = \mathbb{E}\left[ \sum_t \nabla \log \pi(a_t|s_t) \cdot (G_t - b(s_t)) \right]$$

Common baselines:

  • $b = \bar{G}$ (running average)।
  • $b(s_t) = V_\phi(s_t)$ — separate value network → এটাই Actor-Critic।
  • $b = $ mean over trajectory batch।

৪ · Loss function trick (PyTorch)

$\nabla J$ — gradient of $J$, যা maximize করতে চাই। PyTorch minimize করে। Loss = $-\mathbb{E}[\sum_t \log \pi \cdot G_t]$।

$\nabla \log \pi$ — automatic differentiation handle করবে।

REINFORCE Training Loop 1. Sample episode π_θ চালিয়ে τ collect 2. Compute G_t backward sum 3. Subtract b G_t − baseline 4. Loss + step −Σ log π · (G−b) repeat next episode Update Rule θ ← θ + α · Σ_t [∇_θ log π_θ(a_t | s_t) · (G_t − b(s_t))] এক episode থেকে এক gradient update। Pure Monte Carlo।
REINFORCE-এর full training loop — sample, compute, baseline, update। সরলতম policy gradient algorithm।

৫ · CartPole REINFORCE — full PyTorch

Python · REINFORCE
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")
n_actions = env.action_space.n
state_dim = env.observation_space.shape[0]
gamma = 0.99
lr = 1e-3

class Policy(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(state_dim, 128), nn.ReLU(),
            nn.Linear(128, n_actions),
        )
    def forward(self, s):
        return Categorical(logits=self.net(s))

policy = Policy()
opt = optim.Adam(policy.parameters(), lr=lr)
returns_history = []

for ep in range(500):
    state, _ = env.reset()
    log_probs, rewards = [], []
    while True:
        s_t = torch.FloatTensor(state)
        dist = policy(s_t)
        a = dist.sample()
        next_state, r, term, trunc, _ = env.step(a.item())
        log_probs.append(dist.log_prob(a))
        rewards.append(r)
        state = next_state
        if term or trunc: break

    # Compute returns G_t (backward)
    G = 0
    returns = []
    for r in reversed(rewards):
        G = r + gamma * G
        returns.insert(0, G)
    returns = torch.FloatTensor(returns)
    returns = (returns - returns.mean()) / (returns.std() + 1e-8)  # baseline (normalize)

    # Loss
    loss = -(torch.stack(log_probs) * returns).sum()
    opt.zero_grad()
    loss.backward()
    opt.step()

    total_r = sum(rewards)
    returns_history.append(total_r)
    if ep % 25 == 0:
        print(f"Ep {ep:3d} | return = {total_r:.1f} | avg(last 25)={np.mean(returns_history[-25:]):.1f}")

    
৩০০-৫০০ episode-এ — return ১০ থেকে ৪৫০+ (CartPole-v1 max ৫০০)। DQN-এর তুলনায় slow, কিন্তু সরল implementation।

৬ · Variance reduction trick

কোডে দেখুন: (returns - returns.mean()) / (returns.std() + 1e-8) — return normalize করা হয়েছে। এটাই simplest baseline (mean) + scaling।

Empirical-ভাবে — REINFORCE এই trick ছাড়া unstable। mean baseline সরল কিন্তু effective।

৭ · REINFORCE-এর সীমা

  • High variance: Monte Carlo $G$ — long episode-এ noisy।
  • Episode wait: continuing task-এ কাজ করে না।
  • Sample inefficient: on-policy — প্রতি episode-এ সব data discard।
  • Unstable: learning rate-এ very sensitive।

৮ · REINFORCE-এর descendants

  • Actor-Critic: baseline = learned $V$, online TD।
  • A2C/A3C: parallel rollout, advantage estimate।
  • TRPO/PPO: trust region — large step prevent।
  • SAC/DDPG: off-policy variant।

প্রতিটি — REINFORCE-এর কোনো-না-কোনো limitation address করার চেষ্টা।

৯ · ঐতিহাসিক perspective

Williams 1992 paper — "Simple Statistical Gradient-Following Algorithms for Connectionist Reinforcement Learning"। সেই সময়ে:

  • Connectionism (neural net) revival।
  • Tabular RL dominant — function approximation rare।
  • REINFORCE — neural net + RL-এর প্রথম successful marriage।

আজকের ChatGPT-এর RLHF — মূলত এই Williams-এর REINFORCE-এর descendant।

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

প্র ০১Causality trick কেন variance কমায়?

Original: $\sum_t \nabla \log \pi(a_t|s_t) \cdot \sum_k r_k$।

Reduced: $\sum_t \nabla \log \pi(a_t|s_t) \cdot G_t$ where $G_t = \sum_{k \ge t} \gamma^{k-t} r_k$।

Variance argument:

  • Original-এ — past reward (যা $a_t$-এ প্রভাব ফেলেনি) gradient-এ noise।
  • Causality drop — শুধু future reward।
  • Each $a_t$-এর "credit" তুলনামূলকভাবে cleaner।

Bias issue নেই: past rewards expectation-এ $\nabla \log \pi(a_t|s_t)$ থেকে independent। expectation থেকে drop করা valid।

Mathematically: $\mathbb{E}[\nabla \log \pi(a_t|s_t) \cdot r_k] = 0$ for $k < t$ (under appropriate independence condition)।

প্র ০২Return normalization কি unbiased gradient ভাঙে?

Strict-ভাবে — হ্যাঁ, slightly। return-এর mean ও std batch-এর।

$\hat{G}_t = (G_t - \bar{G}) / \hat{\sigma}_G$।

  • $\bar{G}$ — true expectation না, batch sample mean।
  • $\hat{\sigma}_G$ — scale change — gradient direction same, magnitude শুধু changed।

Practical impact:

  • Bias minor, variance reduction major।
  • Theoretically — scale-only adjustment learning rate-এর তুল্য।
  • "Reward whitening" নাম দেওয়া যেতে পারে।

Modern practice: অনেক RL implementation reward normalize/clip করে (PPO-এ standard)। small bias-এর বদলে large variance reduction ভাল trade।

প্র ০৩REINFORCE কেন CartPole-এ DQN-এর চেয়ে slow?

Empirical comparison — CartPole-এ:

  • DQN: ~১০০-২০০ episode-এ solve।
  • REINFORCE: ~৩০০-৫০০ episode।

কেন slower:

  • On-policy: প্রতিটি sample একবার ব্যবহার। DQN replay-এ সব sample বহুবার।
  • High variance: Monte Carlo G — noisy gradient।
  • No bootstrap: per-step learning signal নেই।

কেন তবু practical:

  • Continuous action — DQN argmax problem।
  • Stochastic optimal — REINFORCE natural।
  • Theoretical clarity — ablation study সহজ।
  • RLHF-এ — GPU rollout sample expensive কিন্তু once collected, single update standard।
প্র ০৪RLHF-এ REINFORCE কেন ব্যবহার হয় (PPO-এর বদলে কেন না)?

আসলে RLHF-এ মূলত PPO ব্যবহার হয় — pure REINFORCE নয়। কিন্তু underlying conceptual framework REINFORCE-ই।

OpenAI ChatGPT — RLHF stack:

  • SFT model (supervised fine-tune)।
  • Reward model (human preference থেকে)।
  • PPO (REINFORCE + clipping + KL penalty)।

RLHF-এর জন্য PPO modifications:

  • Per-token policy gradient — $\sum_t \nabla \log \pi(token_t)$।
  • KL constraint — base model থেকে drift এড়ানো।
  • Batched rollout — generate large set, then update।
  • Reward = scalar from RM, KL penalty।

Pure REINFORCE-এর সমস্যা RLHF-এ:

  • Policy collapse — large step generates incoherent text।
  • Training unstable — variance বিশাল।

PPO clipping এই issues address করে। কিন্তু conceptually — log-prob × advantage এর underlying কাঠামো REINFORCE-ই।

DPO (পাঠ ২৯): PPO ছাড়াই preference থেকে directly — RL-এর পথ এড়ানো।

অনুশীলন

  1. Manual G_t: rewards $[1, 2, 3, 4]$, $\gamma=0.9$। $G_0, G_1, G_2$?

    $G_3 = 4$। $G_2 = 3 + 0.9 \cdot 4 = 6.6$। $G_1 = 2 + 0.9 \cdot 6.6 = 7.94$। $G_0 = 1 + 0.9 \cdot 7.94 = 8.146$।

  2. Code modify: উপরের REINFORCE-এ separate value network বানিয়ে $G - V(s)$ baseline ব্যবহার করুন (Actor-Critic preview)।
    class ValueNet(nn.Module):
        def __init__(self):
            super().__init__()
            self.net = nn.Sequential(nn.Linear(state_dim, 128), nn.ReLU(), nn.Linear(128, 1))
        def forward(self, s): return self.net(s).squeeze()

    Train V_φ alongside — MSE loss with G_t target। Use V(s_t) as baseline। পরের পাঠ এই Actor-Critic বিস্তারিত।

  3. Continuous action: Pendulum-এর জন্য (continuous) — Categorical-এর জায়গায় Gaussian policy ব্যবহার করুন।

    Output: mu (Linear), log_std (parameter)। sample: Normal(mu, std).sample()। log_prob = dist.log_prob(action).sum() (multi-D)।

আরও পড়ুন

পূর্ববর্তী পাঠ
পাঠ ১৬ · Policy Gradient theorem