← All notes

Policy Gradients

Policy gradient methods are a class of reinforcement learning algorithms and a sub-class of policy optimization methods. Unlike value-based methods, which learn a value function to derive a policy, policy optimization methods directly learn a policy function π\pi that selects actions without consulting a value function. For policy gradient to apply, the policy function πθ\pi_\theta is parameterized by a differentiable parameter θ\theta.1

Preliminaries: RL and MDPs

Reinforcement learning is about an agent interacting with an environment: at each time step the agent observes the state of the world, takes an action, and the environment responds with a reward and the next state.

The agent–environment loop: the agent receives state S_t and reward R_t, takes action A_t, and the environment returns the next reward R_{t+1} and next state S_{t+1}.
The agent–environment loop. Source: IBM.

The formalism behind this loop is the Markov decision process (MDP): a mathematical model for sequential decision making when outcomes are uncertain. It can be formulated as

M=(S,A,P,r)\mathcal{M} = (\mathcal{S}, \mathcal{A}, P, r)

a state space S\mathcal{S}, an action space A\mathcal{A}, transition probabilities P(st+1st,at)P(s_{t+1} | s_t, a_t), and a reward function r(s,a)r(s, a). The basic objects:

For a trajectory τ\tau, the return is the sum of rewards collected along it:

r(τ)=t=1Tr(st,at)r(\tau) = \sum_{t=1}^{T} r(s_t, a_t)

We want policies that generate high-return trajectories.

RL methods split by where their training data comes from:

This note only covers the online case.

Online RL

Online RL is a loop: run the current policy to collect a batch of data, improve the policy using that batch, and repeat. Over iterations, the policy’s trajectories concentrate on the behaviors that earn reward.

The online RL loop: run the policy to collect a batch of data, improve the policy using that batch, alongside trajectory distributions at iterations 1, 50, and 100 tightening toward successful outcomes.
The online RL loop; trajectories concentrate on rewarded behavior over iterations. Source: Stanford CS224R.

Policy Gradient objective

Goal: learn a policy πθ\pi_\theta that maximizes the expected sum of rewards over all possible episodes that this policy could produce:

θ=argmaxθ  Eτpθ(τ)[t=1Tr(st,at)]\theta^* = \arg\max_\theta \; \mathbb{E}_{\tau \sim p_\theta(\tau)} \left[ \sum_{t=1}^{T} r(s_t, a_t) \right]

where the trajectory distribution factorizes as

pθ(s1,a1,,sT,aT)=p(s1)t=1Tπθ(atst)p(st+1st,at)p_\theta(s_1, a_1, \dots, s_T, a_T) = p(s_1) \prod_{t=1}^{T} \pi_\theta(a_t | s_t) \, p(s_{t+1} | s_t, a_t)

This factorization is the Markov property: the next state depends only on the current state and action, not on the rest of the history.

The expectation inside the objective is usually named J(θ)J(\theta), the objective function:

J(θ)=Eτpθ(τ)[tr(st,at)]J(\theta) = \mathbb{E}_{\tau \sim p_\theta(\tau)} \left[ \sum_{t} r(s_t, a_t) \right]

J(θ)J(\theta) tells us how good the policy is: a higher J(θ)J(\theta) means a better policy, and θ=argmaxθJ(θ)\theta^* = \arg\max_\theta J(\theta).

Computing Eτpθ(τ)[]\mathbb{E}_{\tau \sim p_\theta(\tau)}[\cdot] exactly isn’t possible, since there are millions (or infinitely many) possible trajectories due to randomness in the environment’s transitions and the policy’s action choices. (We’ll see how to get around this with Monte Carlo approximation further below.)

Using the return r(τ)r(\tau) defined above, this is just J(θ)=Eτpθ(τ)[r(τ)]J(\theta) = \mathbb{E}_{\tau \sim p_\theta(\tau)}[r(\tau)]. Writing out the expectation over the trajectory distribution pθ(τ)p_\theta(\tau) gives an equivalent, integral form of the objective:

J(θ)=pθ(τ)r(τ)dτJ(\theta) = \int p_\theta(\tau)\, r(\tau)\, d\tau

To improve the policy using the batch of data we collect with it, we want the gradient θJ(θ)\nabla_\theta J(\theta), because gradient ascent will update

θθ+αθJ(θ)\theta \leftarrow \theta + \alpha \nabla_\theta J(\theta)

Taking the gradient:

θJ(θ)=θpθ(τ)r(τ)dτ\nabla_\theta J(\theta) = \nabla_\theta \int p_\theta(\tau)\, r(\tau)\, d\tau

Move the gradient inside the integral:

=θpθ(τ)r(τ)dτ= \int \nabla_\theta p_\theta(\tau)\, r(\tau)\, d\tau

But θpθ(τ)\nabla_\theta p_\theta(\tau) is difficult to work with. We don’t know how to directly estimate gradients of trajectory probabilities.2

The log-derivative trick rewrites this into something we can estimate. Since

θlogpθ(τ)=θpθ(τ)pθ(τ),\nabla_\theta \log p_\theta(\tau) = \frac{\nabla_\theta p_\theta(\tau)}{p_\theta(\tau)},

multiplying both sides by pθ(τ)p_\theta(\tau) gives

pθ(τ)θlogpθ(τ)=pθ(τ)θpθ(τ)pθ(τ)=θpθ(τ).p_\theta(\tau) \, \nabla_\theta \log p_\theta(\tau) = p_\theta(\tau) \, \frac{\nabla_\theta p_\theta(\tau)}{p_\theta(\tau)} = \nabla_\theta p_\theta(\tau).

Substituting this into the gradient, we replace θpθ(τ)\nabla_\theta p_\theta(\tau) with pθ(τ)θlogpθ(τ)p_\theta(\tau) \, \nabla_\theta \log p_\theta(\tau):

θJ(θ)=pθ(τ)θlogpθ(τ)r(τ)dτ\nabla_\theta J(\theta) = \int p_\theta(\tau) \, \nabla_\theta \log p_\theta(\tau) \, r(\tau) \, d\tau

This now looks like an expectation, since Exp[f(x)]=p(x)f(x)dx\mathbb{E}_{x \sim p}[f(x)] = \int p(x) f(x) \, dx. Therefore

θJ(θ)=Eτpθ(τ)[θlogpθ(τ)r(τ)]\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim p_\theta(\tau)} \left[ \nabla_\theta \log p_\theta(\tau) \, r(\tau) \right]

Intuitively, this term reweights the policy update by how good the trajectory turned out to be.3

This can be estimated using Monte Carlo approximation. A fundamental fact from probability: if Xp(x)X \sim p(x), then

E[f(X)]1Ni=1Nf(Xi)\mathbb{E}[f(X)] \approx \frac{1}{N} \sum_{i=1}^{N} f(X_i)

for samples X1,,XNX_1, \dots, X_N drawn from pp. After collecting NN trajectories by running the policy,

1Ni=1Nθlogpθ(τi)r(τi)\frac{1}{N} \sum_{i=1}^{N} \nabla_\theta \log p_\theta(\tau_i) \, r(\tau_i)

is an unbiased estimate of θJ(θ)\nabla_\theta J(\theta).

Going back to the original gradient integral, θpθ(τ)r(τ)dτ\int \nabla_\theta p_\theta(\tau) \, r(\tau) \, d\tau: why couldn’t we have estimated that one directly with Monte Carlo, instead of going through the log-derivative trick?4

We don’t need the environment dynamics

θlogpθ(τ)\nabla_\theta \log p_\theta(\tau) can be expanded further. Using the trajectory factorization,

logpθ(τ)=logp(s1)+t=1Tlogπθ(atst)+t=1Tlogp(st+1st,at),\log p_\theta(\tau) = \log p(s_1) + \sum_{t=1}^{T} \log \pi_\theta(a_t \mid s_t) + \sum_{t=1}^{T} \log p(s_{t+1} \mid s_t, a_t),

and differentiating with respect to θ\theta:

θlogpθ(τ)=t=1Tθlogπθ(atst),\nabla_\theta \log p_\theta(\tau) = \sum_{t=1}^{T} \nabla_\theta \log \pi_\theta(a_t \mid s_t),

since the initial state distribution p(s1)p(s_1) and the environment dynamics p(st+1st,at)p(s_{t+1} \mid s_t, a_t) don’t depend on θ\theta, only the policy does, so their gradients vanish. This is a huge simplification: computing θJ(θ)\nabla_\theta J(\theta) no longer requires knowing the environment’s transition probabilities at all, only the policy’s own log-probabilities, which we have direct, analytical access to. The gradient and its Monte Carlo estimate become

θJ(θ)=Eτpθ(τ)[(t=1Tθlogπθ(atst))r(τ)]1Ni=1N(t=1Tθlogπθ(ai,tsi,t))r(τi)\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim p_\theta(\tau)} \left[ \left( \sum_{t=1}^{T} \nabla_\theta \log \pi_\theta(a_t \mid s_t) \right) r(\tau) \right] \approx \frac{1}{N} \sum_{i=1}^{N} \left( \sum_{t=1}^{T} \nabla_\theta \log \pi_\theta(a_{i,t} \mid s_{i,t}) \right) r(\tau_i)

Full algorithm

Putting it all together, this is the online RL loop from earlier, made concrete. This is the “REINFORCE algorithm,” the vanilla policy gradient:

  1. Sample trajectories {τi}\{\tau^i\} by running the current policy πθ(atst)\pi_\theta(a_t \mid s_t) in the environment.
  2. Estimate the gradient: θJ(θ)i(tθlogπθ(atisti))(tr(sti,ati))\nabla_\theta J(\theta) \approx \sum_i \left( \sum_t \nabla_\theta \log \pi_\theta(a_t^i \mid s_t^i) \right) \left( \sum_t r(s_t^i, a_t^i) \right).5
  3. Update the policy: θθ+αθJ(θ)\theta \leftarrow \theta + \alpha \nabla_\theta J(\theta).

Then repeat from step 1 with the updated policy.

This algorithm works, but it is noisy and has very high variance. A single lucky episode with a huge reward can dominate the gradient estimate.6

Reward-to-go: fixing credit assignment

One natural question comes up: why should an action at time tt get credit (or blame) for rewards that happened before it? Policy behavior at time tt can’t affect rewards at t<tt' < t; future actions cannot change the past.7

Causality says we should only weight θlogπθ(atst)\nabla_\theta \log \pi_\theta(a_t \mid s_t) by the rewards that came after it, not the whole trajectory’s return. So we replace

t=1Trt\sum_{t'=1}^{T} r_{t'}

with the reward-to-go,

t=tTrt\sum_{t'=t}^{T} r_{t'}

giving a new estimator:

