Skip to content
AI360Xpert
Beta

Forward View of TD(lambda)

Instead of fixing one lookahead, mix every lookahead at once: mostly the one-step return, a little less of the two-step, fading geometrically out to the full episode. One knob slides from TD to Monte Carlo.

Weights 0.30, 0.21 and 0.49 blend the n-step returns 4.70, 6.95 and 6.14 into one lambda target of 5.88, sitting between TD at 4.70 and Monte Carlo at 6.14
Weights 0.30, 0.21 and 0.49 blend the n-step returns 4.70, 6.95 and 6.14 into one lambda target of 5.88, sitting between TD at 4.70 and Monte Carlo at 6.14

Why Does This Exist?

One-step TD and Monte Carlo sit at opposite ends of a real dilemma. Looking one transition ahead gives low-variance targets and updates after every step, but bootstraps on young, biased guesses. Waiting for the full episode removes that bias and replaces it with the compounded noise of the whole trajectory. Multi-step TD compromises with a fixed horizon nn, covered at n-step TD prediction, yet any single nn is arbitrary: short horizons win in some states, long ones in others.

The forward view refuses to choose. It defines the λ\lambda-return GtλG_t^\lambda: a weighted average of all nn-step returns with geometrically fading weights (1−λ)λn−1(1 - \lambda)\lambda^{n-1}, λ\lambda in [0,1][0, 1]. Short low-variance horizons get the most weight, long low-bias horizons get progressively less, and the weights always sum to one. The geometric shape is load-bearing, not decorative: its memoryless property is exactly what lets the online backward view with eligibility traces reproduce this target causally, step by step.

The one idea you need first.

An nn-step return Gt:t+nG_{t:t+n} adds up nn real rewards then bootstraps: Gt:t+n=Rt+1+γRt+2+⋯+γn−1Rt+n+γnVt(St+n)G_{t:t+n} = R_{t+1} + \gamma R_{t+2} + \dots + \gamma^{n-1}R_{t+n} + \gamma^n V_t(S_{t+n}), where γ\gamma in [0,1][0, 1] is the discount factor and VtV_t is the current value guess. At n=1n = 1 this is the TD target; at the episode end it is the Monte Carlo return. Everything below mixes these objects.

Think of It Like This

A concert hall blending direct sound with echoes

A violin note reaches you twice: first straight from the strings, sharp and exact, then as early reflections off the near walls, then as a long fading tail from the far balconies. You never hear the dry pluck alone or the wash of echoes alone; your ear blends the crisp attack with the decaying tail into one warm percept.

Point for point: the direct sound is the 1-step return with weight 1−λ1 - \lambda, the early reflections are the 2-step and 3-step returns with weights (1−λ)λ(1 - \lambda)\lambda and (1−λ)λ2(1 - \lambda)\lambda^2, the tail is the distant horizons and the Monte Carlo remainder, and the blend you perceive is GtλG_t^\lambda. Where it stops: the hall's decay is fixed by architecture, while λ\lambda is a knob you turn deliberately. And real echoes arrive after the note, whereas the forward view's echoes are future rewards that have not happened yet at time tt, which is why this view is theory, not an online algorithm.

How It Actually Works

The lambda return

In continuing tasks the definition runs over every horizon:

Gtλ=(1−λ)∑n=1∞λn−1Gt:t+nG_t^\lambda = (1 - \lambda) \sum_{n=1}^{\infty} \lambda^{n-1} G_{t:t+n}

The weights form a valid distribution because (1−λ)∑k=0∞λk=1(1 - \lambda)\sum_{k=0}^{\infty}\lambda^k = 1. In an episode ending at TT, every nn-step return past T−tT - t equals the full return GtG_t, so the infinite tail collapses into one remainder weight λT−t−1\lambda^{T-t-1}:

Gtλ=(1−λ)∑n=1T−t−1λn−1Gt:t+n+λT−t−1GtG_t^\lambda = (1 - \lambda) \sum_{n=1}^{T-t-1} \lambda^{n-1} G_{t:t+n} + \lambda^{T-t-1} G_t

The finite weights sum to 1−λT−t−11 - \lambda^{T-t-1} and the tail supplies the rest, so the total is exactly 1.01.0. The value update aims at this composite target with step size α\alpha in (0,1](0, 1]: V(St)←V(St)+α[Gtλ−V(St)]V(S_t) \leftarrow V(S_t) + \alpha[G_t^\lambda - V(S_t)].

The two endpoints fall out for free

At λ=0\lambda = 0 every weight except the first vanishes and Gt0=Gt:t+1G_t^0 = G_{t:t+1}, pure one-step TD. At λ=1\lambda = 1 the finite weights vanish and the tail weight is 11, so Gt1=GtG_t^1 = G_t, pure Monte Carlo. Between them the target slides smoothly from low-variance bootstrapping to unbiased full rollouts, which is why λ\lambda reads as a bias-variance knob.

Worked example

Three transitions: S0S_0 with reward 2.02.0, S1S_1 with reward 1.01.0, S2S_2 with reward 4.04.0, then termination. Discount γ=0.9\gamma = 0.9, blend λ=0.7\lambda = 0.7, step size α=0.1\alpha = 0.1. Current guesses V(S0)=1.0V(S_0) = 1.0, V(S1)=3.0V(S_1) = 3.0, V(S2)=5.0V(S_2) = 5.0, terminal 0.00.0. Target at t=0t = 0, where T−t=3T - t = 3.

