Part II · Deep RL · Chapter 6

Function approximation and DQN

When the table is too big: neural Q-functions, experience replay, target networks.

~100 minBefore this: Dynamic programming, MC & TD learning

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 (0.0213,−0.1870,0.0441,0.3302)(0.0213, -0.1870, 0.0441, 0.3302)? 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 10410^4 cells, times 2 actions: fine. Chop them into 100 bins each and you need 2×1082 \times 10^8 entries. An Atari frame is 210×160210 \times 160 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 ss by a feature vector ϕ(s)∈Rd\phi(s) \in \mathbb{R}^d: dd numbers that summarize what matters about the state. Then approximate the value as a weighted sum of features:

V^(s;w)=w⊤ϕ(s)=∑i=1dwi ϕi(s).\hat V(s; w) = w^\top \phi(s) = \sum_{i=1}^{d} w_i \, \phi_i(s).

The weights w∈Rdw \in \mathbb{R}^d are what we learn. We write ww for value-function weights, as Sutton & Barto do, and save θ\theta for neural networks and policies. The key number is dd: you learn dd weights, no matter how many states there are.

Some feature choices, on a one-dimensional state x∈[0,1]x \in [0, 1]:

  • One-hot features (a table). If there are finitely many states, set ϕi(s)=1\phi_i(s) = 1 if ss is the ii-th state and 0 otherwise. Then wiw_i is just the table entry for state ii. Tables are the special case of linear function approximation with no sharing.
  • Bins (state aggregation). Split [0,1][0, 1] into dd intervals; ϕi(x)=1\phi_i(x) = 1 if xx falls in bin ii. Every state in a bin shares one value: generalization within a bin, none across bins.
  • Hat features (piecewise-linear interpolation). Place dd nodes evenly on [0,1][0,1]; ϕi\phi_i is a triangle that is 1 at node ii and falls linearly to 0 at the neighboring nodes. V^\hat V is then the straight-line interpolation between the node values wiw_i.
  • Radial basis functions (RBFs). Gaussian bumps ϕi(x)∝exp⁡ ⁣(−(x−ci)2/2σ2)\phi_i(x) \propto \exp\!\big(-(x - c_i)^2 / 2\sigma^2\big) centered at cic_i. The width σ\sigma 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 [0,1][0, 1], just two of them: ϕ(x)=(1−x,  x)\phi(x) = (1 - x,\; x). So V^(x;w)=w1(1−x)+w2x\hat V(x; w) = w_1 (1 - x) + w_2 x, a straight line with value w1w_1 at x=0x = 0 and w2w_2 at x=1x = 1. Start from w=(0,0)w = (0, 0).

Suppose you observe, from state x=0.8x = 0.8, a return of 1. You want V^(0.8)\hat V(0.8) closer to 1. Treat this as a regression problem: take a gradient step on the squared error 12(1−V^(0.8;w))2\tfrac12 \big(1 - \hat V(0.8; w)\big)^2 with step size α=0.5\alpha = 0.5. The gradient of V^\hat V with respect to ww is just the feature vector, ∇wV^(x;w)=ϕ(x)\nabla_w \hat V(x; w) = \phi(x), so

w←w+α(1−V^(0.8;w)) ϕ(0.8)=(0,0)+0.5×1×(0.2, 0.8)=(0.1, 0.4).w \leftarrow w + \alpha \big(1 - \hat V(0.8; w)\big)\, \phi(0.8) = (0, 0) + 0.5 \times 1 \times (0.2,\, 0.8) = (0.1,\, 0.4).

Now read off values:

xx00.20.50.81
V^(x;w)\hat V(x; w) before00000
V^(x;w)\hat V(x; w) after0.100.160.250.340.40

You updated at one state, x=0.8x = 0.8, and every state’s value moved, including x=0.2x = 0.2, which you have never visited. With a table, only the cell containing 0.8 would have changed.

Two more things to notice. First, V^(0.8)\hat V(0.8) went from 0 to 0.34, not to 1. With step size α\alpha, a single update at xx changes V^(x)\hat V(x) by α∥ϕ(x)∥2\alpha \lVert \phi(x) \rVert^2 times the error, and here ∥ϕ(0.8)∥2=0.04+0.64=0.68\lVert \phi(0.8) \rVert^2 = 0.04 + 0.64 = 0.68. 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?

What should the weights minimize?

With dd 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:

VE‾(w)=∑sμ(s)[Vπ(s)−V^(s;w)]2.\overline{\text{VE}}(w) = \sum_{s} \mu(s) \big[ V^\pi(s) - \hat V(s; w) \big]^2 .

Here μ(s)\mu(s) is the on-policy distribution: the fraction of time the policy π\pi spends in state ss. Spend your limited accuracy where the agent actually goes. Remember this μ\mu; it is the hero of the stability story later in this chapter.

If an oracle handed you Vπ(st)V^\pi(s_t) for each visited state, you would do stochastic gradient descent:

w←w+α[Vπ(st)−V^(st;w)]∇wV^(st;w).w \leftarrow w + \alpha \big[ V^\pi(s_t) - \hat V(s_t; w) \big] \nabla_w \hat V(s_t; w).

There is no oracle. As in Chapter 5, you substitute a target UtU_t that you can compute from experience:

  • Monte Carlo: Ut=GtU_t = G_t, the actual return. Since E[Gt∣st]=Vπ(st)\mathbb{E}[G_t \mid s_t] = V^\pi(s_t), this is an unbiased stand-in, and the update is honest stochastic gradient descent on VE‾\overline{\text{VE}}. It converges (to a local optimum for nonlinear V^\hat V), but it is slow and noisy and must wait for the episode to end.
  • TD(0): Ut=rt+γV^(st+1;w)U_t = r_t + \gamma \hat V(s_{t+1}; w). 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:

w←w+α δt ∇wV^(st;w),δt=rt+γV^(st+1;w)−V^(st;w).w \leftarrow w + \alpha\, \delta_t \, \nabla_w \hat V(s_t; w), \qquad \delta_t = r_t + \gamma \hat V(s_{t+1}; w) - \hat V(s_t; w).