θJ(θ)1Ni=1Nt=1Tθlogπθ(ai,tsi,t)(t=tTr(si,t,ai,t))\nabla_\theta J(\theta) \approx \frac{1}{N} \sum_{i=1}^{N} \sum_{t=1}^{T} \nabla_\theta \log \pi_\theta(a_{i,t} \mid s_{i,t}) \left( \sum_{t'=t}^{T} r(s_{i,t'}, a_{i,t'}) \right)

Before, every action in a trajectory was weighted by the same total return. Now, each action is weighted only by the rewards that came after it, the only ones it could have influenced. Removing this irrelevant past-reward noise decreases variance, while the expected gradient stays correct.8

Baselines: centering the reward

Even with reward-to-go, there’s still a problem.9 If every trajectory in a batch gets a similar, uniformly positive reward, REINFORCE increases the probability of all of them, even the relatively worst one, since it only looks at each trajectory’s raw reward, not how it compares to the rest of the batch.

To fix this, we introduce a baseline bb and subtract it from the reward:

θJ(θ)=E[θlogpθ(τ)(r(τ)b)]\nabla_\theta J(\theta) = \mathbb{E}\left[ \nabla_\theta \log p_\theta(\tau) \left( r(\tau) - b \right) \right]

A natural choice is the batch’s average reward,

b=1Ni=1Nr(τi)b = \frac{1}{N} \sum_{i=1}^{N} r(\tau_i)

so instead of weighting by r(τ)r(\tau), we weight by how much better or worse than average each trajectory did.10 This also reduces variance: rewards that used to be large, similar numbers become small numbers centered at zero, so the gradient magnitude no longer swings wildly from batch to batch.11

Subtracting a constant baseline doesn’t bias the gradient. In expectation,

E[θlogpθ(τ)(r(τ)b)]=E[θlogpθ(τ)r(τ)],\mathbb{E}\left[ \nabla_\theta \log p_\theta(\tau) \left( r(\tau) - b \right) \right] = \mathbb{E}\left[ \nabla_\theta \log p_\theta(\tau) \, r(\tau) \right],

so the estimator stays unbiased. (The full proof isn’t given here; see Stanford CS224R’s policy gradient slides.) The average reward is a pretty good baseline: unbiased, and lower variance.

Limits of baselines: sparse rewards

Even with a baseline, policy gradient still struggles when the reward is sparse, given only once at the very end of a trajectory. The baseline centers a trajectory’s total return around the batch average, but it can’t see inside the trajectory: two trajectories that happen to end with the same final reward get exactly the same weight, even if one made real progress along the way and the other made none at all.12

Because the reward is sparse, the gradient doesn’t know which specific action along the timeline was good or bad; it just scales the entire sequence of actions by the same final number. Policy gradient is still noisy and high-variance in this regime: even with a baseline, it struggles to tell “close failures” from “total failures” apart. Fixing this needs either dense rewards (small intermediate rewards for sub-steps toward the goal) or large batches, so the noise averages out over enough samples.

Implementing this efficiently: the surrogate objective

Putting reward-to-go and the baseline together, the full estimator is

θJ(θ)1Ni=1Nt=1Tθlogπθ(ai,tsi,t)(Gi,tb),Gi,t=t=tTr(si,t,ai,t)\nabla_\theta J(\theta) \approx \frac{1}{N} \sum_{i=1}^{N} \sum_{t=1}^{T} \nabla_\theta \log \pi_\theta(a_{i,t} \mid s_{i,t}) \left( G_{i,t} - b \right), \qquad G_{i,t} = \sum_{t'=t}^{T} r(s_{i,t'}, a_{i,t'})

Computing this naively means calling backward() once per state-action pair: differentiate logπθ(atst)\log \pi_\theta(a_t \mid s_t), multiply by its own (Gtb)(G_t - b), and sum. That’s a lot of individual backward passes.13

Deep learning frameworks are built around a single scalar loss and one backward() call, not thousands of gradients accumulated by hand. So instead we construct a scalar surrogate objective J~(θ)\tilde{J}(\theta): not the true objective J(θ)=E[r]J(\theta) = \mathbb{E}[r], but a quantity whose gradient happens to equal the policy gradient we actually want:

J~(θ)=1Ni=1Nt=1Tlogπθ(ai,tsi,t)(Gi,tb)\tilde{J}(\theta) = \frac{1}{N} \sum_{i=1}^{N} \sum_{t=1}^{T} \log \pi_\theta(a_{i,t} \mid s_{i,t}) \left( G_{i,t} - b \right)

(Gi,tb)(G_{i,t} - b) comes from the environment and the batch of collected data, not from the network, so autograd treats it as a constant with no dependence on θ\theta. Differentiating J~(θ)\tilde{J}(\theta) therefore only differentiates logπθ(ai,tsi,t)\log \pi_\theta(a_{i,t} \mid s_{i,t}), carrying (Gi,tb)(G_{i,t} - b) straight through:

θ[logπθ(atst)(Gtb)]=(Gtb)θlogπθ(atst)\nabla_\theta \left[ \log \pi_\theta(a_t \mid s_t) \left( G_t - b \right) \right] = \left( G_t - b \right) \nabla_\theta \log \pi_\theta(a_t \mid s_t)

which is exactly the policy gradient term above. A single backward() call on J~(θ)\tilde{J}(\theta) therefore gives the same gradient as summing every individual term by hand.14

logπθ(atst)\log \pi_\theta(a_t \mid s_t) is also exactly the term that shows up in maximum likelihood training: for classification, cross-entropy loss is logp(yx)-\log p(y \mid x). The policy gradient loss, logπθ(atst)(Gtb)-\log \pi_\theta(a_t \mid s_t) \, (G_t - b), is the same thing weighted by (Gtb)(G_t - b), a weighted maximum likelihood that pushes up the log-probability of actions in proportion to how much better than the baseline they did.

In PyTorch, this is:

log_probs = policy.log_prob(actions)
loss = -(log_probs * advantages).mean()

optimizer.zero_grad()
loss.backward()
optimizer.step()

where advantages holds (Gtb)(G_t - b) per timestep, and the sign is flipped since optimizers minimize by convention.

On-policy vs. off-policy

Every variant of the estimator covered so far, from vanilla REINFORCE through reward-to-go, the baseline, and the surrogate objective, shares one property: the expectation Eτpθ(τ)[]\mathbb{E}_{\tau \sim p_\theta(\tau)}[\cdot] is over trajectories sampled from the current policy πθ\pi_\theta. But step 3 of the full algorithm changes θ\theta. Once that happens, the trajectories used to compute that gradient no longer come from the policy we’re now trying to improve, so we have to go back to step 1 and collect a fresh batch under the new θ\theta before taking another gradient step.

Definitions: on-policy, an update uses only data from the current policy. Off-policy, an update can reuse data from other, past policies.
Source: Stanford CS224R.

Every estimator above is on-policy: each gradient step needs its own brand-new batch of trajectories, and none of the old data can be reused once θ\theta changes. That makes them sample-inefficient.

Importance sampling: reusing old trajectories

Being on-policy means all the old data is, technically, invalid the moment the policy changes: we need fresh rollouts under the new θ\theta at every single gradient step, which is expensive. Importance sampling gives a way to reuse trajectories collected under an older policy instead.

The general trick: suppose we want an expectation under a distribution pp,

Exp(x)[f(x)]=p(x)f(x)dx\mathbb{E}_{x \sim p(x)}[f(x)] = \int p(x) \, f(x) \, dx

but only have samples from a different distribution q(x)q(x). Multiplying the integrand by q(x)q(x)=1\frac{q(x)}{q(x)} = 1,

p(x)f(x)dx=q(x)p(x)q(x)f(x)dx=Exq(x)[p(x)q(x)f(x)]\int p(x) \, f(x) \, dx = \int q(x) \, \frac{p(x)}{q(x)} \, f(x) \, dx = \mathbb{E}_{x \sim q(x)} \left[ \frac{p(x)}{q(x)} \, f(x) \right]

so an expectation under pp can be estimated from samples drawn from qq instead, as long as each sample is reweighted by the importance weight p(x)q(x)\frac{p(x)}{q(x)}.

Applying this to the policy gradient objective: we want J(θ)=Eτpθ(τ)[r(τ)]J(\theta) = \mathbb{E}_{\tau \sim p_\theta(\tau)}[r(\tau)], the expectation under the current policy πθ\pi_\theta, but only have trajectories τpˉ(τ)\tau \sim \bar{p}(\tau) collected under an older policy πˉ\bar\pi. Importance sampling rewrites the objective as

J(θ)=Eτpˉ(τ)[pθ(τ)pˉ(τ)r(τ)]J(\theta) = \mathbb{E}_{\tau \sim \bar{p}(\tau)} \left[ \frac{p_\theta(\tau)}{\bar{p}(\tau)} \, r(\tau) \right]

so the old trajectories can be reused, each one reweighted by the correction factor pθ(τ)pˉ(τ)\frac{p_\theta(\tau)}{\bar{p}(\tau)}: how much more (or less) likely that trajectory would be under the new policy than under the old one.

To compute this correction factor, recall the trajectory factorization pθ(τ)=p(s1)t=1Tπθ(atst)p(st+1st,at)p_\theta(\tau) = p(s_1) \prod_{t=1}^{T} \pi_\theta(a_t \mid s_t) \, p(s_{t+1} \mid s_t, a_t), and likewise for the old policy, pˉ(τ)=p(s1)t=1Tπˉ(atst)p(st+1st,at)\bar{p}(\tau) = p(s_1) \prod_{t=1}^{T} \bar\pi(a_t \mid s_t) \, p(s_{t+1} \mid s_t, a_t). Both trajectories were generated in the same environment, so the initial state distribution p(s1)p(s_1) and the dynamics p(st+1st,at)p(s_{t+1} \mid s_t, a_t) are identical top and bottom, and cancel out of the ratio:15

pθ(τ)pˉ(τ)=t=1Tπθ(atst)πˉ(atst)\frac{p_\theta(\tau)}{\bar{p}(\tau)} = \prod_{t=1}^{T} \frac{\pi_\theta(a_t \mid s_t)}{\bar\pi(a_t \mid s_t)}

leaving only a ratio of policy probabilities. We never need to know the environment dynamics, only the old and new policies’ probabilities of the actions actually taken.16

Off-policy policy gradient

Combining reward-to-go, the baseline, and importance sampling gives a full off-policy policy gradient: update the latest policy πθ\pi_\theta using trajectories sampled from an old policy πˉ\bar\pi, instead of πθ\pi_\theta itself.

Start from the on-policy reward-to-go-with-baseline estimator, written as an exact expectation:

θJ(θ)=Eτpθ(τ)[(t=1Tθlogπθ(atst))(Gtb)]\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim p_\theta(\tau)} \left[ \left( \sum_{t=1}^{T} \nabla_\theta \log \pi_\theta(a_t \mid s_t) \right) \left( G_t - b \right) \right]

The problem: this expectation is over trajectories τpθ(τ)\tau \sim p_\theta(\tau), sampled from the very policy we’re trying to update, exactly the data we don’t have.

Using importance sampling, rewrite the expectation over the old policy’s trajectory distribution pˉ(τ)\bar{p}(\tau) instead, correcting each sample with the importance weight pθ(τ)pˉ(τ)\frac{p_\theta(\tau)}{\bar{p}(\tau)}:

θJ(θ)=Eτpˉ(τ)[pθ(τ)pˉ(τ)(t=1Tθlogπθ(atst))(Gtb)]\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \bar{p}(\tau)} \left[ \frac{p_\theta(\tau)}{\bar{p}(\tau)} \left( \sum_{t=1}^{T} \nabla_\theta \log \pi_\theta(a_t \mid s_t) \right) \left( G_t - b \right) \right]

and substituting in the per-timestep form of that ratio,

θJ(θ)=Eτpˉ(τ)[t=1Tπθ(atst)πˉ(atst)Importance ratio(t=1Tθlogπθ(atst))(Gtb)]\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \bar{p}(\tau)} \left[ \underbrace{\prod_{t=1}^{T} \frac{\pi_\theta(a_t \mid s_t)}{\bar\pi(a_t \mid s_t)}}_{\text{Importance ratio}} \cdot \left( \sum_{t=1}^{T} \nabla_\theta \log \pi_\theta(a_t \mid s_t) \right) \left( G_t - b \right) \right]

