Skip to content
Snippets Groups Projects
Commit b298d769 authored by anxieuse's avatar anxieuse
Browse files

Add train_on_batch

parent 63e58cfc
No related branches found
No related tags found
No related merge requests found
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Training RL to do Cartpole Balancing\n",
"\n",
"This notebooks is part of [AI for Beginners Curriculum](http://aka.ms/ai-beginners). It has been inspired by [this blog post](https://medium.com/swlh/policy-gradient-reinforcement-learning-with-keras-57ca6ed32555), [official TensorFlow documentation](https://www.tensorflow.org/tutorials/reinforcement_learning/actor_critic) and [this Keras RL example](https://keras.io/examples/rl/actor_critic_cartpole/).\n",
"\n",
"In this example, we will use RL to train a model to balance a pole on a cart that can move left and right on horizontal scale. We will use [OpenAI Gym](https://www.gymlibrary.ml/) environment to simulate the pole.\n",
"\n",
"> **Note**: You can run this lesson's code locally (eg. from Visual Studio Code), in which case the simulation will open in a new window. When running the code online, you may need to make some tweaks to the code, as described [here](https://towardsdatascience.com/rendering-openai-gym-envs-on-binder-and-google-colab-536f99391cc7).\n",
"\n",
"We will start by making sure Gym is installed:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Defaulting to user installation because normal site-packages is not writeable\n",
"Requirement already satisfied: gym in /home/leo/.local/lib/python3.10/site-packages (0.25.0)\n",
"Collecting pygame\n",
" Downloading pygame-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (21.9 MB)\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m21.9/21.9 MB\u001b[0m \u001b[31m3.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n",
"\u001b[?25hRequirement already satisfied: cloudpickle>=1.2.0 in /home/leo/.local/lib/python3.10/site-packages (from gym) (2.1.0)\n",
"Requirement already satisfied: numpy>=1.18.0 in /usr/lib/python3/dist-packages (from gym) (1.21.5)\n",
"Requirement already satisfied: gym-notices>=0.0.4 in /home/leo/.local/lib/python3.10/site-packages (from gym) (0.0.7)\n",
"Installing collected packages: pygame\n",
"Successfully installed pygame-2.1.2\n"
]
}
],
"source": [
"import sys\n",
"!{sys.executable} -m pip install gym pygame"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's create the CartPole environment and see how to operate on it. An environment has the following properties:\n",
"\n",
"* **Action space** is the set of possible actions that we can perform at each step of the simulation\n",
"* **Observation space** is the space of observations that we can make"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Action space: Discrete(2)\n",
"Observation space: 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)\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/leo/.local/lib/python3.10/site-packages/gym/core.py:329: DeprecationWarning: \u001b[33mWARN: Initializing wrapper in old step API which returns one bool instead of two. It is recommended to set `new_step_api=True` to use new step API. This will be the default behaviour in future.\u001b[0m\n",
" deprecation(\n",
"/home/leo/.local/lib/python3.10/site-packages/gym/wrappers/step_api_compatibility.py:39: DeprecationWarning: \u001b[33mWARN: Initializing environment in old step API which returns one bool instead of two. It is recommended to set `new_step_api=True` to use new step API. This will be the default behaviour in future.\u001b[0m\n",
" deprecation(\n"
]
}
],
"source": [
"import gym\n",
"import pygame\n",
"import tqdm\n",
"\n",
"env = gym.make(\"CartPole-v1\")\n",
"\n",
"print(f\"Action space: {env.action_space}\")\n",
"print(f\"Observation space: {env.observation_space}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's see how the simulation works. The following loop runs the simulation, until `env.step` does not return the termination flag `done`. We will randomly chose actions using `env.action_space.sample()`, which means the experiment will probably fail very fast (CartPole environment terminates when the speed of CartPole, its position or angle are outside certain limits).\n",
"\n",
"> Simulation will open in the new window. You can run the code several times and see how it behaves."
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/leo/.local/lib/python3.10/site-packages/gym/core.py:57: DeprecationWarning: \u001b[33mWARN: You are calling render method, but you didn't specified the argument render_mode at environment initialization. To maintain backward compatibility, the environment will render in human mode.\n",
"If you want to render in human mode, initialize the environment in this way: gym.make('EnvName', render_mode='human') and don't call the render method.\n",
"See here for more information: https://www.gymlibrary.ml/content/api/\u001b[0m\n",
" deprecation(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[-0.01024284 0.23410203 -0.02896851 -0.2558368 ] -> 1.0\n",
"[-0.0055608 0.42962533 -0.03408524 -0.5575143 ] -> 1.0\n",
"[ 0.00303171 0.6252088 -0.04523553 -0.86073816] -> 1.0\n",
"[ 0.01553589 0.82091665 -0.06245029 -1.1672944 ] -> 1.0\n",
"[ 0.03195422 1.0167931 -0.08579618 -1.4788847 ] -> 1.0\n",
"[ 0.05229008 1.2128513 -0.11537387 -1.7970835 ] -> 1.0\n",
"[ 0.07654711 1.0191944 -0.15131554 -1.542374 ] -> 1.0\n",
"[ 0.096931 0.82618064 -0.18216303 -1.3004787 ] -> 1.0\n",
"[ 0.11345461 0.63377684 -0.2081726 -1.0699085 ] -> 1.0\n",
"[ 0.12613015 0.83095175 -0.22957078 -1.420047 ] -> 1.0\n",
"Total reward: 10.0\n"
]
}
],
"source": [
"env.reset()\n",
"\n",
"done = False\n",
"total_reward = 0\n",
"while not done:\n",
" env.render()\n",
" obs, rew, done, info = env.step(env.action_space.sample())\n",
" total_reward += rew\n",
" print(f\"{obs} -> {rew}\")\n",
"print(f\"Total reward: {total_reward}\")\n",
"\n",
"env.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Youn can notice that observations contain 4 numbers. They are:\n",
"- Position of cart\n",
"- Velocity of cart\n",
"- Angle of pole\n",
"- Rotation rate of pole\n",
"\n",
"`rew` is the reward we receive at each step. You can see that in CartPole environment you are rewarded 1 point for each simulation step, and the goal is to maximize total reward, i.e. the time CartPole is able to balance without falling.\n",
"\n",
"During reinforcement learning, our goal is to train a **policy** $\\pi$, that for each state $s$ will tell us which action $a$ to take, so essentially $a = \\pi(s)$.\n",
"\n",
"If you want probabilistic solution, you can think of policy as returning a set of probabilities for each action, i.e. $\\pi(a|s)$ would mean a probability that we should take action $a$ at state $s$.\n",
"\n",
"## Policy Gradient Method\n",
"\n",
"In simplest RL algorithm, called **Policy Gradient**, we will train a neural network to predict the next action."
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import tensorflow as tf\n",
"from tensorflow import keras\n",
"import matplotlib.pyplot as plt\n",
"import torch\n",
"\n",
"num_inputs = 4\n",
"num_actions = 2\n",
"\n",
"# model = torch.nn.Sequential(\n",
"# torch.nn.Linear(num_inputs, 2),\n",
"# torch.nn.ReLU(),\n",
"# torch.nn.Linear(2, num_actions)\n",
"# )\n",
"\n",
"model = torch.nn.Sequential(\n",
" torch.nn.Linear(num_inputs, 128, bias=False, dtype=torch.float32),\n",
" torch.nn.ReLU(),\n",
" torch.nn.Linear(128, num_actions, bias = False, dtype=torch.float32),\n",
" torch.nn.Softmax(dim=1)\n",
")\n",
"\n",
"optimizer = torch.optim.Adam(model.parameters(), lr=0.01)\n",
"# optimizer = torch.optim.SGD(model.parameters(), lr=0.01)\n",
"# optimizer = keras.optimizers.Adam(learning_rate=0.01)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We will train the network by running many experiments, and updating our network after each run. Let's define a function that will run the experiment and return the results (so-called **trace**) - all states, actions (and their recommended probabilities), and rewards:"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {},
"outputs": [],
"source": [
"def run_episode(max_steps_per_episode = 10000,render=False): \n",
" states, actions, probs, rewards = [],[],[],[]\n",
" state = env.reset()\n",
" # print(state.dtype)\n",
" for _ in range(max_steps_per_episode):\n",
" if render:\n",
" env.render()\n",
" action_probs = model(torch.from_numpy(np.expand_dims(state,0)))[0]\n",
" action = np.random.choice(num_actions, p=np.squeeze(action_probs.detach().numpy()))\n",
" nstate, reward, done, info = env.step(action)\n",
" if done:\n",
" break\n",
" states.append(state)\n",
" actions.append(action)\n",
" probs.append(action_probs.detach().numpy())\n",
" rewards.append(reward)\n",
" state = nstate\n",
" return np.vstack(states), np.vstack(actions), np.vstack(probs), np.vstack(rewards)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can run one episode with untrained network and observe that total reward (AKA length of episode) is very low:"
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total reward: 24.0\n"
]
}
],
"source": [
"s,a,p,r = run_episode()\n",
"print(f\"Total reward: {np.sum(r)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"One of the tricky aspects of policy gradient algorithm is to use **discounted rewards**. The idea is that we compute the vector of total rewards at each step of the game, and during this process we discount the early rewards using some coefficient $gamma$. We also normalize the resulting vector, because we will use it as weight to affect our training: "
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [],
"source": [
"eps = 0.0001\n",
"\n",
"def discounted_rewards(rewards,gamma=0.99,normalize=True):\n",
" ret = []\n",
" s = 0\n",
" for r in rewards[::-1]:\n",
" s = r + gamma * s\n",
" ret.insert(0, s)\n",
" if normalize:\n",
" ret = (ret-np.mean(ret))/(np.std(ret)+eps)\n",
" return ret"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's do the actual training! We will run 300 episodes, and at each episode we will do the following:\n",
"\n",
"1. Run the experiment and collect the trace\n",
"1. Calculate the difference (`gradients`) between the actions taken, and by predicted probabilities. The less the difference is, the more we are sure that we have taken the right action.\n",
"1. Calculate discounted rewards and multiply gradients by discounted rewards - that will make sure that steps with higher rewards will make more effect on the final result than lower-rewarded ones\n",
"1. Expected target actions for our neural network would be partly taken from the predicted probabilities during the run, and partly from calculated gradients. We will use `alpha` parameter to determine to which extent gradients and rewards are taken into account - this is called *learning rate* of reinforcement algorithm.\n",
"1. Finally, we train our network on states and expected actions, and repeat the process "
]
},
{
"cell_type": "code",
"execution_count": 87,
"metadata": {},
"outputs": [],
"source": [
"def train_on_batch4(states,actions,probs,rewards):\n",
" rewards = discounted_rewards(rewards)\n",
" probs = probs[np.arange(len(probs)),actions]\n",
" # probs = probs.reshape(-1,1)\n",
" rewards = rewards.reshape(-1,1)\n",
" probs = torch.from_numpy(probs)\n",
" rewards = torch.from_numpy(rewards)\n",
" loss = -torch.mean(torch.log(probs) * rewards)\n",
" optimizer.zero_grad()\n",
" loss.backward()\n",
" optimizer.step()\n",
" return loss.item()\n",
"\n",
"def train_on_batch2(states,target):\n",
" states = torch.from_numpy(states)\n",
" # target = target.reshape(-1,1)\n",
" target = torch.from_numpy(target)\n",
" y = model(states)\n",
" print(y)\n",
" loss = -torch.mean(torch.log(y) * target)\n",
" optimizer.zero_grad()\n",
" loss.backward()\n",
" optimizer.step()\n",
" return loss.item()\n",
"\n",
"def train_on_batch(x, y):\n",
" x = torch.from_numpy(x)\n",
" y = torch.from_numpy(y)\n",
" optimizer.zero_grad()\n",
" predictions = model(x)\n",
" loss = torch.nn.CrossEntropyLoss()(predictions, y) # (y, predictions)\n",
" loss.backward()\n",
" optimizer.step()\n",
" return loss.item()\n",
" w.data.sub_(learning_rate * w.grad)\n",
" b.data.sub_(learning_rate * b.grad)\n",
" w.grad.zero_()\n",
" b.grad.zero_()\n",
" return loss"
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 -> 25.0\n",
"100 -> 21.0\n",
"200 -> 7.0\n"
]
},
{
"data": {
"text/plain": [
"[<matplotlib.lines.Line2D at 0x7f8843d55060>]"
]
},
"execution_count": 88,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"alpha = 1e-4\n",
"\n",
"history = []\n",
"for epoch in range(300):\n",
" states, actions, probs, rewards = run_episode()\n",
" one_hot_actions = np.eye(2)[actions.T][0]\n",
" gradients = one_hot_actions-probs\n",
" dr = discounted_rewards(rewards)\n",
" gradients *= dr\n",
" target = alpha*np.vstack([gradients])+probs\n",
" # loss = train_on_batch4(states,actions,probs,rewards)\n",
" train_on_batch(states,target)\n",
" history.append(np.sum(rewards))\n",
" if epoch%100==0:\n",
" print(f\"{epoch} -> {np.sum(rewards)}\")\n",
"\n",
"plt.plot(history)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's run the episode with rendering to see the result:"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/leo/.local/lib/python3.10/site-packages/gym/core.py:57: DeprecationWarning: \u001b[33mWARN: You are calling render method, but you didn't specified the argument render_mode at environment initialization. To maintain backward compatibility, the environment will render in human mode.\n",
"If you want to render in human mode, initialize the environment in this way: gym.make('EnvName', render_mode='human') and don't call the render method.\n",
"See here for more information: https://www.gymlibrary.ml/content/api/\u001b[0m\n",
" deprecation(\n"
]
},
{
"ename": "error",
"evalue": "display Surface quit",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31merror\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_41886/1459719159.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0m_\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrun_episode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/tmp/ipykernel_41886/4189192208.py\u001b[0m in \u001b[0;36mrun_episode\u001b[0;34m(max_steps_per_episode, render)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0m_\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmax_steps_per_episode\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0maction_probs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfrom_numpy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexpand_dims\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0maction\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mchoice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnum_actions\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msqueeze\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maction_probs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdetach\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnumpy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 429\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 430\u001b[0m \u001b[0;34m\"\"\"Renders the environment.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 431\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 432\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 433\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/wrappers/order_enforcing.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[0;34m\"set `disable_render_order_enforcing=True` on the OrderEnforcer wrapper.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 50\u001b[0m )\n\u001b[0;32m---> 51\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 52\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 429\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 430\u001b[0m \u001b[0;34m\"\"\"Renders the environment.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 431\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 432\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 433\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/wrappers/env_checker.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0menv_render_passive_checker\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 54\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 55\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/envs/classic_control/cartpole.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, mode)\u001b[0m\n\u001b[1;32m 215\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrenderer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_renders\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 216\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 217\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_render\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmode\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 218\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 219\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_render\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmode\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"human\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/envs/classic_control/cartpole.py\u001b[0m in \u001b[0;36m_render\u001b[0;34m(self, mode)\u001b[0m\n\u001b[1;32m 296\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 297\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msurf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpygame\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mflip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msurf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 298\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscreen\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mblit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msurf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 299\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmode\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"human\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 300\u001b[0m \u001b[0mpygame\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mevent\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpump\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31merror\u001b[0m: display Surface quit"
]
}
],
"source": [
"_ = run_episode(render=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Hopefully, you can see that pole can now balance pretty well!\n",
"\n",
"## Actor-Critic Model\n",
"\n",
"Actor-Critic model is the further development of policy gradients, in which we build a neural network to learn both the policy and estimated rewards. The network will have two outputs (or you can view it as two separate networks):\n",
"* **Actor** will recommend the action to take by giving us the state probability distribution, as in policy gradient model\n",
"* **Critic** would estimate what the reward would be from those actions. It returns total estimated rewards in the future at the given state.\n",
"\n",
"Let's define such a model: "
]
},
{
"cell_type": "code",
"execution_count": 103,
"metadata": {},
"outputs": [],
"source": [
"num_inputs = 4\n",
"num_actions = 2\n",
"num_hidden = 128\n",
"\n",
"inputs = keras.layers.Input(shape=(num_inputs,))\n",
"common = keras.layers.Dense(num_hidden, activation=\"relu\")(inputs)\n",
"action = keras.layers.Dense(num_actions, activation=\"softmax\")(common)\n",
"critic = keras.layers.Dense(1)(common)\n",
"\n",
"model = keras.Model(inputs=inputs, outputs=[action, critic])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We would need to slightly modify our `run_episode` function to return also critic results:"
]
},
{
"cell_type": "code",
"execution_count": 104,
"metadata": {},
"outputs": [],
"source": [
"def run_episode(max_steps_per_episode = 10000,render=False): \n",
" states, actions, probs, rewards, critic = [],[],[],[],[]\n",
" state = env.reset()\n",
" for _ in range(max_steps_per_episode):\n",
" if render:\n",
" env.render()\n",
" action_probs, est_rew = model(np.expand_dims(state,0))\n",
" action = np.random.choice(num_actions, p=np.squeeze(action_probs[0]))\n",
" nstate, reward, done, info = env.step(action)\n",
" if done:\n",
" break\n",
" states.append(state)\n",
" actions.append(action)\n",
" probs.append(tf.math.log(action_probs[0,action]))\n",
" rewards.append(reward)\n",
" critic.append(est_rew[0,0])\n",
" state = nstate\n",
" return states, actions, probs, rewards, critic"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we will run the main training loop. We will use manual network training process by computing proper loss functions and updating network parameters:"
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"running reward: 5.82 at episode 10\n",
"running reward: 9.43 at episode 20\n",
"running reward: 10.30 at episode 30\n",
"running reward: 10.28 at episode 40\n",
"running reward: 11.00 at episode 50\n",
"running reward: 13.01 at episode 60\n",
"running reward: 21.78 at episode 70\n",
"running reward: 40.54 at episode 80\n",
"running reward: 73.70 at episode 90\n",
"running reward: 100.19 at episode 100\n",
"running reward: 159.20 at episode 110\n",
"Solved at episode 114!\n"
]
}
],
"source": [
"optimizer = keras.optimizers.Adam(learning_rate=0.01)\n",
"huber_loss = keras.losses.Huber()\n",
"episode_count = 0\n",
"running_reward = 0\n",
"\n",
"while True: # Run until solved\n",
" state = env.reset()\n",
" episode_reward = 0\n",
" with tf.GradientTape() as tape:\n",
" _,_,action_probs, rewards, critic_values = run_episode()\n",
" episode_reward = np.sum(rewards)\n",
" \n",
" # Update running reward to check condition for solving\n",
" running_reward = 0.05 * episode_reward + (1 - 0.05) * running_reward\n",
"\n",
" # Calculate discounted rewards that will be labels for our critic\n",
" dr = discounted_rewards(rewards)\n",
"\n",
" # Calculating loss values to update our network\n",
" actor_losses = []\n",
" critic_losses = []\n",
" for log_prob, value, rew in zip(action_probs, critic_values, dr):\n",
" # When we took the action with probability `log_prob`, we received discounted reward of `rew`,\n",
" # while critic predicted it to be `value` \n",
" # First we calculate actor loss, to make actor predict actions that lead to higher rewards\n",
" diff = rew - value\n",
" actor_losses.append(-log_prob * diff)\n",
"\n",
" # The critic loss is to minimize the difference between predicted reward `value` and actual\n",
" # discounted reward `rew`\n",
" critic_losses.append(\n",
" huber_loss(tf.expand_dims(value, 0), tf.expand_dims(rew, 0))\n",
" )\n",
"\n",
" # Backpropagation\n",
" loss_value = sum(actor_losses) + sum(critic_losses)\n",
" grads = tape.gradient(loss_value, model.trainable_variables)\n",
" optimizer.apply_gradients(zip(grads, model.trainable_variables))\n",
"\n",
" # Log details\n",
" episode_count += 1\n",
" if episode_count % 10 == 0:\n",
" template = \"running reward: {:.2f} at episode {}\"\n",
" print(template.format(running_reward, episode_count))\n",
"\n",
" if running_reward > 195: # Condition to consider the task solved\n",
" print(\"Solved at episode {}!\".format(episode_count))\n",
" break\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's run the episode and see how good our model is:"
]
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {},
"outputs": [],
"source": [
"_ = run_episode(render=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, let's close the environment."
]
},
{
"cell_type": "code",
"execution_count": 106,
"metadata": {},
"outputs": [],
"source": [
"env.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Takeaway\n",
"\n",
"We have seen two RL algorithms in this demo: simple policy gradient, and more sophisticated actor-critic. You can see that those algorithms operate with abstract notions of state, action and reward - thus they can be applied to very different environments.\n",
"\n",
"Reinforcement learning allows us to learn the best strategy to solve the problem just by looking at the final reward. The fact that we do not need labelled datasets allows us to repeat simulations many times to optimize our models. However, there are still many challenges in RL, which you may learn if you decide to focus more on this interesting area of AI. "
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.4 64-bit",
"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.4"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment