{ "cells": [ { "cell_type": "markdown", "id": "1fd58348-44b0-48f0-a1ff-e9a0729514dc", "metadata": {}, "source": [ "\n", "\n", "
\n", "\n", " \n", "
\n", " \"Department\n", "
\n", "\n", " \n", "
\n", "

Deep Reinforcement Learning (CS-866)

\n", "

Department of Computer Science

\n", "

University of the Punjab

\n", "

\n", "

CartPole with REINFORCE (Policy Gradient)

\n", "

Instructor: Nazar Khan

\n", "
\n", "\n", " \n", " \n", "\n", " \n", "
\n", "
\n", "\n", "#### Goal\n", "- We will train a neural network that outputs probabilities of actions that can be applied to a cart to balance a pole attached to it.\n", "- The network becomes more likely to repeat actions that led to good outcomes." ] }, { "cell_type": "markdown", "id": "2110f905-78e4-48c9-8fda-f7850a85a020", "metadata": {}, "source": [ "#### Imports" ] }, { "cell_type": "code", "execution_count": 2, "id": "4f302943-0521-4595-8f43-a497a81b6dea", "metadata": {}, "outputs": [], "source": [ "# We import Gymnasium to create RL environments like CartPole\n", "import gymnasium as gym\n", "\n", "# Torch is the PyTorch library for building and training neural networks\n", "import torch\n", "\n", "# nn gives us building blocks for neural networks (layers, activations, etc.)\n", "import torch.nn as nn\n", "\n", "# optim gives us optimization algorithms like Adam to adjust network weights\n", "import torch.optim as optim\n", "\n", "# numpy is a numerical library (we'll use it a tiny bit)\n", "import numpy as np" ] }, { "cell_type": "markdown", "id": "4ec7316a-9902-4f47-85c1-c3d380fb9c6f", "metadata": {}, "source": [ "#### Define the Neural Network that will represent the policy" ] }, { "cell_type": "code", "execution_count": 3, "id": "69250d7a-13e5-4297-916f-94f7b6975ffc", "metadata": {}, "outputs": [], "source": [ "class PolicyNetwork(nn.Module): \n", " # This class defines our neural network (our policy function π(a|s; θ))\n", " # It takes in the state and outputs probabilities for each action.\n", "\n", " def __init__(self, state_dim, action_dim):\n", " # state_dim = number of numbers that describe the state (CartPole has 4)\n", " # action_dim = number of possible actions (CartPole has 2: left or right)\n", " super().__init__()\n", "\n", " # nn.Sequential lets us stack layers in order, like a list\n", " self.net = nn.Sequential(\n", " nn.Linear(state_dim, 128), # first layer: input vector mapped to 128 neurons\n", " nn.ReLU(), # activation function: adds non-linearity\n", " nn.Linear(128, action_dim), # second layer: 128 neurons mapped to number of actions\n", " nn.Softmax(dim=-1) # convert numbers into probabilities\n", " )\n", "\n", " def forward(self, x):\n", " # forward defines how input flows through the network\n", " return self.net(x)" ] }, { "cell_type": "markdown", "id": "55308479-eebc-4066-bb73-361acdef9a0a", "metadata": {}, "source": [ "#### Implementation of the REINFORCE algorithm for learning neural network parameters of optimal policy" ] }, { "cell_type": "code", "execution_count": 4, "id": "fa29aff0-2cf8-4b49-a013-d961fb532441", "metadata": {}, "outputs": [], "source": [ "def reinforce(env_name='CartPole-v1', gamma=0.99, lr=1e-3, episodes=500):\n", "\n", " # Create the environment\n", " env = gym.make(env_name)\n", "\n", " # Get size of state and action spaces from environment\n", " state_dim = env.observation_space.shape[0] # e.g., 4 for CartPole\n", " action_dim = env.action_space.n # e.g., 2 actions\n", "\n", " # Create the neural network policy\n", " policy = PolicyNetwork(state_dim, action_dim)\n", "\n", " # Adam optimizer will adjust neural network weights based on gradients\n", " optimizer = optim.Adam(policy.parameters(), lr=lr)\n", "\n", " # Keep track of total reward each episode to see learning progress\n", " returns_history = []\n", "\n", " # Loop over episodes of training\n", " for episode in range(episodes):\n", "\n", " # Reset environment at start of episode and get initial state\n", " state, _ = env.reset()\n", "\n", " # Lists to store log-probabilities and rewards for this episode\n", " log_probs = [] \n", " rewards = [] \n", "\n", " done = False # episode is not finished yet\n", "\n", " # Generate an episode\n", " while not done:\n", " \n", " # Convert state list/array to PyTorch tensor (NEEDED for network input)\n", " state_tensor = torch.tensor(state, dtype=torch.float32)\n", "\n", " # Forward pass: get action probabilities from policy network\n", " action_probs = policy(state_tensor)\n", "\n", " # Turn probabilities into a \"distribution\" (randomness)\n", " dist = torch.distributions.Categorical(action_probs)\n", "\n", " # Sample an action according to probabilities\n", " action = dist.sample()\n", "\n", " # Save log(probability(action_taken)) for learning update later\n", " log_probs.append(dist.log_prob(action))\n", "\n", " # Take action in environment and observe next state and reward\n", " state, reward, done, truncated, _ = env.step(action.item())\n", "\n", " # Save reward to compute return G later\n", " rewards.append(reward)\n", "\n", " # Episode finished. Now compute returns (discounted reward sums)\n", "\n", " returns = []\n", " G = 0 # return accumulator\n", "\n", " # Compute returns G_t for each time step t, working backwards\n", " for r in reversed(rewards):\n", " G = r + gamma * G # Bellman return formula\n", " returns.insert(0, G)\n", "\n", " # Convert to PyTorch tensor so gradients flow properly\n", " returns = torch.tensor(returns, dtype=torch.float32)\n", "\n", " # Normalize returns. This helps stable training (optional but recommended)\n", " returns = (returns - returns.mean()) / (returns.std() + 1e-9)\n", "\n", " # Compute loss = −Σ log(pi(action|state)) * G_t\n", " # (negative because we want gradient ASCENT, but optimizer does DESCENT)\n", " loss = 0\n", " for log_p, Gt in zip(log_probs, returns):\n", " loss += -log_p * Gt\n", "\n", " # Backpropagation step\n", " optimizer.zero_grad() # clear old gradients\n", " loss.backward() # compute gradients\n", " optimizer.step() # update neural network weights\n", "\n", " # Store total reward for this episode for plotting later\n", " returns_history.append(sum(rewards))\n", "\n", " # Print progress occasionally\n", " if episode % 20 == 0:\n", " print(f\"Episode {episode:4d} | Return = {sum(rewards):.2f}\")\n", "\n", " env.close()\n", " return policy, returns_history\n" ] }, { "cell_type": "markdown", "id": "823fa588-7708-4fe0-8d8d-14be4bcc0a4f", "metadata": {}, "source": [ "#### Train the policy network" ] }, { "cell_type": "code", "execution_count": 5, "id": "24f2c143-7909-47fa-a47b-7e7378ba51fe", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Episode 0 | Return = 8.00\n", "Episode 20 | Return = 12.00\n", "Episode 40 | Return = 17.00\n", "Episode 60 | Return = 51.00\n", "Episode 80 | Return = 28.00\n", "Episode 100 | Return = 29.00\n", "Episode 120 | Return = 40.00\n", "Episode 140 | Return = 66.00\n", "Episode 160 | Return = 53.00\n", "Episode 180 | Return = 87.00\n", "Episode 200 | Return = 32.00\n", "Episode 220 | Return = 152.00\n", "Episode 240 | Return = 118.00\n", "Episode 260 | Return = 85.00\n", "Episode 280 | Return = 87.00\n", "Episode 300 | Return = 262.00\n", "Episode 320 | Return = 133.00\n", "Episode 340 | Return = 341.00\n", "Episode 360 | Return = 448.00\n", "Episode 380 | Return = 193.00\n", "Episode 400 | Return = 340.00\n", "Episode 420 | Return = 346.00\n", "Episode 440 | Return = 496.00\n", "Episode 460 | Return = 183.00\n", "Episode 480 | Return = 516.00\n" ] } ], "source": [ "policy, history = reinforce()" ] }, { "cell_type": "markdown", "id": "73d05033-88ee-44cb-a0e0-6f61668d48b6", "metadata": {}, "source": [ "#### Plot the learning curve" ] }, { "cell_type": "code", "execution_count": 6, "id": "dabd7a3a-3b0a-4a3c-90d7-d0a11e1d8930", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "\n", "plt.plot(history)\n", "plt.xlabel(\"Episode\")\n", "plt.ylabel(\"Return\")\n", "plt.title(\"REINFORCE Learning Curve (CartPole)\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "261266e4-0826-4835-8e42-bbf507515d14", "metadata": {}, "source": [ "#### Run 5 episodes either until failure or until 500 steps (whichever comes earlier)" ] }, { "cell_type": "code", "execution_count": 7, "id": "f880fdf7-052e-4fb2-af34-9ccfd8e9c9c2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Saved REINFORCE_cartpole_episodes.gif\n" ] } ], "source": [ "import imageio\n", "import cv2\n", "from IPython.display import Image\n", "\n", "def record_episodes(num_episodes=5, filename=\"episodes.gif\"):\n", " env = gym.make(\"CartPole-v1\", render_mode=\"rgb_array\")\n", " frames = []\n", " for episode in range(num_episodes):\n", " state, _ = env.reset()\n", " \n", " done = False\n", " step = 0\n", " \n", " while not done:\n", " step += 1\n", " \n", " # Convert state to tensor\n", " state_t = torch.tensor(state, dtype=torch.float32)\n", " probs = policy(state_t)\n", " action = torch.argmax(probs).item()\n", " \n", " # Act\n", " state, reward, terminated, truncated, _ = env.step(action)\n", " done = terminated or truncated\n", " \n", " # Get frame\n", " frame = env.render()\n", " \n", " ## frame is in RGB format but OpenCV expects it in BGR format\n", " #frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)\n", " \n", " # Draw step number on frame\n", " frame = cv2.putText(\n", " frame, \n", " f\"Episode: {episode+1}, Step: {step}\", \n", " (10, 30), \n", " cv2.FONT_HERSHEY_SIMPLEX, \n", " 1, (255, 0, 0), 2\n", " )\n", " \n", " #cv2.imshow(\"CartPole\", frame)\n", " #cv2.waitKey(5) # Adjust speed (ms)\n", " frames.append(frame)\n", " \n", " env.close()\n", " imageio.mimsave(filename, frames, fps=100)\n", " print(f\"Saved {filename}\")\n", " #cv2.destroyAllWindows()\n", "\n", "filename = \"REINFORCE_cartpole_episodes.gif\"\n", "record_episodes(num_episodes=5, filename=filename)" ] }, { "cell_type": "code", "execution_count": 15, "id": "fd3eba4f-b770-4d3b-9eae-b1fb54fb202c", "metadata": {}, "outputs": [], "source": [ "#Image(filename)" ] }, { "cell_type": "markdown", "id": "782d925e-d19d-42e0-bb93-9024a823a3ac", "metadata": {}, "source": [ "
\n", " \"REINFORCE_cartpole_episodes\"\n", "
" ] }, { "cell_type": "markdown", "id": "453382d1-df60-4af7-99cc-02d4bf6b8ddc", "metadata": {}, "source": [ "#### GIFs" ] }, { "cell_type": "code", "execution_count": 9, "id": "f7d62da6-6fb9-4d84-9bf0-03af33bacf5c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Saved trained_cartpole.gif\n" ] } ], "source": [ "import imageio\n", "import cv2\n", "\n", "def record_trained_gif(policy, filename=\"trained_cartpole.gif\", max_steps=500):\n", " env = gym.make(\"CartPole-v1\", render_mode=\"rgb_array\")\n", " state, _ = env.reset()\n", "\n", " frames = []\n", " step = 0\n", " done = False\n", "\n", " while not done and step < max_steps:\n", " step += 1\n", "\n", " # Select greedy action from policy\n", " state_t = torch.tensor(state, dtype=torch.float32)\n", " probs = policy(state_t)\n", " action = torch.argmax(probs).item()\n", "\n", " state, r, terminated, truncated, _ = env.step(action)\n", " done = terminated or truncated\n", "\n", " frame = env.render()\n", " frame = cv2.putText(frame.copy(),\n", " f\"Step {step}\", (10, 30),\n", " cv2.FONT_HERSHEY_SIMPLEX, 1, (255,0,0), 2)\n", "\n", " frames.append(frame)\n", "\n", " env.close()\n", " imageio.mimsave(filename, frames, fps=30)\n", " print(f\"Saved {filename}\")\n", "\n", "\n", "record_trained_gif(policy)\n" ] }, { "cell_type": "markdown", "id": "87ada559-25e5-4754-a10f-b640b9fa0aa4", "metadata": {}, "source": [ "\"trained_cartpole\"" ] }, { "cell_type": "markdown", "id": "e765c573-2ef7-4426-b065-b98b8d81de70", "metadata": {}, "source": [ "#### Random vs Trained Policy" ] }, { "cell_type": "code", "execution_count": 14, "id": "c2f75462-d297-40fa-9281-6cce43d5ad3c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Saved comparison.gif\n" ] } ], "source": [ "from PIL import Image\n", "\n", "def combine_frames(f1, f2):\n", " return np.hstack([f1, f2])\n", "\n", "def record_side_by_side(policy, filename=\"comparison.gif\", max_steps=500):\n", " env1 = gym.make(\"CartPole-v1\", render_mode=\"rgb_array\") # random\n", " env2 = gym.make(\"CartPole-v1\", render_mode=\"rgb_array\") # trained\n", "\n", " s1, _ = env1.reset()\n", " s2, _ = env2.reset()\n", "\n", " frames = []\n", " for step in range(max_steps):\n", "\n", " # --- Random Agent ---\n", " a1 = env1.action_space.sample()\n", " s1, _, d1, t1, _ = env1.step(a1)\n", " frame_random = env1.render()\n", " cv2.putText(frame_random, \"Random\", (10,30),\n", " cv2.FONT_HERSHEY_SIMPLEX, 1, (255,0,0), 2)\n", "\n", " # --- Trained Agent ---\n", " probs = policy(torch.tensor(s2, dtype=torch.float32))\n", " a2 = torch.argmax(probs).item()\n", " s2, _, d2, t2, _ = env2.step(a2)\n", " frame_rl = env2.render()\n", " cv2.putText(frame_rl, \"Trained\", (10,30),\n", " cv2.FONT_HERSHEY_SIMPLEX, 1, (255,0,0), 2)\n", "\n", " frame = combine_frames(frame_random, frame_rl)\n", " frames.append(frame)\n", "\n", " if d1 or t1 or d2 or t2:\n", " break\n", "\n", " env1.close(); env2.close()\n", " imageio.mimsave(filename, frames, fps=5)\n", " print(f\"Saved {filename}\")\n", "\n", "record_side_by_side(policy)\n" ] }, { "cell_type": "markdown", "id": "1539e373-3af2-458b-bb21-726b41758f52", "metadata": {}, "source": [ "\"trained_cartpole\"" ] }, { "cell_type": "markdown", "id": "13c17795-bb19-40f2-be54-b7fae67fcd3e", "metadata": {}, "source": [ "#### Live Policy Probability Plot\n", "\n", "Shows how policy output evolves during one run (probs for LEFT vs RIGHT at every step)" ] }, { "cell_type": "code", "execution_count": 11, "id": "75d26fd7-11a5-4da5-b623-30d60d0996ba", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "from IPython.display import clear_output, display\n", "\n", "def run_with_live_probs(policy, episodes=1, max_steps=500):\n", " env = gym.make(\"CartPole-v1\", render_mode=\"rgb_array\")\n", "\n", " for ep in range(episodes):\n", " state,_ = env.reset()\n", " probs_list = []\n", "\n", " for step in range(max_steps):\n", " state_t = torch.tensor(state, dtype=torch.float32)\n", " probs = policy(state_t).detach().numpy()\n", " probs_list.append(probs)\n", "\n", " action = np.argmax(probs)\n", " state, _, terminated, truncated, _ = env.step(action)\n", "\n", " # live plot\n", " clear_output(wait=True)\n", " plt.figure(figsize=(6,4))\n", " arr = np.array(probs_list)\n", " plt.plot(arr[:,0], label=\"LEFT\")\n", " plt.plot(arr[:,1], label=\"RIGHT\")\n", " plt.ylim(0,1)\n", " plt.title(f\"CartPole Policy Probabilities (Step {step})\")\n", " plt.legend()\n", " plt.grid()\n", " display(plt.gcf())\n", " plt.close()\n", "\n", " if terminated or truncated:\n", " break\n", "\n", " env.close()\n", "\n", "run_with_live_probs(policy)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "65f44c36-6374-4073-8aea-397eb6610e6f", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.0" } }, "nbformat": 4, "nbformat_minor": 5 }