Prioritized Experience Replay
Replay the transitions that still surprise the network first, using a sum tree to draw them fast and weights that undo the sampling bias.
Why Does This Exist?
Uniform replay treats every memory as equal. Each stored transition draws with probability , where is the buffer size. Most memories are boring: the car kept its lane, the corridor stayed empty, the TD error sits near zero. A few are gold: the crash, the first goal, the trap door. Those carry large TD errors and the most learning signal, but uniform sampling may revisit them once in tens of thousands of draws while relearning solved transitions all day.
Schaul and coauthors (DeepMind, ICLR 2016) fixed the allocation with Prioritized Experience Replay. Sample proportional to surprise, measured by absolute TD error , so the network spends its limited minibatches where its predictions are worst. New transitions enter at maximum priority so nothing is skipped unseen. Because skewed sampling biases gradients, each update is multiplied by an importance sampling weight that starts partial and anneals to full correction by late training.
This page covers the sampling rule, the sum tree that makes it fast, and the correction. For the uniform baseline underneath, read experience replay. For the network that consumes these batches, read deep Q-networks.
Think of It Like This
A question box that floats the missed cards to the top
You run exam revision with a box of 1,000 cards. Shuffling uniformly means tonight you redraw the 900 you already know and meet the 12 you keep missing about once a month. Progress crawls.
So you score every card by last error and let the box sort itself. Missed cards float up and get drawn nightly; known cards sink and surface rarely. When a hard card is finally answered cold, its score drops and it sinks on its own. Because nights now overweight the hard cards, you also shrink their marks slightly when grading the mock, or the final score would pretend the whole syllabus is as hard as the worst dozen.
Where it stops: cards sit in discrete piles while PER draws from a continuous distribution through a binary sum tree, and no card box anneals a bias exponent toward 1. The float up plus discount part carries over.
How It Actually Works
A transition is with state, action, reward, next state, done flag, and discount. The TD error says how wrong the current values are. Priority is with small like so zero error items keep a nonzero chance. Sampling probability is with in . At this is uniform; the paper uses . Importance weight is with annealed from about to , and the minibatch loss is the weighted mean of .
The sum tree in O(log N)
Scanning a million priorities per draw costs and kills training. The sum tree stores the values as leaves in a flat array of nodes, with each parent holding the sum of its children and the root holding the total. To draw one sample, split into equal strata, pick one uniform value per stratum, and walk root to leaf going left when the value fits the left sum and right otherwise. That walk takes steps. Updating a priority after its TD error changes walks back up the same path, also .
Worked example
Take with , , . TD magnitudes are , , , , so priorities equal those numbers and the total is . The tree array is : root , left sum , right sum , then the four leaves.
Probabilities divide by : . The item draws more than half the samples. Raw weights are ; dividing by the max gives normalized weights . The most sampled item is discounted hardest at , which stops frequent updates from dragging the whole surface. After training drops the error to , the leaf becomes , its parent drops from to , and the root drops from to . The diagram draws this exact tree and weight row.
Code
The snippet recomputes the four probabilities and weights from the worked example with the standard library only.
import math
priorities = [1.0, 4.0, 0.5, 2.0]total = sum(priorities)probs = [p / total for p in priorities]print([round(p, 4) for p in probs]) # -> [0.1333, 0.5333, 0.0667, 0.2667]
beta = 0.5n = 4raw = [(n * p) ** (-beta) for p in probs]peak = max(raw)normed = [w / peak for w in raw]print([round(w, 4) for w in normed]) # -> [0.7071, 0.3536, 1.0, 0.5]print(round(total, 4)) # -> 7.5The prints match the hand derived total , the percent draw rate for the item, and its discounted weight .
Watch Out For
Zero epsilon locks transitions out forever
The symptom is a slice of the buffer that never replays: its error once hit exactly zero, its priority became , and its sampling probability stuck at . Later policy shifts make those states relevant again, but the network never sees them because the tree never routes there.
Keep , typically to , so every leaf holds a nonzero floor. Log the fraction of leaves below twice ; a growing pile at the floor means the buffer is going stale rather than solved.
Leaving beta low for the whole run
The symptom is a value surface tilted toward dramatic memories: sharp spikes near rare crashes and washed out values elsewhere, even as loss falls. Sampling stayed skewed while the correction never reached full strength, so gradients converged to the wrong fixed point.
Anneal from about toward across training and normalize weights by the batch max for stability. If late training still overreacts to rare items, check the schedule before touching .
The Quick Version
- Priority turns TD surprise into sampling probability .
- A binary sum tree draws and updates in , which is what makes a million size buffer practical.
- Importance weights undo the skew, with annealed to for unbiased late updates.
- With priorities the total is and normalized weights are .
- New transitions enter at max priority and stays positive so no memory locks out.