Skip to content
AI360XpertBeta

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.

One table cell at 3.2 is pulled toward a target of 1.6 built from reward minus 2 plus 0.9 times the next best value 4.0, landing at 3.04, so an estimate trains on another estimate
One table cell at 3.2 is pulled toward a target of 1.6 built from reward minus 2 plus 0.9 times the next best value 4.0, landing at 3.04, so an estimate trains on another estimate

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 γ\gamma between 0 and 1. A reward of 10 three steps out at γ=0.9\gamma = 0.9 is worth 10×0.93=7.2910 \times 0.9^3 = 7.29 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 Q(s,a)Q(s, a) is the discounted return you expect from taking action aa in state ss and behaving well afterward. You learn it one transition at a time. You were in ss, took aa, observed reward rr, and landed in s′s':

Q(s,a)←Q(s,a)+α[ r+γmax⁡a′Q(s′,a′)−Q(s,a) ]Q(s, a) \leftarrow Q(s, a) + \alpha\big[\, r + \gamma \max_{a'} Q(s', a') - Q(s, a) \,\big]

α\alpha is a small step size, rr is the reward just seen, s′s' is where you landed, and max⁡a′Q(s′,a′)\max_{a'} Q(s', a') 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 max⁡\max 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 ss currently rates Q(s,hold)=3.2Q(s, \text{hold}) = 3.2. You hold, two vehicles wait, so r=−2.0r = -2.0, and you land in s′s' whose best action rates max⁡a′Q(s′,a′)=4.0\max_{a'} Q(s', a') = 4.0. With γ=0.9\gamma = 0.9 and α=0.1\alpha = 0.1:

  • Target: r+γmax⁡a′Q(s′,a′)=−2.0+0.9×4.0=−2.0+3.6=1.6r + \gamma \max_{a'} Q(s', a') = -2.0 + 0.9 \times 4.0 = -2.0 + 3.6 = 1.6.
  • Error: 1.6−3.2=−1.61.6 - 3.2 = -1.6. The old estimate was optimistic.
  • New value: 3.2+0.1×(−1.6)=3.2−0.16=3.043.2 + 0.1 \times (-1.6) = 3.2 - 0.16 = 3.04.

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 QQ value per action out, and train it with gradient descent on the squared error against r+γmax⁡a′Q(s′,a′)r + \gamma \max_{a'} Q(s', a'). 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 max⁡\max 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 max⁡\max 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.04

The 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 QQ 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 QQ values while the cross street backs up around the corner. That approach got lucky early, the max⁡\max selected the inflated estimate, and bootstrapping copied it backward instead of averaging it out. Rising average QQ 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

  • Q(s,a)Q(s, a) is the discounted return from taking action aa in state ss and behaving well after. Know it and the policy is an argmax.
  • The Bellman update moves one estimate a fraction α\alpha 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 max⁡\max overestimates on noisy estimates and bootstrapping compounds it, so split selection from scoring.
  • The max⁡\max enumerates actions, so this family needs a small discrete action set.