{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Pixels-to-Control Learning\n",
"\n",
"> In this post, We will take a hands-on-lab of Pixels-to-Control Learning which is related on Deep Reinforcement Learning. This tutorial is the software lab, which is part of \"introduction to deep learning 2021\" offered from MIT 6.S191.\n",
"\n",
"- toc: true \n",
"- badges: true\n",
"- comments: true\n",
"- author: Chanseok Kang\n",
"- categories: [Python, Reinforcement_Learning, Tensorflow, MIT]\n",
"- image: images/Pong-v0.gif"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Copyright Information\n",
"\n",
"Copyright 2021 MIT 6.S191 Introduction to Deep Learning. All Rights Reserved.\n",
"\n",
"Licensed under the MIT License. You may not use this file except in compliance\n",
"with the License. Use and/or modification of this code outside of 6.S191 must\n",
"reference:\n",
"\n",
"© MIT 6.S191: Introduction to Deep Learning\n",
"http://introtodeeplearning.com"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Reinforcement Learning\n",
"\n",
"Reinforcement learning (RL) is a subset of machine learning which poses learning problems as interactions between agents and environments. It often assumes agents have no prior knowledge of a world, so they must learn to navigate environments by optimizing a reward function. Within an environment, an agent can take certain actions and receive feedback, in the form of positive or negative rewards, with respect to their decision. As such, an agent's feedback loop is somewhat akin to the idea of \"trial and error\", or the manner in which a child might learn to distinguish between \"good\" and \"bad\" actions.\n",
"\n",
"In practical terms, our RL agent will interact with the environment by taking an action at each timestep, receiving a corresponding reward, and updating its state according to what it has \"learned\". \n",
"\n",
"![alt text](image/reinforcement-learning-fig1-700.jpg)\n",
"\n",
"While the ultimate goal of reinforcement learning is to teach agents to act in the real, physical world, games provide a convenient proving ground for developing RL algorithms and agents. Games have some properties that make them particularly well suited for RL: \n",
"\n",
"1. In many cases, games have perfectly describable environments. For example, all rules of chess can be formally written and programmed into a chess game simulator;\n",
"2. Games are massively parallelizable. Since they do not require running in the real world, simultaneous environments can be run on large data clusters; \n",
"3. Simpler scenarios in games enable fast prototyping. This speeds up the development of algorithms that could eventually run in the real-world; and\n",
"4. ... Games are fun! \n",
"\n",
"In previous labs, we have explored both supervised (with LSTMs, CNNs) and unsupervised / semi-supervised (with VAEs) learning tasks. Reinforcement learning is fundamentally different, in that we are training a deep learning algorithm to govern the actions of our RL agent, that is trying, within its environment, to find the optimal way to achieve a goal. The goal of training an RL agent is to determine the best next step to take to earn the greatest final payoff or return. In this lab, we focus on building a reinforcement learning algorithm to master two different environments with varying complexity. \n",
"\n",
"1. **Cartpole**: Balance a pole, protruding from a cart, in an upright position by only moving the base left or right. Environment with a low-dimensional observation space.\n",
"2. [**Pong**](https://en.wikipedia.org/wiki/Pong): Beat your competitors (whether other AI or humans!) at the game of Pong. Environment with a high-dimensional observation space -- learning directly from raw pixels.\n",
"\n",
"Let's get started! First we'll import TensorFlow, the course package, and some dependencies."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"import numpy as np\n",
"import base64, io, time, gym\n",
"import IPython, functools\n",
"import matplotlib.pyplot as plt\n",
"from tqdm import tqdm"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To prevent memory allocation in GPU, we need to limit the memory usage."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 Physical GPUs, 1 Logical GPUs\n"
]
}
],
"source": [
"gpus = tf.config.experimental.list_physical_devices('GPU')\n",
"if gpus:\n",
" try:\n",
" # Currently, memory growth needs to be the same across GPUs\n",
" for gpu in gpus:\n",
" tf.config.experimental.set_memory_growth(gpu, True)\n",
" logical_gpus = tf.config.experimental.list_logical_devices('GPU')\n",
" print(len(gpus), \"Physical GPUs,\", len(logical_gpus), \"Logical GPUs\")\n",
" except RuntimeError as e:\n",
" # Memory growth must be set before GPUs have been initialized\n",
" print(e)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Before we dive in, let's take a step back and outline our approach, which is generally applicable to reinforcement learning problems in general:\n",
"\n",
"1. **Initialize our environment and our agent**: here we will describe the different observations and actions the agent can make in the environemnt.\n",
"2. **Define our agent's memory**: this will enable the agent to remember its past actions, observations, and rewards.\n",
"3. **Define a reward function**: describes the reward associated with an action or sequence of actions.\n",
"4. **Define the learning algorithm**: this will be used to reinforce the agent's good behaviors and discourage bad behaviors.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Part 1: Cartpole\n",
"\n",
"### 3.1 Define the Cartpole environment and agent\n",
"\n",
"#### Environment \n",
"\n",
"In order to model the environment for both the Cartpole and Pong tasks, we'll be using a toolkit developed by OpenAI called [OpenAI Gym](https://gym.openai.com/). It provides several pre-defined environments for training and testing reinforcement learning agents, including those for classic physics control tasks, Atari video games, and robotic simulations. To access the Cartpole environment, we can use `env = gym.make(\"CartPole-v0\")`, which we gained access to when we imported the `gym` package. We can instantiate different [environments](https://gym.openai.com/envs/#classic_control) by passing the enivronment name to the `make` function.\n",
"\n",
"One issue we might experience when developing RL algorithms is that many aspects of the learning process are inherently random: initializing game states, changes in the environment, and the agent's actions. As such, it can be helpful to set a initial \"seed\" for the environment to ensure some level of reproducibility. Much like you might use `numpy.random.seed`, we can call the comparable function in gym, `seed`, with our defined environment to ensure the environment's random variables are initialized the same each time."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[1]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"env = gym.make('CartPole-v0')\n",
"env.seed(1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In Cartpole, a pole is attached by an un-actuated joint to a cart, which moves along a frictionless track. The pole starts upright, and the goal is to prevent it from falling over. The system is controlled by applying a force of +1 or -1 to the cart. A reward of +1 is provided for every timestep that the pole remains upright. The episode ends when the pole is more than 15 degrees from vertical, or the cart moves more than 2.4 units from the center of the track. A visual summary of the cartpole environment is depicted below:\n",
"\n",
"\n",
"\n",
"Given this setup for the environment and the objective of the game, we can think about: 1) what observations help define the environment's state; 2) what actions the agent can take. \n",
"\n",
"First, let's consider the observation space. In this Cartpole environment our observations are:\n",
"\n",
"1. Cart position\n",
"2. Cart velocity\n",
"3. Pole angle\n",
"4. Pole rotation rate\n",
"\n",
"We can confirm the size of the space by querying the environment's observation space:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Environment has observation space = Box(-3.4028234663852886e+38, 3.4028234663852886e+38, (4,), float32)\n"
]
}
],
"source": [
"n_observations = env.observation_space\n",
"print(\"Environment has observation space =\", n_observations)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Second, we consider the action space. At every time step, the agent can move either right or left. Again we can confirm the size of the action space by querying the environment:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Number of possible actions that the agent can choose from = 2\n"
]
}
],
"source": [
"n_actions = env.action_space.n\n",
"print(\"Number of possible actions that the agent can choose from =\", n_actions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Cartpole agent\n",
"\n",
"Now that we have instantiated the environment and understood the dimensionality of the observation and action spaces, we are ready to define our agent. In deep reinforcement learning, a deep neural network defines the agent. This network will take as input an observation of the environment and output the probability of taking each of the possible actions. Since Cartpole is defined by a low-dimensional observation space, a simple feed-forward neural network should work well for our agent. We will define this using the `Sequential` API.\n",
"\n",
"> Note: Of course, you can define your custom model, but in this environment, simple model can handle this."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Defines a feed-forward neural network\n",
"def create_cartpole_model():\n",
" model = tf.keras.models.Sequential([\n",
" # First Dense layer\n",
" tf.keras.layers.Dense(units=32, activation='relu'),\n",
" tf.keras.layers.Dense(units=n_actions, activation=None)\n",
" ])\n",
" return model\n",
"\n",
"cartpole_model = create_cartpole_model()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that we have defined the core network architecture, we will define an *action function* that executes a forward pass through the network, given a set of observations, and samples from the output. This sampling from the output probabilities will be used to select the next action for the agent. We will also add support so that the `choose_action` function can handle either a single observation or a batch of observations.\n",
"\n",
"**Critically, this action function is totally general -- we will use this function for both Cartpole and Pong, and it is applicable to other RL tasks, as well!**"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"### Define the agent's action function ###\n",
"\n",
"# Function that takes observations as input, executes a forward pass through model, \n",
"# and outputs a sampled action.\n",
"# Arguments:\n",
"# model: the network that defines our agent\n",
"# observation: observation(s) which is/are fed as input to the model\n",
"# single: flag as to whether we are handling a single observation or batch of\n",
"# observations, provided as an np.array\n",
"# Returns:\n",
"# action: choice of agent action\n",
"def choose_action(model, observation, single=True):\n",
" # add batch dimension to the observation if only a single example was provided\n",
" observation = np.expand_dims(observation, axis=0) if single else observation\n",
"\n",
" # feed the observations through the model to predict the log \n",
" # probabilities of each possible action.\n",
" logits = model.predict(observation)\n",
" \n",
" # Choose an action from the categorical distribution defined by the log \n",
" # probabilities of each possible action.\n",
" action = tf.random.categorical(logits, num_samples=1)\n",
"\n",
" action = action.numpy().flatten()\n",
"\n",
" return action[0] if single else action"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3.2 Define the agent's memory\n",
"\n",
"Now that we have instantiated the environment and defined the agent network architecture and action function, we are ready to move on to the next step in our RL workflow:\n",
"1. **Initialize our environment and our agent**: here we will describe the different observations and actions the agent can make in the environemnt.\n",
"2. **Define our agent's memory**: this will enable the agent to remember its past actions, observations, and rewards.\n",
"3. **Define the learning algorithm**: this will be used to reinforce the agent's good behaviors and discourage bad behaviors.\n",
"\n",
"In reinforcement learning, training occurs alongside the agent's acting in the environment; an *episode* refers to a sequence of actions that ends in some terminal state, such as the pole falling down or the cart crashing. The agent will need to remember all of its observations and actions, such that once an episode ends, it can learn to \"reinforce\" the good actions and punish the undesirable actions via training. Our first step is to define a simple `Memory` buffer that contains the agent's observations, actions, and received rewards from a given episode. We will also add support to combine a list of `Memory` objects into a single `Memory`. This will be very useful for batching, which will help you accelerate training later on in the lab.\n",
"\n",
"**Once again, note the modularity of this memory buffer -- it can and will be applied to other RL tasks as well!**"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"### Agent Memory ###\n",
"\n",
"class Memory:\n",
" def __init__(self): \n",
" self.clear()\n",
"\n",
" # Resets/restarts the memory buffer\n",
" def clear(self): \n",
" self.observations = []\n",
" self.actions = []\n",
" self.rewards = []\n",
"\n",
" # Add observations, actions, rewards to memory\n",
" def add_to_memory(self, new_observation, new_action, new_reward): \n",
" self.observations.append(new_observation)\n",
"\n",
" # update the list of actions with new action\n",
" self.actions.append(new_action)\n",
"\n",
" # update the list of rewards with new reward\n",
" self.rewards.append(new_reward)\n",
"\n",
"# Helper function to combine a list of Memory objects into a single Memory.\n",
"# This will be very useful for batching.\n",
"def aggregate_memories(memories):\n",
" batch_memory = Memory()\n",
" \n",
" for memory in memories:\n",
" for step in zip(memory.observations, memory.actions, memory.rewards):\n",
" batch_memory.add_to_memory(*step)\n",
" \n",
" return batch_memory\n",
"\n",
"# Instantiate a single Memory buffer\n",
"memory = Memory()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3.3 Reward function\n",
"\n",
"We're almost ready to begin the learning algorithm for our agent! The next step is to compute the rewards of our agent as it acts in the environment. Since we (and the agent) is uncertain about if and when the game or task will end (i.e., when the pole will fall), it is useful to emphasize getting rewards **now** rather than later in the future -- this is the idea of discounting. This is a similar concept to discounting money in the case of interest. ecall from lecture, we use reward discount to give more preference at getting rewards now rather than later in the future. The idea of discounting rewards is similar to discounting money in the case of interest.\n",
"\n",
"To compute the expected cumulative reward, known as the **return**, at a given timestep in a learning episode, we sum the discounted rewards expected at that time step $t$, within a learning episode, and projecting into the future. We define the return (cumulative reward) at a time step $t$, $R_{t}$ as:\n",
"\n",
">$R_{t}=\\sum_{k=0}^\\infty\\gamma^kr_{t+k}$\n",
"\n",
"where $0 < \\gamma < 1$ is the discount factor and $r_{t}$ is the reward at time step $t$, and the index $k$ increments projection into the future within a single learning episode. Intuitively, you can think of this function as depreciating any rewards received at later time steps, which will force the agent prioritize getting rewards now. Since we can't extend episodes to infinity, in practice the computation will be limited to the number of timesteps in an episode -- after that the reward is assumed to be zero.\n",
"\n",
"Take note of the form of this sum -- we'll have to be clever about how we implement this function. Specifically, we'll need to initialize an array of zeros, with length of the number of time steps, and fill it with the real discounted reward values as we loop through the rewards from the episode, which will have been saved in the agents memory. What we ultimately care about is which actions are better relative to other actions taken in that episode -- so, we'll normalize our computed rewards, using the mean and standard deviation of the rewards across the learning episode.\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"### Reward function ###\n",
"\n",
"# Helper function that normalizes an np.array x\n",
"def normalize(x):\n",
" x -= np.mean(x)\n",
" x /= np.std(x)\n",
" return x.astype(np.float32)\n",
"\n",
"# Compute normalized, discounted, cumulative rewards (i.e., return)\n",
"# Arguments:\n",
"# rewards: reward at timesteps in episode\n",
"# gamma: discounting factor\n",
"# Returns:\n",
"# normalized discounted reward\n",
"def discount_rewards(rewards, gamma=0.95): \n",
" discounted_rewards = np.zeros_like(rewards)\n",
" R = 0\n",
" for t in reversed(range(0, len(rewards))):\n",
" # update the total discounted reward\n",
" R = R * gamma + rewards[t]\n",
" discounted_rewards[t] = R\n",
" \n",
" return normalize(discounted_rewards)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3.4 Learning algorithm\n",
"\n",
"Now we can start to define the learing algorithm which will be used to reinforce good behaviors of the agent and discourage bad behaviours. In this lab, we will focus on *policy gradient* methods which aim to **maximize** the likelihood of actions that result in large rewards. Equivalently, this means that we want to **minimize** the negative likelihood of these same actions. We achieve this by simply **scaling** the probabilities by their associated rewards -- effectively amplifying the likelihood of actions that resujlt in large rewards.\n",
"\n",
"Since the log function is monotonically increasing, this means that minimizing **negative likelihood** is equivalent to minimizing **negative log-likelihood**. Recall that we can easily compute the negative log-likelihood of a discrete action by evaluting its [softmax cross entropy](https://www.tensorflow.org/api_docs/python/tf/nn/sparse_softmax_cross_entropy_with_logits). Like in supervised learning, we can use stochastic gradient descent methods to achieve the desired minimization. \n",
"\n",
"Let's begin by defining the loss function."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"### Loss function ###\n",
"\n",
"# Arguments:\n",
"# logits: network's predictions for actions to take\n",
"# actions: the actions the agent took in an episode\n",
"# rewards: the rewards the agent received in an episode\n",
"# Returns:\n",
"# loss\n",
"def compute_loss(logits, actions, rewards): \n",
" # compute the negative log probabilities\n",
" neg_logprob = tf.nn.sparse_softmax_cross_entropy_with_logits(\n",
" logits=logits, labels=actions)\n",
" \n",
" # scale the negative log probability by the rewards\n",
" loss = tf.reduce_mean(neg_logprob * rewards)\n",
" \n",
" return loss"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's use the loss function to define a training step of our learning algorithm:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"### Training step (forward and backpropagation) ###\n",
"\n",
"def train_step(model, optimizer, observations, actions, discounted_rewards):\n",
" with tf.GradientTape() as tape:\n",
" # Forward propagate through the agent network\n",
" logits = model(observations)\n",
"\n",
" # compute the loss\n",
" loss = compute_loss(logits, actions, discounted_rewards)\n",
"\n",
" # run backpropagation to minimize the loss using the tape.gradient method\n",
" grads = tape.gradient(loss, model.trainable_variables)\n",
" optimizer.apply_gradients(zip(grads, model.trainable_variables))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3.5 Run cartpole!\n",
"\n",
"Having had no prior knowledge of the environment, the agent will begin to learn how to balance the pole on the cart based only on the feedback received from the environment! Having defined how our agent can move, how it takes in new observations, and how it updates its state, we'll see how it gradually learns a policy of actions to optimize balancing the pole as long as possible. To do this, we'll track how the rewards evolve as a function of training -- how should the rewards change as training progresses?"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"from IPython import display as ipythondisplay\n",
"\n",
"class LossHistory:\n",
" def __init__(self, smoothing_factor=0.0):\n",
" self.alpha = smoothing_factor\n",
" self.loss = []\n",
" def append(self, value):\n",
" self.loss.append( self.alpha*self.loss[-1] + (1-self.alpha)*value if len(self.loss)>0 else value )\n",
" def get(self):\n",
" return self.loss\n",
"\n",
"class PeriodicPlotter:\n",
" def __init__(self, sec, xlabel='', ylabel='', scale=None):\n",
"\n",
" self.xlabel = xlabel\n",
" self.ylabel = ylabel\n",
" self.sec = sec\n",
" self.scale = scale\n",
"\n",
" self.tic = time.time()\n",
"\n",
" def plot(self, data):\n",
" if time.time() - self.tic > self.sec:\n",
" plt.cla()\n",
"\n",
" if self.scale is None:\n",
" plt.plot(data)\n",
" elif self.scale == 'semilogx':\n",
" plt.semilogx(data)\n",
" elif self.scale == 'semilogy':\n",
" plt.semilogy(data)\n",
" elif self.scale == 'loglog':\n",
" plt.loglog(data)\n",
" else:\n",
" raise ValueError(\"unrecognized parameter scale {}\".format(self.scale))\n",
"\n",
" plt.xlabel(self.xlabel); plt.ylabel(self.ylabel)\n",
" ipythondisplay.clear_output(wait=True)\n",
" ipythondisplay.display(plt.gcf())\n",
"\n",
" self.tic = time.time()"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"