This is now genuinely off-policy: it can be computed entirely from trajectories collected under the old policy πˉ\bar\pi, with no fresh rollouts under πθ\pi_\theta required.

But the product term, t=1Tπθ(atst)πˉ(atst)\prod_{t=1}^{T} \frac{\pi_\theta(a_t \mid s_t)}{\bar\pi(a_t \mid s_t)}, is the catch: it multiplies TT per-timestep ratios together, and for longer horizons this product can become vanishingly small or explosively large, since even a small, consistent per-step difference between the two policies compounds multiplicatively over many timesteps.17

Reducing variance: per-timestep importance sampling

Because of this high-variance trajectory-level product, it helps to stop thinking about importance-sampling an entire trajectory at once, and instead importance-sample one timestep, one state-action pair, at a time.18 Instead of

Etrajectory[]\mathbb{E}_{\text{trajectory}}[\cdots]

consider

Estate-action[]\mathbb{E}_{\text{state-action}}[\cdots]

Write each timestep’s importance ratio as ρt=πθ(st,at)πˉ(st,at)\rho_t = \frac{\pi_\theta(s_t, a_t)}{\bar\pi(s_t, a_t)}, and its gradient contribution as gt=θlogπθ(atst)(Gtb)g_t = \nabla_\theta \log \pi_\theta(a_t \mid s_t) \, (G_t - b). The old, trajectory-level estimator, for a single trajectory, was

