Part II · Deep RL · Chapter 6
Function approximation and DQN
When the table is too big: neural Q-functions, experience replay, target networks.
A pole is balanced on a cart. The state is four real numbers: cart position, cart velocity, pole angle, angular velocity. Your Q-learning agent from Chapter 5 keeps one number per state–action pair in a table. Which row does it look up for the state ? There is no such row, and there never will be: the agent will almost surely never see exactly that state again. Yet you know what to do there, because you have seen thousands of states like it.
That is the problem this chapter solves. Tables learn each entry separately; real problems need agents that generalize, so that experience in one state teaches them about similar states. You’ll replace the table with a function that has a fixed number of parameters: first a linear function of hand-made features, then a neural network. You’ll see why this, combined with bootstrapping and off-policy learning, can make value estimates blow up to infinity. Then you’ll meet the Deep Q-Network (DQN), the 2015 agent that learned to play 49 Atari games from raw pixels (a separate network for each game, but one recipe for all), and the small set of engineering fixes that made it work. By the end you’ll have built DQN yourself and trained it to balance CartPole.
Why tables fail
Tabular methods have two problems, one obvious and one deeper.
The obvious one is size. Chop each of CartPole’s four variables into 10 bins and you get cells, times 2 actions: fine. Chop them into 100 bins each and you need entries. An Atari frame is pixels with 128 possible colors; the number of distinct screens is beyond astronomical. This is the curse of dimensionality you met at the end of Chapter 4: the table grows exponentially with the number of state variables.
The deeper problem is no generalization. Even if memory were free, a table learns about a cell only when that exact cell is visited. In a continuous space you essentially never revisit a state, so every update is wasted on a cell you’ll never see again. A finer table makes this worse, not better: each cell gets less data.
The fix is to learn a function with far fewer parameters than there are states, chosen so that states that “look alike” get similar values. Then an update in one state moves the values of its neighbors too. That is a gift (you learn from far less data) and, as you’ll see, a curse (an update meant for one state can damage others).
Values from features
The linear value function
Describe each state by a feature vector : numbers that summarize what matters about the state. Then approximate the value as a weighted sum of features:
The weights are what we learn. We write for value-function weights, as Sutton & Barto do, and save for neural networks and policies. The key number is : you learn weights, no matter how many states there are.
Some feature choices, on a one-dimensional state :
- One-hot features (a table). If there are finitely many states, set if is the -th state and 0 otherwise. Then is just the table entry for state . Tables are the special case of linear function approximation with no sharing.
- Bins (state aggregation). Split into intervals; if falls in bin . Every state in a bin shares one value: generalization within a bin, none across bins.
- Hat features (piecewise-linear interpolation). Place nodes evenly on ; is a triangle that is 1 at node and falls linearly to 0 at the neighboring nodes. is then the straight-line interpolation between the node values .
- Radial basis functions (RBFs). Gaussian bumps centered at . The width sets how far one state’s experience spreads.
- Polynomials, Fourier features, and tile coding (several offset grids of bins, summed) are other classic choices. In a deep network, the last hidden layer is a learned feature vector, and the output layer is exactly this linear formula.
A worked example: watch the neighbors move
Take the simplest hat features on , just two of them: . So , a straight line with value at and at . Start from .
Suppose you observe, from state , a return of 1. You want closer to 1. Treat this as a regression problem: take a gradient step on the squared error with step size . The gradient of with respect to is just the feature vector, , so
Now read off values:
| 0 | 0.2 | 0.5 | 0.8 | 1 | |
|---|---|---|---|---|---|
| before | 0 | 0 | 0 | 0 | 0 |
| after | 0.10 | 0.16 | 0.25 | 0.34 | 0.40 |
You updated at one state, , and every state’s value moved, including , which you have never visited. With a table, only the cell containing 0.8 would have changed.
Two more things to notice. First, went from 0 to 0.34, not to 1. With step size , a single update at changes by times the error, and here . Second, which neighbors move, and by how much, is decided entirely by the features. Choosing features is choosing how your agent generalizes.
Quick check With one-hot (tabular) features, a gradient step at state s changes the estimated value of how many other states?
The gradient of w·φ(s) is φ(s), which is zero in every coordinate except s's own. Only w_s moves, so only V(s) changes. That is why tables can't generalize.
What should the weights minimize?
With weights and many more states, you generally cannot get every state’s value right. You have to decide which errors matter. The standard answer is the mean squared value error, weighted by how often each state is visited:
Here is the on-policy distribution: the fraction of time the policy spends in state . Spend your limited accuracy where the agent actually goes. Remember this ; it is the hero of the stability story later in this chapter.
If an oracle handed you for each visited state, you would do stochastic gradient descent:
There is no oracle. As in Chapter 5, you substitute a target that you can compute from experience:
- Monte Carlo: , the actual return. Since , this is an unbiased stand-in, and the update is honest stochastic gradient descent on . It converges (to a local optimum for nonlinear ), but it is slow and noisy and must wait for the episode to end.
- TD(0): . Lower variance, learns online, but the target is built from our own estimate. This is where things get interesting.
Semi-gradient TD: why “semi”
Plug the TD target into the update:
This is semi-gradient TD(0). For linear features the gradient is , so the update is just . With one-hot features it reduces exactly to the tabular TD(0) of Chapter 5.
Why “semi”? The target depends on too, but the update ignores that dependence. It differentiates only the prediction and treats the target as if it were a fixed label. If you took the true gradient of , you’d get an extra term:
Semi-gradient TD keeps the first half of the chain rule and drops the second half.
So why not use the full gradient? The full-gradient method exists (it is called the residual-gradient algorithm). It really is gradient descent, but on the squared TD error, which is the wrong objective when transitions are random. Minimizing also tries to make the randomness of the next state disappear, which pulls values toward wrong answers. In practice it also learns much more slowly. Exercise 6.3 works through this.
The price of “semi” is that the update is no longer the gradient of any objective. That has a consequence: we lose the guarantee that every step goes downhill. Hold onto this; it is the root of the deadly triad.
A worked TD step, and a moving target
Continue the example, with , and . The agent moves from to with reward 0.
- Prediction: .
- Target: .
- TD error: .
- Update: .
Now recompute the target with the new weights: , so the target is now . The target moved up because we moved toward it. Generalization tied and together, so raising one raised the other. In this example the chase settles down. You will soon see a case where it never does.
What linear TD converges to
With linear features, on-policy sampling, and a suitably decreasing step size, semi-gradient TD(0) converges with probability one (Tsitsiklis & Van Roy, 1997). It converges to the TD fixed point , the weights where the expected update is zero:
In words, the TD error is uncorrelated with every feature, so there is no direction in feature space that the updates still want to move. That is not the same as the best fit , but it is provably not far off:
When is near 1 the bound is loose. TD trades some asymptotic accuracy for much lower variance and faster learning, the same bias–variance trade you saw in Chapter 5.
∂ The TD fixed point is a Galerkin projection, and why on-policy sampling makes it stable
Use matrix notation for a finite MDP under policy : is the matrix whose rows are , the transition matrix, the expected-reward vector, and the state-weighting.
The expected update. Averaging the update over and the next state gives
with and , a matrix (Sutton & Barto’s notation; the bold face is there so you don’t confuse it with state A of the example below). The fixed point solves .
Galerkin. The fixed-point condition says the Bellman residual is orthogonal to every feature column under the inner product . That is exactly a Galerkin method: look for a solution in a subspace (the span of the features) and require the residual of the equation to be orthogonal to that subspace. Equivalently, , where is the Bellman operator and is the -weighted orthogonal projection onto the feature span. Apply a Bellman backup (which leaves the subspace), then project back.
Stability. The error of the expected dynamics obeys . This shrinks for small enough if every eigenvalue of has a positive real part. A sufficient condition is that is positive definite, meaning for every ( need not be symmetric; if for complex , then ). Write . Then . By Cauchy–Schwarz in the -inner product, .
Now the crucial step. If is the stationary distribution of (that is, ), then
using Jensen’s inequality and then stationarity. So does not stretch vectors in the -norm, and when the features are linearly independent (and every state has ). On-policy linear TD is stable. (In an episodic task, leaks probability to the terminal state and entrywise; both inequalities still hold.)
If is not the on-policy distribution, the inequality can fail: the states moves you to may carry little weight in , so their errors are rarely corrected. If stretches some by more than a factor in the -norm, can exceed , can have an eigenvalue with negative real part, and the expected update pushes away from the fixed point. That is the deadly triad in one line of linear algebra. The two-state example below has (a matrix), which is negative for .
The same argument shows is a -contraction in when is on-policy (projection is non-expansive, and contracts by ). This is the function-approximation version of the contraction argument from Chapter 4.
From V to Q: semi-gradient Q-learning
For control we need action values. Approximate (for example with features ) and plug in Q-learning’s target from Chapter 5:
That’s the whole algorithm. Everything else in this chapter is about making it work when is a neural network.
See it: generalization on a continuous corridor
The demo below is a continuous version of Chapter 5’s random walk. A robot sits on a rail at position . It makes no decisions, so this is prediction (estimating of a fixed behavior), as in Chapter 5. Each step it drifts right by 0.025 on average, with Gaussian noise of standard deviation 0.05; a wall at stops it from going further left. Reaching pays and ends the episode; every step that lands in the puddle costs ; . The dashed curve is the true , computed exactly. The solid curve is a linear that you can teach by hand (tap the plot) or train with semi-gradient TD(0) (Play). The faint bumps are the pieces ; the curve is their sum. The strip at the bottom shows the features themselves, with the ones touched by the latest update highlighted.
All three feature types here are normalized so that at every . One consequence: an update with step size closes at most a fraction of the gap at the updated state, which makes the TD slider easy to read.
Tap anywhere on the plot: one gradient step moves V at that position exactly onto your tap. Watch what else moves.
What to try: (1) With RBFs, tap high above x = 0.3. Watch V rise around 0.3, by less with distance. (2) Switch to Bins (a table) and tap again: only one bin moves. (3) Press Play with RBFs, σ = 0.06; the RMS error falls to about 0.05 within a few hundred episodes. Reset, set σ = 0.25, and play again: the curve is smooth, but it can't follow the sharp rise after the puddle. (4) Compare Bins with 12 features against Bins with 40: the coarse table learns faster at first, the fine one ends up more accurate.
The demo shows the central trade-off of function approximation. Coarse or wide features generalize aggressively: each weight pools data from a large region, so a rough answer appears quickly, but you can’t represent sharp changes, and an update in one place disturbs values far away. Fine or narrow features can represent much more detail, but each one learns only from the data that lands near it, and you drift back toward the slow, data-hungry behavior of a table. Neural networks partly escape this trade-off by learning features that are wide where the value is smooth and narrow where it changes fast.
Neural network Q-functions
Hand-designed features work for a 1D corridor. They do not work for raw pixels: nobody knows how to write down by hand. A neural network learns its own features. In a Q-network , the hidden layers compute a learned feature vector, and the last layer is a linear map from it to Q-values.
DQN made one architectural choice that you will reuse in the lab. The network takes the state as input and outputs one Q-value per action, a vector in . A single forward pass then gives and the greedy action, instead of one pass per action.
For Atari, Mnih et al. (2015) fed the network the last 4 frames, downsampled to grayscale and stacked. One frame does not show which way the ball is moving; four frames do. Stacking frames makes the state (closer to) Markov, the fix you met in Chapter 3. The network had three convolutional layers, a 512-unit fully connected layer, and one output per action (up to 18). For CartPole the state is already Markov and tiny, so an MLP with two hidden layers is plenty.
The obvious first attempt, often called naive deep Q-learning, is semi-gradient Q-learning with a network:
for each step t:
pick a_t epsilon-greedily from Q(s_t, ·; θ)
observe r_t, s_{t+1}
y = r_t + γ · max_a' Q(s_{t+1}, a'; θ) # 0 instead of the max if s_{t+1} is terminal
take one gradient step on (y − Q(s_t, a_t; θ))², with y held constant
It usually fails. In the lab’s ablation run (python run.py --compare), removing just the target network from DQN on CartPole makes the Q-values explode (the loss climbs past ), and the greedy agent drops the pole after about 10 steps. To understand why, we need a small example where everything can be computed by hand.
The deadly triad
Sutton & Barto name three ingredients that are each useful on their own but can cause divergence when all three are present. They call the combination the deadly triad:
- Function approximation: states share parameters, so an update to one state changes others.
- Bootstrapping: targets are built from current estimates () rather than actual returns.
- Off-policy learning: the distribution of updates differs from the distribution of states the target policy would actually visit.
Any two are fine. Tabular Q-learning is off-policy and bootstraps, and it converges. On-policy linear TD bootstraps and approximates, and the Derivation above shows it is stable. Monte Carlo with function approximation is plain regression. All three together can diverge, even with linear features, even with the exact expected updates, and even when a perfect solution exists.
The smallest example:
Here is the example from Sutton & Barto (section 11.2). There are two states, A and B. A leads to B with reward 0; B leads to the end with reward 0. The true values are . Use one shared weight with features and , so
represents the truth exactly. Start at .
Now the off-policy part. Under the policy you are evaluating, every visit to A is followed by a visit to B, so on-policy data would update B exactly as often as A. Suppose instead that the data you learn from contains the A → B transition over and over, and B is never updated. In Sutton & Barto’s version, A → B is a fragment of a larger MDP: the behavior policy keeps leaving B through actions the target policy would never take, and those samples get importance weight 0, so B’s update never happens. All you need here is the result: many updates at A, none at B, a distribution of updates the target policy would never produce. The semi-gradient TD update on A → B is
For the factor is bigger than 1, so grows geometrically. With and , each update multiplies by , and after 100 updates . The step size doesn’t matter: a smaller only slows the explosion.
Why? Each update tries to make equal to . The only way to raise is to raise , which raises twice as much, which raises the target. The target runs away faster than the prediction can chase it, and nothing ever pulls back down, because B is never updated.
Now remove one ingredient at a time:
- Make it on-policy. Every episode passes through A and B, so update on both transitions. The B update has target 0 (B leads to the end): . Per episode, is multiplied by , and it converges to 0. The update at B, which sees B’s value is too high, keeps things in check.
- Drop function approximation. Give A and B separate table entries. moves toward and stops there. That is wrong (B was never corrected), but finite.
- Drop bootstrapping. Use the Monte Carlo return as the target for A, which is 0. Then .
Run all four cases yourself:
What to try: (1) Press Run with all three ingredients on: a straight line upward on the log scale is exponential divergence. (2) Turn off Off-policy updates: the error plunges (the dashed line keeps the previous run for comparison). (3) Turn Off-policy back on, then turn off Function approximation instead: V(A) settles at 2γ = 1.8, which is wrong but finite. (4) With all three on, drag γ below 0.5 and press Run: now it converges. (5) Turn on Target network: does it save you?
Quick check In the w → 2w example with all three ingredients on and γ = 0.9, what does halving the step size α do?
Each update multiplies w by 1 + α(2γ − 1). For γ > 1/2 that factor exceeds 1 for every α > 0. A smaller α just makes the factor closer to 1, so it takes more updates to blow up.
How this shows up in deep Q-learning
Naive deep Q-learning has all three ingredients at full strength, plus some extra problems of its own:
- Off-policy by construction. The target uses (the greedy policy) while the data comes from ε-greedy behavior. On top of that, the states you update on are whatever you happened to visit recently, not a balanced sample.
- Correlated samples. Consecutive states in an episode are nearly identical. Training on them in order is like fitting a regression on data sorted by : the network fits the current region, forgets the rest (catastrophic forgetting), and the gradient steps are far from the independent samples that stochastic gradient descent assumes.
- A target that moves with every step. The target uses the same you are updating, and is usually very close to . A step that raises also raises , which raises the target: the mechanism, now in a million dimensions.
- The max is optimistic. over noisy estimates is biased upward, and bootstrapping copies that bias backward through the states. More on this under Double DQN.
- Unbounded error sizes. A single surprising transition can produce a huge error, a huge gradient, and a huge jump in .
DQN: four stabilizers
Mnih et al. (2013; 2015) kept semi-gradient Q-learning and added fixes aimed at exactly these problems. None of them comes with a convergence proof. Together they worked well enough to learn dozens of Atari games with one set of hyperparameters.
1. Experience replay
Store every transition in a replay buffer that holds the last transitions (DQN used ). For each gradient step, sample a minibatch uniformly at random from the buffer instead of using the latest transition. Experience replay goes back to Lin (1992). It helps in three ways:
- Decorrelation. A random minibatch mixes transitions from many episodes and many parts of the state space, much closer to the independent samples that stochastic gradient descent expects.
- Data reuse. Each transition is used in many updates, not once. Environment steps are often the expensive part, so this matters.
- Smoothing. The buffer holds data from many past policies, so the training distribution changes slowly even when the greedy policy flips suddenly.
Replay is possible only because Q-learning is off-policy: its target is valid no matter which policy produced the transition. SARSA’s target needs from the current policy, so stale transitions would give the wrong target. Notice the tension, though. Replay makes the data more off-policy, since it comes from older policies, which is one leg of the triad. It still helps on balance, because its decorrelating and smoothing effects dominate.
2. A target network
Keep a second copy of the network, with parameters , and compute targets with it:
Every steps, copy (DQN used updates). In between, the targets stay put. Each block of steps then becomes an ordinary supervised regression: fit to fixed labels computed from . Updates to no longer drag their own targets along within a block.
There is a nice way to see the whole loop. Each block approximately computes (one Bellman optimality backup, done by regression), and then the copy sets up the next block. That is value iteration from Chapter 4, with each sweep replaced by a regression on sampled data. Value iteration converges because is a contraction. DQN hopes that the regression errors stay small enough not to undo that.
A common variant is the soft or Polyak update, with small (like 0.005) every step. It’s popular in continuous-control methods. The lab implements both.
3. The Huber loss (error clipping)
DQN limits how hard any single transition can pull on . Write the error as . The Huber loss is quadratic for small errors and linear for large ones:
Its gradient is the squared-error gradient, clipped to . Mnih et al. describe this as clipping the error term to ; it is the same thing. Huge errors are common early in training and after rare events. With Huber they push with bounded force, and they still push in the right direction. In PyTorch it is F.smooth_l1_loss.
4. Reward clipping
Atari scores range from single digits to tens of thousands per game. DQN clipped every reward to (positive rewards become , negative ones ). This kept the scale of Q-values and gradients similar across games, so one learning rate worked for all 49 of them. The cost is real: the agent can no longer tell a small reward from a big one. Later work replaced clipping with smarter normalization. For CartPole, where every reward is already , it doesn’t matter.
The DQN algorithm
DQN with experience replay and a target network
1. initialize Q-network θ, target network θ⁻ ← θ, empty replay buffer D (capacity N)
2. for step t = 0, 1, 2, ...:
3. ε ← schedule(t) # e.g. 1.0 → 0.05 linearly
4. a_t ← random action with prob. ε, else argmax_a Q(s_t, a; θ)
5. take a_t, observe r_t, s_{t+1}, done_t; store (s_t, a_t, r_t, s_{t+1}, done_t) in D
6. sample a random minibatch of B transitions (s, a, r, s', done) from D
7. y ← r + γ (1 − done) max_a' Q(s', a'; θ⁻) # no gradient through y
8. loss ← mean Huber(Q(s, a; θ) − y)
9. take a gradient step on the loss with respect to θ
10. every C steps: θ⁻ ← θ
The Nature paper’s settings, for reference: buffer , minibatch , , , RMSProp with learning rate , ε annealed from 1.0 to 0.1 over the first million frames, one update every 4 actions, each action repeated for 4 frames. CartPole needs a much smaller scale: the lab uses a 50k buffer, , , Adam at , and 50,000 steps, which takes about a minute on a laptop.
A worked minibatch
Here is one loss computation by hand, with tiny Q-tables standing in for networks so you can see every number. Three states, two actions, . The online and target networks are
| state 0 | 0 | 2.2 | 0 | 0 |
| state 1 | 5 | 1 | 1 | 3 |
| state 2 | 0 | 4 | 2 | −1 |
and the minibatch is = , , .
- Targets from : ; ; (terminal, no bootstrap).
- Predictions : 2.2, 5, 4.
- Errors : , , .
- Huber: , then , then . Mean loss .
- Gradient with respect to the three entries used: . The two big errors push with the same capped force.
Exercise 6.7 has you redo this with Double DQN targets. The lab’s tests use exactly these numbers.
Quick check Which of these does the target network NOT do?
A frozen target removes the 'chase your own tail' effect within a block. But if the projected backup expands, as in the w → 2w example with γ > 1/2, the copies still blow up. It is a practical stabilizer, not a guarantee.
The Atari result
Mnih et al. (2015) evaluated DQN on 49 Atari 2600 games, training a separate agent on each game but with the same network architecture, hyperparameters and training procedure for all of them. The agent saw only the screen pixels and the score. The paper reports that DQN outperformed the best previous reinforcement learning methods on 43 of the games, and reached more than 75% of a professional human games tester’s score on 29 of them, which is more than half. It did best on reactive games like Breakout and Pong. It did essentially nothing on games that need long-range exploration, most famously Montezuma’s Revenge, where rewards are rare and ε-greedy dithering almost never finds them.
The paper’s ablations matter for this chapter. Removing experience replay hurt dramatically. Removing the target network also hurt, by less. The combination was best. The 2013 workshop version (Mnih et al., “Playing Atari with Deep Reinforcement Learning”) already had replay but not the separate target network.
Improvements in brief
DQN started a long line of refinements. Each one targets a specific weakness.
Double DQN: fixing optimism
Suppose that in state all actions are truly worth 0, but your estimates are noisy: each is or with equal probability, independently. The true is 0. The estimated max is unless both estimates come out at , which happens with probability . So . The max of noisy estimates is biased upward, and bootstrapping copies that bias into the values of the states before .
You met the fix in Chapter 5 as Double Q-learning: don’t use the same noisy numbers to choose the best action and to evaluate it. Double DQN (van Hasselt, Guez & Silver, 2016) gets a second estimator for free from the target network:
The online network picks the action and the target network scores it. If the two networks’ errors are not perfectly correlated, the action that happens to look best by luck under won’t get the same lucky score under . It’s a one-line change (Task 4 in the lab). It reduced overestimation and improved scores on many Atari games, and the lab uses it by default.
Dueling networks, prioritized replay, and more
- Dueling architecture (Wang et al., 2016). Split the head into a state value and per-action advantages , and recombine them as . Many states don’t depend much on the action, so the shared learns from every update.
- Prioritized experience replay (Schaul et al., 2016). Sample transitions in proportion to (the paper writes α for this exponent), so the surprising ones get replayed more. Importance-sampling weights correct the bias that this non-uniform sampling introduces.
- Multi-step targets. Use , the n-step returns of Chapter 5. They bootstrap less, so information propagates faster.
- Distributional RL (C51, Bellemare et al., 2017). Learn the whole distribution of returns rather than only its mean.
- Noisy networks (Fortunato et al., 2018). Learnable noise in the weights replaces ε-greedy for exploration.
- Rainbow (Hessel et al., 2018). Combines all six (Double, prioritized, dueling, multi-step, distributional, noisy) and reports results far above any one of them alone on Atari. Its ablations found prioritized replay and multi-step returns to be among the most important pieces.
Where value-based methods run out
DQN-style methods are still a strong choice for discrete-action problems where data is expensive (replay reuses every sample many times). But they have limits that motivate the rest of Part II:
- Discrete actions only. Acting needs . With 18 actions that’s 18 numbers. With a continuous action (a torque) it’s an optimization problem at every step. Actor-critic methods such as DDPG and SAC learn a separate network to do that maximization; Chapter 10 describes SAC.
- Deterministic greedy policies. The greedy policy picks one action. In games and partially observed problems, the best policy is sometimes random (think rock–paper–scissors), and ε-greedy is a crude way to be random.
- No guarantees. As you’ve seen, the deadly triad is present by design. Training curves are often jumpy, and runs with different seeds can differ a lot. Your lab run will show this.
- Indirect. You learn values and derive the policy from them. A small change in Q can flip the argmax and change behavior abruptly.
The alternative is to parameterize the policy itself and push it uphill on expected return. That is the subject of Chapter 7, and it is the family that LLM training is built on.
Quick check Why can DQN learn from a replay buffer full of transitions generated by old policies, while SARSA cannot do so directly?
SARSA's target r + γQ(s′, a′) needs a′ sampled from the current policy. An old transition's a′ came from an old policy. Q-learning's target r + γ max Q(s′, ·) only needs (s, a, r, s′), so it is valid whatever policy collected the transition.
Summary
- Tables don’t scale and don’t generalize. Replace them with a parametric or so that experience in one state updates similar states. Tables are the special case of linear features that are one-hot.
- Linear function approximation: , gradient . The features decide how updates spread: wide features learn fast but blur detail; narrow features are precise but data-hungry.
- Semi-gradient TD updates , treating the bootstrapped target as a constant label. It’s not the gradient of any objective, but on-policy linear TD provably converges to the TD fixed point (a Galerkin projection of the Bellman equation).
- The deadly triad is function approximation + bootstrapping + off-policy updates. Any two are fine; all three can diverge. The example blows up geometrically, by a factor of per update.
- DQN = semi-gradient Q-learning with a neural network plus stabilizers: experience replay (decorrelate, reuse data), a target network (fixed regression targets for C steps, like approximate value iteration), the Huber loss (bounded gradients), and reward clipping (one scale across games).
- Double DQN removes most of the upward bias of by choosing the action with the online network and scoring it with the target network. Dueling heads, prioritized replay, multi-step returns, distributional RL and noisy nets combine into Rainbow.
- Value-based methods need a max over actions and give deterministic policies, so they don’t suit continuous actions or LLMs. That motivates policy gradients.
Exercises
(a) Show that semi-gradient TD(0) with one-hot features (the -th unit vector) is exactly the tabular TD(0) update from Chapter 5.
(b) Now use state aggregation: states are grouped, and , where is the group of . Write out the update. Which values change after an update at ?
Solution
(a) With , , so is the value table. The gradient is . The update changes only coordinate :
which is tabular TD(0) with .
(b) , and the update is . One weight changes, and with it the value of every state in the same group as . Nothing outside the group changes. This is the “Bins” setting in the corridor demo: generalization within a bin, none across bins.
Continue the hat-feature example. After the first update you had with . Now you observe a return of 0 from state . Take one gradient step with toward that target.
(a) What are the new weights?
(b) What is now? It was 0.34. You never touched in this step. Explain what happened in one sentence.
Solution
(a) . Prediction: . Error: . Update:
(b) . It fell from 0.34 to about 0.32, because the two states share weights: lowering the value at also lowered the line everywhere, a little. This is interference, the flip side of generalization. Here it’s mild and sensible. When features are poorly matched to the problem, an update in one region can undo learning in another. Catastrophic forgetting in neural networks is the extreme case.
Consider one state with a random next state and reward . Define the TD error .
(a) Compute (the full gradient) and compare it with the semi-gradient update direction .
(b) Show that , where the expectation and variance are over given .
(c) Use (b) to explain why minimizing the sampled squared TD error (the residual-gradient method) can converge to the wrong values when transitions are stochastic, while the expected semi-gradient update is zero exactly when .
Hint
Solution
(a) . Therefore
The semi-gradient keeps only . The dropped term, , would move the next state’s value to shrink the error, that is, change the target to agree with the prediction.
(b) Let , a random variable given , and , a constant. Then and
(c) The quantity we care about is : it is zero exactly when satisfies the Bellman equation . The residual-gradient method minimizes , which adds the variance term (plus terms involving ). That term depends on through the next-state values, so the minimizer trades Bellman accuracy for making flatter across possible next states. With deterministic transitions the variance term is constant and both agree; with stochastic transitions they don’t. Fixing this requires two independent samples of from the same (the “double sampling” problem), which you usually can’t get from a single stream of experience.
The expected semi-gradient update is , because the gradient doesn’t depend on . It vanishes exactly when , the Bellman condition. In the linear case, the sum of these conditions over states, weighted by , gives the TD fixed point .
Use the two-state example: , , A → B → end, all rewards 0.
(a) Off-policy (update only on A → B): show that . For which does diverge, for any ?
(b) On-policy: each sweep updates on A → B and then on B → end. Find the per-sweep factor and show that it converges for and every .
(c) Compute the Derivation’s matrix (here a matrix, a number) in both cases. For a deterministic transition it is , where counts how often is updated per sweep: , off-policy and on-policy. Use . Connect the sign of to (a) and (b).
Hint
Solution
(a) The target for A is , the prediction is , and the gradient is . So . The factor exceeds 1 exactly when , so diverges for every when (and every ). At it stays put; below, it converges.
(b) The B update: target 0 (terminal), prediction , gradient 2, so . The per-sweep factor is . For : , which ranges from (at ) to (at ). Always in , so .
(c) Off-policy, only the A term: . On-policy, add the B term , so . The expected dynamics are . Positive means the update pulls toward 0 (for small ); negative means it pushes away. Off-policy, exactly when : indeed , the factor from (a). On-policy, for every , matching (b). (The sweep in (b) applies the two updates one after the other, so its factor differs from by a term of order ; the sign story is the same.) This is the scalar version of the Derivation’s statement that on-policy weighting makes positive definite.
Add a target network to the off-policy two-state example: within a block, the target for A is with frozen, and after updates you set .
(a) Show that after updates in a block starting from ,
(b) Deduce the per-block growth factor, and show that the iteration over blocks diverges when for any . What happens as ?
(c) Interpret: what problem does the target network solve, and what problem does it not solve?
Hint
Solution
(a) Within a block, . The fixed point is , and . So after steps from ,
(b) Dividing by , the per-block factor is , a weighted average of and with positive weights (for ). If , then and : geometric growth from block to block. As , and . Each block fits its regression exactly, and you’re running value iteration with an expanding projected backup: .
(c) The target network removes the moving-target problem: within a block, the regression has fixed labels and converges (the inner recursion contracts at rate ). It doesn’t fix the projected Bellman backup being an expansion under an off-policy weighting, here . Only changing the distribution of updates (on-policy), the features, or the use of bootstrapping removes that. You can watch the stepwise growth in the triad demo with the target network on.
(a) In CartPole, each step advances time by s. Why are consecutive states and highly correlated? What goes wrong if each gradient step uses only the latest transition?
(b) Describe what DQN becomes with a replay buffer of capacity 1, and with a buffer so large that it never discards anything over a long run. Name one problem of each extreme.
(c) Replay makes the data more off-policy. Why does DQN tolerate this, and why would you need importance weights to do the same thing with SARSA or with policy-gradient methods?
Hint
Solution
(a) The state changes by roughly times its rates of change per step, so . Fifty consecutive transitions all describe nearly the same situation. Gradient steps on them in sequence fit to that one region and overwrite what the network knew about other regions (catastrophic forgetting). The effective step is also many correlated steps in the same direction, which acts like a much larger learning rate and destabilizes training.
(b) Capacity 1: each update uses the latest transition, so you get naive online deep Q-learning: maximally correlated, no reuse, and the training distribution jumps around whenever the policy changes. Infinite capacity: after a long run, most of the buffer comes from early, poor policies (for example, the random-action phase). Uniform sampling then spends most updates on states the current policy never visits and dilutes the recent, relevant data. Memory also becomes a problem. In practice is chosen to hold many episodes but to turn over a few times during training. In the lab, --no-replay (buffer = 128) still learns somewhat, but more erratically.
(c) Q-learning’s target evaluates the greedy policy using only , so it’s a valid sample of the Bellman optimality backup for whichever policy chose . The behavior policy affects only which pairs get updated, the weighting , not the correctness of each target. That’s exactly the off-policy leg of the triad, which is why it’s not entirely free. SARSA’s target uses drawn from the policy being evaluated; an old came from an old policy, so the target estimates the wrong policy’s value unless you reweight by . Policy-gradient estimators are expectations under the current policy, so they need the same kind of ratio (Chapter 9).
Use the Q-tables and minibatch from the worked minibatch section (). Recompute the targets, errors, Huber losses and mean loss with Double DQN targets: choose with the online table, then evaluate with the target table. Compare with the standard DQN loss of .
Solution
- Transition 1: . The online row is , so ; the target table gives . .
- Transition 2: . The online row is , so ; the target gives . .
- Transition 3: terminal, .
Predictions are unchanged: . Errors: , , . Huber: , , . Mean: .
The Double targets are lower (1.5 vs 2.5, and −0.5 vs 1). The standard max picked the target network’s largest entry in each row; Double DQN asks the target network about the action the online network likes, which here is not the target network’s favorite. When the two disagree, this removes the upward bias of the max. The lab’s test_task4_loss_value checks both numbers.
All actions in state have true value 0. Your estimates are independent, each or with probability .
(a) Compute for general . What is it for and (the full Atari action set)?
(b) Now suppose you have a second, independent set of estimates with the same distribution. Pick (break ties uniformly) and report . Show this is unbiased.
(c) The bias in (a) enters the target . Explain in two sentences why bootstrapping makes the problem worse than a single biased target.
Hint
Solution
(a) only if all estimates are , which has probability ; otherwise it is . So
For : . For : . With many actions, the max is essentially always the most optimistic error.
(b) is a function of only, and is independent of . So , since each has mean 0 whatever index is chosen. Selection and evaluation use different noise, so luck in one doesn’t carry into the other. Double DQN approximates this with and , which are not fully independent, so it reduces the bias rather than eliminating it.
(c) The overestimated target at raises ; when itself is a next state for an earlier state, that inflated value is again maxed over (picking up fresh optimistic noise) and copied backward. Bias accumulates along chains of states, discounted by at each step, and with function approximation the inflated values also leak into neighboring states.
Corridor demo.
(a) Choose Hats with 2 features (a straight line). Tap a target of 1.0 at . Which states moved the most, and which the least? Now press Play. Why does the learned line never match the true curve, however long you train?
(b) Train Bins with 12 features, then Bins with 40 features (Reset between runs; α = 0.2). Which has lower RMS error after about 100 episodes, and which after about 500? Explain the crossover in terms of generalization and resolution. Then try RBFs with σ = 0.06 and σ = 0.25: why does the wide one plateau at a higher error?
(c) Set α to 1.0 and press Play. Then set α to 0.05. Describe the curve’s behavior in each case and connect it to the “constant step size” discussion of Chapter 2.
Triad demo.
(d) With all three ingredients on and α = 0.1, find the smallest γ (to two decimals) for which the run diverges. Does your answer depend on α? Check against Exercise 6.4.
(e) Turn the target network on with γ = 0.9. Read off roughly how much grows per 25-sweep block and compare with Exercise 6.5.
Hint
Solution
(a) With , a tap at changes in direction . The right end moves most, the left end least, and every point moves by an amount that varies linearly with . The true value is strongly curved (flat and negative, a dip at the puddle, then a sharp rise), and a straight line can’t represent it. TD converges to the TD fixed point within the family of lines; the remaining error is approximation error, not lack of training.
(b) With the demo’s seed, 12 bins are ahead early (RMS about 0.10 vs 0.22 after 100 episodes): each bin covers a wide stretch, so it collects data quickly and every update teaches a whole region. By about 300 episodes the 40-bin table has caught up and then stays ahead (about 0.06 vs 0.08 at 500 episodes): its bins are narrow enough to follow the sharp rise after the puddle, which a 12-step staircase can’t. Coarse features have low variance and high approximation error; fine features the reverse. Which is better depends on how much data you have.
The wide RBF (σ = 0.25) blurs the value over a width comparable to the puddle, so it can’t represent the sharp rise no matter how long you train; it plateaus near RMS 0.1 while σ = 0.06 gets to about 0.05. Exact numbers wobble because of the constant step size.
(c) With α = 1 each TD update closes a large part of the gap toward a single noisy target, so the curve jumps around and never settles (with the demo’s seed the RMS error keeps fluctuating, anywhere from about 0.04 to 0.4). With α = 0.05 the curve moves slowly and smoothly: it’s far off after 50 episodes (about 0.3) but reaches about 0.03 by 1,000. A constant α makes the estimate an exponentially weighted average of recent targets with effective memory about updates: large α means fast tracking but noisy, small α means slow but accurate. That’s the same trade-off as in nonstationary bandits.
(d) γ = 0.51. The threshold is exactly γ = 1/2: at 0.50, stays at 1 (the factor is exactly 1); at 0.51 it grows slowly, by a factor per sweep. It doesn’t depend on α, because the factor exceeds 1 exactly when . α only changes the speed.
(e) Within each block approaches ; with α = 0.1 and C = 25, , so the per-block factor is . You see a staircase rising about 1.74× per block, which is slower than without a target network ( per 25 sweeps) but still divergent.
In exercises/06-dqn/dqn.py, implement the five tasks in order, running python -m pytest -q -k taskN after each:
linear_td0_update: semi-gradient TD(0) for linear features. The tests include the worked examples from this chapter and the divergence.ReplayBuffer.pushandReplayBuffer.sample: a circular buffer.QNetwork,epsilon_by_step,select_action: the acting side.compute_td_targets(standard and Double DQN) anddqn_loss(Huber). The tests use the worked minibatch from this chapter and check that no gradient flows through the target.update_target(hard and soft) andtrain_step.
Then run python run.py. In about a minute it trains on CartPole and saves dqn_cartpole.png. Finally, break it on purpose: python run.py --compare trains full DQN, DQN without a target network, and DQN without replay, all from the same seed. Describe what you see in each panel, in particular the loss curve of the run without a target network.
Hint
(1 - done) in the target, and computing the target with gradients enabled (wrap it in torch.no_grad()). For dqn_loss, pick out with q_all.gather(1, actions.unsqueeze(1)).squeeze(1).Solution
The reference implementation is in exercises/06-dqn/solutions/dqn.py. The key lines:
- Task 1:
delta = r + gamma * (0 if done else w @ phi_next) - w @ phi, thenreturn w + alpha * delta * phi. Gradient of is , and the target is a plain number. - Task 2: write into slot
idx, thenidx = (idx + 1) % capacityandsize = min(size + 1, capacity). Sample indices withrng.integers(0, size, batch_size), never pastsize. - Task 4: inside
torch.no_grad(),next_v = target_net(next_obs).max(1).values(or, for Double DQN,argmaxfrom the online net andgatherfrom the target net), theny = r + gamma * (1 - done) * next_v. The loss isF.smooth_l1_loss(q_sa, y). Theno_gradis the code version of “semi-gradient”: the test that uses the same network as online and target checks that the gradient matches exactly. - Task 5:
p_t.mul_(1 - tau).add_(tau * p)underno_grad;train_stepis sample, loss,zero_grad,backward,clip_grad_norm_,step.
What you should see. Full DQN on seed 0 reaches a final greedy return around 430 out of 500. The evaluation curve is jumpy: drops from near 500 to about 150 and back are normal for DQN. Note that the loss rises early in training and then stays roughly flat. The DQN loss is not a progress meter: as Q-values grow toward their true scale (about 100 for ) and the targets move, the regression errors grow too. Without the target network, the Q-values diverge: the loss climbs to around and the agent never balances the pole for more than about 10 steps. That’s the deadly triad in a real network. Without replay (buffer of 128), the agent learns something, but less reliably: on seed 0 it ends around 210 instead of 430. Your numbers will differ with other seeds; the ranking is what matters.
Files: README.md, dqn.py, train.py, test_dqn.py, run.py, cartpole.py
cd 06-dqn
python -m pytest -q # your code: tests fail until you fill in the TODOs
RL_SOLUTIONS=1 python -m pytest -q # reference solutions: all passFirst time? See Exercises & setup.
Further reading
- Sutton & Barto (2018), Reinforcement Learning: An Introduction, ch. 9–11. Chapter 9 covers linear function approximation and semi-gradient TD (including the TD fixed-point bound); chapter 11 covers the deadly triad, the example and Baird’s counterexample. The best next step after this chapter.
- Mnih et al. (2015), Human-level control through deep reinforcement learning. The Nature DQN paper. Read the Methods section for every detail, and the extended data tables for the hyperparameters and the replay/target-network ablation.
- Mnih et al. (2013), Playing Atari with Deep Reinforcement Learning. The short workshop paper that started it: seven games, replay, no separate target network yet.
- van Hasselt, Guez & Silver (2016), Deep Reinforcement Learning with Double Q-learning. Clear explanation of overestimation and the one-line fix.
- Hessel et al. (2018), Rainbow: Combining Improvements in Deep Reinforcement Learning. A tour of six DQN extensions with an ablation of each; also a good reading list.
- van Hasselt et al. (2018), Deep Reinforcement Learning and the Deadly Triad. An empirical study of when deep Q-learning actually diverges, and which ingredients matter.
- Tsitsiklis & Van Roy (1997), An analysis of temporal-difference learning with function approximation. The convergence proof for on-policy linear TD, with examples of off-policy and nonlinear divergence. For when you want the full mathematics.
- Wang et al. (2016), Dueling Network Architectures for Deep Reinforcement Learning, and Schaul et al. (2016), Prioritized Experience Replay. Two of Rainbow’s ingredients, each short and readable.
Next, you’ll stop learning values and start optimizing the policy directly: Chapter 7, Policy gradients and REINFORCE.