← All tutorials

Deep Q-Networks (DQN)

One value per move, learned with a network, a replay buffer and a target network.

On this page

1 The banana arena 2 Q-values and Q-learning 3 Epsilon-greedy exploration 4 Q-learning on a 6 by 6 grid 5 Fixed versus decaying epsilon 6 From a Q-table to a Deep Q-Network 7 Stepping through one DQN update 8 Phases of training 9 Double and Dueling DQN 10 DQN in practice 11 The trained agent 12 DQN needs discrete actions 13 References

In reinforcement learning an agent tries moves, receives a score, and learns which moves lead to a higher score.

The running example is an agent that collects bananas in a Unity ML-Agents arena (ML-Agents is Unity's toolkit for game-like training worlds); the code is in the Value-based-methods repo. The agent picks one of four moves (forward, backward, turn left, turn right) and gets +1 for a yellow banana and −1 for a blue one.

A Deep Q-Network (DQN) learns one number per move, the Q-value: how much future score that move is worth in the current situation. A neural network maps what the agent senses to those numbers, the agent takes the move with the biggest one, and training pushes each number toward the reward that followed plus the value of the next situation.

DQN acts every step but learns only every fourth step (Figure 1).

1acts and remembers the step2learns from random past steps3a slow copy of the network catches up123456789101112environment step4every 300 steps the episode endsand random moves get a little rarer
Figure 1. ① Every step, ② and ③ every 4th step, ④ once per episode.

1 The banana arena

The world is a square room of yellow and blue bananas. The episode score is yellows minus blues when a fixed clock runs out.

The agent senses the room as 37 numbers: its velocity plus ray sensors, each reporting the distance to the nearest yellow banana, blue banana or wall.

37 numbers invelocity + ray-sensor readingsnetwork4 Q-values outforwardbackleftright← take the biggest
Figure 2. The whole input is 37 numbers and the whole output is 4 Q-values, one per move.

The arena counts as solved when the average score over 100 consecutive episodes reaches 13, the solve rule of the Udacity deep RL course project the repo comes from.

The snippet plays one episode. brain_name keys the arena's one agent in the output of env; agent holds the network and the replay buffer (Section 6); eps is the chance of a random move (Section 3).

DECAY, EPS_MIN = 0.998, 0.01

out = env.reset(train_mode=True)
info = out[brain_name]
s = info.vector_observations[0]
score = 0
while True:
    a = agent.act(s, eps)
    out = env.step(a)
    info = out[brain_name]
    r = info.rewards[0]
    obs = info.vector_observations
    s_next = obs[0]
    d = info.local_done[0]
    agent.step(s, a, r, s_next, d)
    score += r
    s = s_next
    if d:
        break
eps = max(eps * DECAY, EPS_MIN)
  1. 1Resets the arena and reads the first 37-number state.
  2. 2Plays one episode: act, observe the reward, the next state and the episode-end flag d, and pass all five to the agent.
  3. 3Decays ε once per episode, never below EPS_MIN.

2 Q-values and Q-learning

One banana step carries the example: from situation s the agent moves forward onto a yellow banana, +1. What the agent wants is all the reward still to come, the return (Figure 3).

Discount γ = 0.99100.9910.9820.37100…steps waited+1 every step forever sums to 100
Figure 3. A +1 counts less the longer it waits.
The return adds up every reward still to come, each counted a little less for every step it waits.
Gt = rt+1 + γ·rt+2 + γ²·rt+3 + …
Gt is the return from step t; rt+1 is the next reward. The discount γ (0.99 in the Banana repo, 0.9 in the RL Basics story) adds one factor per step of waiting, so a banana now beats one later and an endless run stays finite, 100 in Figure 3. The line is one number, all reward from step t on; the Q-value below averages it and the Bellman equation splits it.

The same move can land in different spots, so its value is the return it earns on average (Figure 4).

Three tries of the forward move3.23.63.303.403.52average ≈ 3.41
Figure 4. Three returns of one move; their average is its Q-value.
A Q-value is the return a move earns on average, if every later move is the best one.
Q(s,a) = E[Gt ∣ st = s, at = a]
E averages over many tries, weighting each by its chance (expected value); ∣ reads "given that" (conditional probability): only tries starting with move a in s count. Best moves after the first make Q(s,a) the value of acting well, often written Q*, the one Q-learning learns (Sutton and Barto, 2018). A policy turns a situation into a move; DQN's takes the largest Q-value (Section 3). The Bellman equation below writes this average one step at a time; Section 9 splits Q in two.

The Bellman equation skips the wait for whole episodes: it peels off the first reward and lets the best next Q-value stand in for the rest (Figure 5).

The guesses at the next spot s′2.5fwd0.4back1.2left1.9rightmax1 + 0.99 × 2.5= 3.475
Figure 5. Reward now plus the discounted best next guess.
A move is worth its reward now plus the discounted value of the best move from where it lands.
Q(s,a) = E[r + γ·maxa′ Q(s′,a′)]
r is the return's first reward, peeled off. max Q(s′,a′) is the value of playing well from the landing spot s′, so γ times it stands in for the rest of the return; E averages over where the move lands. The update below computes the inside from one real step.

The guesses at s′ are the table's, not true averages, so the target 3.475 disagrees with the stale entry for forward, 2.0. Q-learning moves the entry part of the way (Figure 6); over many visits it hovers near the true average, about 3.41.

Learning rate α = 0.3δ = 1.4750.3 of δentry 2.0new 2.44target 3.475
Figure 6. The entry moves 0.3 of the way to the target.
After each move, its table entry shifts part of the way toward one real reward plus the best next guess.
y = r + γ·maxa′ Q(s′,a′)
The target y is the inside of the Bellman equation, computed from one real step.
δ = y − Q(s,a)
δ, the temporal-difference (TD) error, compares two estimates of the same move made one step apart: the target, after the reward, minus the entry Q(s,a), before it. Positive means better than the table thought; an entry at its target gets δ = 0.
Q(s,a) ← Q(s,a) + α·δ
The arrow means "is replaced by". The learning rate α (alpha below) moves the entry only that share of δ, so one lucky step cannot overwrite everything learned. The grid in Section 4 runs these lines; Section 6.3 keeps them with a network.

The snippet runs one tabular update; a is the move just taken.

best = max(Q[s_next])
target = r + gamma * best
td = target - Q[s][a]
Q[s][a] += alpha * td
  1. 1Computes the target y, line 1 of the Q-learning card.
  2. 2Computes td, the TD error δ (line 2), and moves the entry a share α of it (line 3).

3 Epsilon-greedy exploration

Always taking the best-known move never tries anything new, so with chance epsilon, ε, the agent moves at random.

Epsilon starts at 1.0, all random, and shrinks after every episode (Figure 7).

Repo decay 0.998fwdbackleftright2.5at ε = 0.04fwd played 0.970.96 + 0.04 / 4 = 0.97argmax = fwd, max = 2.5ε 0.82 after 1000.04 after 1,600episodes
Figure 7. The greedy move still plays 97 percent of the time.
Usually take the best-looking move, sometimes a random one, and randomize less every episode.
a = argmaxa Q(s,a) or a random move, chance ε
argmax returns the move with the largest Q, max its value: forward and 2.5 in Figure 7. ε, epsilon, is the chance of a random move, drawn from all four. The line acts on the Q-values.
ε ← max(ε·0.998, 0.01)
This max, of two numbers, keeps ε at or above the floor 0.01, so exploration fades but never stops; the repo's 1,600 episodes end at 0.04, above it. The race in Section 5 runs this line with 0.97; Section 9 uses argmax to pick its move.

Random moves still teach the agent, because Q-learning is off-policy: its target uses the best next move, whatever move comes next.

4 Q-learning on a 6 by 6 grid

A 6 by 6 banana room is the largest table you can watch fill; the real agent's 37 numbers need the network of Section 6.

+1 yellow -1 blue agent start last path wall bump +1/-1 banana eaten best Q (number, color)

Figure 8. Q-learning on a 6 by 6 grid: squares near yellows turn orange first.

Score per episode (toy world)

The state is the square plus which yellows are still uneaten. Toy α 0.3, γ 0.9, ε decay 0.99; the real run uses Adam (gradient descent that sizes each weight's step from its recent gradients) at 5e-4, γ 0.99, decay 0.998.

5 Fixed versus decaying epsilon

One agent keeps ε at 1.0; the other uses the rule of Section 3 with 0.97 instead of 0.998, so the race ends fast.

Fixed agent, epsilon 1.0
Decaying agent, epsilon 1.0 to 0.01
+1 yellow -1 blue agent start color: the square's best Q +1/-1 reward wall bump

Figure 9. Same learning rule; only the decaying ε reaches a high score.

Average score, last 20 episodes (both agents)

Toy α 0.3, γ 0.9.

6 From a Q-table to a Deep Q-Network

Tabular Q-learning (Watkins, 1989) needs one row per situation, too many for 37 continuous numbers, so DQN (Mnih et al., 2015) replaces the table with a neural network: 37 numbers in, four Q-values out.

A network shares weights across situations, so one update shifts many Q-values; training turns unstable in two ways, and DQN adds one fix for each.

The DQN paper learned control from raw screen pixels with a convolutional network (layers that scan the input for small local patterns), stabilized by both fixes (Figure 10). The banana agent feeds 37 numbers instead of pixels.

Beat the best earlier RL method43 of 49Above 75% of a human tester's score29 of 4902549 games
Figure 10. DQN on 49 Atari 2600 games, from the DQN paper (2015).

6.1 Experience replay

The first fix is experience replay. Consecutive steps are almost identical, so training on them in order fits one corner of the arena and forgets the rest.

A replay buffer stores past steps, and training draws random batches from it (Figure 11).

One stored stepsstate37numbersamove1 of 4rreward+1, −1or 0s′next state37numbersdend flag0 or 1replay buffer, the last 100,000 stepsa batch of 64, drawn at random
Figure 11. One stored step, and the random batch drawn from the buffer.
Replay buffer, oldest to newestgreen dots = random picksTime orderone corner onlybatchRandom drawthe whole arenabatchcolor = spot in the arena
Figure 12. A time-order batch is one corner; a random batch spans the arena.
import random
from collections import deque

BUFFER_SIZE = 100_000
BATCH = 64
buf = deque(maxlen=BUFFER_SIZE)
buf.append((s, a, r, s_next, d))
batch = random.sample(buf, BATCH)
  1. 1Creates a replay buffer that drops the oldest memory when full.
  2. 2Stores one transition with its episode-end flag d and samples a random batch.

6.2 Target networks

The second fix is the target network. If the network being trained also computes its own target, every gradient step (one small weight change that lowers the loss) moves the guess and the target together, and the Q-values can keep growing instead of settling.

A second copy computes the targets and trails the live network by a small share per update, the repo's soft update. The 2015 DQN paper instead copied the live network into it every 10,000 updates, frozen in between.

6.3 One training step with both fixes

One step is the Section 2 update with three changes: a network replaces the table, a slow copy computes the target, and the correction becomes a gradient step on the squared TD error (Figure 13); Figure 14 follows one weight.

Two memories in one batchmemory 1, yellow, r = +1guess 2.0target 3.475gap² 2.18memory 2, blue at clock end, r = −1guess 0.5target −1gap² 2.25loss = average 2.21
Figure 13. The loss averages the squared gaps.
One weight, repo τ = 0.001one updateafter 1,000 updatestarget 0.50 to 0.5003live 0.80gap 0.30gap 0.11target 0.69live 0.80
Figure 14. Each update moves the target 0.1 percent of the gap.
The target network sets a target, the live network chases it, and the target slowly follows.
y = r + γ·maxa′ Q(s′,a′;θ⁻)
The Q-learning target, with the target network's weights θ⁻ (after the semicolon) held still during the step. The repo's learn_step (Section 10) multiplies the γ term by 1 − d, where d (done) is 1 at an episode end. The arena ends every episode on a clock, so a time-out counts as a true end, a small approximation. The repo uses the Double target of Section 9, which edits only this max.
loss = E[(y − Q(s,a;θ))²]
The TD error squared, with the live guess Q(s,a;θ), averaged over a batch of 64 memories that stands in for the Q-value's average E. A network has no entry to change, so a gradient step lowers this loss; squaring makes the worst guesses pull hardest.
θ⁻ ← τθ + (1−τ)θ⁻
The target weights move a share τ toward the live weights θ; τ = 1 would bring back the moving target. This moving average spans about 1/τ = 1,000 updates, about 4,000 environment steps. Section 7 steps through these lines.

The same three lines in PyTorch, with opt an Adam optimizer over q_live; s, a, r and s_next are a batch of 64 steps.

import torch
import torch.nn.functional as F

GAMMA, TAU = 0.99, 1e-3

def soft_update(tgt, src):
    old = list(tgt.parameters())
    new = list(src.parameters())
    with torch.no_grad():
        for t, p in zip(old, new):
            t.lerp_(p, TAU)

with torch.no_grad():
    q_next = q_target(s_next)
    best = q_next.max(1, True)
    y = r + GAMMA * best.values
q_sa = q_live(s).gather(1, a)
loss = F.mse_loss(q_sa, y)
opt.zero_grad()
loss.backward()
opt.step()
soft_update(q_target, q_live)
  1. 1Defines soft_update: moves each target weight a fraction τ toward the live weight.
  2. 2Computes the target y from the target network, without gradients; max(1, True) keeps it a (64, 1) column (line 1).
  3. 3Moves Q(s, a) toward y by mean squared error (line 2).
  4. 4Soft-updates the target network (line 3).

7 Stepping through one DQN update

Step 1: Act

Figure 15. One DQN training step, in five parts.

The lines are the card of Section 6.3; the numbers follow memory 1 of Figure 13.

y = r + γ·maxa′ Q(s′,a′;θ⁻)
loss = E[(y − Q(s,a;θ))²]
θ⁻ ← τθ + (1−τ)θ⁻
y the target valueQ(s′,a′;θ⁻) the target network's guess at the best next mover the rewardγ the discount· means times; max picks the largestQ(s,a;θ) the live network's guessE the average over the 64 memoriesloss the squared errorθ⁻ the target network's weightsθ the live weightsτ the soft-update share← is replaced by

8 Phases of training

Epsilon (Section 3) sets the pace.

Score of every episode in the DQN banana run: near zero for about 200 episodes, a noisy climb to episode 800, then scores centred near 11 to 15 around the solve bar of 13

Figure 16. Repo run scores: ① random, ② a noisy climb, ③ near the solve bar.

9 Double and Dueling DQN

The repo's network is a Dueling Double DQN: Double changes the target, Dueling how each Q-value is built (both on Value-Based RL).

The max of noisy guesses runs high even when no move beats another (Figure 17, left). Double DQN (van Hasselt et al., 2016) lets the live network pick and the target network score.

Every move truly worth 0At s′, two networksfwd−0.2back0.1left−0.4righttrue 0max 0.3too highlive 2.2, 0.6, 1.0, 2.4picks right, 2.4target scores it 1.9its own pick, fwd 2.51 + 0.99 × 1.9 = 2.881plain DQN gives 3.475
Figure 17. The noisy max overshoots; a second scorer lowers the target.
In the double target the live network picks the next move and the target network evaluates it.
a* = argmaxa′ Q(s′,a′;θ)
The live weights θ pick a* by argmax: turn right, the largest live guess in Figure 17, 2.4.
y = r + γ·Q(s′,a*;θ⁻)
The target weights θ⁻ score it. Their own guesses are those of Figure 5, so alone they would pick forward and give the plain 3.475; scoring turn right gives 2.881 (Figure 17). One network that picks and scores inflates the target with any move it overrates; a lagged second copy is less likely to overrate the same one. Only the max of the target line in Section 6.3 changes; learn_step in Section 10 computes it. TD3 (Twin Delayed DDPG, Continuous-Action Actor-Critic, section 2) uses the same idea.

Dueling (Wang et al., 2016) splits the network into a situation value and each move's edge, then adds them (Figure 18).

The four guesses at s+12.5fwd−1.10.4back−0.31.2left+0.41.9rightV 1.5
Figure 18. The spot value plus each move's edge gives the four guesses.
One value for the situation plus each move's edge over the average move.
Q(s,a) = V(s) + A(s,a) − meana′ A(s,a′)
V(s) is the situation's value whatever move follows; the advantage A(s,a) is how much better move a is than average. V 2.5 with every advantage 1.0 lower gives the same guesses; subtracting the mean over the four moves a′ at s fixes the split at V 1.5 (Figure 18). A bad situation is then learned once, in V. Only how Q is built changes; QNetwork below builds the sum.

The repo's network as a module; q_live and q_target are the two copies used above.

import torch.nn as nn
import torch.nn.functional as F
from torch.optim import Adam

class QNetwork(nn.Module):
    def __init__(self, h=64):
        super().__init__()
        self.l1 = nn.Linear(37, h)
        self.l2 = nn.Linear(h, h)
        self.v = nn.Linear(h, 1)
        self.adv = nn.Linear(h, 4)

    def forward(self, s):
        f = F.relu(self.l1(s))
        f = self.l2(f)
        h = F.relu(self.l2(f))
        v = self.v(h)
        a = self.adv(h)
        m = a.mean(1, True)
        return v + a - m

q_live = QNetwork()
q_target = QNetwork()
w = q_live.state_dict()
q_target.load_state_dict(w)
params = q_live.parameters()
opt = Adam(params, lr=5e-4)
  1. 137 inputs, 64-unit layers, a 1-output V head and a 4-output A head, as in the repo's qn.py.
  2. 2Like the repo, applies the second layer twice, then returns V + A minus the mean A.
  3. 3Builds the live network, a target with the same starting weights, and Adam at 5e-4.

10 DQN in practice

Figure 19 lists the repo's settings; Stable-Baselines3 has a widely used PyTorch DQN.

Acting costs one forward pass; a learning step runs both networks on a batch of 64. The 100,000-step buffer is about 30 MB as 32-bit floats.

Setting and repo valueRaising itLowering it
Network
37 in, 64-unit layers, V (1) and A (4)
more capacity, slowercheaper, may underfit
Replay buffer
100,000
more varied, adapts slowerforgets faster, batches alike
Batch
64
steadier, more computecheaper, noisier
Learn every
4 steps
fewer updates, cheapermore reuse; can chase noise
Adam learning rate
5e-4
faster; Q-values can divergesteadier, slower
Discount γ
0.99
far rewards count moreonly near rewards count
Target update τ
1e-3
faster, less stable; τ = 1 copiessteadier; τ = 0 freezes
ε start, floor
1.0, 0.01
more random moves latea floor of 0 stops exploring
ε decay
× 0.998 per episode
explores longersettles on the first route that scores
Figure 19. Settings in the Value-based-methods repo and their effects.

learn_step is the repo's learning step, using q_live, q_target, opt and soft_update from above.

import torch
import torch.nn.functional as F

GAMMA, TAU = 0.99, 1e-3

@torch.no_grad()
def td_target(r, s_next, done):
    live_q = q_live(s_next)
    a2 = live_q.argmax(1, True)
    tgt_q = q_target(s_next)
    q_t = tgt_q.gather(1, a2)
    keep = 1 - done
    return r + GAMMA * q_t * keep

def learn_step(batch):
    s, a, r, s_next, done = batch
    y = td_target(r, s_next, done)
    q_sa = q_live(s).gather(1, a)
    loss = F.mse_loss(q_sa, y)
    opt.zero_grad()
    loss.backward()
    opt.step()
    soft_update(q_target, q_live)
  1. 1The target of the Double DQN card in Section 9: the live network picks the next move and the target network scores it; keep is 0 at the end of an episode.
  2. 2Regresses Q(s, a) toward y.
  3. 3Soft-updates the target network.

Three details keep this loop correct.

11 The trained agent

Figure 20. The trained agent holds course and curves around blue bananas.

12 DQN needs discrete actions

DQN takes the max over four Q-values; a continuous action, such as a Reacher arm's joint torque, any real number from −1 to 1, leaves no list to take the max over.

Deep Deterministic Policy Gradient (DDPG): Actor and Critic solves this by keeping the Q-value and adding a second network, the actor, that outputs the action directly.

13 References

  1. Mnih, V., et al. (2015). Human-level control through deep reinforcement learning. Nature 518, 529 to 533. doi:10.1038/nature14236.
  2. van Hasselt, H., et al. (2016). Deep Reinforcement Learning with Double Q-learning. AAAI. arXiv:1509.06461.
  3. Wang, Z., et al. (2016). Dueling Network Architectures for Deep Reinforcement Learning. ICML. arXiv:1511.06581.
  4. Sutton, R. S., and Barto, A. G. (2018). Reinforcement Learning: An Introduction, 2nd edition. MIT Press.
  5. Watkins, C. J. C. H. (1989). Learning from Delayed Rewards. PhD thesis, University of Cambridge.