The three nn-step returns from t=0t = 0:

  • G0:1=2.0+0.9×3.0=4.7000G_{0:1} = 2.0 + 0.9 \times 3.0 = 4.7000
  • G0:2=2.0+0.9×1.0+0.81×5.0=2.90+4.05=6.9500G_{0:2} = 2.0 + 0.9 \times 1.0 + 0.81 \times 5.0 = 2.90 + 4.05 = 6.9500
  • G0:3=G0=2.0+0.90+0.81×4.0=2.90+3.24=6.1400G_{0:3} = G_0 = 2.0 + 0.90 + 0.81 \times 4.0 = 2.90 + 3.24 = 6.1400

The weights: w1=1−0.7=0.30w_1 = 1 - 0.7 = 0.30, w2=0.30×0.7=0.21w_2 = 0.30 \times 0.7 = 0.21, tail w3=0.72=0.49w_3 = 0.7^2 = 0.49, summing to 1.001.00. The blend: 0.30×4.70+0.21×6.95+0.49×6.14=1.4100+1.4595+3.0086=5.87810.30 \times 4.70 + 0.21 \times 6.95 + 0.49 \times 6.14 = 1.4100 + 1.4595 + 3.0086 = 5.8781. Against the endpoints, TD alone aims at 4.704.70 and Monte Carlo at 6.146.14; the 0.70.7 blend lands at 5.885.88 between them. The update moves V(S0)V(S_0) from 1.01.0 to 1.0+0.1×(5.8781−1.0)=1.48781.0 + 0.1 \times (5.8781 - 1.0) = 1.4878.

Code

The nn-step returns, the blended target, the full λ\lambda spectrum, and the offline update, using only the standard library.

from typing import Dict, List

def n_step(rewards: List[float], start: int, n: int, gamma: float, boot: float) -> float:    """n-step return from index start: n real rewards, then bootstrap value."""    total = 0.0    discount = 1.0    for k in range(start, min(start + n, len(rewards))):        total += discount * rewards[k]        discount *= gamma    if start + n < len(rewards):        total += discount * boot    return total

def lambda_return(rewards: List[float], start: int, gamma: float, lam: float, values: List[float]) -> float:    """Episodic forward lambda return at index start."""    remaining = len(rewards) - start    targets = [n_step(rewards, start, n, gamma, values[start + n]) for n in range(1, remaining + 1)]    blended = sum((1.0 - lam) * lam ** (n - 1) * targets[n - 1] for n in range(1, remaining))    return blended + lam ** (remaining - 1) * targets[-1]

rewards = [2.0, 1.0, 4.0]values = [1.0, 3.0, 5.0, 0.0]  # last entry is the terminal value V = 0.0gamma, lam, alpha = 0.9, 0.7, 0.1print(f"{n_step(rewards, 0, 1, gamma, values[1]):.4f}")  # -> 4.7000print(f"{n_step(rewards, 0, 2, gamma, values[2]):.4f}")  # -> 6.9500print(f"{n_step(rewards, 0, 3, gamma, 0.0):.4f}")  # -> 6.1400print(f"{lambda_return(rewards, 0, gamma, lam, values):.4f}")  # -> 5.8781for test in [0.0, 0.5, 0.7, 1.0]:    print(f"{test:.1f} {lambda_return(rewards, 0, gamma, test, values):.4f}")  # -> 0.0 4.7000, 0.5 5.6225, 0.7 5.8781, 1.0 6.1400print(f"{values[0] + alpha * (lambda_return(rewards, 0, gamma, lam, values) - values[0]):.4f}")  # -> 1.4878

Every figure matches the hand trace, including the 5.62255.6225 midpoint of the spectrum at λ=0.5\lambda = 0.5.

Watch Out For

Shipping the forward view as an online algorithm

The definition peeks at rewards that have not arrived yet: at step tt the target needs Rt+1R_{t+1} through RTR_T. Code that computes it directly cannot update until the episode ends, grows unbounded buffers on continuing tasks, and reintroduces Monte Carlo latency into what was supposed to be online learning.

The symptom is an agent that learns in end-of-episode jerks or an implementation that never finalizes a target on non-terminating streams. The fix is the division of labor the theory intends: keep the forward view as the specification, and run the backward view with eligibility traces for the causal updates.

Parking lambda at 1 on long noisy episodes

With λ=1\lambda = 1 the target is the full Monte Carlo return, so every bit of trajectory noise lands in the update. On episodes hundreds of steps long with stochastic transitions, targets swing so widely that learning stalls despite being unbiased.

The symptom is high-variance updates and a value curve that thrashes while short-horizon runs on the same task settle. The fix is an intermediate λ\lambda around 0.70.7 to 0.90.9: keep most of the low bias while the geometric tail damps the far noise. Reserve exactly 11 for short deterministic episodes where the variance has nowhere to hide.

The Quick Version

  • The λ\lambda-return blends every nn-step return with geometric weights (1−λ)λn−1(1 - \lambda)\lambda^{n-1} plus a tail weight λT−t−1\lambda^{T-t-1} on the full return, always summing to one.
  • At λ=0\lambda = 0 it is one-step TD and at λ=1\lambda = 1 it is Monte Carlo; between them it trades bootstrap bias against rollout variance.
  • In the worked episode the weights 0.300.30, 0.210.21, 0.490.49 blend 4.704.70, 6.956.95, 6.146.14 into 5.885.88, moving V(S0)V(S_0) from 1.001.00 to 1.491.49.
  • The forward view needs future rewards, so it cannot run online; it is the specification that eligibility traces implement causally.
  • On long noisy episodes prefer an intermediate lambda near 0.70.7 to 0.90.9 over exactly 11.