Skip to content
AI360Xpert
Beta

Backward View of TD(lambda)

Keep a fading memory of states you just visited. When surprise arrives, share it backward at once instead of waiting for the episode to end.

Traces 1.00 0.72 0.52 weight one TD error of 2.00, lifting A to 1.31, B to 2.31 and C to 3.20 in a single broadcast
Traces 1.00 0.72 0.52 weight one TD error of 2.00, lifting A to 1.31, B to 2.31 and C to 3.20 in a single broadcast

Why Does This Exist?

The forward view defines the λ\lambda return as a mix of all future nn-step returns. Clean theory, but you must see the future to compute it. In continuing tasks or live control you cannot pause for the episode to finish.

The backward view makes the same idea causal. Keep an eligibility trace z(s)z(s) per state, a fading note of how recently you were there. On each step compute one scalar surprise and broadcast it to every state weighted by its trace. Credit flows the moment the transition lands, with O(∣S∣)O(|\mathcal{S}|) memory and no trajectory store.

This page covers the online mechanism. See eligibility traces for trace variants, forward view TD lambda for the lookahead definition, and their equivalence for when the totals match.

You need temporal difference learning first: R+γV(s′)−V(s)R + \gamma V(s') - V(s) as prediction error.

Think of It Like This

A megaphone echo down a trail

Runners pass checkpoints A, then B, then C. A marshal at C spots a spring and shouts once through a megaphone. The runner at C hears it full volume, the B runner hears a fainter echo, the A runner hears a faint rumble, and anyone still at base hears nothing.

The shout is the scalar error δ\delta. Loudness by position is the trace zz. Everyone updates at once without waiting for the race to end.

Where it stops: sound fades with distance and wind, while trace decay is exact math, γλ\gamma \lambda per step, plus a +1+1 bump for the state you stand on.

How It Actually Works

Keep values V(s)V(s) and traces z(s)z(s), both zeroed at episode start. With discount γ\gamma and decay λ\lambda, accumulating traces update as:

z(s)←γλz(s)+1(s=St)z(s) \leftarrow \gamma \lambda z(s) + 1(s = S_t)

Here 1(s=St)1(s = S_t) is one for the visited state and zero else. Then compute the one step error for St→St+1S_t \to S_{t+1} with reward RR:

δ=R+γV(St+1)−V(St)\delta = R + \gamma V(S_{t+1}) - V(S_t)

Then broadcast with step size α\alpha:

V(s)←V(s)+αδz(s)V(s) \leftarrow V(s) + \alpha \delta z(s)

Fresh states with z≈1z \approx 1 take the full hit. Old states take a decayed share. Unvisited states with z=0z = 0 take nothing.

Worked example

Chain A→B→C→endA \to B \to C \to \text{end} with R=0.0,1.0,5.0R = 0.0, 1.0, 5.0. Set γ=0.9\gamma = 0.9, λ=0.8\lambda = 0.8, so decay is 0.720.72, and α=0.1\alpha = 0.1. Start V=[1.00,2.00,3.00]V = [1.00, 2.00, 3.00], traces zero.

Step A→BA \to B: traces [1.00,0.00,0.00][1.00, 0.00, 0.00]. Error δ0=0.0+0.9×2.00−1.00=0.80\delta_0 = 0.0 + 0.9 \times 2.00 - 1.00 = 0.80. Then V(A)=1.00+0.1×0.80×1.00=1.08V(A) = 1.00 + 0.1 \times 0.80 \times 1.00 = 1.08.

Step B→CB \to C: traces [0.72,1.00,0.00][0.72, 1.00, 0.00]. Error δ1=1.0+0.9×3.00−2.00=1.70\delta_1 = 1.0 + 0.9 \times 3.00 - 2.00 = 1.70. Then V(A)=1.08+0.1×1.70×0.72=1.2024V(A) = 1.08 + 0.1 \times 1.70 \times 0.72 = 1.2024 and V(B)=2.00+0.1×1.70×1.00=2.17V(B) = 2.00 + 0.1 \times 1.70 \times 1.00 = 2.17. Note A learns while the agent stands at B.

Step C→endC \to \text{end}: traces [0.5184,0.72,1.00][0.5184, 0.72, 1.00] since 0.72×0.72=0.51840.72 \times 0.72 = 0.5184. Error δ2=5.0−3.00=2.00\delta_2 = 5.0 - 3.00 = 2.00. Then V(A)=1.2024+0.1×2.00×0.5184=1.3061V(A) = 1.2024 + 0.1 \times 2.00 \times 0.5184 = 1.3061, V(B)=2.17+0.1×2.00×0.72=2.3140V(B) = 2.17 + 0.1 \times 2.00 \times 0.72 = 2.3140, V(C)=3.00+0.2=3.20V(C) = 3.00 + 0.2 = 3.20.

One 2.002.00 surprise updates all three states at once.

Code

Same three steps, no hidden state.

from typing import Dict, List

def td_broadcast(    values: Dict[str, float],    traces: Dict[str, float],    gamma: float,    lam: float,    alpha: float,    state: str,    reward: float,    v_next: float,) -> float:    """Decay traces, bump the visit, broadcast one TD error."""    decay = gamma * lam    for s in traces:        traces[s] = decay * traces[s] + (1.0 if s == state else 0.0)    delta = reward + gamma * v_next - values[state]    for s in values:        values[s] += alpha * delta * traces[s]    return delta

states: List[str] = ["A", "B", "C"]v: Dict[str, float] = {"A": 1.0, "B": 2.0, "C": 3.0}z: Dict[str, float] = {"A": 0.0, "B": 0.0, "C": 0.0}
print(f"{td_broadcast(v, z, 0.9, 0.8, 0.1, 'A', 0.0, v['B']):.4f}")  # -> 0.8000print(f"{td_broadcast(v, z, 0.9, 0.8, 0.1, 'B', 1.0, v['C']):.4f}")  # -> 1.7000print(f"{td_broadcast(v, z, 0.9, 0.8, 0.1, 'C', 5.0, 0.0):.4f}")  # -> 2.0000print(f"{v['A']:.4f} {v['B']:.4f} {v['C']:.4f}")  # -> 1.3061 2.3140 3.2000

Deltas and finals match the hand run: 0.800.80, 1.701.70, 2.002.00, ending [1.3061,2.3140,3.20][1.3061, 2.3140, 3.20].

Watch Out For

Decaying only on reward or forgetting episode reset

Traces must decay every step, even when R=0R = 0, and must zero at each episode start.

Symptom: ancient states grab huge credit when a late reward lands, or episode two inherits episode one's memory. Fix: apply γλ\gamma \lambda unconditionally and clear zz in the env reset. Use replacing traces, set to 11 not +1+1, when loops revisit fast.

The Quick Version

  • Traces zz are fading visit memory with per step factor γλ\gamma \lambda, here 0.720.72.
  • Each step broadcasts one scalar δ=R+γV(s′)−V(s)\delta = R + \gamma V(s') - V(s) to all states via αδz\alpha \delta z.
  • The run gives deltas 0.800.80, 1.701.70, 2.002.00 and finals 1.30611.3061, 2.31402.3140, 3.203.20.
  • Decay every step and reset each episode or credit leaks across time.
  • Offline totals match the forward λ\lambda return under small steps.