(t=1Tρt)(t=1Tgt)\left( \prod_{t=1}^{T} \rho_t \right) \left( \sum_{t=1}^{T} g_t \right)

one giant product of ratios, multiplied by one giant sum of gradient contributions: every gtg_t in the sum gets scaled by the same trajectory-wide weight. The new, per-timestep estimator is instead

t=1Tρtgt\sum_{t=1}^{T} \rho_t \, g_t

Now every timestep carries its own weight. Instead of

trajectoryone huge weight\text{trajectory} \longrightarrow \text{one huge weight}

we have

Each gtg_t is scaled only by its own timestep’s ratio, not by a product of every ratio in the trajectory. This is exactly why per-timestep importance sampling reduces variance so dramatically.

But there’s a new problem: a policy doesn’t output πθ(st,at)\pi_\theta(s_t, a_t) directly, only πθ(atst)\pi_\theta(a_t \mid s_t). Expanding π(s,a)\pi(s, a) with the chain rule of probability, π(s,a)=π(as)π(s)\pi(s, a) = \pi(a \mid s) \, \pi(s), splits ρt\rho_t into two factors:

ρt=πθ(st,at)πˉ(st,at)=πθ(atst)πˉ(atst)πθ(st)πˉ(st)\rho_t = \frac{\pi_\theta(s_t, a_t)}{\bar\pi(s_t, a_t)} = \frac{\pi_\theta(a_t \mid s_t)}{\bar\pi(a_t \mid s_t)} \cdot \frac{\pi_\theta(s_t)}{\bar\pi(s_t)}

The first factor, the ratio of action probabilities, is easy: both policies hand it to us directly. The second factor, the ratio of state marginals, is not. π(st)\pi(s_t) isn’t something either policy outputs: it’s the probability that the agent reaches state sts_t at all, which depends on the policy, every action taken before tt, and the environment’s transition dynamics, none of which we have direct access to.

In practice, this state-marginal ratio is just approximated as 11. If πθ\pi_\theta is only a small update away from πˉ\bar\pi, the two policies tend to visit nearly the same states, so πθ(st)πˉ(st)\pi_\theta(s_t) \approx \bar\pi(s_t) and

πθ(st)πˉ(st)1\frac{\pi_\theta(s_t)}{\bar\pi(s_t)} \approx 1

This approximation only holds when the two policies are close together.

Substituting it back in gives a fully practical estimator:

θJ(θ)1Ni=1Nt=1Tπθ(ai,tsi,t)πˉ(ai,tsi,t)Probability ratioθlogπθ(ai,tsi,t)(Gi,tb)\nabla_\theta J(\theta) \approx \frac{1}{N} \sum_{i=1}^{N} \sum_{t=1}^{T} \underbrace{\frac{\pi_\theta(a_{i,t} \mid s_{i,t})}{\bar\pi(a_{i,t} \mid s_{i,t})}}_{\text{Probability ratio}} \, \nabla_\theta \log \pi_\theta(a_{i,t} \mid s_{i,t}) \left( G_{i,t} - b \right)

The huge trajectory-length product is gone. What’s left is a single probability ratio per timestep, far more stable than multiplying together TT of them.

When does our off-policy approximation stop working?

The per-timestep estimator above leans on approximating πθ(st)πˉ(st)1\frac{\pi_\theta(s_t)}{\bar\pi(s_t)} \approx 1, which quietly assumes πθ\pi_\theta hasn’t drifted far from πˉ\bar\pi.19 Once the new policy diverges enough, this breaks: the two policies start visiting different states, so the trajectories collected under πˉ\bar\pi no longer describe what πθ\pi_\theta would actually encounter.20

To keep the approximation valid, we can constrain how far a single update is allowed to move the policy: require the new policy to stay close to the old one, measured by KL divergence,

DKL(πθπˉ)δD_{\mathrm{KL}}(\pi_\theta \,\|\, \bar\pi) \leq \delta