This is semi-gradient TD(0). For linear features the gradient is ϕ(st)\phi(s_t), so the update is just w←w+αδtϕ(st)w \leftarrow w + \alpha \delta_t \phi(s_t). With one-hot features it reduces exactly to the tabular TD(0) of Chapter 5.

Why “semi”? The target rt+γV^(st+1;w)r_t + \gamma \hat V(s_{t+1}; w) depends on ww too, but the update ignores that dependence. It differentiates only the prediction V^(st;w)\hat V(s_t; w) and treats the target as if it were a fixed label. If you took the true gradient of 12δt2\tfrac12 \delta_t^2, you’d get an extra term:

−∇w12δt2=δt[∇wV^(st;w)−γ∇wV^(st+1;w)].-\nabla_w \tfrac12 \delta_t^2 = \delta_t \big[ \nabla_w \hat V(s_t; w) - \gamma \nabla_w \hat V(s_{t+1}; w) \big].

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 E[δt2]\mathbb{E}[\delta_t^2] 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 w=(0.1,0.4)w = (0.1, 0.4), γ=0.9\gamma = 0.9 and α=0.5\alpha = 0.5. The agent moves from x=0.5x = 0.5 to x′=0.8x' = 0.8 with reward 0.

  • Prediction: V^(0.5)=0.5×0.1+0.5×0.4=0.25\hat V(0.5) = 0.5 \times 0.1 + 0.5 \times 0.4 = 0.25.
  • Target: r+γV^(0.8)=0+0.9×0.34=0.306r + \gamma \hat V(0.8) = 0 + 0.9 \times 0.34 = 0.306.
  • TD error: δ=0.306−0.25=0.056\delta = 0.306 - 0.25 = 0.056.
  • Update: w←(0.1,0.4)+0.5×0.056×(0.5,0.5)=(0.114, 0.414)w \leftarrow (0.1, 0.4) + 0.5 \times 0.056 \times (0.5, 0.5) = (0.114,\, 0.414).

Now recompute the target with the new weights: V^(0.8)=0.114×0.2+0.414×0.8=0.354\hat V(0.8) = 0.114 \times 0.2 + 0.414 \times 0.8 = 0.354, so the target is now 0.9×0.354=0.3190.9 \times 0.354 = 0.319. The target moved up because we moved toward it. Generalization tied x=0.5x = 0.5 and x=0.8x = 0.8 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 wTDw_\text{TD}, the weights where the expected update is zero:

Es∼μ[δ ϕ(s)]=0.\mathbb{E}_{s \sim \mu}\big[ \delta \, \phi(s) \big] = 0 .

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 min⁡wVE‾(w)\min_w \overline{\text{VE}}(w), but it is provably not far off:

VE‾(wTD)  ≤  11−γ min⁡wVE‾(w).\overline{\text{VE}}(w_\text{TD}) \;\le\; \frac{1}{1-\gamma}\, \min_w \overline{\text{VE}}(w).

When γ\gamma 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 π\pi: Φ\Phi is the ∣S∣×d|\mathcal S| \times d matrix whose rows are ϕ(s)⊤\phi(s)^\top, PπP_\pi the transition matrix, rπr_\pi the expected-reward vector, and D=diag(μ)D = \text{diag}(\mu) the state-weighting.

The expected update. Averaging the update α δ ϕ(s)\alpha\,\delta\,\phi(s) over s∼μs \sim \mu and the next state gives

E[Δw]=α Φ⊤D(rπ+γPπΦw−Φw)=α (b−Aw),\mathbb{E}[\Delta w] = \alpha\, \Phi^\top D \big( r_\pi + \gamma P_\pi \Phi w - \Phi w \big) = \alpha\,(b - A w),

with b=Φ⊤Drπb = \Phi^\top D r_\pi and A=Φ⊤D(I−γPπ)Φ\mathbf{A} = \Phi^\top D (I - \gamma P_\pi) \Phi, a d×dd \times d 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 AwTD=b\mathbf{A} w_\text{TD} = b.

Galerkin. The fixed-point condition says the Bellman residual rπ+γPπΦw−Φwr_\pi + \gamma P_\pi \Phi w - \Phi w is orthogonal to every feature column under the inner product ⟨u,v⟩D=u⊤Dv\langle u, v \rangle_D = u^\top D v. 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, ΦwTD=Π Tπ(ΦwTD)\Phi w_\text{TD} = \Pi\, T^\pi(\Phi w_\text{TD}), where Tπv=rπ+γPπvT^\pi v = r_\pi + \gamma P_\pi v is the Bellman operator and Π\Pi is the DD-weighted orthogonal projection onto the feature span. Apply a Bellman backup (which leaves the subspace), then project back.

Stability. The error e=w−wTDe = w - w_\text{TD} of the expected dynamics w←w+α(b−Aw)w \leftarrow w + \alpha (b - \mathbf{A}w) obeys e←(I−αA) ee \leftarrow (I - \alpha \mathbf{A})\, e. This shrinks for small enough α\alpha if every eigenvalue of A\mathbf{A} has a positive real part. A sufficient condition is that A\mathbf{A} is positive definite, meaning u⊤Au>0u^\top \mathbf{A} u > 0 for every u≠0u \neq 0 (A\mathbf{A} need not be symmetric; if Au=λu\mathbf{A}u = \lambda u for complex uu, then Re λ ∥u∥2=Re(uˉ⊤Au)>0\text{Re}\,\lambda \, \lVert u \rVert^2 = \text{Re}(\bar u^\top \mathbf{A} u) > 0). Write v=Φuv = \Phi u. Then u⊤Au=v⊤Dv−γ v⊤DPπvu^\top \mathbf{A} u = v^\top D v - \gamma\, v^\top D P_\pi v. By Cauchy–Schwarz in the DD-inner product, v⊤DPπv≤∥v∥D∥Pπv∥Dv^\top D P_\pi v \le \lVert v \rVert_D \lVert P_\pi v \rVert_D.

Now the crucial step. If μ\mu is the stationary distribution of PπP_\pi (that is, μ⊤Pπ=μ⊤\mu^\top P_\pi = \mu^\top), then

