Eligibility Traces
Eligibility traces act as a fading short-term memory that records which states were recently visited, allowing a single reward to immediately credit past decisions without waiting for the episode to end.
Why Does This Exist?
In multi-step decision problems, reinforcement learning faces the fundamental temporal credit assignment problem: when an agent receives a delayed reward after dozens of choices, which earlier actions actually caused the success?
Standard one-step temporal difference learning, , updates solely the single immediately preceding state . If a crucial choice was made ten steps before reaching a rewarding terminal state, information requires ten distinct, full traversals through that trajectory to trickle back to the source. Conversely, pure Monte Carlo methods update the entire sequence of visited states at once, but they require waiting until the episode terminates, cannot function in non-terminating continuing environments, and exhibit notoriously high variance.
Eligibility traces exist to bridge this gap. They provide an incremental, online backward-view mechanism that unifies one-step bootstrapping and multi-step Monte Carlo learning. By maintaining a decaying vector of state eligibilities , a single one-step prediction error is instantly broadcast backward across every state that contributed to the current situation—achieving multi-step credit assignment on every single time step with minimal computational cost.
Think of It Like This
Footprints in the Sand and the Ocean Tide
Imagine walking across a sandy beach hunting for a buried treasure chest nestled among coastal dunes.
Every time you place a foot on the sand, you press a deep, fresh imprint into the shoreline. As the minutes tick by, ocean breezes and rising tides gently wash across the beach: your older footprints gradually soften and erode, while your most recent steps remain sharp and deep. If you circle back and step in the exact same spot twice, the indentation becomes even deeper.
The instant you unearth the gold chest, an aerial drone does not merely drop a celebratory reward on your final resting spot. Instead, it looks down at the entire coastline and scatters gold dust along your trail:
- The freshest, deepest footprints right next to the chest receive the largest share of gold dust.
- Footprints from five minutes earlier receive a moderate sprinkle.
- Faint indentations from the start of your journey receive a small dusting.
- Unvisited stretches of sand receive nothing at all.
Where the analogy breaks down: Real ocean tides erode footprints non-linearly depending on wind gusts and shoreline moisture. In reinforcement learning, eligibility traces decay strictly exponentially by a deterministic factor on each discrete clock tick. Furthermore, tabular eligibility traces store a single scalar memory bucket per discrete state label rather than distinct spatial coordinates across continuous physical terrain.
How It Actually Works
The Eligibility Trace Vector and Decay Dynamics
An eligibility trace is an additional short-term memory variable associated with each state (or feature weight). We denote the eligibility trace vector at time step by , initialized to zero at the beginning of an episode:
On every time step , two mathematical operations modify the trace vector:
- Decay: All existing state traces decay exponentially by the product of the environmental discount factor and the trace-decay parameter :
- Visit Activation: The state visited at time , , receives an eligibility bump indicating its recency.
Practitioners commonly distinguish two classic formulation styles for tabular traces:
1. Accumulating Traces
In accumulating traces, every visit to state adds directly to the trace without bound:
When an agent loops through the same state repeatedly within a short window, the trace sums up geometrically:
2. Replacing Traces
In replacing traces (introduced by Singh and Sutton, 1996), visiting a state resets its eligibility to rather than adding to whatever residual trace remained:
Replacing traces prevent cyclic loops and high-frequency state visitations from inflating credit disproportionately, delivering substantially more stable performance in tasks with dense revisiting.
Backward Broadcast of the TD Error
Once the transition from to yields immediate reward , the agent computes the standard one-step Temporal Difference error:
Instead of updating only the value of , this prediction error is broadcast across all states simultaneously, weighted by their active eligibility and learning rate :
The parameter governs the time horizon of credit assignment:
- (): . The trace persists for only one step, reducing the algorithm strictly to standard one-step bootstrapping.
- (): Traces decay only by discount factor . Over the course of an entire episode, the cumulative updates match those of full Monte Carlo returns, yet are computed incrementally on every transition.
- (): Smoothly blends short-term bootstrapping with long-horizon rollouts, achieving lower variance than Monte Carlo and lower asymptotic bias than one-step TD.
Worked numerical example
Consider an environment with three non-terminal states and terminal state .
- Initial values: , , .
- Hyperparameters: learning rate , discount factor , trace decay .
- Effective trace decay: .
An agent executes the four-step sequence: .
Step 0: Visit , transition to with reward
- Traces updated:
- Accumulating: , , .
- Replacing: , , .
- TD error: .
- Value update: . Values remain all .
Step 1: In , transition back to with reward
- Traces updated:
- Both methods decay previous traces by : .
- State visited: , .
- TD error: .
- Value update: All values remain .
Step 2: Revisit , transition to with reward
- Trace decay prior to visit bump:
- had , decaying to .
- had , decaying to .
- Visit :
- Accumulating trace: .
- Replacing trace: (reset, suppressing the loop buildup).
- TD error: . Values remain .
Step 3: In , terminal transition with reward
- Trace decay and bump for :
- Accumulating trace:
- Replacing trace:
- Accumulating trace:
- TD error at terminal transition ():
- Broadcast update: .
| State | Accumulating Trace | Updated | Replacing Trace | Updated |
|---|---|---|---|---|
In a single episode, the reward received in immediately backward-propagated to both and . Under accumulating traces, earned extra credit because it was visited twice; under replacing traces, its credit was bounded cleanly.
Code
from typing import Dict, List, Literal, Tuple
def simulate_eligibility_traces( trajectory: List[Tuple[str, float]], gamma: float = 0.9, lam: float = 0.8, alpha: float = 0.5, trace_mode: Literal["accumulating", "replacing"] = "accumulating",) -> Tuple[Dict[str, float], Dict[str, float]]: """Simulate online backward-view TD(lambda) updates over an episode.
Args: trajectory: Sequence of (state, incoming_reward) steps terminating in 'terminal'. gamma: Environmental discount factor in [0, 1]. lam: Trace-decay parameter lambda in [0, 1]. alpha: Step-size learning rate in (0, 1]. trace_mode: Either 'accumulating' or 'replacing'.
Returns: A tuple of (final_state_values, final_eligibility_traces). """ non_terminal_states = sorted( list({state for state, _ in trajectory if state != "terminal"} )) values: Dict[str, float] = {s: 0.0 for s in non_terminal_states} traces: Dict[str, float] = {s: 0.0 for s in non_terminal_states} decay = gamma * lam
print(f"=== {trace_mode.upper()} TRACES (gamma*lambda = {decay:.2f}) ===")
for t in range(len(trajectory) - 1): state_t, _ = trajectory[t] next_state, reward_t1 = trajectory[t + 1]
# 1. Decay all active traces for s in traces: traces[s] *= decay
# 2. Update eligibility for the current visited state if trace_mode == "accumulating": traces[state_t] += 1.0 elif trace_mode == "replacing": traces[state_t] = 1.0 else: raise ValueError(f"Unknown trace mode: {trace_mode}")
# 3. Compute 1-step TD prediction error next_v = 0.0 if next_state == "terminal" else values[next_state] delta_t = reward_t1 + gamma * next_v - values[state_t]
# 4. Broadcast error backward to all eligible states for s in values: values[s] += alpha * delta_t * traces[s]
trace_repr = {s: f"{traces[s]:.4f}" for s in traces} value_repr = {s: f"{values[s]:.3f}" for s in values} print( f"Step {t} ({state_t} -> {next_state}, R={reward_t1:.1f}) | " f"delta={delta_t:+.1f} | Traces: {trace_repr} | Values: {value_repr}" )
return values, traces
if __name__ == "__main__": # Trajectory: S_A -> S_B -> S_A -> S_C -> terminal with reward +10.0 episode: List[Tuple[str, float]] = [ ("S_A", 0.0), ("S_B", 0.0), ("S_A", 0.0), ("S_C", 0.0), ("terminal", 10.0), ]
vals_accum, _ = simulate_eligibility_traces( episode, gamma=0.9, lam=0.8, alpha=0.5, trace_mode="accumulating" ) print() vals_repl, _ = simulate_eligibility_traces( episode, gamma=0.9, lam=0.8, alpha=0.5, trace_mode="replacing" )
print("\n--- Final Value Comparison ---") for s in sorted(vals_accum.keys()): print( f"State {s}: Accumulating = {vals_accum[s]:.3f} | " f"Replacing = {vals_repl[s]:.3f}" )
# Expected Output:# === ACCUMULATING TRACES (gamma*lambda = 0.72) ===# Step 0 (S_A -> S_B, R=0.0) | delta=+0.0 | Traces: {'S_A': '1.0000', 'S_B': '0.0000', 'S_C': '0.0000'} | Values: {'S_A': '0.000', 'S_B': '0.000', 'S_C': '0.000'}# Step 1 (S_B -> S_A, R=0.0) | delta=+0.0 | Traces: {'S_A': '0.7200', 'S_B': '1.0000', 'S_C': '0.0000'} | Values: {'S_A': '0.000', 'S_B': '0.000', 'S_C': '0.000'}# Step 2 (S_A -> S_C, R=0.0) | delta=+0.0 | Traces: {'S_A': '1.5184', 'S_B': '0.7200', 'S_C': '0.0000'} | Values: {'S_A': '0.000', 'S_B': '0.000', 'S_C': '0.000'}# Step 3 (S_C -> terminal, R=10.0) | delta=+10.0 | Traces: {'S_A': '1.0932', 'S_B': '0.5184', 'S_C': '1.0000'} | Values: {'S_A': '5.466', 'S_B': '2.592', 'S_C': '5.000'}## === REPLACING TRACES (gamma*lambda = 0.72) ===# Step 0 (S_A -> S_B, R=0.0) | delta=+0.0 | Traces: {'S_A': '1.0000', 'S_B': '0.0000', 'S_C': '0.0000'} | Values: {'S_A': '0.000', 'S_B': '0.000', 'S_C': '0.000'}# Step 1 (S_B -> S_A, R=0.0) | delta=+0.0 | Traces: {'S_A': '0.7200', 'S_B': '1.0000', 'S_C': '0.0000'} | Values: {'S_A': '0.000', 'S_B': '0.000', 'S_C': '0.000'}# Step 2 (S_A -> S_C, R=0.0) | delta=+0.0 | Traces: {'S_A': '1.0000', 'S_B': '0.7200', 'S_C': '0.0000'} | Values: {'S_A': '0.000', 'S_B': '0.000', 'S_C': '0.000'}# Step 3 (S_C -> terminal, R=10.0) | delta=+10.0 | Traces: {'S_A': '0.7200', 'S_B': '0.5184', 'S_C': '1.0000'} | Values: {'S_A': '3.600', 'S_B': '2.592', 'S_C': '5.000'}## --- Final Value Comparison ---# State S_A: Accumulating = 5.466 | Replacing = 3.600# State S_B: Accumulating = 2.592 | Replacing = 2.592# State S_C: Accumulating = 5.000 | Replacing = 5.000Watch Out For
Runaway Trace Accumulation in Continuing Tasks with λ=1
In episodic problems with terminal resets, setting neatly mirrors Monte Carlo returns. However, in continuing tasks (environments without terminal states, like balance control or industrial robotics) or Markov Decision Processes with dense cyclical orbits, setting alongside an undiscounted or weakly discounted objective () causes accumulating traces to explode.
Because accumulating traces append on every revisit without decaying to zero, the sum diverges toward infinity:
When an unexpected non-zero TD error eventually arrives, multiplying it by an inflated trace vector produces an enormous weight jump (), instantly blowing up value estimates, destabilizing the policy, and causing numerical NaN gradient crashes.
Concrete Fixes:
- Switch to Replacing or Dutch Traces: Replacing traces cap maximum state eligibility at , preventing revisits from amplifying values out of proportion.
- Guarantee Exponential Decay: Ensure the compound decay factor satisfies strictly, bounding the maximum asymptotic trace amplitude to .
- Reset at Episode Horizons: In episodic settings, unconditionally zero the trace vector () immediately when an episode concludes.
The Quick Version
- Fading short-term memory: Eligibility traces maintain a vector tracking state visit recency and frequency, decaying exponentially by on each transition.
- Backward credit assignment: Instead of waiting for episode termination, any TD error is instantly broadcast backward across all states via .
- Unifying TD and Monte Carlo: Setting reduces to standard 1-step , while setting replicates the credit distribution of full Monte Carlo rollouts online.
- Accumulating vs. replacing: Accumulating traces add eligibility upon revisits (), which risks divergence in cycles; replacing traces reset eligibility to , ensuring numerical stability.