KL divergence measures how different two probability distributions are. If πθ\pi_\theta stays within δ\delta of πˉ\bar\pi at every state, the old data stays relevant, the state-marginal approximation πθ(s)πˉ(s)\pi_\theta(s) \approx \bar\pi(s) stays reasonable, and the resulting gradient estimate stays trustworthy.

This constraint shouldn’t be checked at a single state; it should hold on average, over the states the old policy actually visited:

Esπˉ[DKL(πθ(s)πˉ(s))]δ\mathbb{E}_{s \sim \bar\pi} \left[ D_{\mathrm{KL}}\big(\pi_\theta(\cdot \mid s) \,\|\, \bar\pi(\cdot \mid s)\big) \right] \leq \delta

Pick a small threshold δ\delta, and reject (or shrink) any update that would push the policy further than that. This constrained-optimization idea, keeping every update inside a “trust region” around the old policy, is exactly what TRPO is built around.


This topic is what convinced me to start writing these notes properly and publishing them here, so I can come back later and find the detailed examples waiting in the footnotes instead of re-deriving them from scratch. Made while going through Stanford’s CS224R and talking it through with GPT-5.5.

Footnotes

  1. Adapted from Wikipedia, “Policy gradient method”.

  2. It’s not that the gradient doesn’t exist; it’s that it’s not convenient to estimate directly from data.

    Recall the factorization of pθ(τ)p_\theta(\tau):

    pθ(τ)=p(s1)t=1Tπθ(atst)p(st+1st,at)p_\theta(\tau) = p(s_1) \prod_{t=1}^{T} \pi_\theta(a_t \mid s_t) \, p(s_{t+1} \mid s_t, a_t)

    Differentiating θpθ(τ)\nabla_\theta p_\theta(\tau) means differentiating this entire product with respect to θ\theta:

    θ(p(s1)t=1Tπθ(atst)p(st+1st,at))\nabla_\theta \left( p(s_1) \prod_{t=1}^{T} \pi_\theta(a_t \mid s_t) \, p(s_{t+1} \mid s_t, a_t) \right)

    In reinforcement learning we usually don’t know the environment’s transition probabilities. We can sample transitions by interacting with the environment, but we don’t have an analytical formula for them.

  3. The update for a trajectory τi\tau_i is r(τi)tθlogπθ(atst)r(\tau_i) \sum_t \nabla_\theta \log \pi_\theta(a_t \mid s_t): it scales the gradient of the log-probability of the actions taken by how much reward they earned. If a trajectory gets a large reward, r(τ)r(\tau) is positive and large, and gradient ascent increases logπθ(atst)\log \pi_\theta(a_t \mid s_t) for the actions taken: “these actions led to success, make them more likely.” If the reward is low or negative, the opposite happens: “these actions were bad, reduce their probability.”

    A 3D reward landscape with three trajectories climbing toward higher reward; two reach a high-reward peak (marked with checkmarks) and one plateaus at a low-reward region (marked with an X).
    Trajectories that climb toward higher reward get reinforced; ones that don't, don't. Source: Stanford CS224R.

    Concretely, suppose a trajectory gets Ri=100R_i = 100. Then

    100(tθlogπθ(atst))100 \left( \sum_t \nabla_\theta \log \pi_\theta(a_t \mid s_t) \right)

    is a large update, and the actions in that trajectory become more likely. If instead Ri=0R_i = 0, there’s almost no update. If Ri=50R_i = -50, the update flips direction entirely: the policy learns “avoid doing what I just did.”

  4. Plain Monte Carlo only estimates expectations. If you sample trajectories τpθ(τ)\tau \sim p_\theta(\tau), averaging computes

    Eτpθ(τ)[f(τ)]=pθ(τ)f(τ)dτ,\mathbb{E}_{\tau \sim p_\theta(\tau)}[f(\tau)] = \int p_\theta(\tau) \, f(\tau) \, d\tau,

    not f(τ)dτ\int f(\tau) \, d\tau. The first expression, θpθ(τ)r(τ)dτ\int \nabla_\theta p_\theta(\tau) \, r(\tau) \, d\tau, is not an expectation, since there’s no pθ(τ)p_\theta(\tau) multiplying the integrand. If you ran the policy and averaged θpθ(τi)r(τi)\nabla_\theta p_\theta(\tau_i) \, r(\tau_i) over the sampled trajectories, the law of large numbers says you’d be estimating

    pθ(τ)θpθ(τ)r(τ)dτ,\int p_\theta(\tau) \, \nabla_\theta p_\theta(\tau) \, r(\tau) \, d\tau,

    a different integral, not the one we want. Short of that, the only way to compute θpθ(τ)r(τ)dτ\int \nabla_\theta p_\theta(\tau) \, r(\tau) \, d\tau exactly is brute force: enumerate every possible trajectory, which is infeasible since there are enormously many of them, often effectively infinite.

    After the log-derivative trick, using θpθ(τ)=pθ(τ)θlogpθ(τ)\nabla_\theta p_\theta(\tau) = p_\theta(\tau) \, \nabla_\theta \log p_\theta(\tau), the integral becomes

    pθ(τ)θlogpθ(τ)r(τ)dτ,\int p_\theta(\tau) \, \nabla_\theta \log p_\theta(\tau) \, r(\tau) \, d\tau,

    which now is an expectation:

    Eτpθ(τ)[θlogpθ(τ)r(τ)].\mathbb{E}_{\tau \sim p_\theta(\tau)} \left[ \nabla_\theta \log p_\theta(\tau) \, r(\tau) \right].
  5. Suppose we collect 3 episodes:

    EpisodeReturntθlogπθ\sum_t \nabla_\theta \log \pi_\theta
    1102
    251
    3203

    Each episode’s contribution is gi=(tθlogπθ)×returng_i = \left( \sum_t \nabla_\theta \log \pi_\theta \right) \times \text{return}:

    g1=2×10=20,g2=1×5=5,g3=3×20=60g_1 = 2 \times 10 = 20, \quad g_2 = 1 \times 5 = 5, \quad g_3 = 3 \times 20 = 60

    Averaging over the 3 episodes:

    20+5+603=28.3\frac{20 + 5 + 60}{3} = 28.3

    which gives the update θθ+α(28.3)\theta \leftarrow \theta + \alpha (28.3).

  6. Take the same 3 episodes, but suppose episode 3 happened to get a lucky return of 10001000 instead of 2020:

    EpisodeReturntθlogπθ\sum_t \nabla_\theta \log \pi_\thetagig_i
    110220
    2515
    3100033000

    The average becomes 20+5+300031008\frac{20 + 5 + 3000}{3} \approx 1008, almost entirely determined by episode 3. Resample a different batch of 3 episodes without that lucky outlier, and the estimate could be back down near 2828, even though the policy hasn’t changed at all. That swing between batches, driven by which episodes happened to be sampled, is the high variance.

  7. Imagine a robot walking:

    TimeActionReward
    t=1t=1step forward+1+1
    t=2t=2step forward+1+1
    t=3t=3step forward+1+1
    t=4t=4big step backward10-10
    t=5t=5step forward+1+1

    Total reward: 1+1+110+1=61 + 1 + 1 - 10 + 1 = -6.

    Under the original REINFORCE estimator, every action gets multiplied by this same total return, R=6R = -6: θlogπθ(a1s1)\nabla_\theta \log \pi_\theta(a_1 \mid s_1) is scaled by 6-6, and so is θlogπθ(a2s2)\nabla_\theta \log \pi_\theta(a_2 \mid s_2), and so on for every timestep:

    g=6θlogπθ(a1)6θlogπθ(a2)6θlogπθ(a3)6θlogπθ(a4)6θlogπθ(a5)g = -6 \, \nabla_\theta \log \pi_\theta(a_1) - 6 \, \nabla_\theta \log \pi_\theta(a_2) - 6 \, \nabla_\theta \log \pi_\theta(a_3) - 6 \, \nabla_\theta \log \pi_\theta(a_4) - 6 \, \nabla_\theta \log \pi_\theta(a_5)

    Even the three good forward steps get blamed: they didn’t cause the backward mistake at t=4t=4, yet REINFORCE blames the entire trajectory for it.

  8. Using the same rewards (1,1,1,10,11, 1, 1, -10, 1), the reward-to-go estimator gives each action its own future return, Gt=t=tTrtG_t = \sum_{t'=t}^{T} r_{t'}:

    G1=1+1+110+1=6,G2=1+110+1=7,G3=110+1=8,G_1 = 1+1+1-10+1 = -6, \quad G_2 = 1+1-10+1 = -7, \quad G_3 = 1-10+1 = -8, G4=10+1=9,G5=1G_4 = -10+1 = -9, \quad G_5 = 1

    Now each action gets a learning signal that reflects its actual consequences. In particular, G5=1G_5 = 1: the last forward step, which happened entirely after the mistake, correctly gets a positive signal instead of sharing in the 6-6 blame. The resulting gradient estimate is

    g=6θlogπθ(a1)7θlogπθ(a2)8θlogπθ(a3)9θlogπθ(a4)+1θlogπθ(a5)g = -6 \, \nabla_\theta \log \pi_\theta(a_1) - 7 \, \nabla_\theta \log \pi_\theta(a_2) - 8 \, \nabla_\theta \log \pi_\theta(a_3) - 9 \, \nabla_\theta \log \pi_\theta(a_4) + 1 \, \nabla_\theta \log \pi_\theta(a_5)

    each action weighted by its own GtG_t instead of the same flat 6-6.

  9. Suppose we run 3 trajectories:

    TrajectoryReward
    τ1\tau_1100
    τ2\tau_2110
    τ3\tau_3105

    Average reward: 105105. All three trajectories get a positive update, even τ1\tau_1, which is actually the worst trajectory of the batch: REINFORCE just sees “reward =100= 100, increase probability,” without accounting for the fact that 100100 is below average.

  10. With b=105b = 105: trajectory 1 gets 100105=5100 - 105 = -5, a negative signal, reduce its probability. Trajectory 2 gets 110105=+5110 - 105 = +5, a positive signal, increase it. Trajectory 3 gets 105105=0105 - 105 = 0, no update, exactly average behavior. This is a much more informative learning signal than the raw rewards.

  11. Suppose a batch of rewards is [99,100,101,102,98][99, 100, 101, 102, 98]. Without a baseline, the gradient is scaled by these large, similar numbers, so its magnitude fluctuates with whatever the rewards happen to be. Subtracting the average (100100) gives [1,0,+1,+2,2][-1, 0, +1, +2, -2]: much smaller numbers, centered at zero, with below-average trajectories now correctly getting a negative signal.

  12. Consider training a robot policy πθ\pi_\theta to fold a jacket, with reward r(s,a)r(s, a) given only at the end of the episode: 1.01.0 for a neatly folded jacket, 0.50.5 for an okay job with some wrinkles, 00 for failing to fold it at all. Suppose 4 trajectories:

    TrajectoryOutcomeReward
    τ1\tau_1Doesn’t touch the jacket00
    τ2\tau_2Folds only the sleeves0.50.5
    τ3\tau_3Flattens the jacket but doesn’t fold it00
    τ4\tau_4Folds it perfectly1.01.0

    The baseline is b=0+0.5+0+1.04=0.375b = \frac{0 + 0.5 + 0 + 1.0}{4} = 0.375, and each trajectory’s weight is r(τi)br(\tau_i) - b:

    τ1:00.375=0.375,τ2:0.50.375=+0.125\tau_1: 0 - 0.375 = -0.375, \qquad \tau_2: 0.5 - 0.375 = +0.125 τ3:00.375=0.375,τ4:1.00.375=+0.625\tau_3: 0 - 0.375 = -0.375, \qquad \tau_4: 1.0 - 0.375 = +0.625

    τ1\tau_1 and τ3\tau_3 get the exact same weight, 0.375-0.375, even though flattening the jacket (τ3\tau_3) is a real step toward folding it, while never touching it (τ1\tau_1) makes no progress at all. Because the reward only arrives at the end, the baseline has no way to tell these two failures apart.

  13. Suppose N=100N = 100 trajectories, each with T=1000T = 1000 steps. There are 100×1000=100,000100 \times 1000 = 100{,}000 terms θlogπθ(atst)\nabla_\theta \log \pi_\theta(a_t \mid s_t) in the sum. Calling backward() separately for each one would mean 100,000 backward passes, one per state-action pair, far too slow to be practical.

  14. J~(θ)\tilde{J}(\theta) isn’t a meaningful loss value on its own the way a supervised loss is, where a lower number means a better fit. Its only job is to make loss.backward() produce the correct gradient; the number itself isn’t something worth tracking for its own sake.

  15. Writing out the full ratio before canceling anything,

    pθ(τ)pˉ(τ)=p(s1)t=1Tπθ(atst)p(st+1st,at)p(s1)t=1Tπˉ(atst)p(st+1st,at)\frac{p_\theta(\tau)}{\bar{p}(\tau)} = \frac{p(s_1) \prod_{t=1}^{T} \pi_\theta(a_t \mid s_t) \, p(s_{t+1} \mid s_t, a_t)}{p(s_1) \prod_{t=1}^{T} \bar\pi(a_t \mid s_t) \, p(s_{t+1} \mid s_t, a_t)}

    the initial state distribution and the environment dynamics appear identically in the numerator and denominator, so they cancel:

    pθ(τ)pˉ(τ)=p(s1)t=1Tπθ(atst)p(st+1st,at)p(s1)t=1Tπˉ(atst)p(st+1st,at)=t=1Tπθ(atst)πˉ(atst)\frac{p_\theta(\tau)}{\bar{p}(\tau)} = \frac{\textcolor{#3b82f6}{\cancel{p(s_1)}} \prod_{t=1}^{T} \pi_\theta(a_t \mid s_t) \, \textcolor{#e07856}{\cancel{p(s_{t+1} \mid s_t, a_t)}}}{\textcolor{#3b82f6}{\cancel{p(s_1)}} \prod_{t=1}^{T} \bar\pi(a_t \mid s_t) \, \textcolor{#e07856}{\cancel{p(s_{t+1} \mid s_t, a_t)}}} = \prod_{t=1}^{T} \frac{\pi_\theta(a_t \mid s_t)}{\bar\pi(a_t \mid s_t)}

    the initial state distribution (blue) and the transition dynamics (orange) drop out entirely, leaving only the ratio of policy probabilities at each timestep.

  16. Suppose a single-step decision between two actions, and the old and new policies assign:

    Actionπˉ\bar\pi (old)πθ\pi_\theta (new)
    Left0.80.4
    Right0.20.6

    If the trajectory we collected took Right, its importance weight is w=πθ(Right)πˉ(Right)=0.60.2=3w = \frac{\pi_\theta(\text{Right})}{\bar\pi(\text{Right})} = \frac{0.6}{0.2} = 3: this trajectory becomes three times as important, since the new policy likes Right much more than the old one did. If instead the trajectory had taken Left, w=πθ(Left)πˉ(Left)=0.40.8=0.5w = \frac{\pi_\theta(\text{Left})}{\bar\pi(\text{Left})} = \frac{0.4}{0.8} = 0.5: it becomes half as important, since the new policy likes Left less than the old one did. This is exactly how old data gets corrected for the policy having changed.

  17. Suppose every one of the TT per-timestep ratios happens to be a modest 1.11.1 (the new policy is just slightly more likely to take each action than the old one). Over T=50T=50 steps, the product is 1.1501171.1^{50} \approx 117, a huge multiplier. If instead every ratio is 0.90.9, the product is 0.9500.0050.9^{50} \approx 0.005, close to zero. A slight, consistent per-step difference between the two policies compounds multiplicatively over a long trajectory, which is why this off-policy estimator tends to be very high variance in practice.

  18. This is similar in spirit to the move from the trajectory’s return to the reward-to-go GtG_t earlier in this note: both replace one trajectory-wide quantity with a separate quantity per timestep. There, the motivation was credit assignment, an action shouldn’t be weighted by rewards it couldn’t have influenced. Here, the motivation is variance reduction, an action shouldn’t be reweighted by a product of every ratio in the trajectory, only its own. Same move, different reason.

  19. Suppose the old and new policies choose between two actions:

    Actionπˉ\bar\pi (old)πθ\pi_\theta (new)
    Left0.510.55
    Right0.490.45

    These policies behave almost identically, so trajectories collected under πˉ\bar\pi are still representative of what πθ\pi_\theta would experience. Compare a case where the policies diverge sharply instead:

    Actionπˉ\bar\pi (old)πθ\pi_\theta (new)
    Left0.990.01
    Right0.010.99

    Here the old data is almost entirely “Left” trajectories, but the new policy almost always chooses “Right.” The old dataset no longer describes what the new policy actually does.

  20. Suppose πˉ\bar\pi always explores the left side of a maze (start → left corridor → treasure), while πθ\pi_\theta always goes right (start → right corridor → monster). The old dataset has zero experience with the right corridor, so there’s no way to estimate what πθ\pi_\theta would actually earn there: the data needed to make that estimate was never collected.