∥Pπv∥D2=∑sμ(s)(∑s′Pπ(s′∣s)v(s′))2≤∑sμ(s)∑s′Pπ(s′∣s)v(s′)2=∑s′μ(s′)v(s′)2=∥v∥D2,\lVert P_\pi v \rVert_D^2 = \sum_s \mu(s) \Big( \sum_{s'} P_\pi(s'|s) v(s') \Big)^2 \le \sum_s \mu(s) \sum_{s'} P_\pi(s'|s) v(s')^2 = \sum_{s'} \mu(s') v(s')^2 = \lVert v \rVert_D^2,

using Jensen’s inequality and then stationarity. So PπP_\pi does not stretch vectors in the DD-norm, and u⊤Au≥(1−γ)∥v∥D2>0u^\top \mathbf{A} u \ge (1 - \gamma) \lVert v \rVert_D^2 > 0 when the features are linearly independent (and every state has μ(s)>0\mu(s) > 0). On-policy linear TD is stable. (In an episodic task, PπP_\pi leaks probability to the terminal state and μ⊤Pπ≤μ⊤\mu^\top P_\pi \le \mu^\top entrywise; both inequalities still hold.)

If μ\mu is not the on-policy distribution, the inequality ∥Pπv∥D≤∥v∥D\lVert P_\pi v \rVert_D \le \lVert v \rVert_D can fail: the states PπP_\pi moves you to may carry little weight in DD, so their errors are rarely corrected. If PπP_\pi stretches some vv by more than a factor 1/γ1/\gamma in the DD-norm, γ v⊤DPπv\gamma\, v^\top D P_\pi v can exceed v⊤Dvv^\top D v, A\mathbf{A} can have an eigenvalue with negative real part, and the expected update pushes ww away from the fixed point. That is the deadly triad in one line of linear algebra. The two-state example below has A=1−2γ\mathbf{A} = 1 - 2\gamma (a 1×11 \times 1 matrix), which is negative for γ>1/2\gamma > 1/2.

The same argument shows ΠTπ\Pi T^\pi is a γ\gamma-contraction in ∥⋅∥D\lVert \cdot \rVert_D when μ\mu is on-policy (projection is non-expansive, and TπT^\pi contracts by γ\gamma). 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 Q(s,a;w)Q(s, a; w) (for example with features ϕ(s,a)\phi(s, a)) and plug in Q-learning’s target from Chapter 5:

w←w+α[rt+γmax⁡a′Q(st+1,a′;w)−Q(st,at;w)]∇wQ(st,at;w).w \leftarrow w + \alpha \Big[ r_t + \gamma \max_{a'} Q(s_{t+1}, a'; w) - Q(s_t, a_t; w) \Big] \nabla_w Q(s_t, a_t; w).

That’s the whole algorithm. Everything else in this chapter is about making it work when QQ 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 x∈[0,1]x \in [0, 1]. It makes no decisions, so this is prediction (estimating VV 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 x=0x = 0 stops it from going further left. Reaching x=1x = 1 pays +1+1 and ends the episode; every step that lands in the puddle costs 0.20.2; γ=0.96\gamma = 0.96. The dashed curve is the true V(x)V(x), computed exactly. The solid curve is a linear V^(x;w)\hat V(x; w) that you can teach by hand (tap the plot) or train with semi-gradient TD(0) (Play). The faint bumps are the pieces wiϕi(x)w_i \phi_i(x); 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 ∑iϕi(x)=1\sum_i \phi_i(x) = 1 at every xx. One consequence: an update with step size α\alpha closes at most a fraction α\alpha of the gap at the updated state, which makes the TD slider easy to read.

Interactive Generalization: one update, many states
Features:

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 ϕ(screen)\phi(\text{screen}) by hand. A neural network learns its own features. In a Q-network Q(s,a;θ)Q(s, a; \theta), 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 R∣A∣\mathbb{R}^{|\mathcal{A}|}. A single forward pass then gives max⁡a′Q(s′,a′;θ)\max_{a'} Q(s', a'; \theta) 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 84×8484 \times 84 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 10810^8), 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:

  1. Function approximation: states share parameters, so an update to one state changes others.
  2. Bootstrapping: targets are built from current estimates (r+γV^(s′)r + \gamma \hat V(s')) rather than actual returns.
  3. 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: w→2ww \to 2w

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 V(A)=V(B)=0V(A) = V(B) = 0. Use one shared weight ww with features ϕ(A)=1\phi(A) = 1 and ϕ(B)=2\phi(B) = 2, so

V^(A)=w,V^(B)=2w.\hat V(A) = w, \qquad \hat V(B) = 2w.

w=0w = 0 represents the truth exactly. Start at w=1w = 1.

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

w←w+α(0+γ⋅2w−w)⋅1=(1+α(2γ−1)) w.w \leftarrow w + \alpha \big( 0 + \gamma \cdot 2w - w \big) \cdot 1 = \big(1 + \alpha (2\gamma - 1)\big)\, w .

For γ>1/2\gamma > 1/2 the factor is bigger than 1, so ww grows geometrically. With α=0.1\alpha = 0.1 and γ=0.9\gamma = 0.9, each update multiplies ww by 1.081.08, and after 100 updates w≈1.08100≈2200w \approx 1.08^{100} \approx 2200. The step size doesn’t matter: a smaller α\alpha only slows the explosion.

Why? Each update tries to make V^(A)\hat V(A) equal to γV^(B)=1.8w\gamma \hat V(B) = 1.8w. The only way to raise V^(A)\hat V(A) is to raise ww, which raises V^(B)\hat V(B) twice as much, which raises the target. The target runs away faster than the prediction can chase it, and nothing ever pulls V^(B)\hat V(B) 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): w←w+α(0−2w)⋅2=(1−4α)ww \leftarrow w + \alpha (0 - 2w) \cdot 2 = (1 - 4\alpha) w. Per episode, ww is multiplied by 1.08×0.6=0.6481.08 \times 0.6 = 0.648, 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. V(A)V(A) moves toward γV(B)=1.8\gamma V(B) = 1.8 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 w←(1−α)w→0w \leftarrow (1 - \alpha) w \to 0.

Run all four cases yourself:

Interactive The deadly triad on the w → 2w example
r = 0r = 0endA1.00B2.00V(A) = wV(B) = 2w

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?

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 max⁡a′\max_{a'} (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 xx: 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 y=r+γmax⁡a′Q(s′,a′;θ)y = r + \gamma \max_{a'} Q(s', a'; \theta) uses the same θ\theta you are updating, and s′s' is usually very close to ss. A step that raises Q(s,a)Q(s, a) also raises Q(s′,⋅)Q(s', \cdot), which raises the target: the w→2ww \to 2w mechanism, now in a million dimensions.
  • The max is optimistic. max⁡a′\max_{a'} 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 θ\theta.

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 (st,at,rt,st+1,donet)(s_t, a_t, r_t, s_{t+1}, \text{done}_t) in a replay buffer D\mathcal{D} that holds the last NN transitions (DQN used N=106N = 10^6). 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 r+γmax⁡a′Q(s′,a′)r + \gamma \max_{a'} Q(s', a') is valid no matter which policy produced the transition. SARSA’s target needs at+1a_{t+1} 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 θ−\theta^-, and compute targets with it:

y=r+γ (1−done)max⁡a′Q(s′,a′;θ−).y = r + \gamma \, (1 - \text{done}) \max_{a'} Q(s', a'; \theta^-).

Every CC steps, copy θ−←θ\theta^- \leftarrow \theta (DQN used C=10,000C = 10{,}000 updates). In between, the targets stay put. Each block of CC steps then becomes an ordinary supervised regression: fit Q(⋅;θ)Q(\cdot; \theta) to fixed labels computed from θ−\theta^-. Updates to θ\theta no longer drag their own targets along within a block.

There is a nice way to see the whole loop. Each block approximately computes Qk+1≈TQkQ_{k+1} \approx \mathcal{T} Q_k (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 T\mathcal T 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, θ−←τθ+(1−τ)θ−\theta^- \leftarrow \tau \theta + (1 - \tau) \theta^- with small τ\tau (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 θ\theta. Write the error as e=Q(s,a;θ)−ye = Q(s, a; \theta) - y. The Huber loss is quadratic for small errors and linear for large ones:

L(e)={12e2if ∣e∣≤1,∣e∣−12otherwise,dLde=clip(e,−1,1).L(e) = \begin{cases} \tfrac12 e^2 & \text{if } \lvert e \rvert \le 1, \\ \lvert e \rvert - \tfrac12 & \text{otherwise,} \end{cases} \qquad \frac{dL}{de} = \text{clip}(e, -1, 1).

Its gradient is the squared-error gradient, clipped to [−1,1][-1, 1]. Mnih et al. describe this as clipping the error term to [−1,1][-1, 1]; 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 [−1,1][-1, 1] (positive rewards become +1+1, negative ones −1-1). 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 +1+1, 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 N=106N = 10^6, minibatch B=32B = 32, C=104C = 10^4, γ=0.99\gamma = 0.99, RMSProp with learning rate 2.5×10−42.5 \times 10^{-4}, ε 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, B=128B = 128, C=500C = 500, Adam at 5×10−45 \times 10^{-4}, 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, γ=0.5\gamma = 0.5. The online and target networks are

Q(⋅,a0;θ)Q(\cdot, a_0; \theta)Q(⋅,a1;θ)Q(\cdot, a_1; \theta)Q(⋅,a0;θ−)Q(\cdot, a_0; \theta^-)Q(⋅,a1;θ−)Q(\cdot, a_1; \theta^-)
state 002.200
state 15113
state 2042−1

and the minibatch is (s,a,r,s′,done)(s, a, r, s', \text{done}) = (0,a1,1,1,0)(0, a_1, 1, 1, 0), (1,a0,0,2,0)(1, a_0, 0, 2, 0), (2,a1,−1,1,1)(2, a_1, -1, 1, 1).

  • Targets from θ−\theta^-: y1=1+0.5max⁡(1,3)=2.5y_1 = 1 + 0.5 \max(1, 3) = 2.5; y2=0+0.5max⁡(2,−1)=1y_2 = 0 + 0.5 \max(2, -1) = 1; y3=−1y_3 = -1 (terminal, no bootstrap).
  • Predictions Q(s,a;θ)Q(s, a; \theta): 2.2, 5, 4.
  • Errors ee: −0.3-0.3, 44, 55.
  • Huber: 0.5×0.09=0.0450.5 \times 0.09 = 0.045, then 4−0.5=3.54 - 0.5 = 3.5, then 5−0.5=4.55 - 0.5 = 4.5. Mean loss =8.045/3≈2.68= 8.045 / 3 \approx 2.68.
  • Gradient with respect to the three entries used: clip(e,−1,1)/3=(−0.1,  0.33,  0.33)\text{clip}(e, -1, 1)/3 = (-0.1, \; 0.33, \; 0.33). 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?

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 s′s' all actions are truly worth 0, but your estimates are noisy: each is +1+1 or −1-1 with equal probability, independently. The true max⁡a′Q\max_{a'} Q is 0. The estimated max is +1+1 unless both estimates come out at −1-1, which happens with probability 1/41/4. So E[max⁡]=34(1)+14(−1)=0.5\mathbb{E}[\max] = \tfrac34(1) + \tfrac14(-1) = 0.5. The max of noisy estimates is biased upward, and bootstrapping copies that bias into the values of the states before s′s'.

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:

y=r+γ Q(s′,  arg⁡max⁡a′Q(s′,a′;θ);  θ−).y = r + \gamma\, Q\big(s', \; \arg\max_{a'} Q(s', a'; \theta); \; \theta^- \big).

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 θ\theta won’t get the same lucky score under θ−\theta^-. 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 V(s)V(s) and per-action advantages A(s,a)A(s, a), and recombine them as Q=V+(A−meanaA)Q = V + \big(A - \text{mean}_a A\big). Many states don’t depend much on the action, so the shared VV learns from every update.
  • Prioritized experience replay (Schaul et al., 2016). Sample transitions in proportion to ∣δ∣ω\lvert \delta \rvert^{\omega} (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 rt+γrt+1+⋯+γn−1rt+n−1+γnmax⁡a′Q(st+n,a′)r_t + \gamma r_{t+1} + \dots + \gamma^{n-1} r_{t+n-1} + \gamma^n \max_{a'} Q(s_{t+n}, a'), 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 arg⁡max⁡aQ(s,a)\arg\max_a Q(s, a). 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?

Summary

  • Tables don’t scale and don’t generalize. Replace them with a parametric V^(s;w)\hat V(s; w) or Q(s,a;θ)Q(s, a; \theta) so that experience in one state updates similar states. Tables are the special case of linear features that are one-hot.
  • Linear function approximation: V^=w⊤ϕ(s)\hat V = w^\top \phi(s), gradient ϕ(s)\phi(s). The features decide how updates spread: wide features learn fast but blur detail; narrow features are precise but data-hungry.
  • Semi-gradient TD updates w←w+αδt∇wV^(st;w)w \leftarrow w + \alpha \delta_t \nabla_w \hat V(s_t; w), 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 w→2ww \to 2w example blows up geometrically, by a factor of 1+α(2γ−1)1 + \alpha(2\gamma - 1) 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 max⁡\max 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

Exercise 6.1Tables are linear function approximationConcept●○○

(a) Show that semi-gradient TD(0) with one-hot features ϕ(s)=es\phi(s) = e_s (the ss-th unit vector) is exactly the tabular TD(0) update from Chapter 5.

(b) Now use state aggregation: states are grouped, and ϕ(s)=eg(s)\phi(s) = e_{g(s)}, where g(s)g(s) is the group of ss. Write out the update. Which values change after an update at ss?

Solution

(a) With ϕ(s)=es\phi(s) = e_s, V^(s;w)=w⊤es=ws\hat V(s; w) = w^\top e_s = w_s, so ww is the value table. The gradient is ∇wV^(st;w)=est\nabla_w \hat V(s_t; w) = e_{s_t}. The update w←w+αδtestw \leftarrow w + \alpha \delta_t e_{s_t} changes only coordinate sts_t:

wst←wst+α(rt+γwst+1−wst),w_{s_t} \leftarrow w_{s_t} + \alpha \big( r_t + \gamma w_{s_{t+1}} - w_{s_t} \big),

which is tabular TD(0) with V(s)=wsV(s) = w_s.

(b) V^(s;w)=wg(s)\hat V(s; w) = w_{g(s)}, and the update is wg(st)←wg(st)+α(rt+γwg(st+1)−wg(st))w_{g(s_t)} \leftarrow w_{g(s_t)} + \alpha \big( r_t + \gamma w_{g(s_{t+1})} - w_{g(s_t)} \big). One weight changes, and with it the value of every state in the same group as sts_t. Nothing outside the group changes. This is the “Bins” setting in the corridor demo: generalization within a bin, none across bins.

Exercise 6.2Interference by handPen & paper●○○

Continue the hat-feature example. After the first update you had w=(0.1,0.4)w = (0.1, 0.4) with ϕ(x)=(1−x,x)\phi(x) = (1 - x, x). Now you observe a return of 0 from state x=0.1x = 0.1. Take one gradient step with α=0.5\alpha = 0.5 toward that target.

(a) What are the new weights?

(b) What is V^(0.8)\hat V(0.8) now? It was 0.34. You never touched x=0.8x = 0.8 in this step. Explain what happened in one sentence.

Solution

(a) ϕ(0.1)=(0.9,0.1)\phi(0.1) = (0.9, 0.1). Prediction: V^(0.1)=0.9×0.1+0.1×0.4=0.13\hat V(0.1) = 0.9 \times 0.1 + 0.1 \times 0.4 = 0.13. Error: 0−0.13=−0.130 - 0.13 = -0.13. Update:

w←(0.1,0.4)+0.5×(−0.13)×(0.9,0.1)=(0.1−0.0585,  0.4−0.0065)=(0.0415,  0.3935).w \leftarrow (0.1, 0.4) + 0.5 \times (-0.13) \times (0.9, 0.1) = (0.1 - 0.0585,\; 0.4 - 0.0065) = (0.0415,\; 0.3935).

(b) V^(0.8)=0.2×0.0415+0.8×0.3935=0.0083+0.3148=0.3231\hat V(0.8) = 0.2 \times 0.0415 + 0.8 \times 0.3935 = 0.0083 + 0.3148 = 0.3231. It fell from 0.34 to about 0.32, because the two states share weights: lowering the value at x=0.1x = 0.1 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.

Exercise 6.3Why treat the target as a constant?Pen & paper●●○

Consider one state ss with a random next state s′s' and reward rr. Define the TD error δ=r+γV^(s′;w)−V^(s;w)\delta = r + \gamma \hat V(s'; w) - \hat V(s; w).

(a) Compute −∇w12δ2-\nabla_w \tfrac12 \delta^2 (the full gradient) and compare it with the semi-gradient update direction δ∇wV^(s;w)\delta \nabla_w \hat V(s; w).

(b) Show that E[δ2]=(E[δ])2+Var[r+γV^(s′;w)]\mathbb{E}[\delta^2] = \big(\mathbb{E}[\delta]\big)^2 + \text{Var}\big[r + \gamma \hat V(s'; w)\big], where the expectation and variance are over (r,s′)(r, s') given ss.

(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 V^(s)=E[r+γV^(s′)]\hat V(s) = \mathbb{E}[r + \gamma \hat V(s')].

Hint
In (b), V^(s;w)\hat V(s; w) is a constant given ss; only r+γV^(s′;w)r + \gamma \hat V(s'; w) is random. Use E[X2]=(EX)2+VarX\mathbb{E}[X^2] = (\mathbb{E}X)^2 + \text{Var}X.
Solution

(a) ∇wδ=γ∇wV^(s′;w)−∇wV^(s;w)\nabla_w \delta = \gamma \nabla_w \hat V(s'; w) - \nabla_w \hat V(s; w). Therefore

−∇w12δ2=−δ ∇wδ=δ[∇wV^(s;w)−γ∇wV^(s′;w)].-\nabla_w \tfrac12 \delta^2 = -\delta\, \nabla_w \delta = \delta \big[ \nabla_w \hat V(s; w) - \gamma \nabla_w \hat V(s'; w) \big].

The semi-gradient keeps only δ∇wV^(s;w)\delta \nabla_w \hat V(s; w). The dropped term, −γδ∇wV^(s′;w)-\gamma \delta \nabla_w \hat V(s'; w), would move the next state’s value to shrink the error, that is, change the target to agree with the prediction.

(b) Let X=r+γV^(s′;w)X = r + \gamma \hat V(s'; w), a random variable given ss, and c=V^(s;w)c = \hat V(s; w), a constant. Then δ=X−c\delta = X - c and

E[δ2]=E[(X−c)2]=(EX−c)2+VarX=(Eδ)2+Var[r+γV^(s′;w)].\mathbb{E}[\delta^2] = \mathbb{E}[(X - c)^2] = (\mathbb{E}X - c)^2 + \text{Var}X = (\mathbb{E}\delta)^2 + \text{Var}\big[r + \gamma \hat V(s'; w)\big].

(c) The quantity we care about is (Eδ)2(\mathbb{E}\delta)^2: it is zero exactly when V^(s)\hat V(s) satisfies the Bellman equation V^(s)=E[r+γV^(s′)]\hat V(s) = \mathbb{E}[r + \gamma \hat V(s')]. The residual-gradient method minimizes E[δ2]\mathbb{E}[\delta^2], which adds the variance term γ2Var[V^(s′)]\gamma^2 \text{Var}[\hat V(s')] (plus terms involving rr). That term depends on ww through the next-state values, so the minimizer trades Bellman accuracy for making V^\hat V 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 s′s' from the same ss (the “double sampling” problem), which you usually can’t get from a single stream of experience.

The expected semi-gradient update is E[δ]∇wV^(s;w)\mathbb{E}[\delta] \nabla_w \hat V(s; w), because the gradient doesn’t depend on s′s'. It vanishes exactly when E[δ]=0\mathbb{E}[\delta] = 0, the Bellman condition. In the linear case, the sum of these conditions over states, weighted by μ\mu, gives the TD fixed point Eμ[δϕ(s)]=0\mathbb{E}_\mu[\delta \phi(s)] = 0.

Exercise 6.4Anatomy of the w → 2w divergencePen & paper●●○

Use the two-state example: V^(A)=w\hat V(A) = w, V^(B)=2w\hat V(B) = 2w, A → B → end, all rewards 0.

(a) Off-policy (update only on A → B): show that wk+1=(1+α(2γ−1))wkw_{k+1} = \big(1 + \alpha(2\gamma - 1)\big) w_k. For which γ\gamma does ww diverge, for any α>0\alpha > 0?

(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 α=0.1\alpha = 0.1 and every γ∈[0,1]\gamma \in [0, 1].

(c) Compute the Derivation’s matrix A\mathbf{A} (here a 1×11 \times 1 matrix, a number) in both cases. For a deterministic transition s→s′s \to s' it is A=∑sμ(s) ϕ(s)(ϕ(s)−γ ϕ(s′))\mathbf{A} = \sum_s \mu(s)\, \phi(s) \big(\phi(s) - \gamma\, \phi(s')\big), where μ(s)\mu(s) counts how often ss is updated per sweep: μ(A)=1\mu(A) = 1, μ(B)=0\mu(B) = 0 off-policy and μ(A)=μ(B)=1\mu(A) = \mu(B) = 1 on-policy. Use ϕ(end)=0\phi(\text{end}) = 0. Connect the sign of A\mathbf{A} to (a) and (b).

Hint
For (b), the B update is w←w+α(0−2w)⋅2w \leftarrow w + \alpha(0 - 2w)\cdot 2. For (c), all rewards are 0, so b=0b = 0 and the expected update per sweep is Δw=−αAw\Delta w = -\alpha \mathbf{A} w.
Solution

(a) The target for A is 0+γV^(B)=2γw0 + \gamma \hat V(B) = 2\gamma w, the prediction is ww, and the gradient is ϕ(A)=1\phi(A) = 1. So w←w+α(2γw−w)=(1+α(2γ−1))ww \leftarrow w + \alpha(2\gamma w - w) = (1 + \alpha(2\gamma - 1)) w. The factor exceeds 1 exactly when 2γ−1>02\gamma - 1 > 0, so ww diverges for every α>0\alpha > 0 when γ>1/2\gamma > 1/2 (and every w0≠0w_0 \neq 0). At γ=1/2\gamma = 1/2 it stays put; below, it converges.

(b) The B update: target 0 (terminal), prediction 2w2w, gradient 2, so w←w+α(0−2w)⋅2=(1−4α)ww \leftarrow w + \alpha(0 - 2w) \cdot 2 = (1 - 4\alpha) w. The per-sweep factor is f=(1+α(2γ−1))(1−4α)f = (1 + \alpha(2\gamma - 1))(1 - 4\alpha). For α=0.1\alpha = 0.1: f=(0.9+0.2γ)(0.6)f = (0.9 + 0.2\gamma)(0.6), which ranges from 0.540.54 (at γ=0\gamma = 0) to 0.660.66 (at γ=1\gamma = 1). Always in (0,1)(0, 1), so w→0w \to 0.

(c) Off-policy, only the A term: A=1⋅1⋅(1−2γ)=1−2γ\mathbf{A} = 1 \cdot 1 \cdot (1 - 2\gamma) = 1 - 2\gamma. On-policy, add the B term 1⋅2⋅(2−γ⋅0)=41 \cdot 2 \cdot (2 - \gamma \cdot 0) = 4, so A=5−2γ\mathbf{A} = 5 - 2\gamma. The expected dynamics are w←w−αAw=(1−αA)ww \leftarrow w - \alpha \mathbf{A} w = (1 - \alpha \mathbf{A}) w. Positive A\mathbf{A} means the update pulls ww toward 0 (for small α\alpha); negative A\mathbf{A} means it pushes away. Off-policy, A<0\mathbf{A} < 0 exactly when γ>1/2\gamma > 1/2: indeed 1−αA=1+α(2γ−1)1 - \alpha \mathbf{A} = 1 + \alpha(2\gamma - 1), the factor from (a). On-policy, A≥3>0\mathbf{A} \ge 3 > 0 for every γ≤1\gamma \le 1, matching (b). (The sweep in (b) applies the two updates one after the other, so its factor differs from 1−αA1 - \alpha \mathbf{A} by a term of order α2\alpha^2; the sign story is the same.) This is the scalar version of the Derivation’s statement that on-policy weighting makes A\mathbf{A} positive definite.

Exercise 6.5Does a target network fix w → 2w?Pen & paper●●●

Add a target network to the off-policy two-state example: within a block, the target for A is γ⋅2w−\gamma \cdot 2 w^- with w−w^- frozen, and after CC updates you set w−←ww^- \leftarrow w.

(a) Show that after CC updates in a block starting from w=w−w = w^-,

w=(1−α)Cw−+[1−(1−α)C] 2γ w−.w = (1-\alpha)^C w^- + \big[1 - (1-\alpha)^C\big]\, 2\gamma\, w^- .

(b) Deduce the per-block growth factor, and show that the iteration over blocks diverges when γ>1/2\gamma > 1/2 for any C≥1C \ge 1. What happens as C→∞C \to \infty?

(c) Interpret: what problem does the target network solve, and what problem does it not solve?

Hint
Within a block the update is w←(1−α)w+α⋅2γw−w \leftarrow (1 - \alpha) w + \alpha \cdot 2\gamma w^-, a linear recursion with a constant input. Its fixed point is 2γw−2\gamma w^-.
Solution

(a) Within a block, wk+1=(1−α)wk+α 2γw−w_{k+1} = (1-\alpha) w_k + \alpha\, 2\gamma w^-. The fixed point is w⋆=2γw−w^\star = 2\gamma w^-, and wk+1−w⋆=(1−α)(wk−w⋆)w_{k+1} - w^\star = (1 - \alpha)(w_k - w^\star). So after CC steps from w0=w−w_0 = w^-,

wC=w⋆+(1−α)C(w−−w⋆)=(1−α)Cw−+[1−(1−α)C]2γw−.w_C = w^\star + (1-\alpha)^C (w^- - w^\star) = (1-\alpha)^C w^- + \big[1 - (1-\alpha)^C\big] 2\gamma w^- .

(b) Dividing by w−w^-, the per-block factor is g=(1−α)C+[1−(1−α)C]2γg = (1-\alpha)^C + \big[1 - (1-\alpha)^C\big] 2\gamma, a weighted average of 11 and 2γ2\gamma with positive weights (for 0<α<10 < \alpha < 1). If γ>1/2\gamma > 1/2, then 2γ>12\gamma > 1 and g>1g > 1: geometric growth from block to block. As C→∞C \to \infty, (1−α)C→0(1-\alpha)^C \to 0 and g→2γg \to 2\gamma. Each block fits its regression exactly, and you’re running value iteration with an expanding projected backup: w−←2γw−w^- \leftarrow 2\gamma w^-.

(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 1−α1 - \alpha). It doesn’t fix the projected Bellman backup being an expansion under an off-policy weighting, here w−↦2γw−w^- \mapsto 2\gamma w^-. 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.

Exercise 6.6Replay, correlation, and buffer sizeConcept●●○

(a) In CartPole, each step advances time by Δt=0.02\Delta t = 0.02 s. Why are consecutive states sts_t and st+1s_{t+1} 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
For (b), think about how old the oldest transition is relative to the current policy, and about how often the rare useful transitions get sampled.
Solution

(a) The state changes by roughly Δt\Delta t times its rates of change per step, so st+1≈sts_{t+1} \approx s_t. Fifty consecutive transitions all describe nearly the same situation. Gradient steps on them in sequence fit QQ 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 NN 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 r+γmax⁡a′Q(s′,a′)r + \gamma \max_{a'} Q(s', a') evaluates the greedy policy using only (s,a,r,s′)(s, a, r, s'), so it’s a valid sample of the Bellman optimality backup for (s,a)(s, a) whichever policy chose aa. The behavior policy affects only which (s,a)(s, a) pairs get updated, the weighting μ\mu, 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 a′a' drawn from the policy being evaluated; an old a′a' came from an old policy, so the target estimates the wrong policy’s value unless you reweight by π(a′∣s′)/b(a′∣s′)\pi(a'|s')/b(a'|s'). Policy-gradient estimators are expectations under the current policy, so they need the same kind of ratio ρt(θ)\rho_t(\theta) (Chapter 9).

Exercise 6.7A DQN loss by hand, with Double DQNPen & paper●○○

Use the Q-tables and minibatch from the worked minibatch section (γ=0.5\gamma = 0.5). Recompute the targets, errors, Huber losses and mean loss with Double DQN targets: choose a⋆=arg⁡max⁡a′Q(s′,a′;θ)a^\star = \arg\max_{a'} Q(s', a'; \theta) with the online table, then evaluate Q(s′,a⋆;θ−)Q(s', a^\star; \theta^-) with the target table. Compare with the standard DQN loss of 8.045/3≈2.688.045 / 3 \approx 2.68.

Solution
  • Transition 1: s′=1s' = 1. The online row is (5,1)(5, 1), so a⋆=a0a^\star = a_0; the target table gives Q(1,a0;θ−)=1Q(1, a_0; \theta^-) = 1. y1=1+0.5×1=1.5y_1 = 1 + 0.5 \times 1 = 1.5.
  • Transition 2: s′=2s' = 2. The online row is (0,4)(0, 4), so a⋆=a1a^\star = a_1; the target gives Q(2,a1;θ−)=−1Q(2, a_1; \theta^-) = -1. y2=0+0.5×(−1)=−0.5y_2 = 0 + 0.5 \times (-1) = -0.5.
  • Transition 3: terminal, y3=−1y_3 = -1.

Predictions are unchanged: 2.2,5,42.2, 5, 4. Errors: 0.70.7, 5.55.5, 55. Huber: 0.5×0.49=0.2450.5 \times 0.49 = 0.245, 5.5−0.5=5.05.5 - 0.5 = 5.0, 5−0.5=4.55 - 0.5 = 4.5. Mean: 9.745/3≈3.259.745/3 \approx 3.25.

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.

Exercise 6.8How optimistic is the max?Pen & paper●●○

All nn actions in state s′s' have true value 0. Your estimates Q^1,…,Q^n\hat Q_1, \dots, \hat Q_n are independent, each +1+1 or −1-1 with probability 1/21/2.

(a) Compute E[max⁡iQ^i]\mathbb{E}[\max_i \hat Q_i] for general nn. What is it for n=2n = 2 and n=18n = 18 (the full Atari action set)?

(b) Now suppose you have a second, independent set of estimates Q~i\tilde Q_i with the same distribution. Pick i⋆=arg⁡max⁡iQ^ii^\star = \arg\max_i \hat Q_i (break ties uniformly) and report Q~i⋆\tilde Q_{i^\star}. Show this is unbiased.

(c) The bias in (a) enters the target r+γmax⁡a′Q(s′,a′)r + \gamma \max_{a'} Q(s', a'). Explain in two sentences why bootstrapping makes the problem worse than a single biased target.

Hint
The max is −1-1 only if every estimate is −1-1.
Solution

(a) max⁡iQ^i=−1\max_i \hat Q_i = -1 only if all nn estimates are −1-1, which has probability 2−n2^{-n}; otherwise it is +1+1. So

E[max⁡iQ^i]=(1−2−n)⋅1+2−n⋅(−1)=1−21−n.\mathbb{E}\big[\max_i \hat Q_i\big] = (1 - 2^{-n}) \cdot 1 + 2^{-n} \cdot (-1) = 1 - 2^{1-n}.

For n=2n = 2: 1−1/2=0.51 - 1/2 = 0.5. For n=18n = 18: 1−2−17≈0.999991 - 2^{-17} \approx 0.99999. With many actions, the max is essentially always the most optimistic error.

(b) i⋆i^\star is a function of Q^\hat Q only, and Q~\tilde Q is independent of Q^\hat Q. So E[Q~i⋆]=E[E[Q~i⋆∣Q^]]=E[0]=0\mathbb{E}[\tilde Q_{i^\star}] = \mathbb{E}\big[\mathbb{E}[\tilde Q_{i^\star} \mid \hat Q]\big] = \mathbb{E}[0] = 0, since each Q~i\tilde Q_i 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 θ\theta and θ−\theta^-, which are not fully independent, so it reduces the bias rather than eliminating it.

(c) The overestimated target at s′s' raises Q(s,a)Q(s, a); when ss 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 γ\gamma at each step, and with function approximation the inflated values also leak into neighboring states.

Exercise 6.9Explore both demosExplore the demo●●○

Corridor demo.

(a) Choose Hats with 2 features (a straight line). Tap a target of 1.0 at x=0.8x = 0.8. 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 ∣V(A)∣\lvert V(A) \rvert grows per 25-sweep block and compare with Exercise 6.5.

Hint
For (b), there is no single best resolution: it depends on how much data you have. For (c), recall that a constant step size gives an exponentially weighted average of recent targets.
Solution

(a) With ϕ(x)=(1−x,x)\phi(x) = (1 - x, x), a tap at x=0.8x = 0.8 changes ww in direction (0.2,0.8)(0.2, 0.8). The right end moves most, the left end least, and every point moves by an amount that varies linearly with xx. 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 1/α1/\alpha 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, ww stays at 1 (the factor is exactly 1); at 0.51 it grows slowly, by a factor 1.0021.002 per sweep. It doesn’t depend on α, because the factor 1+α(2γ−1)1 + \alpha(2\gamma - 1) exceeds 1 exactly when γ>1/2\gamma > 1/2. α only changes the speed.

(e) Within each block ∣V(A)∣\lvert V(A) \rvert approaches 2γw−2\gamma w^-; with α = 0.1 and C = 25, (0.9)25≈0.07(0.9)^{25} \approx 0.07, so the per-block factor is 0.07+0.93×1.8≈1.740.07 + 0.93 \times 1.8 \approx 1.74. You see a staircase rising about 1.74× per block, which is slower than without a target network (1.0825≈6.81.08^{25} \approx 6.8 per 25 sweeps) but still divergent.

Exercise 6.10Build DQN (the coding lab)Code●●○

In exercises/06-dqn/dqn.py, implement the five tasks in order, running python -m pytest -q -k taskN after each:

  1. linear_td0_update: semi-gradient TD(0) for linear features. The tests include the worked examples from this chapter and the w→2ww \to 2w divergence.
  2. ReplayBuffer.push and ReplayBuffer.sample: a circular buffer.
  3. QNetwork, epsilon_by_step, select_action: the acting side.
  4. compute_td_targets (standard and Double DQN) and dqn_loss (Huber). The tests use the worked minibatch from this chapter and check that no gradient flows through the target.
  5. update_target (hard and soft) and train_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
The two easiest bugs to make: forgetting (1 - done) in the target, and computing the target with gradients enabled (wrap it in torch.no_grad()). For dqn_loss, pick out Q(si,ai)Q(s_i, a_i) 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, then return w + alpha * delta * phi. Gradient of w⊤ϕw^\top\phi is ϕ\phi, and the target is a plain number.
  • Task 2: write into slot idx, then idx = (idx + 1) % capacity and size = min(size + 1, capacity). Sample indices with rng.integers(0, size, batch_size), never past size.
  • Task 4: inside torch.no_grad(), next_v = target_net(next_obs).max(1).values (or, for Double DQN, argmax from the online net and gather from the target net), then y = r + gamma * (1 - done) * next_v. The loss is F.smooth_l1_loss(q_sa, y). The no_grad is the code version of “semi-gradient”: the test that uses the same network as online and target checks that the gradient matches clip(e,−1,1) ∇Q(s,a)/B\text{clip}(e, -1, 1)\,\nabla Q(s,a)/B exactly.
  • Task 5: p_t.mul_(1 - tau).add_(tau * p) under no_grad; train_step is 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 γ=0.99\gamma = 0.99) and the targets move, the regression errors grow too. Without the target network, the Q-values diverge: the loss climbs to around 10810^8 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 pass

First time? See Exercises & setup.

Further reading

Next, you’ll stop learning values and start optimizing the policy directly: Chapter 7, Policy gradients and REINFORCE.