Semi-Gradient TD(0) in RL
Learn a value guess after every step by aiming at your own next guess, while refusing to chase the target as it moves.
Why Does This Exist?
Monte Carlo prediction waits for the episode to end, then learns from the full return that actually happened. That target is honest but slow: useless for continuing tasks that never end, and noisy enough that learning crawls.
Temporal difference learning refuses to wait. After a single transition it builds a bootstrapped target from its own next guess:
Here in discounts the future and is the shared weight vector. The catch is visible in the formula: the target contains the very weights being learned. This page covers the standard fix, the semi-gradient update, and one computed step. The action-value version is semi-gradient SARSA, the patient alternative is gradient Monte Carlo, and the mechanism underneath is temporal difference learning.
The one idea you need first
With linear features, the value guess is a dot product , and its gradient with respect to is the feature vector . That single identity turns the whole update into one vector push. Full grounding lives in value function approximation.
Think of It Like This
Two hikers shouting distance guesses down the trail
Two hikers walk a trail in fog. The one behind shouts, how far to the hut? The one ahead shouts back their own guess, plus the steps already walked. The behind hiker adjusts toward that answer without arguing about how the ahead hiker guessed.
Both guesses come from the same guidebook, so the target moves as the guidebook gets edited. That is bootstrapping: learning from your own moving estimate instead of waiting to reach the hut. It is fast, and it only works if you treat the ahead guess as fixed for this one adjustment.
Where the analogy stops: hikers can eventually reach the hut and check. In continuing tasks the agent never does, so the fixed point it settles at, not the truth, is the real object of study.
How It Actually Works
The squared error against the bootstrapped target is . A true gradient would differentiate through too, since holds . The semi-gradient method freezes the target instead, , and steps only along the current guess:
Here is the step size. For linear features this simplifies to with TD error . On-policy with linear features this converges to a fixed point whose error stays within of the best linear fit (Tsitsiklis and Van Roy, 1997). Off-policy it can diverge, which is the subject of the deadly triad.
Worked example
Two features, , , weights . Current features , next features , reward .
Current guess: . Next guess: .
Target: . Error: .
Update: . New current guess: , moved toward the target, exactly .
Code
The same step in plain Python:
w = [0.5, 1.0]x_now = [1.0, 2.0]x_next = [0.5, 1.0]reward, gamma, alpha = 2.0, 0.9, 0.1
v_now = sum(a * b for a, b in zip(w, x_now))v_next = sum(a * b for a, b in zip(w, x_next))target = reward + gamma * v_nextdelta = target - v_noww = [a + alpha * delta * b for a, b in zip(w, x_now)]
print(f"v_now {v_now:.4f}") # -> v_now 2.5000print(f"v_next {v_next:.4f}") # -> v_next 1.2500print(f"target {target:.4f}") # -> target 3.1250print(f"delta {delta:.4f}") # -> delta 0.6250print(f"w [{w[0]:.4f}, {w[1]:.4f}]") # -> w [0.5625, 1.1250]Watch Out For
Treating the semi-gradient as a true gradient
The update looks like gradient descent, so teams monitor a loss curve and expect it to fall monotonically. It will not. There is no fixed loss here because every step rewrites the target, so the curve wobbles even in healthy runs. The symptom is a debugging chase for a bug that does not exist.
Fix it by tracking the TD error magnitude and the weight norm instead of a loss, and by keeping data on-policy while you validate the setup.
Going off-policy without a correction diverges
Collect data under one policy while evaluating another, keep bootstrapping, keep the features, and weights can blow up instead of settling. The symptom is a weight norm that climbs round after round toward overflow. This is one leg of the deadly triad.
Fix it by staying on-policy for plain TD, or by switching to a gradient TD method built for the off-policy case.
The Quick Version
- Semi-gradient TD(0) learns after every step from , its own next guess, instead of waiting for the episode to end.
- It freezes the target during differentiation, stepping only along the current features with .
- In the worked step, reward 2.0 and next guess 1.25 give target 3.125, error 0.625, and weights [0.5625, 1.125].
- On-policy linear TD converges near the best fit, but the same update off-policy can diverge.
- It is not gradient descent on a fixed loss, so monitor TD error and weight norms, not a loss curve.