Course materials · Reinforcement Learning for Electrical Engineering

Week 4 Double DQN

English translation

[Key objectives]

[Q-value overestimation]

1. Example of Q-value overestimation: A game

2. How DQN overestimation arises

3. Policy distortion and problems

[Double DQN idea]

[Double DQN training update]

[Exercise: train CartPole using Double DQN]

1. Environment setup

import gymnasium as gym
import random
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
from collections import deque

env = gym.make('CartPole-v1', render_mode = 'rgb_array')
state_size = env.observation_space.shape[0]
action_size = env.action_space.n

2. Define the DQN model

class DQNAgent(nn.Module):
    def __init__(self, state_size, action_size):
        super(DQNAgent, self).__init__()
        self.fc1 = nn.Linear(state_size, 128)
        self.fc2 = nn.Linear(128, 128)
        self.fc3 = nn.Linear(128, action_size)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        return self.fc3(x)

3. Set hyperparameters

learning_rate = 0.0003
gamma = 0.99  # Discount factor
epsilon = 1.0  # Initial exploration probability
epsilon_min = 0.01
epsilon_decay = 0.995
batch_size = 128
memory_size = 30000
target_update_frequency = 5  # Target network update interval

4. Create the DQN agent and target network

agent = DQNAgent(state_size, action_size)
target_agent = DQNAgent(state_size, action_size)
target_agent.load_state_dict(agent.state_dict())
optimizer = optim.Adam(agent.parameters(), lr=learning_rate)
criterion = nn.MSELoss()

5. Initialize the experience replay buffer

memory = deque(maxlen=memory_size)

6. Training function (train)

def train(batch_size):
    minibatch = random.sample(memory, batch_size)
    states = torch.tensor(np.array([transition[0] for transition in minibatch]), dtype=torch.float32)
    actions = torch.tensor(np.array([transition[1] for transition in minibatch]), dtype=torch.long)
    rewards = torch.tensor(np.array([transition[2] for transition in minibatch]), dtype=torch.float32)
    next_states = torch.tensor(np.array([transition[3] for transition in minibatch]), dtype=torch.float32)
    dones = torch.tensor(np.array([transition[4] for transition in minibatch]), dtype=torch.float32)

    # Double DQN key step: Select the action with the highest next-state Q-value using the agent network
    next_actions = agent(next_states).argmax(1).unsqueeze(1)
    # Compute the Q-value of the selected action using the target network
    target_q_values = target_agent(next_states).gather(1, next_actions).squeeze()
    targets = rewards + gamma * target_q_values * (1 - dones)

    predicted_q_values = agent(states).gather(1, actions.unsqueeze(1)).squeeze()
    loss = criterion(predicted_q_values, targets)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

7. Training loop

# Training loop
episodes = 100
for e in range(episodes):
    state, _ = env.reset()
    state = torch.tensor(state, dtype=torch.float32)
    done = False
    total_reward = 0

    while not done:
        if random.random() < epsilon:
            action = env.action_space.sample()
        else:
            action = agent(state).argmax().item()
        next_state, reward, terminated, truncated, _ = env.step(action)
        next_state = torch.tensor(next_state, dtype=torch.float32)
        done = terminated or truncated

        total_reward += reward
        memory.append((state, action, reward, next_state, done))

        if len(memory) >= batch_size:
            train(batch_size)

        state = next_state
        if e % target_update_frequency == 0:
            target_agent.load_state_dict(agent.state_dict())

        if epsilon > epsilon_min:
            epsilon *= epsilon_decay

    if (e+1) % 20 == 0:
        print(f"Episode {e + 1}/{episodes}, Total Reward: {total_reward}, Epsilon: {epsilon:.3f}")
Episode 20/100, Total Reward: 302.0, Epsilon: 0.010
Episode 40/100, Total Reward: 133.0, Epsilon: 0.010
Episode 60/100, Total Reward: 132.0, Epsilon: 0.010
Episode 80/100, Total Reward: 124.0, Epsilon: 0.010
Episode 100/100, Total Reward: 138.0, Epsilon: 0.010

8. Evaluate the trained agent (rendering with GIF)

import imageio
import os

frames = []
state, _ = env.reset()
state = torch.tensor(state, dtype=torch.float32)
done = False
for i in range(3000): # 3000 steps simulation
  action = agent(state).argmax().item()
  next_state, reward, terminated, truncated, _ = env.step(action)
  next_state = torch.tensor(next_state, dtype=torch.float32)
  state = next_state
  done = terminated or truncated
  frames.append(env.render())

  if done:
    break

imageio.mimsave('cartpole_simulation_doubleDQN.gif', frames, duration=33) # save to gif file
print("GIF saved as cartpole_simulation.gif")
GIF saved as cartpole_simulation.gif