From 4b85a34082ce84e3be153fce7692cf5380bd8ab9 Mon Sep 17 00:00:00 2001 From: Dmitri Soshnikov Date: Fri, 18 Jun 2021 21:08:05 +0300 Subject: [PATCH] Add Reinforcement Learning lesson 2 --- 8-Reinforcement/2-Gym/README.md | 277 ++++++++- 8-Reinforcement/2-Gym/assignment.md | 38 +- .../2-Gym/images/cartpole-balance.gif | Bin 0 -> 41888 bytes .../2-Gym/images/cartpole-nobalance.gif | Bin 0 -> 43717 bytes 8-Reinforcement/2-Gym/images/cartpole.png | Bin 0 -> 926 bytes 8-Reinforcement/2-Gym/images/mountaincar.png | Bin 0 -> 8161 bytes 8-Reinforcement/2-Gym/notebook.ipynb | 569 ++++++++++++++++++ 8-Reinforcement/2-Gym/solution/CartPole.ipynb | 0 8 files changed, 856 insertions(+), 28 deletions(-) create mode 100644 8-Reinforcement/2-Gym/images/cartpole-balance.gif create mode 100644 8-Reinforcement/2-Gym/images/cartpole-nobalance.gif create mode 100644 8-Reinforcement/2-Gym/images/cartpole.png create mode 100644 8-Reinforcement/2-Gym/images/mountaincar.png create mode 100644 8-Reinforcement/2-Gym/notebook.ipynb delete mode 100644 8-Reinforcement/2-Gym/solution/CartPole.ipynb diff --git a/8-Reinforcement/2-Gym/README.md b/8-Reinforcement/2-Gym/README.md index a934f98a6..787cff628 100644 --- a/8-Reinforcement/2-Gym/README.md +++ b/8-Reinforcement/2-Gym/README.md @@ -1,55 +1,280 @@ -# [Lesson Topic] +# CartPole Skating -Add a sketchnote if possible/appropriate +The problem we have been solving in the previous lesson might seem like a toy problem, not really applicable for real life scenarios. This is not the case, because many real world problems are like that - including playing chess or go. They are similar, because we also have a board with given rules and **discrete state**. -![Embed a video here if available](video-url) +In this lesson we will apply the same principles of Q-Learning to a problem with **continuous state**, i.e. a state that is given by one or more real numbers. We will deal with the following problem: -## [Pre-lecture quiz](link-to-quiz-app) 45 +> **Problem**: If Peter wants to escape from the wolf, he needs to be able to move faster than him. We will see how Peter can learn to skate, in particular, to keep balance, using Q-Learning. -Describe what we will learn +We will use a simplified version of balancing known as **CartPole** problem. In cartpole world, we have a horizontal slider that can move left or right, and the goal is to balance a pole staying on top of it. -### Introduction + -Describe what will be covered +## Prerequisites -> Notes +In this lesson, we will be using a library called **OpenAI Gym** to simulate different **environments**. It is preferred to 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). +## OpenAI Gym -### Prerequisite +In the previous lesson, the rules of the game and the state were given by `Board` class, which we defined ourselves. Here we will use a special **sumulation environment**, which will simulate the physics behind the balancing pole. One of the most popular simulation environments for training Reinforcement Learning algorithms is called [Gym](https://gym.openai.com/), which is maintained by [OpenAI](https://openai.com/). By using gym we can create difference **environments**: from cartpole simulation to Atari games. -What steps should have been covered before this lesson? +> **Note**: You can see other environments available from OpenAI Gym [here](https://gym.openai.com/envs/#classic_control). -### Preparation +First, let's install the gym and import required libraries: -Preparatory steps to start this lesson +```python +import sys +!{sys.executable} -m pip install gym ---- +import gym +import matplotlib.pyplot as plt +import numpy as np +import random +``` + +## CartPole Environment + +To work with CartPole balancing problem, we need to initialize corresponding environment. Each environment is associated with: +* **Observation space** that defines the structure of information that we receive from the environment. For cartpole problem, we receive position of the pole, velocity and some other values. +* **Action space** that defines possible actions. In our case action space is discrete, and consists of two actions - **left** and **right**. + +```python +env = gym.make("CartPole-v1") +print(env.action_space) +print(env.observation_space) +print(env.action_space.sample()) +``` + +To see how the environment works, let's run a short simulation for 100 steps. At each step, we provide one of the actions to be taken - in this simulation we just randomly select an action from `action_space`. Run the code below and see what it leads to. + +> **Note**: Remember that it is preferred to run this code on local Python installation! + +```python +env.reset() + +for i in range(100): + env.render() + env.step(env.action_space.sample()) +env.close() +``` + +You should be seeing something similar to this one: + +![](images/cartpole-nobalance.gif) + +During simulation, we need to get observatons in order to decide how to act. In fact, `step` function returns us back current observations, reward function, and the `done` flag that indicates whether it makes sense to continue the simulation or not: + +```python +env.reset() + +done = False +while not done: + env.render() + obs, rew, done, info = env.step(env.action_space.sample()) + print(f"{obs} -> {rew}") +env.close() +``` +You will end up seeing something like this in the notebook output: +```text +[ 0.03403272 -0.24301182 0.02669811 0.2895829 ] -> 1.0 +[ 0.02917248 -0.04828055 0.03248977 0.00543839] -> 1.0 +[ 0.02820687 0.14636075 0.03259854 -0.27681916] -> 1.0 +[ 0.03113408 0.34100283 0.02706215 -0.55904489] -> 1.0 +[ 0.03795414 0.53573468 0.01588125 -0.84308041] -> 1.0 +... +[ 0.17299878 0.15868546 -0.20754175 -0.55975453] -> 1.0 +[ 0.17617249 0.35602306 -0.21873684 -0.90998894] -> 1.0 +``` + +The observation vector that is returned at each step of the simulation contains the following values: +* Position of cart +* Velocity of cart +* Angle of pole +* Rotation rate of pole + +We can get min and max value of those numbers: + +```python +print(env.observation_space.low) +print(env.observation_space.high) +``` + +You may also notice that reward value on each simulation step is always 1. This is because our goal is to survive as long as possible, i.e. keep the pole to a reasonably vertical position for the longest period of time. + +> In fact, CartPole simulation is considered solved if we manage to get the average reward of 195 over 100 consecutive trials. + +## State Discretization + +In Q=Learning, we need to build Q-Table that defines what to do at each state. To be able to do this, we need state to be **discreet**, more precisely, it should contain finite number of disctete values. Thus, we need somehow to **discretize** our observations, mapping them to finite set of states. + +There are a few ways we can do this: + +* If we know the interval of a certain value, we can divide this interval into a number of **bins**, and then replace the value by the number of bin that it belongs to. This can be done using numpy [`digitize`](https://numpy.org/doc/stable/reference/generated/numpy.digitize.html) method. In this case, we will precisely know the state size, because it will depend on the number of bins we select for digitalization. +* We can use linear interpolation to bring values to some finite interval (say, from -20 to 20), and then convert numbers to integers by rounding them. This gives us a bit less control on the size of the state, especially if we do not know the exact ranges of input values. For example, in our case 2 out of 4 values do not have upper/lower bounds on their values, which may result in the infinite number of states. + +In our example, we will go with the second approach. As you may notice later, despite undefined upper/lower bounds, those value rarely take values outside of certain finite intervals, thus those states with extreme values will be very rare. + +Here is the function that will take the observation from our model, and produces a tuple of 4 integer values: + +```python +def discretize(x): + return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int)) +``` +Let's also explore other discretization method using bins: +```python +def create_bins(i,num): + return np.arange(num+1)*(i[1]-i[0])/num+i[0] + +print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10)) -[Step through content in blocks] +ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # intervals of values for each parameter +nbins = [20,20,10,10] # number of bins for each parameter +bins = [create_bins(ints[i],nbins[i]) for i in range(4)] -## [Topic 1] +def discretize_bins(x): + return tuple(np.digitize(x[i],bins[i]) for i in range(4)) +``` + +Let's now run a short simulation and observe those discrete environment values. Feel free to try both `discretize` and `discretize_bins` and see if there is a difference. + +> **Note**: `discretize_bins` returns the bin number, which is 0-based, thus for values of input variable around 0 it returns the number from the middle of the interval (10). In `discretize`, we did not care about the range of output values, allowing them to be negative, thus the state values are not shifted, and 0 corresponds to 0. + +```python +env.reset() + +done = False +while not done: + #env.render() + obs, rew, done, info = env.step(env.action_space.sample()) + #print(discretize_bins(obs)) + print(discretize(obs)) +env.close() +``` +> **Note**: Uncomment the line starting with `env.render` if you want to see how environment executes. Otherwise you can execute it in the background, which is faster. We will use this "invisible" execution during our Q-Learning process. + +## Q-Table Structure + +In our previous lesson, the state was a simple pair of numbers from 0 to 8, and thus it was convenient to represent Q-Table by numpy tensor with shape 8x8x2. If we use bins discretization, the size of our state vector is also known, so we can use the same approach and represent state by an array of shape 20x20x10x10x2 (here 2 is the dimension of action space, and first dimensions correspond to the number of bins we have selected to use for each of the parameters in observation space). + +However, sometimes precise dimensions of the observation space are not known. In case of `discretize` function, we may never be sure that our state stays within certain limits, because some of the original values are not bound. Thus, we will use slightly different approach and represent Q-Table by a dictionary. We will use the pair *(state,action)* as the dictionary key, and the value would correspond to Q-Table entry value. + +```python +Q = {} +actions = (0,1) + +def qvalues(state): + return [Q.get((state,a),0) for a in actions] +``` + +Here we also define a function `qvalues`, which returns a list of Q-Table values for a given state that correspond to all possible actions. If the entry is not present in the Q-Table, we will return 0 as the default. + +## Let's Start Q-Learning! + +Now we are ready to teach Peter to balance! First, let's set some hyperparameterers: + +```python +# hyperparameters +alpha = 0.3 +gamma = 0.9 +epsilon = 0.90 +``` + +Here, `alpha` is the **learning rate** that defines to which extent we should adjust the current values of Q-Table at each step. In previous lesson we have started with 1, and then decreased `alpha` to lower values during training. In this example we will keep it constant just for simplicity, and you can experiment with adjusting `alpha` values later. + +`gamma` is the **discount factor** that shows to which extent we should prioritize future reward over current reward. + +`epsilon` is the **exploration/exploitation factor** that determines whether we should prefer exploration to exploitation or vice versa. In our algorithm, we will in `epsilon` percent of the cases select the next action according to Q-Table values, and in the remaining number of cases we will execute random action. This will allow us to explore the areas of search space that we have never seen before. + +> In terms of balancing - choosing random action (exploration) would act as a random punch in the wrong direction, and the pole would have to learn how to recover the balance from those "mistakes" + +We would also make two improvements to our algorithm from the previous lesson: + +* Calculating average cumulative reward over a number of simulations. We will print the progress each 5000 iterations, and we will average out our cumulative reward over that period of time. It means that if we get more than 195 point - we can consider the problem solved, with even higher quality than required. +* We will calculate maximum average cumulative result `Qmax`, and we will store the Q-Table corresponding to that result. When you run the training you will notice that sometimes the average cumulative result starts to drop, and we want to keep the values of Q-Table that correspond to the best model observed during training. + +We will also collect all cumulative rewards at each simulation at `rewards` vector for further plotting. -### Task: +```python +def probs(v,eps=1e-4): + v = v-v.min()+eps + v = v/v.sum() + return v -Work together to progressively enhance your codebase to build the project with shared code: +Qmax = 0 +cum_rewards = [] +rewards = [] +for epoch in range(100000): + obs = env.reset() + done = False + cum_reward=0 + # == do the simulation == + while not done: + s = discretize(obs) + if random.random() Qmax: + Qmax = np.average(cum_rewards) + Qbest = Q + cum_rewards=[] ``` -✅ Knowledge Check - use this moment to stretch students' knowledge with open questions +What you may notice from those results: +* We are very close achieving the goal of getting 195 cumulative reward over 100+ consecutive runs of the simulation, or we may have actually achieved it! Even if we get smaller numbers, we still do not know, because we average over 5000 runs, and only 100 runs is required in the formal criteria. +* Sometimes the reward start to drop, which means that we can "destroy" already learnt values in Q-Table with the ones that make situation worse -## [Topic 2] +To make learning more stable, it makes sense to adjust some of our hyperparameters during training. In particular: +* For **learning rate**, `alpha`, we may start with values close to 1, and then keep decreasing the parameter. With time, we will be getting good probability values in Q-Table, and thus we should be adjusting them slightly, and not overwriting completely with new values. +* We may want to increase the `eplilon` slowly, in order to be exploring less, and expliting more. It probably makes sense to start with lower value of `epsilon`, and move up to almost 1 -## [Topic 3] +> **Task 1**: Play with hyperparameter values and see if you can achieve higher cumulative reward. Are you getting above 195? +> **Task 2**: To formally solve the problem, you need to get 195 average reward across 100 consecutive runs. Measure that during training and make sure that you have formally solved the problem! + +## Seeing the Result in Action + +Now it would be interesting to actually see how the trained model behaves. Let's run the simulation, and we will be following the same action selection strategy as during training: sampling according to the probability distribution in Q-Table: ## 🚀Challenge Add a challenge for students to work on collaboratively in class to enhance the project -Optional: add a screenshot of the completed lesson's UI if appropriate +```python +obs = env.reset() +done = False +while not done: + s = discretize(obs) + env.render() + v = probs(np.array(qvalues(s))) + a = random.choices(actions,weights=v)[0] + obs,_,done,_ = env.step(a) +env.close() +``` + +You should see something like this: + +![](images/cartpole-balance.gif) + +> **Task 3**: Here, we were using the final copy of Q-Table, which may not be the best one. Remember that we have stored the best-performing Q-Table into `Qbest` variable! Try the same example with the best-performing Q-Table by copying `Qbest` over to `Q` and see if you notice the difference. + +> **Task 4**: Here we were not selecting the best action on each step, but rather sampling with corresponding probability distribution. Would it make more sense to always select the best action, with highest Q-Table value? This can be easily done by using `np.argmax` function to find out the action number corresponding to highers Q-Table value. Implement this strategy and see if it improves the balancing. + +## [Post-lecture quiz](link-to-quiz-app) -## [Post-lecture quiz](link-to-quiz-app) 46 +## Assignment: [Train Mountain Car](assignment.md) -## Review & Self Study +## Conclusion -## Assignment [Assignment Name](assignment.md) +We have now learnt how to train agents to achieve good results just by providing them a reward function that defines the desired state of the game, and by giving it an opportinity to intellegently explore the search space. We have successfully applied Q-Learning algorithm in the cases of discrete and continuous environments, but with discrete actions. In the are of reinforcement learning, we need to further study situations where action state is also continuous, and when observation space is much more complex, such as the image from Atarti game screen. In those problems we often need to use more powerful machine learning techniques, such as neural networks, in order to achieve good results. Those more advanced topics are the subject of more advanced Deep Reinforcement Learning course. \ No newline at end of file diff --git a/8-Reinforcement/2-Gym/assignment.md b/8-Reinforcement/2-Gym/assignment.md index d4badb79f..1e755bdb0 100644 --- a/8-Reinforcement/2-Gym/assignment.md +++ b/8-Reinforcement/2-Gym/assignment.md @@ -1,9 +1,43 @@ -# [Assignment Name] +# Train Mountain Car +[OpenAI Gym](http://gym.openai.com) has been designed in such a way that all environments provide the same API - i.e. the same methods `reset`, `step` and `render`, and the same abstractions of **action space** and **observation space**. Thus is should be possible to adapt the same reinforcement learning algorithms to different environments with minimal code changes. + +## Mountain Car Environment + +[Mountain Car environment](https://gym.openai.com/envs/MountainCar-v0/) contains a car stuck in a valley: + + + +The goal is to get out of the valley and capture the flag, by doing at each step one of the following actions: + +| Value | Meaning | +|---|---| +| 0 | Accelerate to the left | +| 1 | Do not accelerate | +| 2 | Accelerate to the right | + +The main trick of this problem is, however, that the car's engine is not strong enough to scale the mountain in a single pass. Therefore, the only way to succeed is to drive back and forth to build up momentum. + +Observation space consists of just two values: + +| Num | Observation | Min | Max | +|-----|--------------|-----|-----| +| 0 | Car Position | -1.2| 0.6 | +| 1 | Car Velocity | -0.07 | 0.07 | + +Reward system for the mountain car is rather tricky: + + * Reward of 0 is awarded if the agent reached the flag (position = 0.5) on top of the mountain. + * Reward of -1 is awarded if the position of the agent is less than 0.5. + +Episode terminates if the car position is more than 0.5, or episode length is greater than 200. ## Instructions +Adapt our reinforcement learning algorithm to solve the mountain car problem. Start with existing [notebook.ipynb](notebook.ipynb) code, substitute new environment, change state discretization functions, and try to make existing algorithm to train with minimal code modifications. Optimize the result by adjusting hyperparameters. + +> **Note**: Hyperparameters adjustment is likely to be needed to make algorithm converge. ## Rubric | Criteria | Exemplary | Adequate | Needs Improvement | | -------- | --------- | -------- | ----------------- | -| | | | | +| | Q-Learning algorithm is successfully adapted from CartPole example, with minimal code modifications, which is able to solve the problem of capturing the flag under 200 steps. | A new Q-Learning algorithm has been adopted from the Internet, but is well-documented; or existing algorithm adopted, but does not reach desired results | Student was not able to successfully adopt any algorithm, but has mede substantial steps towards solution (implemented state discretization, Q-Table data structure, etc.) | diff --git a/8-Reinforcement/2-Gym/images/cartpole-balance.gif b/8-Reinforcement/2-Gym/images/cartpole-balance.gif new file mode 100644 index 0000000000000000000000000000000000000000..836845a8bdeb55902cc95eecc02a5f2c6e4c305d GIT binary patch literal 41888 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 **Problem**: If Peter wants to escape from the wolf, he needs to be able to move faster than him. We will see how Peter can learn to skate, in particular, to keep balance, using Q-Learning.\n", + "\n", + "We will use a simplified version of balancing known as **CartPole** problem. In cartpole world, we have a horizontal slider that can move left or right, and the goal is to balance a pole staying on top of it.\n", + "\n", + "\n", + "\n", + "## OpenAI Gym\n", + "\n", + "In the previous lesson, the rules of the game and the state were given by `Board` class, which we defined ourselves. Here we will use a special **sumulation environment**, which will simulate the physics behind the balancing pole. One of the most popular simulation environments for training Reinforcement Learning algorithms is called [Gym](https://gym.openai.com/), which is maintained by [OpenAI](https://openai.com/). By using gym we can create difference **environments**: from cartpole simulation to Atari games. \n", + "\n", + "First, let's install the gym and import required libraries:" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Collecting gym\n Downloading gym-0.18.3.tar.gz (1.6 MB)\nRequirement already satisfied: scipy in c:\\winapp\\conda\\lib\\site-packages (from gym) (1.4.1)\nRequirement already satisfied: numpy>=1.10.4 in c:\\winapp\\conda\\lib\\site-packages (from gym) (1.18.1)\nCollecting pyglet<=1.5.15,>=1.4.0\n Downloading pyglet-1.5.15-py3-none-any.whl (1.1 MB)\nRequirement already satisfied: Pillow<=8.2.0 in c:\\winapp\\conda\\lib\\site-packages (from gym) (8.2.0)\nCollecting cloudpickle<1.7.0,>=1.2.0\n Downloading cloudpickle-1.6.0-py3-none-any.whl (23 kB)\nBuilding wheels for collected packages: gym\n Building wheel for gym (setup.py): started\n Building wheel for gym (setup.py): finished with status 'done'\n Created wheel for gym: filename=gym-0.18.3-py3-none-any.whl size=1657521 sha256=23743ca1a46d6268b5aed87007ab14cf9597d1f0e9c38417574741617f6bb13f\n Stored in directory: c:\\users\\dmitr\\appdata\\local\\pip\\cache\\wheels\\1a\\ec\\6d\\705d53925f481ab70fd48ec7728558745eeae14dfda3b49c99\nSuccessfully built gym\nInstalling collected packages: pyglet, cloudpickle, gym\nSuccessfully installed cloudpickle-1.6.0 gym-0.18.3 pyglet-1.5.15\n" + ] + } + ], + "source": [ + "import sys\n", + "!{sys.executable} -m pip install gym " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import gym\n", + "import matplotlib.pyplot as plt\n", + "from IPython import display\n", + "import numpy as np\n", + "import random" + ] + }, + { + "source": [ + "## CartPole Environment\n", + "\n", + "To work with CartPole balancing problem, we need to initialize corresponding environment. Each environment is associated with:\n", + "* **Observation space** that defines the structure of information that we receive from the environment. For cartpole problem, we receive position of the pole, velocity and some other values.\n", + "* **Action space** that defines possible actions. In our case action space is discrete, and consists of two actions - **left** and **right**." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Discrete(2)\nBox(-3.4028234663852886e+38, 3.4028234663852886e+38, (4,), float32)\n1\n" + ] + } + ], + "source": [ + "env = gym.make(\"CartPole-v1\")\n", + "print(env.action_space)\n", + "print(env.observation_space)\n", + "print(env.action_space.sample())" + ] + }, + { + "source": [ + "To see how the environment works, let's run a short simulation for 100 steps. At each step, we provide one of the actions to be taken - in this simulation we just randomly select an action from `action_space`. Run the code below and see what it leads to.\n", + "\n", + "> **Note**: It is preferred to run this 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)." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stderr", + "text": [ + "c:\\winapp\\conda\\lib\\site-packages\\gym\\logger.py:30: UserWarning: \u001b[33mWARN: You are calling 'step()' even though this environment has already returned done = True. You should always call 'reset()' once you receive 'done = True' -- any further steps are undefined behavior.\u001b[0m\n warnings.warn(colorize('%s: %s'%('WARN', msg % args), 'yellow'))\n" + ] + } + ], + "source": [ + "env.reset()\n", + "\n", + "for i in range(100):\n", + " env.render()\n", + " env.step(env.action_space.sample())\n", + "env.close()" + ] + }, + { + "source": [ + "During simulation, we need to get observatons in order to decide how to act. In fact, `step` function returns us back current observations, reward function, and the `done` flag that indicates whether it makes sense to continue the simulation or not:" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[ 0.03403272 -0.24301182 0.02669811 0.2895829 ] -> 1.0\n", + "[ 0.02917248 -0.04828055 0.03248977 0.00543839] -> 1.0\n", + "[ 0.02820687 0.14636075 0.03259854 -0.27681916] -> 1.0\n", + "[ 0.03113408 0.34100283 0.02706215 -0.55904489] -> 1.0\n", + "[ 0.03795414 0.53573468 0.01588125 -0.84308041] -> 1.0\n", + "[ 4.86688335e-02 7.30636325e-01 -9.80354340e-04 -1.13072712e+00] -> 1.0\n", + "[ 0.06328156 0.9257711 -0.0235949 -1.42371736] -> 1.0\n", + "[ 0.08179698 1.1211767 -0.05206924 -1.72368043] -> 1.0\n", + "[ 0.10422052 0.92668783 -0.08654285 -1.44764396] -> 1.0\n", + "[ 0.12275427 0.73273015 -0.11549573 -1.18320812] -> 1.0\n", + "[ 0.13740888 0.53928047 -0.13915989 -0.9288471 ] -> 1.0\n", + "[ 0.14819448 0.34628356 -0.15773684 -0.68293142] -> 1.0\n", + "[ 0.15512016 0.54320334 -0.17139546 -1.0208266 ] -> 1.0\n", + "[ 0.16598422 0.35072788 -0.191812 -0.78648764] -> 1.0\n", + "[ 0.17299878 0.15868546 -0.20754175 -0.55975453] -> 1.0\n", + "[ 0.17617249 0.35602306 -0.21873684 -0.90998894] -> 1.0\n" + ] + } + ], + "source": [ + "env.reset()\n", + "\n", + "done = False\n", + "while not done:\n", + " env.render()\n", + " obs, rew, done, info = env.step(env.action_space.sample())\n", + " print(f\"{obs} -> {rew}\")\n", + "env.close()" + ] + }, + { + "source": [ + "The observation vector that is returned at each step of the simulation contains the following values:\n", + "* Position of cart\n", + "* Velocity of cart\n", + "* Angle of pole\n", + "* Rotation rate of pole\n", + "\n", + "We can get min and max value of those numbers:\n" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38]\n[4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38]\n" + ] + } + ], + "source": [ + "print(env.observation_space.low)\n", + "print(env.observation_space.high)" + ] + }, + { + "source": [ + "You may also notice that reward value on each simulation step is always 1. This is because our goal is to survive as long as possible, i.e. keep the pole to a reasonably vertical position for the longest period of time.\n", + "\n", + "> In fact, CartPole simulation is considered solved if we manage to get the average reward of 195 over 100 consecutive trials." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "## State Discretization\n", + "\n", + "In Q=Learning, we need to build Q-Table that defines what to do at each state. To be able to do this, we need state to be **discreet**, more precisely, it should contain finite number of disctete values. Thus, we need somehow to **discretize** our observations, mapping them to finite set of states.\n", + "\n", + "There are a few ways we can do this:\n", + "* If we know the interval of a certain value, we can divide this interval into a number of **bins**, and then replace the value by the number of bin that it belongs to. This can be done using numpy [`digitize`](https://numpy.org/doc/stable/reference/generated/numpy.digitize.html) method. In this case, we will precisely know the state size, because it will depend on the number of bins we select for digitalization.\n", + "* We can use linear interpolation to bring values to some finite interval (say, from -20 to 20), and then convert numbers to integers by rounding them. This gives us a bit less control on the size of the state, especially if we do not know the exact ranges of input values. For example, in our case 2 out of 4 values do not have upper/lower bounds on their values, which may result in the infinite number of states.\n", + "\n", + "In our example, we will go with the second approach. As you may notice later, despite undefined upper/lower bounds, those value rarely take values outside of certain finite intervals, thus those states with extreme values will be very rare.\n", + "\n", + "Here is the function that will take the observation from our model, and produces a tuple of 4 integer values:" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def discretize(x):\n", + " return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))" + ] + }, + { + "source": [ + "Let's also explore other discretization method using bins:" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Sample bins for interval (-5,5) with 10 bins\n [-5. -4. -3. -2. -1. 0. 1. 2. 3. 4. 5.]\n" + ] + } + ], + "source": [ + "def create_bins(i,num):\n", + " return np.arange(num+1)*(i[1]-i[0])/num+i[0]\n", + "\n", + "print(\"Sample bins for interval (-5,5) with 10 bins\\n\",create_bins((-5,5),10))\n", + "\n", + "ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # intervals of values for each parameter\n", + "nbins = [20,20,10,10] # number of bins for each parameter\n", + "bins = [create_bins(ints[i],nbins[i]) for i in range(4)]\n", + "\n", + "def discretize_bins(x):\n", + " return tuple(np.digitize(x[i],bins[i]) for i in range(4))" + ] + }, + { + "source": [ + "Let's now run a short simulation and observe those discrete environemnt values. Feel free to try both `discretize` and `discretize_bins` and see if there is a difference.\n", + "\n", + "> **Note**: `discretize_bins` returns the bin number, which is 0-based, thus for values of input variable around 0 it returns the number from the middle of the interval (10). In `discretize`, we did not care about the range of output values, allowing them to be negative, thus the state values are not shifted, and 0 corresponds to 0." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "(0, 0, 3, 3)\n(0, 0, 4, 0)\n(0, 0, 4, 3)\n(0, 0, 4, 0)\n(0, 0, 4, 3)\n(0, 0, 5, 0)\n(0, 0, 5, 3)\n(0, -1, 6, 7)\n(0, -2, 7, 10)\n(0, -3, 10, 13)\n(0, -3, 12, 16)\n(0, -4, 15, 19)\n(0, -3, 19, 17)\n(0, -4, 23, 20)\n" + ] + } + ], + "source": [ + "env.reset()\n", + "\n", + "done = False\n", + "while not done:\n", + " #env.render()\n", + " obs, rew, done, info = env.step(env.action_space.sample())\n", + " #print(discretize_bins(obs))\n", + " print(discretize(obs))\n", + "env.close()" + ] + }, + { + "source": [ + "## Q-Table Structure\n", + "\n", + "In our previous lesson, the state was a simple pair of numbers from 0 to 8, and thus it was convenient to represent Q-Table by numpy tensor with shape 8x8x2. If we use bins discretization, the size of our state vector is also known, so we can use the same approach and represent state by an array of shape 20x20x10x10x2 (here 2 is the dimension of action space, and first dimensions correspond to the number of bins we have selected to use for each of the parameters in observation space).\n", + "\n", + "However, sometimes precise dimensions of the observation space are not known. In case of `discretize` function, we may never be sure that our state stays within certain limits, because some of the original values are not boind. Thus, we will use slightly different approach and represent Q-Table by a dictionary. We will use the pair *(state,action)* as the dictionary key, and the value would correspond to Q-Table entry value. " + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "Q = {}\n", + "actions = (0,1)\n", + "\n", + "def qvalues(state):\n", + " return [Q.get((state,a),0) for a in actions]" + ] + }, + { + "source": [ + "Here we also define a function `qvalues`, which returns a list of Q-Table values for a given state that correspond to all possible actions. If the entry is not present in the Q-Table, we will return 0 as the default.\n", + "\n", + "## Let's Start Q-Learning!\n", + "\n", + "Now we are ready to teach Peter to balance! First, let's set some hyperparameterers:" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "# hyperparameters\n", + "alpha = 0.3\n", + "gamma = 0.90\n", + "epsilon = 0.9" + ] + }, + { + "source": [ + "Here, `alpha` is the **learning rate** that defines to which extent we should adjust the current values of Q-Table at each step. In previous lesson we have started with 1, and then decreased `alpha` to lower values during training. In this example we will keep it constant just for simplicity, and you can experiment with adjusting `alpha` values later.\n", + "\n", + "`gamma` is the **discount factor** that shows to which extent we should prioritize future reward over current reward.\n", + "\n", + "`epsilon` is the **exploration/exploitation factor** that determines whether we should prefer exploration to exploitation or vice versa. In our algorithm, we will in `epsilon` percent of the cases select the next action according to Q-Table values, and in the remaining number of cases we will execute random action. This will allow us to explore the areas of search space that we have never seen before. \n", + "\n", + "> In terms of balancing - chosing random action (exploration) would act as a random punch in the wrong direction, and the pole would have to learn how to recover the balance from those \"mistakes\"\n", + "\n", + "We would also make two improvements to our algorithm from the previous lesson:\n", + "\n", + "* Calculating average cumulative reward over a number of simulations. We will print the progress each 5000 iterations, and we will average out our cumulative reward over that period of time. It means that if we get more than 195 point - we can consider the problem solved, with even higher quality than required.\n", + "* We will calculate maximim average cumulative result `Qmax`, and we will store the Q-Table corresponding to that result. When you run the training you will notice that sometimes the average cumulative result starts to drop, and we want to keep the values of Q-Table that correspond to the best model observed during training.\n", + "\n", + "We will also collect all cumulative rewards at each simulaiton at `rewards` vector for further plotting." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "0: 36.0, alpha=0.3, epsilon=0.9\n", + "5000: 79.9322, alpha=0.3, epsilon=0.9\n", + "10000: 136.5074, alpha=0.3, epsilon=0.9\n", + "15000: 166.0206, alpha=0.3, epsilon=0.9\n", + "20000: 179.561, alpha=0.3, epsilon=0.9\n", + "25000: 195.6424, alpha=0.3, epsilon=0.9\n", + "30000: 213.3106, alpha=0.3, epsilon=0.9\n", + "35000: 227.8582, alpha=0.3, epsilon=0.9\n", + "40000: 230.849, alpha=0.3, epsilon=0.9\n", + "45000: 246.6194, alpha=0.3, epsilon=0.9\n", + "50000: 270.2226, alpha=0.3, epsilon=0.9\n", + "55000: 266.2084, alpha=0.3, epsilon=0.9\n", + "60000: 281.3548, alpha=0.3, epsilon=0.9\n", + "65000: 285.2666, alpha=0.3, epsilon=0.9\n", + "70000: 298.7658, alpha=0.3, epsilon=0.9\n", + "75000: 314.9734, alpha=0.3, epsilon=0.9\n", + "80000: 325.5224, alpha=0.3, epsilon=0.9\n", + "85000: 325.1302, alpha=0.3, epsilon=0.9\n", + "90000: 330.4744, alpha=0.3, epsilon=0.9\n", + "95000: 309.4724, alpha=0.3, epsilon=0.9\n" + ] + } + ], + "source": [ + "def probs(v,eps=1e-4):\n", + " v = v-v.min()+eps\n", + " v = v/v.sum()\n", + " return v\n", + "\n", + "random.seed(13)\n", + "\n", + "Qmax = 0\n", + "cum_rewards = []\n", + "rewards = []\n", + "for epoch in range(100000):\n", + " obs = env.reset()\n", + " done = False\n", + " cum_reward=0\n", + " # == do the simulation ==\n", + " while not done:\n", + " s = discretize(obs)\n", + " if random.random() Qmax:\n", + " Qmax = np.average(cum_rewards)\n", + " Qbest = Q\n", + " cum_rewards=[]" + ] + }, + { + "source": [ + "What you may notice from those results:\n", + "* We are very close achieving the goal of getting 195 cumulative reward over 100+ consecutive runs of the simulation, or we may have actually achieved it! Even if we get smaller numbers, we still do not know, because we average over 5000 runs, and only 100 runs is required in the formal criteria.\n", + "* Sometimes the reward start to drop, which means that we can \"destroy\" already learnt values in Q-Table with the ones that make situation worse\n", + "\n", + "To make learning more stable, it makes sense to adjust some of our hyperparameters during training. In particular:\n", + "* For **learning rate**, `alpha`, we may start with values close to 1, and then keep decreasing the parameter. With time, we will be getting good probability values in Q-Table, and thus we should be adjusting them slightly, and not overwriting completely with new values.\n", + "* We may want to increase the `eplilon` slowly, in order to be exploring less, and expliting more. It probably makes sense to start with lower value of `epsilon`, and move up to almost 1\n", + "\n", + "> **Task 1**: Play with hyperparameter values and see if you can achieve higher cumulative reward. Are you getting above 195?\n", + "\n", + "> **Task 2**: To formally solve the problem, you need to get 195 average reward across 100 consecutive runs. Measure that during training and make sure that you have formally solved the problem!" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "## Seeing the Result in Action\n", + "\n", + "Now it would be interesting to actually see how the trained model behaves. Let's run the simulation, and we will be following the same action selection strategy as during training: sampling according to the probability distribution in Q-Table: " + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "obs = env.reset()\n", + "done = False\n", + "while not done:\n", + " s = discretize(obs)\n", + " env.render()\n", + " v = probs(np.array(qvalues(s)))\n", + " a = random.choices(actions,weights=v)[0]\n", + " obs,_,done,_ = env.step(a)\n", + "env.close()" + ] + }, + { + "source": [ + "> **Task 3**: Here, we were using the final copy of Q-Table, which may not be the best one. Remember that we have stored the best-performing Q-Table into `Qbest` variable! Try the same example with the best-performing Q-Table by copying `Qbest` over to `Q` and see if you notice the difference.\n", + "\n", + "> **Task 4**: Here we were not selecting the best action on each step, but rather sampling with corresponding probability distribution. Would it make more sense to always select the best action, with highest Q-Table value? This can be easily done by using `np.argmax` function to find out the action number corresponding to highers Q-Table value. Implement this strategy and see if it improves the balancing.\n", + "\n", + "## Saving result to animated GIF\n", + "\n", + "If you want to impress your friends, you may want to send them the animated GIF picture of the balancing pole. To do this, we can invoke `env.render` to produce an image frame, and then save those to animated GIF using PIL library:" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "159\n" + ] + } + ], + "source": [ + "from PIL import Image\n", + "obs = env.reset()\n", + "done = False\n", + "i=0\n", + "ims = []\n", + "while not done:\n", + " s = discretize(obs)\n", + " img=env.render(mode='rgb_array')\n", + " ims.append(Image.fromarray(img))\n", + " v = probs(np.array([Qbest.get((s,a),0) for a in actions]))\n", + " a = random.choices(actions,weights=v)[0]\n", + " obs,_,done,_ = env.step(a)\n", + " i+=1\n", + "env.close()\n", + "ims[0].save('images/cartpole-balance.gif',save_all=True,append_images=ims[1::2],loop=0,duration=5)\n", + "print(i)" + ] + }, + { + "source": [ + "## Conclusion\n", + "\n", + "We have now learnt how to train agents to achieve good results just by providing them a reward function that defines the desired state of the game, and by giving it an opportinity to intellegently explore the search space. We have successfully applied Q-Learning algorithm in the cases of discrete and continuous environments, but with discrete actions. In the are of reinforcement learning, we need to further study situations where action state is also continuous, and when observation space is much more complex, such as the image from Atarti game screen. In those problems we often need to use more powerful machine learning techniques, such as neural networks, in order to achieve good results. Those more advanced topics are the subject of more advanced Deep Reinforcement Learning course." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ] +} \ No newline at end of file diff --git a/8-Reinforcement/2-Gym/solution/CartPole.ipynb b/8-Reinforcement/2-Gym/solution/CartPole.ipynb deleted file mode 100644 index e69de29bb..000000000