Off-Policy Prediction via Importance Sampling
Judge a policy you never run by watching a different policy act, then reweighting each episode by how much more or less likely your policy would have acted the same way.
Why Does This Exist?
Learning and acting pull in opposite directions. To find a good policy you must try doubtful actions, but to score a policy you must play it straight. On-policy methods force one policy to do both jobs, so every update throws away yesterday's episodes and every evaluation is polluted by exploration noise.
Off-policy prediction splits the jobs. The behavior policy touches the environment: it explores, plays safe, or just keeps the lights on. The target policy never acts at all; it is the candidate you want scored. Raw episode returns belong to , so averaging them scores the wrong policy. Importance sampling is the correction that turns 's episodes into an unbiased score for .
This page covers prediction: scoring one fixed target policy from behavior data. The variance-reduced sibling is ordinary vs weighted importance sampling, and the on-policy counterpart that needs no correction is on-policy first-visit MC control.
The one idea you need first
An expectation under one distribution can be estimated with samples from another, as long as every outcome possible under the first is possible under the second. That support condition is the coverage assumption: implies . Each sample gets multiplied by the probability ratio , which upweights underrepresented outcomes and downweights overrepresented ones. Everything below is that trick applied step by step along a trajectory.
Think of It Like This
The campus poll that forecasts a whole city
A polling crew wants to forecast a city vote but only manages to interview students on one campus. Among students, 80 percent back the transit plan and 20 percent back highways. Census data says the city as a whole splits 40 percent transit, 60 percent highways. Averaging raw student answers would call the election wrong by a mile.
So each answer gets a correction weight. A transit answer counts , half a vote, because students overrepresent that camp. A highway answer counts , triple, because students underrepresent it. The weighted average now estimates the city, even though no non-student was ever interviewed.
Where it stops: a poll corrects one answer with one ratio. An episode is a chain of decisions, so RL multiplies a ratio per step, and that product can swing wildly in long episodes in a way polls never do.
How It Actually Works
Write for the target policy's action probability and for the behavior policy's. For a trajectory from step to termination , the probability of that exact action sequence under each policy differs only in the action probabilities, because the environment dynamics appear in both and cancel:
That product is the importance sampling ratio. Multiply the episode return by it, and the behavior expectation equals the target value exactly: . Average over episodes and you have ordinary importance sampling:
Unbiased, model-free, and built only from action probabilities you already know.
Worked example
One 3-step episode with discount and rewards 2, 1, 5, so the raw return is . The policies disagree at every step:
- Step 0: , , ratio
- Step 1: , , ratio
- Step 2: , , ratio
The cumulative ratio is , and the corrected vote of this episode is .
A second episode from the same start has return with cumulative ratio , giving . The ordinary importance sampling estimate across both episodes is , an unbiased read on a policy that never touched the environment.
Code
This snippet computes the corrected return for both episodes above:
def corrected_return(rewards: list[float], ratios: list[float]) -> tuple[float, float, float]: """Raw return, cumulative ratio rho, and rho-weighted return.""" rho = 1.0 for ratio in ratios: rho = rho * ratio total = 0.0 for reward in rewards: total = total + reward weighted = rho * total return round(total, 2), round(rho, 2), round(weighted, 2)
print(corrected_return([2.0, 1.0, 5.0], [1.60, 0.50, 2.00])) # -> (8.0, 1.6, 12.8)print(corrected_return([1.0, 3.0], [0.75, 1.00])) # -> (4.0, 0.75, 3.0)Both outputs match the hand computation, and their mean is the value estimate.
Watch Out For
Products of ratios explode on long episodes
Ordinary importance sampling is unbiased, but its variance grows exponentially with horizon. Each extra step multiplies another ratio, so one episode that happens to match everywhere can carry a ratio in the hundreds while typical episodes sit near zero. The average then jerks around with every new batch, and short experiments look conclusive right before they reverse.
Watch the distribution of itself, not just the estimate. If a handful of episodes dominate the sum, switch to weighted importance sampling, per-decision ratios that stop the product at each reward, or a one-step TD method that never forms the long product at all.
A zero in the behavior policy silently voids the guarantee
If anywhere , coverage is broken: that action never appears in the data, the ratio divides by zero, and the estimate is biased with no warning from the math. Deterministic loggers are the usual culprit, since a policy that always picks one action assigns zero to everything else.
Keep the behavior policy soft, with every action playable in every visited state, and assert before dividing. A cheap guard is to log per batch and investigate any batch that touches zero.
The Quick Version
- Off-policy prediction scores a target policy using episodes gathered by a separate behavior policy .
- Coverage requires wherever , or the correction divides by zero.
- The trajectory ratio cancels environment dynamics, so the method is fully model-free.
- Weighting returns by is unbiased: episode ratios 1.60, 0.50 and 2.00 give and turn 8.0 into 12.80.
- Long products mean wild variance, so check the spread of and prefer weighted or per-decision variants on long horizons.