Experience Replay
Experience replay stores past transitions in a circular memory buffer and trains on random mini-batches, breaking time correlations for stable deep network training.
Why Does This Exist?
The first attempts to combine RL with deep networks in the early 2010s kept collapsing. Value estimates swung wildly and agents failed on rudimentary games. Two properties of sequential data caused it.
First, correlation. Gradient descent assumes training samples are independent and identically distributed, but an RL agent collects data in order: depends strongly on . Thirty seconds of open highway trains the network only on straightaways, and when the sharp curve finally arrives, the cornering weights are gone. That is catastrophic forgetting wearing a racing suit.
Second, waste. In online RL each transition trains once and is discarded. Robot actuators wear out and game engines run at fixed frame rates, so throwing away hard-won experience after one update squanders the training signal.
Long-Ji Lin proposed the fix in 1992, and Mnih and co-authors revived it in the 2015 Deep Q-Network paper. Store transitions in a circular buffer and train on randomized mini-batches drawn from it. Shuffling breaks the time correlations, restoring near-independent samples, and each transition gets reused dozens of times.
This page covers the buffer mechanics, the sampling rule, and the off-policy catch. For the algorithm that made replay famous, read deep Q-networks. For sampling surprising transitions first, read prioritized experience replay. For the second stabilizing trick DQN paired with replay, read target networks.
The one idea you need first: a transition tuple. Each step produces , where in flags whether the episode ended. The buffer stores these tuples; everything else is machinery for reusing them well.
Think of It Like This
Film review instead of sideline shouting
A basketball player who learns only from the last play is doomed. Miss a three and the next five minutes go to shooting form while defense and passing rot. The latest event dictates all learning, so performance swings wildly.
A coaching staff with a video archive does it differently. They record every possession all season, then build each review reel by shuffling: a turnover from three weeks ago, a box-out from last night, an inbound play from two months back, a clutch free throw, a foul from yesterday. Because the reel mixes opponents, months, and situations, the player builds balanced habits instead of overreacting to whatever happened five seconds ago.
Where it stops: human coaches hand-pick the embarrassing clips, biasing review toward mistakes. Uniform replay gives every stored transition an equal shot regardless of surprise, which is exactly what prioritized experience replay changes.
How It Actually Works
Each step appends its tuple to a fixed-capacity store:
with typically to in deep RL. Memory is a circular queue: when full, the newcomer overwrites the oldest entry at index . Training draws a mini-batch of size (commonly 32, 64, or 256) uniformly at random:
and minimizes the mean squared error against a semi-static target network :
Why shuffling stabilizes training
For a batch of size with lag- correlations , the gradient estimator variance is:
In a sequential stream sits near 0.8 to 0.99, which inflates the bracket far above 1 and shrinks the effective batch. Uniform sampling across thousands of past episodes drives toward zero, collapsing the bracket to 1 and restoring standard gradient behavior.
The off-policy catch
Buffered transitions were collected by older policies, so the data distribution lags the current one. That is fine for off-policy methods (Q-learning, DQN, DDPG, TD3, SAC), which evaluate a target policy from arbitrary behavior data. It breaks on-policy methods (REINFORCE, A2C, plain PPO), whose update math assumes fresh data from the current policy. Replay plus on-policy updates without correction drifts and collapses.
Worked example: a four-slot buffer
Capacity , batch size . Five transitions arrive:
- into slot 0
- into slot 1
- into slot 2
- into slot 3, buffer full
- overwrites slot 0
Buffer now holds with the write pointer at index 1. The sampler draws batch : one early small-state transition, one far-away large-state transition. The chain is broken by construction.
Take a linear model with , matching target weights, and . For : next values are for both actions, so ; prediction is ; error . For : next values are , so ; prediction is ; error . Loss:
Code
A pure-Python transcription of the worked example: the ring buffer, the wrap-around, and the loss on batch .
capacity = 4buffer = []pointer = 0
transitions = [ (1.0, 0, 0.0, 2.0, False), (2.0, 1, 1.0, 3.0, False), (3.0, 0, 0.0, 4.0, False), (4.0, 1, 2.0, 5.0, True), (10.0, 0, 0.5, 11.0, False),]
for t in transitions: if len(buffer) < capacity: buffer.append(t) else: buffer[pointer] = t pointer = (pointer + 1) % capacity
w = [0.5, 0.5]gamma = 0.9
def td_error(t): s, a, r, s_next, done = t q_next_max = max(wt * s_next for wt in w) target = r + (0.0 if done else gamma * q_next_max) return target - w[a] * s
batch = [buffer[1], buffer[0]]errors = [td_error(t) for t in batch]loss = sum(e ** 2 for e in errors) / len(errors)
print([t[0] for t in buffer]) # -> [10.0, 2.0, 3.0, 4.0]print(pointer) # -> 1print([round(e, 4) for e in errors]) # -> [1.35, 0.45]print(round(loss, 4)) # -> 1.0125Watch Out For
Feeding replay data to an on-policy algorithm
The symptom is a policy that improves briefly, then drifts and collapses. The cause: transitions in came from older policy versions, but on-policy update math assumes every sample came from the current one. PPO, A2C, and SARSA consume stale distributions they were never derived for.
The fix is architectural, not a hyperparameter. Use replay only with off-policy methods (DQN, DDPG, TD3, SAC). If an on-policy method must reuse data, that reuse needs explicit correction machinery, which is a different algorithm, not a tweak.
Sizing the buffer by guesswork
Too small (under about ) and the buffer cannot decorrelate: consecutive batches still look sequential, forgetting returns, and the agent cycles through local policies. Too large (past about ) and the buffer fills with ancient random-exploration junk that dilutes fresh, competent play and slows convergence to a crawl.
The fix: start near to for Atari-scale or continuous control tasks, then check the age mix. If rare high-value transitions keep getting overwritten before they train, that is the signal to upgrade to prioritized experience replay, not to blindly grow .
The Quick Version
- Replay stores transitions in a fixed-size circular buffer and trains on uniform random mini-batches.
- Shuffling breaks time correlations and restores the near-independent samples gradient descent assumes.
- Each expensive environment step gets reused across dozens of updates instead of training once and dying.
- The five-transition example overwrites slot 0, batches , and computes loss from errors and .
- Replay is strictly off-policy: buffered data comes from older policies, so on-policy methods must stay away.