Created
June 11, 2024 05:40
-
-
Save radekosmulski/87d16335d3d7b89b0e8f308a6c1550c0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"id": "ce9df70b", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Code from: https://github.com/openai/spinningup/blob/master/spinup/algos/pytorch/vpg\n", | |
"# with minor modifications to simplify reasoning about what the code does.\n", | |
"#\n", | |
"# Calculations should be equivalent, they just don't happen in parallel over `mpi`,\n", | |
"# we are using `gymnasium`, the maintained fork of openai's `gym`, etc." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"id": "85b6c11c", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import numpy as np\n", | |
"import scipy.signal\n", | |
"from gymnasium.spaces import Box, Discrete\n", | |
"\n", | |
"import torch\n", | |
"import torch.nn as nn\n", | |
"from torch.distributions.normal import Normal\n", | |
"from torch.distributions.categorical import Categorical\n", | |
"\n", | |
"\n", | |
"def combined_shape(length, shape=None):\n", | |
" if shape is None:\n", | |
" return (length,)\n", | |
" return (length, shape) if np.isscalar(shape) else (length, *shape)\n", | |
"\n", | |
"\n", | |
"def mlp(sizes, activation, output_activation=nn.Identity):\n", | |
" layers = []\n", | |
" for j in range(len(sizes)-1):\n", | |
" act = activation if j < len(sizes)-2 else output_activation\n", | |
" layers += [nn.Linear(sizes[j], sizes[j+1]), act()]\n", | |
" return nn.Sequential(*layers)\n", | |
"\n", | |
"\n", | |
"def count_vars(module):\n", | |
" return sum([np.prod(p.shape) for p in module.parameters()])\n", | |
"\n", | |
"\n", | |
"def discount_cumsum(x, discount):\n", | |
" \"\"\"\n", | |
" magic from rllab for computing discounted cumulative sums of vectors.\n", | |
"\n", | |
" input: \n", | |
" vector x, \n", | |
" [x0, \n", | |
" x1, \n", | |
" x2]\n", | |
"\n", | |
" output:\n", | |
" [x0 + discount * x1 + discount^2 * x2, \n", | |
" x1 + discount * x2,\n", | |
" x2]\n", | |
" \"\"\"\n", | |
" return scipy.signal.lfilter([1], [1, float(-discount)], x[::-1], axis=0)[::-1]\n", | |
"\n", | |
"\n", | |
"class Actor(nn.Module):\n", | |
"\n", | |
" def _distribution(self, obs):\n", | |
" raise NotImplementedError\n", | |
"\n", | |
" def _log_prob_from_distribution(self, pi, act):\n", | |
" raise NotImplementedError\n", | |
"\n", | |
" def forward(self, obs, act=None):\n", | |
" # Produce action distributions for given observations, and \n", | |
" # optionally compute the log likelihood of given actions under\n", | |
" # those distributions.\n", | |
" pi = self._distribution(obs)\n", | |
" logp_a = None\n", | |
" if act is not None:\n", | |
" logp_a = self._log_prob_from_distribution(pi, act)\n", | |
" return pi, logp_a\n", | |
"\n", | |
"\n", | |
"class MLPCategoricalActor(Actor):\n", | |
" \n", | |
" def __init__(self, obs_dim, act_dim, hidden_sizes, activation):\n", | |
" super().__init__()\n", | |
" self.logits_net = mlp([obs_dim] + list(hidden_sizes) + [act_dim], activation)\n", | |
"\n", | |
" def _distribution(self, obs):\n", | |
" logits = self.logits_net(obs)\n", | |
" return Categorical(logits=logits)\n", | |
"\n", | |
" def _log_prob_from_distribution(self, pi, act):\n", | |
" return pi.log_prob(act)\n", | |
"\n", | |
"\n", | |
"class MLPGaussianActor(Actor):\n", | |
"\n", | |
" def __init__(self, obs_dim, act_dim, hidden_sizes, activation):\n", | |
" super().__init__()\n", | |
" log_std = -0.5 * np.ones(act_dim, dtype=np.float32)\n", | |
" self.log_std = torch.nn.Parameter(torch.as_tensor(log_std))\n", | |
" self.mu_net = mlp([obs_dim] + list(hidden_sizes) + [act_dim], activation)\n", | |
"\n", | |
" def _distribution(self, obs):\n", | |
" mu = self.mu_net(obs)\n", | |
" std = torch.exp(self.log_std)\n", | |
" return Normal(mu, std)\n", | |
"\n", | |
" def _log_prob_from_distribution(self, pi, act):\n", | |
" return pi.log_prob(act).sum(axis=-1) # Last axis sum needed for Torch Normal distribution\n", | |
"\n", | |
"\n", | |
"class MLPCritic(nn.Module):\n", | |
"\n", | |
" def __init__(self, obs_dim, hidden_sizes, activation):\n", | |
" super().__init__()\n", | |
" self.v_net = mlp([obs_dim] + list(hidden_sizes) + [1], activation)\n", | |
"\n", | |
" def forward(self, obs):\n", | |
" return torch.squeeze(self.v_net(obs), -1) # Critical to ensure v has right shape.\n", | |
"\n", | |
"\n", | |
"\n", | |
"class MLPActorCritic(nn.Module):\n", | |
"\n", | |
"\n", | |
" def __init__(self, observation_space, action_space, \n", | |
" hidden_sizes=(64,64), activation=nn.Tanh):\n", | |
" super().__init__()\n", | |
"\n", | |
" obs_dim = observation_space.shape[0]\n", | |
"\n", | |
" # policy builder depends on action space\n", | |
" if isinstance(action_space, Box):\n", | |
" self.pi = MLPGaussianActor(obs_dim, action_space.shape[0], hidden_sizes, activation)\n", | |
" elif isinstance(action_space, Discrete):\n", | |
" self.pi = MLPCategoricalActor(obs_dim, action_space.n, hidden_sizes, activation)\n", | |
"\n", | |
" # build value function\n", | |
" self.v = MLPCritic(obs_dim, hidden_sizes, activation)\n", | |
"\n", | |
" def step(self, obs):\n", | |
" with torch.no_grad():\n", | |
" pi = self.pi._distribution(obs)\n", | |
" a = pi.sample()\n", | |
" logp_a = self.pi._log_prob_from_distribution(pi, a)\n", | |
" v = self.v(obs)\n", | |
" return a.numpy(), v.numpy(), logp_a.numpy()\n", | |
"\n", | |
" def act(self, obs):\n", | |
" return self.step(obs)[0]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"id": "892e41f2", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import numpy as np\n", | |
"import torch\n", | |
"from torch.optim import Adam\n", | |
"import gymnasium as gym\n", | |
"import time\n", | |
"\n", | |
"\n", | |
"class VPGBuffer:\n", | |
" \"\"\"\n", | |
" A buffer for storing trajectories experienced by a VPG agent interacting\n", | |
" with the environment, and using Generalized Advantage Estimation (GAE-Lambda)\n", | |
" for calculating the advantages of state-action pairs.\n", | |
" \"\"\"\n", | |
"\n", | |
" def __init__(self, obs_dim, act_dim, size, gamma=0.99, lam=0.95):\n", | |
" self.obs_buf = np.zeros(combined_shape(size, obs_dim), dtype=np.float32)\n", | |
" self.act_buf = np.zeros(combined_shape(size, act_dim), dtype=np.float32)\n", | |
" self.adv_buf = np.zeros(size, dtype=np.float32)\n", | |
" self.rew_buf = np.zeros(size, dtype=np.float32)\n", | |
" self.ret_buf = np.zeros(size, dtype=np.float32)\n", | |
" self.val_buf = np.zeros(size, dtype=np.float32)\n", | |
" self.logp_buf = np.zeros(size, dtype=np.float32)\n", | |
" self.gamma, self.lam = gamma, lam\n", | |
" self.ptr, self.path_start_idx, self.max_size = 0, 0, size\n", | |
"\n", | |
" def store(self, obs, act, rew, val, logp):\n", | |
" \"\"\"\n", | |
" Append one timestep of agent-environment interaction to the buffer.\n", | |
" \"\"\"\n", | |
" assert self.ptr < self.max_size # buffer has to have room so you can store\n", | |
" self.obs_buf[self.ptr] = obs\n", | |
" self.act_buf[self.ptr] = act\n", | |
" self.rew_buf[self.ptr] = rew\n", | |
" self.val_buf[self.ptr] = val\n", | |
" self.logp_buf[self.ptr] = logp\n", | |
" self.ptr += 1\n", | |
"\n", | |
" def finish_path(self, last_val=0):\n", | |
" \"\"\"\n", | |
" Call this at the end of a trajectory, or when one gets cut off\n", | |
" by an epoch ending. This looks back in the buffer to where the\n", | |
" trajectory started, and uses rewards and value estimates from\n", | |
" the whole trajectory to compute advantage estimates with GAE-Lambda,\n", | |
" as well as compute the rewards-to-go for each state, to use as\n", | |
" the targets for the value function.\n", | |
"\n", | |
" The \"last_val\" argument should be 0 if the trajectory ended\n", | |
" because the agent reached a terminal state (died), and otherwise\n", | |
" should be V(s_T), the value function estimated for the last state.\n", | |
" This allows us to bootstrap the reward-to-go calculation to account\n", | |
" for timesteps beyond the arbitrary episode horizon (or epoch cutoff).\n", | |
" \"\"\"\n", | |
"\n", | |
" path_slice = slice(self.path_start_idx, self.ptr)\n", | |
" rews = np.append(self.rew_buf[path_slice], last_val)\n", | |
" vals = np.append(self.val_buf[path_slice], last_val)\n", | |
" \n", | |
" # the next two lines implement GAE-Lambda advantage calculation\n", | |
" deltas = rews[:-1] + self.gamma * vals[1:] - vals[:-1]\n", | |
" self.adv_buf[path_slice] = discount_cumsum(deltas, self.gamma * self.lam)\n", | |
" \n", | |
" # the next line computes rewards-to-go, to be targets for the value function\n", | |
" self.ret_buf[path_slice] = discount_cumsum(rews, self.gamma)[:-1]\n", | |
" \n", | |
" self.path_start_idx = self.ptr\n", | |
"\n", | |
" def get(self):\n", | |
" \"\"\"\n", | |
" Call this at the end of an epoch to get all of the data from\n", | |
" the buffer, with advantages appropriately normalized (shifted to have\n", | |
" mean zero and std one). Also, resets some pointers in the buffer.\n", | |
" \"\"\"\n", | |
" assert self.ptr == self.max_size # buffer has to be full before you can get\n", | |
" self.ptr, self.path_start_idx = 0, 0\n", | |
" # the next two lines implement the advantage normalization trick\n", | |
" adv_mean, adv_std = np.mean(self.adv_buf), np.std(self.adv_buf)\n", | |
" self.adv_buf = (self.adv_buf - adv_mean) / adv_std\n", | |
" data = dict(obs=self.obs_buf, act=self.act_buf, ret=self.ret_buf,\n", | |
" adv=self.adv_buf, logp=self.logp_buf)\n", | |
" return {k: torch.as_tensor(v, dtype=torch.float32) for k,v in data.items()}" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"id": "13cf855f", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"gamma = 0.99\n", | |
"steps_per_epoch = 10000\n", | |
"epochs = 500\n", | |
"train_v_iters=80\n", | |
"max_ep_len=1000\n", | |
"pi_lr=3e-4\n", | |
"vf_lr=1e-3\n", | |
"lam=0.97" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"id": "a6bf491b", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def evaluate(n_runs=1000):\n", | |
" env = gym.make('CartPole-v1')\n", | |
" obs, info = env.reset()\n", | |
" ep_lens = []\n", | |
" for num_runs in range(n_runs):\n", | |
" ep_len = 0\n", | |
" while True:\n", | |
" a, _, _ = ac.step(torch.as_tensor(obs, dtype=torch.float32))\n", | |
" obs, _, terminated, truncated, _ = env.step(a)\n", | |
" ep_len += 1\n", | |
" if terminated or truncated:\n", | |
" obs, info = env.reset()\n", | |
" break\n", | |
" ep_lens.append(ep_len)\n", | |
" return np.mean(ep_lens)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"id": "a2babac1", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"env = gym.make('CartPole-v1')\n", | |
"# Create actor-critic module\n", | |
"ac = MLPActorCritic(env.observation_space, env.action_space, hidden_sizes=[64]*2)\n", | |
"buf = VPGBuffer(env.observation_space.shape, env.action_space.shape, steps_per_epoch, gamma, lam)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"id": "20543bb8", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"CPU times: user 6.96 s, sys: 0 ns, total: 6.96 s\n", | |
"Wall time: 6.96 s\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"23.994" | |
] | |
}, | |
"execution_count": 7, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"%%time\n", | |
"\n", | |
"evaluate()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"id": "43365dc6", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# for saving models during training\n", | |
"!mkdir -p models/vgp_cartpole" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"id": "320f16ba", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"image/png": "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", | |
"text/plain": [ | |
"<Figure size 640x480 with 1 Axes>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"import matplotlib.pyplot as plt\n", | |
"from IPython import display\n", | |
"\n", | |
"# Create a figure and axis\n", | |
"fig, ax = plt.subplots()\n", | |
"\n", | |
"# Initialize lists to store the data\n", | |
"avg_ep_lens = []\n", | |
"\n", | |
"# Function to update the graph\n", | |
"def update_graph(epoch, avg_ep_len):\n", | |
" avg_ep_lens.append(avg_ep_len)\n", | |
" \n", | |
" # Clear the previous plot\n", | |
" ax.clear()\n", | |
" \n", | |
" # Plot the updated data\n", | |
" ax.plot(avg_ep_lens)\n", | |
" \n", | |
" # Set labels and title\n", | |
" ax.set_xlabel('Epoch')\n", | |
" ax.set_ylabel('Mean episode length')\n", | |
" ax.set_title('VPG on Cartpole-v1 -- episode length vs epoch')\n", | |
" \n", | |
" # Display the updated plot\n", | |
" display.display(plt.gcf())\n", | |
" display.clear_output(wait=True)\n", | |
"\n", | |
"\n", | |
"# Set up function for computing VPG policy loss\n", | |
"def compute_loss_pi(data):\n", | |
" obs, act, adv, logp_old = data['obs'], data['act'], data['adv'], data['logp']\n", | |
"\n", | |
" # Policy loss\n", | |
" pi, logp = ac.pi(obs, act)\n", | |
" loss_pi = -(logp * adv).mean()\n", | |
"\n", | |
" # Useful extra info\n", | |
" approx_kl = (logp_old - logp).mean().item()\n", | |
" ent = pi.entropy().mean().item()\n", | |
" pi_info = dict(kl=approx_kl, ent=ent)\n", | |
"\n", | |
" return loss_pi, pi_info\n", | |
"\n", | |
"# Set up function for computing value loss\n", | |
"def compute_loss_v(data):\n", | |
" obs, ret = data['obs'], data['ret']\n", | |
" return ((ac.v(obs) - ret)**2).mean()\n", | |
"\n", | |
"# Set up optimizers for policy and value function\n", | |
"pi_optimizer = Adam(ac.pi.parameters(), lr=pi_lr)\n", | |
"vf_optimizer = Adam(ac.v.parameters(), lr=vf_lr)\n", | |
"\n", | |
"def update():\n", | |
" data = buf.get()\n", | |
"\n", | |
" # Get loss and info values before update\n", | |
" pi_l_old, pi_info_old = compute_loss_pi(data)\n", | |
" pi_l_old = pi_l_old.item()\n", | |
" v_l_old = compute_loss_v(data).item()\n", | |
"\n", | |
" # Train policy with a single step of gradient descent\n", | |
" pi_optimizer.zero_grad()\n", | |
" loss_pi, pi_info = compute_loss_pi(data)\n", | |
" loss_pi.backward()\n", | |
" pi_optimizer.step()\n", | |
"\n", | |
" # Value function learning\n", | |
" for i in range(train_v_iters):\n", | |
" vf_optimizer.zero_grad()\n", | |
" loss_v = compute_loss_v(data)\n", | |
" loss_v.backward()\n", | |
" vf_optimizer.step()\n", | |
"\n", | |
" # Log changes from update\n", | |
" kl, ent = pi_info['kl'], pi_info_old['ent']\n", | |
"\n", | |
"# Prepare for interaction with environment\n", | |
"(o, _), ep_ret, ep_len = env.reset(), 0, 0\n", | |
"\n", | |
"for epoch in range(epochs):\n", | |
" ep_lens = []\n", | |
" for t in range(steps_per_epoch):\n", | |
" a, v, logp = ac.step(torch.as_tensor(o, dtype=torch.float32))\n", | |
"\n", | |
" next_o, r, terminated, truncated, _ = env.step(a)\n", | |
" ep_ret += r\n", | |
" ep_len += 1\n", | |
"\n", | |
" # save and log\n", | |
" buf.store(o, a, r, v, logp)\n", | |
"\n", | |
" # Update obs (critical!)\n", | |
" o = next_o\n", | |
" epoch_ended = t==steps_per_epoch-1\n", | |
"\n", | |
" if terminated or truncated or epoch_ended:\n", | |
" if terminated or truncated or epoch_ended:\n", | |
" _, v, _ = ac.step(torch.as_tensor(o, dtype=torch.float32))\n", | |
" else:\n", | |
" v = 0\n", | |
" buf.finish_path(v)\n", | |
" if terminated or truncated:\n", | |
" ep_lens.append(ep_len)\n", | |
" (o, _), ep_ret, ep_len = env.reset(), 0, 0\n", | |
" # Perform VPG update!\n", | |
" update()\n", | |
" \n", | |
" torch.save(ac, f'models/vgp_cartpole/{epoch}.pth')\n", | |
" update_graph(epoch, np.mean(ep_lens))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "0611dfdb", | |
"metadata": {}, | |
"source": [ | |
"500 is the max in the `v1` version of the environment (means the policy can survive till the end of the episode!) so we are doing quite well here :)." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"id": "d9308ac1", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"495.811" | |
] | |
}, | |
"execution_count": 10, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"evaluate()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"id": "851ed777", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"496\n" | |
] | |
} | |
], | |
"source": [ | |
"env = gym.make('CartPole-v1', render_mode=\"rgb_array\")\n", | |
"obs, info = env.reset()\n", | |
"\n", | |
"images = [\n", | |
" env.render()\n", | |
"]\n", | |
"\n", | |
"ep_len = 0\n", | |
"while True:\n", | |
" a, _, _ = ac.step(torch.as_tensor(obs, dtype=torch.float32))\n", | |
" obs, _, terminated, truncated, _ = env.step(a)\n", | |
" images.append(env.render())\n", | |
" ep_len += 1\n", | |
" if terminated or truncated:\n", | |
" obs, info = env.reset()\n", | |
" break\n", | |
"\n", | |
"print(ep_len)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"id": "d890b91c", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"/home/radek/miniforge3/envs/cleanrl/lib/python3.10/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\n", | |
" warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\n", | |
"/home/radek/miniforge3/envs/cleanrl/lib/python3.10/site-packages/pkg_resources/__init__.py:2870: DeprecationWarning: Deprecated call to `pkg_resources.declare_namespace('google')`.\n", | |
"Implementing implicit namespace packages (as specified in PEP 420) is preferred to `pkg_resources.declare_namespace`. See https://setuptools.pypa.io/en/latest/references/keywords.html#keyword-namespace-packages\n", | |
" declare_namespace(pkg)\n", | |
"/home/radek/miniforge3/envs/cleanrl/lib/python3.10/site-packages/pkg_resources/__init__.py:2870: DeprecationWarning: Deprecated call to `pkg_resources.declare_namespace('mpl_toolkits')`.\n", | |
"Implementing implicit namespace packages (as specified in PEP 420) is preferred to `pkg_resources.declare_namespace`. See https://setuptools.pypa.io/en/latest/references/keywords.html#keyword-namespace-packages\n", | |
" declare_namespace(pkg)\n", | |
"IMAGEIO FFMPEG_WRITER WARNING: input image is not divisible by macro_block_size=16, resizing from (600, 400) to (608, 400) to ensure video compatibility with most codecs and players. To prevent resizing, make your input image divisible by the macro_block_size or set the macro_block_size to 1 (risking incompatibility).\n", | |
"[swscaler @ 0x564abc0] Warning: data is not aligned! This can lead to a speed loss\n" | |
] | |
} | |
], | |
"source": [ | |
"import imageio\n", | |
"\n", | |
"imageio.mimsave('cartpole.mp4', images)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"id": "51442706", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"imageio.mimsave('cartpole.gif', images)" | |
] | |
} | |
], | |
"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.10.14" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment