Q-Learning and DQN
Learn what each action is finally worth, then take the biggest number. Two tricks made that work with a network: sampled replay, and a target that holds still.
Why Does This Exist?
A signal controller sits at the crossing of two arterial roads. Every second it holds the current green or switches phase. It sees the queue on each approach, which phase runs, and for how long. Its reward each second is minus the number of vehicles sitting still.
The reinforcement learning setup gives you the vocabulary for this: states, actions, a reward, a discount factor. It does not give you a controller. But suppose you knew how much each action was ultimately worth in every situation, counting everything that follows. Then you would never plan again. Look up two numbers, take the bigger one.
This page covers learning those worths with Q-learning and scaling that to a network with DQN. The setup underneath is on the reinforcement learning foundations page, and the alternative family that learns a policy directly is policy gradient methods.
The one idea you need first. Future rewards count less than immediate ones through a discount factor between 0 and 1. A reward of 10 three steps out at is worth now. That single number is what makes "finally worth" a finite quantity.
Think of It Like This
Reading the next road sign instead of driving the whole way
You want to know how long the drive is from your depot to the port. The honest method is to drive it and time it. The cheap method: drive to the next junction, note that it took six minutes, and read the sign there saying the port is 48 minutes on. Your depot estimate is now 54. One measured hop, plus a sign you trusted.
The obvious objection is correct: if that sign is wrong, your new number is wrong too. What saves the scheme is that the sign nearest the port is the easiest to get right, and corrections propagate back one junction at a time. Start with signs full of nonsense, keep measuring single hops, and the whole map converges.
Where the analogy stops: road signs are fixed while you learn, but Q-learning's "signs" are its own estimates, which move as it learns. That moving target is exactly what breaks the naive network version below, and fixing it is half of DQN.
How It Actually Works
The Bellman update, and why bootstrapping is the trick
The action value is the discounted return you expect from taking action in state and behaving well afterward. You learn it one transition at a time. You were in , took , observed reward , and landed in :
is a small step size, is the reward just seen, is where you landed, and is the best value your current estimates claim is available there. The bracket is how wrong the old estimate was, and you move a fraction of the way toward fixing it. Nothing in that expression waits for an episode to end. You updated a value from one real step plus your own guess about the next state, which is bootstrapping, and it lets learning start before any outcome finishes. The never asks how you behaved, so old logs from any controller still teach the greedy policy. That is off-policy.
Worked example
One transition at the intersection. Holding green in state currently rates . You hold, two vehicles wait, so , and you land in whose best action rates . With and :
- Target: .
- Error: . The old estimate was optimistic.
- New value: .
The cell moves a tenth of the way from 3.2 toward 1.6. Thousands of such nudges, spread across states, converge on the true worths.
What breaks when the table becomes a network
A table works while states are countable, then the state space explodes and most cells sit empty forever. So predict instead of store: feed the state into a small network, read one value per action out, and train it with gradient descent on the squared error against . Two things go wrong, and neither is a bug.
The target moves. That label comes from the very weights being updated, so every step shifts what you fit toward. Consecutive transitions are near-identical. Traffic one second apart is the same scene with rounding differences, while gradient descent expects roughly independent samples.
Replay, a frozen target, and the bias that survives
The replay buffer stores the last million transitions and serves random batches, which breaks the correlation and reuses each expensive real-world second dozens of times. The target network is a frozen copy of the weights used only for the label, refreshed every few thousand steps, so each stretch of training is ordinary regression against a fixed label. Those two fixes are most of what DQN was.
One bias survives them. Taking the largest of several noisy estimates returns a number too high on average, because the selects whichever estimate got the luckiest noise, and bootstrapping compounds it upward. Double Q-learning splits selection from scoring: one set of weights picks the action, an independent set scores it. And the hard limit lives in the itself: you must enumerate the actions, so two signal phases work fine and a continuous valve position needs a different family.
Code
The point of this snippet is the update as arithmetic: one Bellman step on the worked numbers above, nothing else.
def q_update(q: float, reward: float, next_best: float, gamma: float, alpha: float) -> tuple[float, float, float]: target = reward + gamma * next_best error = target - q return target, error, q + alpha * error
target, error, new_q = q_update(3.2, -2.0, 4.0, 0.9, 0.1)print(round(target, 2))# -> 1.6print(round(error, 2))# -> -1.6print(round(new_q, 2))# -> 3.04The cell drops from 3.2 to 3.04, exactly the hand computation in the worked example.
Watch Out For
Training against a target that moves with you
The symptom is a loss that falls for thousands of steps, climbs for thousands more, then falls again, while average creeps past any return the environment could physically produce. The buffer, the rewards, the shapes, and the learning rate all check out, because nothing is broken. You are regressing onto a label computed from the weights you are changing.
Freeze a copy of the network, compute the label from that copy, and refresh it every few thousand steps. If the oscillation survives, lengthen the refresh interval before touching the learning rate: the wobble's period tracks the refresh interval, not the step size.
A policy confident about actions it got lucky with
The symptom is a controller that strongly prefers holding green on one approach with glowing values while the cross street backs up around the corner. That approach got lucky early, the selected the inflated estimate, and bootstrapping copied it backward instead of averaging it out. Rising average next to flat real returns is the pattern to watch.
Pick actions with the online network and score them with the target network, the double estimator from above. And distrust high values on rarely tried actions most, because that is where the luck hides.
The Quick Version
- is the discounted return from taking action in state and behaving well after. Know it and the policy is an argmax.
- The Bellman update moves one estimate a fraction toward the reward plus the discounted best estimate at the next state, which is bootstrapping.
- Off-policy learning means exploratory behavior and old logs both teach the greedy policy.
- With a network the label moves with the weights and consecutive samples correlate, so DQN adds a replay buffer and a frozen target network.
- The overestimates on noisy estimates and bootstrapping compounds it, so split selection from scoring.
- The enumerates actions, so this family needs a small discrete action set.