Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Chapter 19 - Reinforcement Learning for Decision Making in Complex Environments

Open in Colab Open in Kaggle

Chapter 19 - Reinforcement Learning for Decision Making in Complex Environments

Implementing our first RL algorithm

Working with the existing environments in OpenAI Gym

Purpose of This Section

  • Demonstrates how to implement Q-learning to solve a grid-world task.

  • Uses OpenAI Gym environments to provide a practical RL simulation framework.

  • Focuses on tabular Q-learning, the most basic model-free RL method.

Introducing OpenAI Gym Toolkit

  • OpenAI Gym: A library of environments for benchmarking RL algorithms.

  • Includes environments from simple to complex (e.g., CartPole, MountainCar, robotics).

  • Gym provides:

    • A unified API for agent-environment interaction.

    • Tools for rendering and stepping through environments.

Looking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com
Collecting gym
  Downloading gym-0.26.2.tar.gz (721 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 721.7/721.7 kB 22.6 MB/s eta 0:00:00
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Requirement already satisfied: numpy>=1.18.0 in /usr/local/lib/python3.10/dist-packages (from gym) (1.22.2)
Requirement already satisfied: cloudpickle>=1.2.0 in /usr/local/lib/python3.10/dist-packages (from gym) (2.2.1)
Collecting gym_notices>=0.0.4 (from gym)
  Downloading gym_notices-0.0.8-py3-none-any.whl.metadata (1.0 kB)
Downloading gym_notices-0.0.8-py3-none-any.whl (3.0 kB)
Building wheels for collected packages: gym
  Building wheel for gym (pyproject.toml) ... done
  Created wheel for gym: filename=gym-0.26.2-py3-none-any.whl size=827745 sha256=4d987f726634dfbd8a8da8912681f3deb5a534e4e190767b465f9daa6e208361
  Stored in directory: /tmp/pip-ephem-wheel-cache-nxita8rx/wheels/b9/22/6d/3e7b32d98451b4cd9d12417052affbeeeea012955d437da1da
Successfully built gym
Installing collected packages: gym_notices, gym
Successfully installed gym-0.26.2 gym_notices-0.0.8
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.

Working with Gym Environments

  • Use gym.make(env_name) to load an environment.

  • Common API methods:

    • env.reset() — initializes a new episode, returns initial observation.

    • env.step(action) — executes an action, returns:

      • next_state, reward, done, info

    • env.render() — (optional) visualizes the environment state.

  • Spaces:

    • env.observation_space — defines state format (e.g., Box)

    • env.action_space — defines possible actions (e.g., Discrete)

The Environment: CartPole-v1

  • Classic RL task where a cart must balance a pole using left/right moves.

  • States (Box(4,)): Continuous vector of 4 values (cart position, velocity, pole angle, and pole tip’s velocity) for min and max each

  • Actions: 0 (move left), 1 (move right)

<IPython.core.display.Image object>
Box([-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], [4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38], (4,), float32)
Discrete(2)
(array([-0.00863925, 0.02971761, -0.02400191, -0.04376195], dtype=float32), {})

Every time we call env.step(), it returns:

  • An array for the new state (or observations)

  • A reward (a scalar value of type float)

  • A termination flag (True or False)

  • A Python dictionary containing auxiliary information

(array([-0.00804489, -0.16505209, -0.02487715, 0.24125256], dtype=float32), 1.0, False, False, {})

A grid world example

<IPython.core.display.Image object>
<IPython.core.display.Image object>
/tmp/ipykernel_34168/3032829683.py:243: MatplotlibDeprecationWarning: Passing the radius parameter of __init__() positionally is deprecated since Matplotlib 3.6; the parameter will become keyword-only two minor releases later.
  goal = patches.RegularPolygon((gc + 0.5, gr + 0.5), 3, 0.4, color='gold')
<Figure size 600x500 with 1 Axes>
Action: 3, New State: 0, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 1, New State: 1, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 2, New State: 7, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 0, New State: 1, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 1, New State: 2, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 0, New State: 2, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 0, New State: 2, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 2, New State: 8, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 1, New State: 9, Reward: 0.0
<Figure size 600x500 with 1 Axes>
Action: 1, New State: 10, Reward: -1.0

Solving a grid world problem with Q-learning

Episode 1: Moves = 10, Reward = -1.00, Epsilon = 0.904
Episode 2: Moves = 60, Reward = -1.00, Epsilon = 0.495
Episode 3: Moves = 18, Reward = -1.00, Epsilon = 0.413
Episode 4: Moves = 28, Reward = -1.00, Epsilon = 0.312
Episode 5: Moves = 6, Reward = -1.00, Epsilon = 0.293
Episode 6: Moves = 48, Reward = -1.00, Epsilon = 0.181
Episode 7: Moves = 43, Reward = -1.00, Epsilon = 0.118
Episode 8: Moves = 149, Reward = -1.00, Epsilon = 0.099
Episode 9: Moves = 13, Reward = -1.00, Epsilon = 0.099
Episode 10: Moves = 135, Reward = -1.00, Epsilon = 0.099
Episode 11: Moves = 173, Reward = -1.00, Epsilon = 0.099
Episode 12: Moves = 42, Reward = -1.00, Epsilon = 0.099
Episode 13: Moves = 13, Reward = -1.00, Epsilon = 0.099
Episode 14: Moves = 427, Reward = 1.00, Epsilon = 0.099
Episode 15: Moves = 50, Reward = -1.00, Epsilon = 0.099
Episode 16: Moves = 264, Reward = -1.00, Epsilon = 0.099
Episode 17: Moves = 43, Reward = -1.00, Epsilon = 0.099
Episode 18: Moves = 115, Reward = -1.00, Epsilon = 0.099
Episode 19: Moves = 57, Reward = 1.00, Epsilon = 0.099
Episode 20: Moves = 12, Reward = -1.00, Epsilon = 0.099
Episode 21: Moves = 37, Reward = -1.00, Epsilon = 0.099
Episode 22: Moves = 92, Reward = 1.00, Epsilon = 0.099
Episode 23: Moves = 17, Reward = -1.00, Epsilon = 0.099
Episode 24: Moves = 149, Reward = -1.00, Epsilon = 0.099
Episode 25: Moves = 66, Reward = 1.00, Epsilon = 0.099
Episode 26: Moves = 125, Reward = 1.00, Epsilon = 0.099
Episode 27: Moves = 74, Reward = -1.00, Epsilon = 0.099
Episode 28: Moves = 17, Reward = 1.00, Epsilon = 0.099
Episode 29: Moves = 293, Reward = 1.00, Epsilon = 0.099
Episode 30: Moves = 83, Reward = 1.00, Epsilon = 0.099
Episode 31: Moves = 27, Reward = -1.00, Epsilon = 0.099
Episode 32: Moves = 40, Reward = -1.00, Epsilon = 0.099
Episode 33: Moves = 15, Reward = 1.00, Epsilon = 0.099
Episode 34: Moves = 23, Reward = 1.00, Epsilon = 0.099
Episode 35: Moves = 43, Reward = 1.00, Epsilon = 0.099
Episode 36: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 37: Moves = 12, Reward = 1.00, Epsilon = 0.099
Episode 38: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 39: Moves = 12, Reward = 1.00, Epsilon = 0.099
Episode 40: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 41: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 42: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 43: Moves = 9, Reward = 1.00, Epsilon = 0.099
Episode 44: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 45: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 46: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 47: Moves = 7, Reward = -1.00, Epsilon = 0.099
Episode 48: Moves = 11, Reward = 1.00, Epsilon = 0.099
Episode 49: Moves = 12, Reward = 1.00, Epsilon = 0.099
Episode 50: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 51: Moves = 11, Reward = 1.00, Epsilon = 0.099
Episode 52: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 53: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 54: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 55: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 56: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 57: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 58: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 59: Moves = 9, Reward = 1.00, Epsilon = 0.099
Episode 60: Moves = 11, Reward = 1.00, Epsilon = 0.099
Episode 61: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 62: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 63: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 64: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 65: Moves = 9, Reward = 1.00, Epsilon = 0.099
Episode 66: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 67: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 68: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 69: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 70: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 71: Moves = 5, Reward = -1.00, Epsilon = 0.099
Episode 72: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 73: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 74: Moves = 12, Reward = 1.00, Epsilon = 0.099
Episode 75: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 76: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 77: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 78: Moves = 9, Reward = 1.00, Epsilon = 0.099
Episode 79: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 80: Moves = 11, Reward = 1.00, Epsilon = 0.099
Episode 81: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 82: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 83: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 84: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 85: Moves = 12, Reward = 1.00, Epsilon = 0.099
Episode 86: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 87: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 88: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 89: Moves = 9, Reward = 1.00, Epsilon = 0.099
Episode 90: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 91: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 92: Moves = 9, Reward = 1.00, Epsilon = 0.099
Episode 93: Moves = 10, Reward = 1.00, Epsilon = 0.099
Episode 94: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 95: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 96: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 97: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 98: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 99: Moves = 8, Reward = 1.00, Epsilon = 0.099
Episode 100: Moves = 9, Reward = 1.00, Epsilon = 0.099
<Figure size 1400x600 with 2 Axes>

Top plot

The agent is learning how to reach the goal quickly, and over time, it reduces randomness by exploiting known good paths.

  • Episodes 0–30: High variance — the agent is exploring a lot, sometimes hitting traps, sometimes wandering.

  • Episodes ~30 onward: Steady drop to ~10–20 moves. This is exactly what you want — it’s a sign the agent has learned the optimal or near-optimal path and is executing it consistently.

Bottom plot

The agent is initially bad (as expected), then improves, and finally consistently reaches the goal without hitting traps.

  • First 10–15 episodes: Mostly -1 rewards — hitting traps.

  • Then intermittent 1/-1 rewards: Agent is learning, testing actions, sometimes gets the gold.

  • After ~30–35: Mostly consistent +1 rewards.

A glance at deep Q-learning

Problems in Tabular Q-learning

Limitations of Tabular Q-learning

  • Previous example used a discrete state space of size 30.

  • Q-values were stored in a Python dictionary — feasible for small, finite MDPs.

Problems with Tabular Methods

  • Large or continuous state spaces make tabular representation intractable.

  • Infinite or near-infinite states cannot be individually stored.

  • Sparse visitation: some states might never be visited, making generalization difficult.

Solution: Function Approximation

  • Replace tabular value functions with parametric functions:

    • Instead of V(St)V(S_t) or Q(St,At)Q(S_t, A_t), use:

      • vw(xs)v_w(x_s) to approximate vπ(s)v_\pi(s)

      • qw(xs,a)q_w(x_s, a) to approximate qπ(s,a)q_\pi(s, a)

  • Here:

    • ww: parameters (e.g., weights of a neural network)

    • xsx_s: featurized representation of state ss

Deep Q-Networks (DQN)

  • A deep Q-network (DQN) is when qw(xs,a)q_w(x_s, a) is modeled using a deep neural network (DNN).

  • The DNN takes the state (or its features) as input and outputs Q-values for each possible action.

Training DQN

  • Q-values are updated using the Q-learning update rule (we’ve seen in the lecture). One modification is in the agent’s choose_action() method, which, in the code of the previous section for Q-learning, was simply accessing the action values stored in a dictionary. Now, this function should be changed to perform a forward pass of the NN model for computing the action values.

Key Insight

  • DQN allows agents to generalize across unseen states by learning from features.

  • Makes RL feasible in complex, high-dimensional, or continuous environments where tabular methods fail.

<IPython.core.display.Image object>

DQN: Replay memory

Challenges with Neural Network Approximation

  1. Interference Between Updates:

    • In tabular Q-learning, updating a value for a specific state-action pair doesn’t affect others.

    • With a neural network, updating the weights to fit one state-action pair (s,a)(s, a) affects outputs for other states as well.

  2. Changing Distribution of States:

    • In supervised learning, multiple epochs can be used to train on a fixed dataset.

    • In Q-learning, the environment is non-stationary: as the agent learns, it visits different states, making older samples less relevant or less likely to reoccur.

  3. Lack of IID Assumption:

    • Neural networks assume training samples are IID (independent and identically distributed).

    • In reinforcement learning, transitions (st,at,rt,st+1)(s_t, a_t, r_t, s_{t+1}) are sequential and correlated, violating the IID assumption.

To address these issues, replay memory (also known as experience replay) is introduced.

Transition Storage

  • As the agent interacts with the environment, it generates transition quintuples: qv(xs,a)=(st,at,rt,st+1,done)q_v(x_s, a) = (s_t, a_t, r_t, s_{t+1}, \text{done})

(These transitions are stored in a finite-sized buffer.)

Buffer Management

  • The memory buffer is often implemented as a list or queue.

  • When the buffer is full, the oldest transition is removed to make room for the new one: memory.pop(0)

Sampling and Training

  • Instead of training on the most recent transition, the network trains on mini-batches randomly sampled from the buffer

    • Breaks the correlation between samples.

    • Helps with better generalization and stability of learning.

<IPython.core.display.Image object>

Determining the Target Values for Computing the Loss in DQN

To train a Deep Q-Network (DQN), we need to adapt the update rule from tabular Q-learning to work with neural networks. This involves how we compute the target values used to calculate the loss.

Transition Quintuple

Each stored transition TT in the replay buffer is of the form:

T=(xs,a,r,xs’,done)T = (x_s, a, r, x_{s’}, \text{done})

Where:

  • xsx_s: current state features

  • aa: action taken

  • rr: reward received

  • xs′x_{s{\prime}}: next state features

  • done\text{done}: boolean indicating if the episode has ended

Dual Forward Passes

To compute the loss, we perform two forward passes through the DQN:

  1. Current state: qw(xs,:)→ gives Q-values for all actions at state xsq_w(x_s, :) \quad \text{→ gives Q-values for all actions at state } x_s

  2. Next state: qw(xs’,:)→ gives Q-values for all actions at next state xs’q_w(x_{s’}, :) \quad \text{→ gives Q-values for all actions at next state } x_{s’}

Here, the notation qw(x,:)q_w(x, :) refers to the vector of Q-values for all possible actions a∈Aa \in \mathcal{A}.

Target Value Construction

Using the Q-learning update rule, the target value for the selected action aa is:

y=r+γmax⁡a’∈Aqw(xs’,a’)y = r + \gamma \max_{a’ \in \mathcal{A}} q_w(x_{s’}, a’)

Instead of forming a scalar target, we construct a target Q-value vector that:

  • Retains the original predicted Q-values for all a′≠aa{\prime} \neq a

  • Replaces only the entry for action aa with the computed target yy

Loss Function

We use a regression objective with:

  • Predictions: qw(xs,:)q_w(x_s, :)

  • Target values: vector with the updated entry at index aa

  • Loss: Mean Squared Error (MSE) between prediction and target vector

L=MSE(qw(xs,:),target vector)\mathcal{L} = \text{MSE}\left(q_w(x_s, :), \text{target vector}\right)
  • This ensures the loss is non-zero only for the action aa.

  • Other entries are unchanged, so gradients will only flow through the relevant output.

<IPython.core.display.Image object>

Implementing a deep Q-learning algorithm

<IPython.core.display.Image object>