├── Slides - Introdução.pdf ├── Slides - Q-Learning.pdf ├── LICENSE ├── README.md ├── .gitignore ├── Pong - Participante.ipynb └── Pong - Gabarito.ipynb /Slides - Introdução.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turing-usp/Workshop-de-Aprendizado-por-Reforco/HEAD/Slides - Introdução.pdf -------------------------------------------------------------------------------- /Slides - Q-Learning.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turing-usp/Workshop-de-Aprendizado-por-Reforco/HEAD/Slides - Q-Learning.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Grupo Turing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 👾 Workshop de Aprendizado por Reforço 2 | 3 | Este repositório contém os materiais do **Workshop de Aprendizado por Reforço**, organizado pelo **[Grupo Turing](https://www.facebook.com/grupoturing.usp/)**. 4 | 5 | ### [🎥 Vídeo Completo do Workshop (Ago/2020)](https://youtu.be/FxcWqI-l29E) 6 | 7 | ### [👩‍💻 Complete o código do Workshop na nuvem!](https://colab.research.google.com/github/GrupoTuring/Workshop-de-Aprendizado-por-Reforco/blob/colab/Pong%20-%20Participante.ipynb) 8 | 9 | A parte prática do workshop foi desenvolvida utilizando Jupyter Notebooks ([guia de uso](https://medium.com/turing-talks/turing-talks-6-data-science-libraries-6c2599838b3e)). 10 | Caso queira rodar no seu próprio computador, basta instalar o [Python](https://www.python.org/downloads/), o [Jupyter](https://jupyter.org/install.html) e rodar o seguinte comando: 11 | 12 | ```bash 13 | pip install -U turing-envs 14 | ``` 15 | 16 | ## 📚 Material do Workshop 17 | 18 | ### - [👩‍🏫 Slides - Introdução](Slides%20-%20Introdução.pdf) 19 | ### - [👩‍🏫 Slides - Q-Learning](Slides%20-%20Q-Learning.pdf) 20 | ### - [👩‍💻 Notebook do Participante](https://colab.research.google.com/github/GrupoTuring/Workshop-de-Aprendizado-por-Reforco/blob/colab/Pong%20-%20Participante.ipynb) 21 | - **[Na Nuvem! (Recomendado)](https://colab.research.google.com/github/GrupoTuring/Workshop-de-Aprendizado-por-Reforco/blob/colab/Pong%20-%20Participante.ipynb)** 22 | - **[No Github](Pong%20-%20Participante.ipynb)** 23 | ### - [👩‍⚖️ Notebook Gabarito](https://colab.research.google.com/github/GrupoTuring/Workshop-de-Aprendizado-por-Reforco/blob/colab/Pong%20-%20Gabarito.ipynb) 24 | - **[Na Nuvem! (Recomendado)](https://colab.research.google.com/github/GrupoTuring/Workshop-de-Aprendizado-por-Reforco/blob/colab/Pong%20-%20Gabarito.ipynb)** 25 | - **[No Github](Pong%20-%20Gabarito.ipynb)** 26 | 27 | ## 🧠 Grupo Turing 28 | 29 | Facebook: https://www.facebook.com/grupoturing.usp/ 30 | 31 | Instagram: https://www.instagram.com/grupoturing.usp/ 32 | 33 | LinkedIn: https://www.linkedin.com/company/grupo-turing/ 34 | 35 | Medium: https://medium.com/turing-talks 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 2 | 3 | # Created by https://www.toptal.com/developers/gitignore/api/jupyternotebooks,python 4 | # Edit at https://www.toptal.com/developers/gitignore?templates=jupyternotebooks,python 5 | 6 | ### JupyterNotebooks ### 7 | # gitignore template for Jupyter Notebooks 8 | # website: http://jupyter.org/ 9 | 10 | .ipynb_checkpoints 11 | */.ipynb_checkpoints/* 12 | 13 | # IPython 14 | profile_default/ 15 | ipython_config.py 16 | 17 | # Remove previous ipynb_checkpoints 18 | # git rm -r .ipynb_checkpoints/ 19 | 20 | ### Python ### 21 | # Byte-compiled / optimized / DLL files 22 | __pycache__/ 23 | *.py[cod] 24 | *$py.class 25 | 26 | # C extensions 27 | *.so 28 | 29 | # Distribution / packaging 30 | .Python 31 | build/ 32 | develop-eggs/ 33 | dist/ 34 | downloads/ 35 | eggs/ 36 | .eggs/ 37 | lib/ 38 | lib64/ 39 | parts/ 40 | sdist/ 41 | var/ 42 | wheels/ 43 | pip-wheel-metadata/ 44 | share/python-wheels/ 45 | *.egg-info/ 46 | .installed.cfg 47 | *.egg 48 | MANIFEST 49 | 50 | # PyInstaller 51 | # Usually these files are written by a python script from a template 52 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 53 | *.manifest 54 | *.spec 55 | 56 | # Installer logs 57 | pip-log.txt 58 | pip-delete-this-directory.txt 59 | 60 | # Unit test / coverage reports 61 | htmlcov/ 62 | .tox/ 63 | .nox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *.cover 70 | *.py,cover 71 | .hypothesis/ 72 | .pytest_cache/ 73 | 74 | # Translations 75 | *.mo 76 | *.pot 77 | 78 | # Django stuff: 79 | *.log 80 | local_settings.py 81 | db.sqlite3 82 | db.sqlite3-journal 83 | 84 | # Flask stuff: 85 | instance/ 86 | .webassets-cache 87 | 88 | # Scrapy stuff: 89 | .scrapy 90 | 91 | # Sphinx documentation 92 | docs/_build/ 93 | 94 | # PyBuilder 95 | target/ 96 | 97 | # Jupyter Notebook 98 | 99 | # IPython 100 | 101 | # pyenv 102 | .python-version 103 | 104 | # pipenv 105 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 106 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 107 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 108 | # install all needed dependencies. 109 | #Pipfile.lock 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .env 123 | .venv 124 | env/ 125 | venv/ 126 | ENV/ 127 | env.bak/ 128 | venv.bak/ 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # End of https://www.toptal.com/developers/gitignore/api/jupyternotebooks,python 152 | 153 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) 154 | 155 | *.pickle 156 | -------------------------------------------------------------------------------- /Pong - Participante.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 🎮 Workshop de Introdução ao Aprendizado por Reforço\n", 8 | "⠀\n", 9 | "\n", 10 | "Bem vindes ao **Workshop de Introdução ao Aprendizado por Reforço**, organizado pelo Grupo Turing! \n", 11 | "\n", 12 | "O objetivo deste evento é ensinar o básico necessário da área de Aprendizado por Reforço utilizando um dos maiores clássicos da história dos video-games: ***Pong***.\n", 13 | "\n", 14 | "![Pong](https://media2.giphy.com/media/aTGwuEFyg6d8c/giphy.gif)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "# Instala os ambientes do Grupo Turing\n", 24 | "!pip install -U turing-envs" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "## 🏓 Sobre o Pong\n", 32 | "\n", 33 | "Começaremos falando sobre o problema, ou seja, sobre o jogo Pong. Este que foi o primeiro jogo de video-game lucrativo da história, publicado em 1972, constando 48 anos de legado.\n", 34 | "\n", 35 | "Pong simula uma partida de tênis, existem duas \"raquetes\" e uma bola, e o objetivo de cada uma das raquetes é não somente evitar que a bola passe por ela, como também fazer com que esta passe pela linha que a outra raquete protege, criando assim a premissa que sustenta o interesse pelo jogo. Queremos então desenvolver um algoritmo capaz de — sem nenhuma explicação adicional — maximizar as suas recompensas, sendo as ações, os estados e as recompensas, todas relativas ao jogo Pong. Teremos no final, portanto, um modelo treinado capaz de bom desempenho dentro do ambiente. " 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "## 💻 Programando..." 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "### Importando o Gym\n", 50 | "\n", 51 | "O **[Gym](https://gym.openai.com/)** é uma biblioteca desenvolvida pela OpenAI que contém várias implementações prontas de ambientes de Aprendizagem por Reforço. Ela é muito utilizada quando se quer testar um algoritmo de agente sem ter o trabalho de programar seu próprio ambiente.\n", 52 | "\n", 53 | "\"Exemplos\n", 54 | "
Exemplo de Ambientes do Gym
\n", 55 | "
\n", 56 | "\n", 57 | "Para se ter acesso a esses ambientes, basta importar o Gym da seguinte forma:" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "import gym" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "### O que é um Ambiente?\n", 74 | "\n", 75 | "Um **Ambiente** de Aprendizagem por Reforço é um espaço que representa o nosso problema, é o objeto com o qual o nosso agente deve interagir para cumprir sua função. Isso significa que o agente toma **ações** nesse ambiente, e recebe **recompensas** dele com base na qualidade de sua tomada de decisões.\n", 76 | "\n", 77 | "Todos os ambientes são dotados de um **espaço de observações**, que é a forma pela qual o agente recebe informações e deve se basear para a tomada de decisões, e um **espaço de ações**, que especifica as ações possíveis do agente. No xadrez, por exemplo, o espaço de observações seria o conjunto de todas as configurações diferentes do tabuleiro, e o espaço de ações seria o conjunto de todos os movimentos permitidos.\n", 78 | "\n", 79 | "\"Uma\n", 80 | "\n", 81 | "### Como Funciona um Ambiente do Gym?\n", 82 | "\n", 83 | "Agora que você já sabe o que é um ambiente, é preciso entender como nosso agente interage efetivamente com ele. Todos os ambientes do Gym possuem alguns métodos simples para facilitar a comunicação com eles:\n", 84 | "\n", 85 | "
\n", 86 | "\n", 87 | "| Método | Funcionalidade |\n", 88 | "| :------------- |:----------------------------------------------------- |\n", 89 | "| `reset()` | Inicializa o ambiente e recebe a observação inicial |\n", 90 | "| `step(acao)` | Executa uma ação e recebe a observação e a recompensa |\n", 91 | "| `render()` | Renderiza o ambiente |\n", 92 | "| `close()` | Fecha o ambiente |\n", 93 | "\n", 94 | "
\n", 95 | "\n", 96 | "Assim, o código para interagir com o ambiente costuma seguir o seguinte modelo:\n", 97 | "\n", 98 | "---\n", 99 | "\n", 100 | "```python\n", 101 | "env = gym.make(\"Nome do Ambiente\") # Cria o ambiente\n", 102 | "estado = env.reset() # Inicializa o ambiente\n", 103 | "done = False # Variável que diz se acabou\n", 104 | "\n", 105 | "while not done:\n", 106 | " env.render() # Renderiza o ambiente\n", 107 | " acao = random() # Define alguma ação\n", 108 | " estado, recompensa, done, info = env.step(acao) # Executa uma ação\n", 109 | " \n", 110 | "env.close() # Fecha o ambiente\n", 111 | "```\n", 112 | "\n", 113 | "---" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "### Criando um Ambiente\n", 121 | "\n", 122 | "Para utilizar um dos ambientes do Gym, nós usamos a função ```gym.make()```, passando o nome do ambiente desejado como parâmetro e guardando o valor retornado em uma variável que chamaramos de ```env```. A lista com todos os ambientes do gym pode ser encontrada [aqui](https://gym.openai.com/envs/#classic_control). Nesse workshop, utilizaremos um ambiente de pong do Grupo Turing, que requer a instalação do [Turing Envs](https://github.com/GrupoTuring/turing-envs)." 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "env = gym.make(\"turing_envs:pong-easy-v0\")\n", 132 | "env.seed(0)" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "Nesse caso, nós vamos utilizar o ambiente ```turing_envs:pong-easy-v0```, um ambiente que reproduz o jogo _Pong_.\n", 140 | "\n", 141 | "\"Ambiente\n", 142 | "\n", 143 | "#### Características do Pong\n", 144 | "\n", 145 | "Antes de treinar qualquer agente, primeiro é preciso entender melhor quais as características do nosso ambiente.\n", 146 | "\n", 147 | "O **Espaço de Observação** do pong (modo fácil) é definido por 2 informações:\n", 148 | "\n", 149 | "| Estado | Informação |\n", 150 | "| :-------- | :------------------------------------ |\n", 151 | "| 0 | Distância _x_ entre a bola e o agente |\n", 152 | "| 1 | Distância _y_ entre a bola e o agente |\n", 153 | "\n", 154 | "Dessa forma, a cada instante recebemos uma lista da observação com o seguinte formato:" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": { 161 | "tags": [] 162 | }, 163 | "outputs": [], 164 | "source": [ 165 | "print(env.observation_space.sample())" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "Já o **Espaço de Ação** é composto por três ações: mover o jogador para cima, baixo, ou deixá-lo parado:\n", 173 | "\n", 174 | "| Ação | Significado |\n", 175 | "| :--- | :--------------- |\n", 176 | "| 0 | Ficar parado |\n", 177 | "| 1 | Mover para baixo |\n", 178 | "| 2 | Mover para cima |\n", 179 | "\n", 180 | "Por exemplo, para mover a barra para a cima, fazemos `env.step(2)`." 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "metadata": { 187 | "tags": [] 188 | }, 189 | "outputs": [], 190 | "source": [ 191 | "print(env.action_space.sample())" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": {}, 197 | "source": [ 198 | "Por fim, cada vez que tomamos uma ação, recebemos do ambiente uma **recompensa**, conforme a tabela abaixo:\n", 199 | "\n", 200 | "| Ocorrência | Recompensa |\n", 201 | "| :------------------ | ---------: |\n", 202 | "| Ponto do Agente | $+500$ |\n", 203 | "| Ponto do Oponente | $-500$ |\n", 204 | "| Vitória do Agente | $+2000$ |\n", 205 | "| Vitória do Oponente | $-2000$ |\n", 206 | "\n", 207 | "O primeiro jogador a fazer quatro pontos ganha o jogo. Além disso, as recompensas são cumulativas. Isso significa que se o oponente fizer um ponto _e_ ganhar o jogo, a recompensa é de $-2500$." 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "### ✍ Exercício de Gym\n", 215 | "\n", 216 | "Agora que você já entende como o Gym funciona, vamos tentar aplicar esse conhecimento criando uma função que roda um episódio de Pong tomando ações aleatórias!\n", 217 | "\n", 218 | "OBS: Lembrete das funções do Gym\n", 219 | "\n", 220 | "| Método | Funcionalidade |\n", 221 | "| :--------------------- |:------------------------------------------------------- |\n", 222 | "| `reset()` | Inicializa o ambiente e recebe a observação inicial |\n", 223 | "| `step(acao)` | Executa uma ação e recebe a observação e a recompensa |\n", 224 | "| `render()` | Renderiza o ambiente |\n", 225 | "| `close()` | Fecha o ambiente |" 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": null, 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [ 234 | "# Essa função deve rodar um episodio de Pong escolhendo ações aleatórias\n", 235 | "def rodar_ambiente():\n", 236 | " # Criando o ambiente 'turing_envs:pong-easy-v0'\n", 237 | " env = gym.make(\"turing_envs:pong-easy-v0\")\n", 238 | "\n", 239 | " # Resete o ambiente e receba o primeiro estado\n", 240 | " estado = ...\n", 241 | "\n", 242 | " # Inicializando done como false\n", 243 | " done = False\n", 244 | "\n", 245 | " # Loop de treino\n", 246 | " while not done:\n", 247 | " # Escolha uma acao aleatoria\n", 248 | " acao = ...\n", 249 | "\n", 250 | " # Tome essa acao e receba as informacoes do estado seguinte\n", 251 | " prox_estado, recompensa, done, info = ...\n", 252 | "\n", 253 | " # Renderize o ambiente\n", 254 | " ...\n", 255 | "\n", 256 | " # Atualizando o estado\n", 257 | " estado = prox_estado\n", 258 | "\n", 259 | " # Fechando o ambiente\n", 260 | " env.close()" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "# Testando a função\n", 270 | "rodar_ambiente()" 271 | ] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "metadata": {}, 276 | "source": [ 277 | "## 👩‍💻 Algoritmo" 278 | ] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "metadata": {}, 283 | "source": [ 284 | "Primeiramente, precisaremos utilizar uma biblioteca chamada ***NumPy*** para auxiliar nas computações. Esta é uma biblioteca do Python capaz de manusear diversas computações matemáticas com maestria e será importante futuramente para o nosso trabalho." 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": null, 290 | "metadata": { 291 | "tags": [] 292 | }, 293 | "outputs": [], 294 | "source": [ 295 | "import numpy as np # Importando a biblioteca NumPy\n", 296 | "import gym # Importando a Biblioteca Gym\n", 297 | "\n", 298 | "# Criando o nosso Ambiente: Pong\n", 299 | "env = gym.make(\"turing_envs:pong-easy-v0\")\n", 300 | "\n", 301 | "# Número total de ações: 3\n", 302 | "# 0 = parado; 1 = baixo; 2 = cima\n", 303 | "n_acoes = env.action_space.n\n", 304 | "\n", 305 | "print('Número de ações:', n_acoes)" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "### 🔢 Discretizando o nosso Estado\n", 313 | "\n", 314 | "Como comentamos anteriormente, o estado que o nosso agente recebe consiste das distâncias horizontal e vertical da raquete controlada até a bola. Dessa forma, se a nossa tela possuir 800 unidades de largura e 600 unidades de altura, a quantidade total de diferentes estados possíveis seria aproximadamente $3 \\times 800 \\times 600 = 960000$.\n", 315 | "\n", 316 | "Como Q-Learning é um algoritmo que guarda em uma tabela as estimativas do Q de cada ação para cada estado, esse gigantesco número de estados exigiria não somente guardar como atualizar cada um desses Q. Não é uma situação ideal.\n", 317 | "\n", 318 | "Para simplificar (e agilizar) a situação, \"discretizar\" os nossos estados é razoável e esperado. Faremos com que estados similares o suficiente sejam considerados como iguais e comparilhem das mesmas estimativas, já que não faz sentido distinguir o estado (502,234) do estado (515,222)." 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": null, 324 | "metadata": {}, 325 | "outputs": [], 326 | "source": [ 327 | "def discretiza_estado(estado):\n", 328 | " return tuple(round(x/10) for x in estado)" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "metadata": {}, 334 | "source": [ 335 | "### 🔀 Escolhendo Ações\n", 336 | "\n", 337 | "Para o processo de de escolha de ação, é necessário lembrar do dilema entre **Exploração** e **Explotação**. Nosso modelo precisa estabelecer um equilíbrio entre **explorar o ambiente**, escolhendo ações que ele não costuma tomar para encontrar alguma solução que ele não havia pensado antes, e **aproveitar** o conhecimento que já possui, tomando ações que ele acredita serem as melhores para maximizar as recompensas que receberá no episódio.\n", 338 | "\n", 339 | "De forma a assegurar que o agente busque tanto novas alternativas que podem gerar melhores resultados quanto seja capaz de utilizar o aprendizado obtido de forma a maximizar seu retorno, existem diversas estratégias para a escolha de exploração e explotação. Uma das mais utilizadas, que também vamos utilizar aqui, é a seleção de ações pela estratégia do **\"$\\epsilon$-greedy\"**.\n", 340 | "\n", 341 | "#### A Estratégia **$\\epsilon$-greedy**\n", 342 | "\n", 343 | "O algoritmo \"$\\epsilon$-greedy\" é definido da seguinte forma: é retirado um número aleatório, no intervalo entre 0 e 1. caso este número tenha valor inferior ao valor do epsilon, a escolha será de uma ação aleatória, o que configura exploração. Caso este número seja superior ao epsilon, a ação a ser tomada é a que gera a maior recompensa de acordo com os valores da tabela Q.\n", 344 | "\n", 345 | "Este valor de $\\epsilon$ não é constante ao longo do treinamento. Inicialmente, este valor é alto, incentivando a maior exploração do ambiente. A medida que o treinamento ocorre, mais informação sobre o ambiente é adquirida, conseguindo uma tabela Q mais representativa da realidade. Dessa forma, quanto mais avançado no treinamento, menor a necessidade de exploração e maior a necessidade de exploitar o conhecimento adquirido para maximizar a recompensa. Esta atualização do $\\epsilon$ é chamada **\"$\\epsilon$-decay\"** (decaimento do epsilon). Também é estabelecido um valor mínimo para o $\\epsilon$, para que o agente nunca pare completamente de explorar o ambiente." 346 | ] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": null, 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [ 354 | "# Constantes da Política Epsilon Greedy\n", 355 | "# Epsilon: probabilidade de experimentar uma ação aleatória\n", 356 | "EPSILON = 0.7 # Valor inicial do epsilon\n", 357 | "EPSILON_MIN = 0.01 # Valor mínimo de epsilon\n", 358 | "DECAIMENTO = 0.98 # Fator de decaímento do epsilon (por episódio)" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": null, 364 | "metadata": {}, 365 | "outputs": [], 366 | "source": [ 367 | "def escolhe_acao(env, Q, estado, epsilon):\n", 368 | " # Se não conhecermos ainda o estado, inicializamos o Q de cada ação como 0\n", 369 | " if estado not in Q.keys(): Q[estado] = [0] * n_acoes\n", 370 | "\n", 371 | " # Escolhemos um número aleatório com \"np.random.random()\"\n", 372 | " # Se esse número for menor que epsilon, tomamos uma ação aleatória\n", 373 | " if np.random.random() < epsilon:\n", 374 | " # Escolhemos uma ação aleatória, com env.action_space.sample()\n", 375 | " acao = ...\n", 376 | " else:\n", 377 | " # Escolhemos a melhor ação para o estado atual, com np.argmax()\n", 378 | " acao = ...\n", 379 | " return acao" 380 | ] 381 | }, 382 | { 383 | "cell_type": "markdown", 384 | "metadata": {}, 385 | "source": [ 386 | "Para rodar uma partida, são necessárias algumas etapas. Inicialmente, o ambiente é reiniciado, de forma a inicar um novo episódio. Em seguida, é necessário discretizar o estado, pelos motivos já explicados acima. Esta discretização deve ocorrer toda vez em que estamos em um novo estado.\n", 387 | "\n", 388 | "Enquanto o ambiente não chega em seu estado terminal, indicado pela variável \"done\", será feito o processo de escolha de ações e, uma vez escolhida, deve-se receber do ambiente o próximo estado, a recompensa que a ação escolhida gerou, além do sinal se estamos no estado terminal. Todo o processo é repetido novamente para o próximo estado, até o final do episódio.\n", 389 | "\n", 390 | "Como explicado na seção sobre a biblioteca \"Gym\", \"env.render()\" tem como papel mostrar o ambiente (neste caso, a partida de Pong)" 391 | ] 392 | }, 393 | { 394 | "cell_type": "code", 395 | "execution_count": null, 396 | "metadata": {}, 397 | "outputs": [], 398 | "source": [ 399 | "def roda_partida(env, Q, renderiza=True):\n", 400 | " # Resetamos o ambiente\n", 401 | " estado = env.reset()\n", 402 | "\n", 403 | " # Discretizamos o estado\n", 404 | " estado = ...\n", 405 | " \n", 406 | " done = False\n", 407 | " retorno = 0\n", 408 | " \n", 409 | " while not done:\n", 410 | " # Escolhemos uma ação\n", 411 | " acao = ...\n", 412 | "\n", 413 | " # Tomamos nossa ação escolhida e recebemos informações do próximo estado\n", 414 | " prox_estado, recompensa, done, info = ...\n", 415 | "\n", 416 | " # Discretizamos o próximo estado\n", 417 | " prox_estado = ...\n", 418 | "\n", 419 | " # Renderizamos o ambiente\n", 420 | " if renderiza:\n", 421 | " env.render()\n", 422 | "\n", 423 | " retorno += recompensa\n", 424 | " estado = prox_estado\n", 425 | "\n", 426 | " print(f'retorno {retorno:.1f}, '\n", 427 | " f'placar {env.score[0]}x{env.score[1]}')\n", 428 | " \n", 429 | " env.close()" 430 | ] 431 | }, 432 | { 433 | "cell_type": "code", 434 | "execution_count": null, 435 | "metadata": { 436 | "tags": [] 437 | }, 438 | "outputs": [], 439 | "source": [ 440 | "# Rodamos uma partida de Pong\n", 441 | "Q = {}\n", 442 | "roda_partida(env, Q)" 443 | ] 444 | }, 445 | { 446 | "cell_type": "markdown", 447 | "metadata": {}, 448 | "source": [ 449 | "## 🏋️‍♀️ Treinamento\n", 450 | "\n", 451 | "Agora sim chegaremos no treinamento propriamente dito. Usando os conceitos vistos na apresentação e nas seções anteriores do notebook, podemos definir a função de treinamento que vai permitir que o agente aprenda a jogar PONG por meio de Q-Learning tabular." 452 | ] 453 | }, 454 | { 455 | "cell_type": "markdown", 456 | "metadata": {}, 457 | "source": [ 458 | "O algoritmo se baseia na atualização de estimativas dos valores Q para cada par estado-ação, de forma a chegar a uma tabela cada vez mais próxima da realidade do ambiente. Dessa forma, devemos atualizar cada entrada da tabela de acordo com a **equação do Q-Learning**:\n", 459 | "\n", 460 | "$$Q*(s,a) \\leftarrow Q*(s,a) + \\alpha \\cdot \\left[r + \\gamma \\cdot \\max_{a'} (Q(s',a')) - Q(s, a)\\right]$$\n", 461 | "\n", 462 | "Esta equação corrige o valor do Q(s,a) de acordo com os valores anteriores somados a uma parecela de correção, de forma a minimizar o erro. A recompensa é representada por r, enquanto os outros parâmetros estão explicados a seguir:" 463 | ] 464 | }, 465 | { 466 | "cell_type": "markdown", 467 | "metadata": {}, 468 | "source": [ 469 | "* \"ALFA\" ($\\alpha$): algoritmos de aprendizado de máquina costumam precisar de uma forma de serem otimizados. Q-learning trabalha em cima de gradientes, uma entidade matemática que indica a direção para maximizar (ou minimizar) uma função. Dispondo dessa direção, precisamos informar qual deve ser o tamanho do passo a ser dado antes de atualizar a nova \"direção ideal\".\n", 470 | "\n", 471 | "* \"GAMA\" ($\\gamma$): denota o quanto desejamos que nosso algoritmo considere eventos futuros. Se \"$\\gamma = 1$\", nosso algoritmo avaliará que a situação futura ser melhor que a atual é tão importante quanto a recompensa da situação atual em si, por outro lado, se \"$\\gamma = 0$\", os eventos futuros não apresentam importância alguma para nosso algoritmo. \n", 472 | "\n", 473 | "* \"Q\" é um dicionário, ou seja, uma estrtura de dados capaz de buscar elementos de forma rápida. Nós o usaremos para guardar valores relativos às estimativas do algoritmo." 474 | ] 475 | }, 476 | { 477 | "cell_type": "code", 478 | "execution_count": null, 479 | "metadata": {}, 480 | "outputs": [], 481 | "source": [ 482 | "# Hiperparâmetros do Q-Learning\n", 483 | "ALFA = 0.05 # Learning rate\n", 484 | "GAMA = 0.9 # Fator de desconto\n", 485 | "\n", 486 | "# Dicionário dos valores de Q\n", 487 | "# Chaves: estados; valores: qualidade Q atribuida a cada ação\n", 488 | "Q = {}" 489 | ] 490 | }, 491 | { 492 | "cell_type": "code", 493 | "execution_count": null, 494 | "metadata": {}, 495 | "outputs": [], 496 | "source": [ 497 | "def atualiza_q(Q, estado, acao, recompensa, prox_estado):\n", 498 | " # para cada estado ainda não descoberto, iniciamos seu valor como nulo\n", 499 | " if estado not in Q.keys(): Q[estado] = [0] * n_acoes\n", 500 | " if prox_estado not in Q.keys(): Q[prox_estado] = [0] * n_acoes\n", 501 | "\n", 502 | " # equação do Q-Learning\n", 503 | " Q[estado][acao] = ..." 504 | ] 505 | }, 506 | { 507 | "cell_type": "markdown", 508 | "metadata": {}, 509 | "source": [ 510 | "Pickle é uma maneira de salvar dados em um arquivo independente. Dessa forma, podemos gravar os valores da nossa tabela Q em um arquivo próprio, ficando disponível para ser acessada em outro momento. Assim, podemos efetivamente salvar o modelo treinado para ser utilizado posteriormente. Abaixo, já estão presentes as funções de salvar e de abrir as tabelas com pickle." 511 | ] 512 | }, 513 | { 514 | "cell_type": "code", 515 | "execution_count": null, 516 | "metadata": {}, 517 | "outputs": [], 518 | "source": [ 519 | "import pickle\n", 520 | "\n", 521 | "def salva_tabela(Q, nome = 'model.pickle'):\n", 522 | " with open(nome, 'wb') as pickle_out:\n", 523 | " pickle.dump(Q, pickle_out)\n", 524 | "\n", 525 | "def carrega_tabela(nome = 'model.pickle'):\n", 526 | " with open(nome, 'rb') as pickle_out:\n", 527 | " return pickle.load(pickle_out)" 528 | ] 529 | }, 530 | { 531 | "cell_type": "markdown", 532 | "metadata": {}, 533 | "source": [ 534 | "A função de treinamento tem estrutura semelhante à função roda_partida, conforme visto anteriormente. A cada episódio, o embiente deve ser reiniciado e discretizado, e deve indicar que o episódio ainda não chegou em sua condição terminal. Devemos também zerar o valor da recompensa, pois não devemos utilizar o retorno do episódio anterior.\n", 535 | "\n", 536 | "Enquanto o episódio não chega no final, o agente deve escolher uma ação e tomar a ação escolhida. Uma vez tomada a ação, o ambiente fornece o próximo estado, a recompensa recebida com a escolha, a indicação se o estado é terminal e informações sobre o ambiente.\n", 537 | "\n", 538 | "Em seguida, devemos discretizar o próximo estado e atualizar os valores de q, o retorno e o estado atual.\n", 539 | "\n", 540 | "Por fim, devemos atualizar o valor do epsilon, de acordo com o método $\\epsilon$-greedy, onde deve ocorrer o decaimento do epsilon, mas seu valor nunca deve ser inferior ao valor mínimo definido.\n", 541 | "\n" 542 | ] 543 | }, 544 | { 545 | "cell_type": "markdown", 546 | "metadata": {}, 547 | "source": [ 548 | "* `N_EPISODIOS` dita quantas vezes o agente deverá \"reviver\" o ambiente (vitórias e derrotas) antes de acabar seu treinamento." 549 | ] 550 | }, 551 | { 552 | "cell_type": "code", 553 | "execution_count": null, 554 | "metadata": {}, 555 | "outputs": [], 556 | "source": [ 557 | "N_EPISODIOS = 250 # quantidade de episódios que treinaremos" 558 | ] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": null, 563 | "metadata": {}, 564 | "outputs": [], 565 | "source": [ 566 | "def treina(env, Q):\n", 567 | " retornos = [] # retorno de cada episódio\n", 568 | " epsilon = EPSILON\n", 569 | "\n", 570 | " for episodio in range(1, N_EPISODIOS+1):\n", 571 | " # resetar o ambiente\n", 572 | " estado = env.reset()\n", 573 | "\n", 574 | " # discretizar o estado inicial\n", 575 | " estado = ...\n", 576 | " \n", 577 | " done = False\n", 578 | " retorno = 0\n", 579 | " \n", 580 | " while not done:\n", 581 | " # escolher uma ação\n", 582 | " ...\n", 583 | "\n", 584 | " # tomar a ação\n", 585 | " ...\n", 586 | "\n", 587 | " # discretizar o próximo estado\n", 588 | " ...\n", 589 | "\n", 590 | " atualiza_q(Q, estado, acao, recompensa, prox_estado)\n", 591 | "\n", 592 | " retorno += recompensa\n", 593 | " estado = prox_estado\n", 594 | "\n", 595 | " # calcular o próximo epsilon\n", 596 | " epsilon = ...\n", 597 | " epsilon = max(epsilon, EPSILON_MIN)\n", 598 | "\n", 599 | " retornos.append(retorno)\n", 600 | "\n", 601 | " if episodio % 10 == 0:\n", 602 | " salva_tabela(Q)\n", 603 | "\n", 604 | " print(f'episódio {episodio}, '\n", 605 | " f'retorno {retorno:7.1f}, '\n", 606 | " f'retorno médio (últimos 10 episódios) {np.mean(retornos[-10:]):7.1f}, '\n", 607 | " f'placar {env.score[0]}x{env.score[1]}, '\n", 608 | " f'epsilon: {epsilon:.3f}')\n", 609 | " \n", 610 | " env.close()" 611 | ] 612 | }, 613 | { 614 | "cell_type": "code", 615 | "execution_count": null, 616 | "metadata": { 617 | "tags": [] 618 | }, 619 | "outputs": [], 620 | "source": [ 621 | "treina(env, Q)" 622 | ] 623 | }, 624 | { 625 | "cell_type": "markdown", 626 | "metadata": {}, 627 | "source": [ 628 | "## 🏓 Testando nosso Agente Treinado" 629 | ] 630 | }, 631 | { 632 | "cell_type": "code", 633 | "execution_count": null, 634 | "metadata": { 635 | "tags": [] 636 | }, 637 | "outputs": [], 638 | "source": [ 639 | "roda_partida(env, Q)" 640 | ] 641 | } 642 | ], 643 | "metadata": { 644 | "kernelspec": { 645 | "display_name": "Python 3", 646 | "language": "python", 647 | "name": "python3" 648 | }, 649 | "language_info": { 650 | "codemirror_mode": { 651 | "name": "ipython", 652 | "version": 3 653 | }, 654 | "file_extension": ".py", 655 | "mimetype": "text/x-python", 656 | "name": "python", 657 | "nbconvert_exporter": "python", 658 | "pygments_lexer": "ipython3", 659 | "version": "3.8.1" 660 | } 661 | }, 662 | "nbformat": 4, 663 | "nbformat_minor": 4 664 | } 665 | -------------------------------------------------------------------------------- /Pong - Gabarito.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 🎮 Workshop de Introdução ao Aprendizado por Reforço\n", 8 | "⠀\n", 9 | "\n", 10 | "Bem vindes ao **Workshop de Introdução ao Aprendizado por Reforço**, organizado pelo Grupo Turing! \n", 11 | "\n", 12 | "O objetivo deste evento é ensinar o básico necessário da área de Aprendizado por Reforço utilizando um dos maiores clássicos da história dos video-games: ***Pong***.\n", 13 | "\n", 14 | "![Pong](https://media2.giphy.com/media/aTGwuEFyg6d8c/giphy.gif)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "# Instala os ambientes do Grupo Turing\n", 24 | "!pip install -U turing-envs" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "## 🏓 Sobre o Pong\n", 32 | "\n", 33 | "Começaremos falando sobre o problema, ou seja, sobre o jogo Pong. Este que foi o primeiro jogo de video-game lucrativo da história, publicado em 1972, constando 48 anos de legado.\n", 34 | "\n", 35 | "Pong simula uma partida de tênis, existem duas \"raquetes\" e uma bola, e o objetivo de cada uma das raquetes é não somente evitar que a bola passe por ela, como também fazer com que esta passe pela linha que a outra raquete protege, criando assim a premissa que sustenta o interesse pelo jogo. Queremos então desenvolver um algoritmo capaz de — sem nenhuma explicação adicional — maximizar as suas recompensas, sendo as ações, os estados e as recompensas, todas relativas ao jogo Pong. Teremos no final, portanto, um modelo treinado capaz de bom desempenho dentro do ambiente. " 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "## 💻 Programando..." 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "### Importando o Gym\n", 50 | "\n", 51 | "O **[Gym](https://gym.openai.com/)** é uma biblioteca desenvolvida pela OpenAI que contém várias implementações prontas de ambientes de Aprendizagem por Reforço. Ela é muito utilizada quando se quer testar um algoritmo de agente sem ter o trabalho de programar seu próprio ambiente.\n", 52 | "\n", 53 | "\"Exemplos\n", 54 | "
Exemplo de Ambientes do Gym
\n", 55 | "
\n", 56 | "\n", 57 | "Para se ter acesso a esses ambientes, basta importar o Gym da seguinte forma:" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "import gym" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "### O que é um Ambiente?\n", 74 | "\n", 75 | "Um **Ambiente** de Aprendizagem por Reforço é um espaço que representa o nosso problema, é o objeto com o qual o nosso agente deve interagir para cumprir sua função. Isso significa que o agente toma **ações** nesse ambiente, e recebe **recompensas** dele com base na qualidade de sua tomada de decisões.\n", 76 | "\n", 77 | "Todos os ambientes são dotados de um **espaço de observações**, que é a forma pela qual o agente recebe informações e deve se basear para a tomada de decisões, e um **espaço de ações**, que especifica as ações possíveis do agente. No xadrez, por exemplo, o espaço de observações seria o conjunto de todas as configurações diferentes do tabuleiro, e o espaço de ações seria o conjunto de todos os movimentos permitidos.\n", 78 | "\n", 79 | "\"Uma\n", 80 | "\n", 81 | "### Como Funciona um Ambiente do Gym?\n", 82 | "\n", 83 | "Agora que você já sabe o que é um ambiente, é preciso entender como nosso agente interage efetivamente com ele. Todos os ambientes do Gym possuem alguns métodos simples para facilitar a comunicação com eles:\n", 84 | "\n", 85 | "
\n", 86 | "\n", 87 | "| Método | Funcionalidade |\n", 88 | "| :------------- |:----------------------------------------------------- |\n", 89 | "| `reset()` | Inicializa o ambiente e recebe a observação inicial |\n", 90 | "| `step(acao)` | Executa uma ação e recebe a observação e a recompensa |\n", 91 | "| `render()` | Renderiza o ambiente |\n", 92 | "| `close()` | Fecha o ambiente |\n", 93 | "\n", 94 | "
\n", 95 | "\n", 96 | "Assim, o código para interagir com o ambiente costuma seguir o seguinte modelo:\n", 97 | "\n", 98 | "---\n", 99 | "\n", 100 | "```python\n", 101 | "env = gym.make(\"Nome do Ambiente\") # Cria o ambiente\n", 102 | "estado = env.reset() # Inicializa o ambiente\n", 103 | "done = False # Variável que diz se acabou\n", 104 | "\n", 105 | "while not done:\n", 106 | " env.render() # Renderiza o ambiente\n", 107 | " acao = random() # Define alguma ação\n", 108 | " estado, recompensa, done, info = env.step(acao) # Executa uma ação\n", 109 | " \n", 110 | "env.close() # Fecha o ambiente\n", 111 | "```\n", 112 | "\n", 113 | "---" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "### Criando um Ambiente\n", 121 | "\n", 122 | "Para utilizar um dos ambientes do Gym, nós usamos a função ```gym.make()```, passando o nome do ambiente desejado como parâmetro e guardando o valor retornado em uma variável que chamaramos de ```env```. A lista com todos os ambientes do gym pode ser encontrada [aqui](https://gym.openai.com/envs/#classic_control). Nesse workshop, utilizaremos um ambiente de pong do Grupo Turing, que requer a instalação do [Turing Envs](https://github.com/GrupoTuring/turing-envs)." 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "env = gym.make(\"turing_envs:pong-easy-v0\")\n", 132 | "env.seed(0)" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "Nesse caso, nós vamos utilizar o ambiente ```turing_envs:pong-easy-v0```, um ambiente que reproduz o jogo _Pong_.\n", 140 | "\n", 141 | "\"Ambiente\n", 142 | "\n", 143 | "#### Características do Pong\n", 144 | "\n", 145 | "Antes de treinar qualquer agente, primeiro é preciso entender melhor quais as características do nosso ambiente.\n", 146 | "\n", 147 | "O **Espaço de Observação** do pong (modo fácil) é definido por 2 informações:\n", 148 | "\n", 149 | "| Estado | Informação |\n", 150 | "| :-------- | :------------------------------------ |\n", 151 | "| 0 | Distância _x_ entre a bola e o agente |\n", 152 | "| 1 | Distância _y_ entre a bola e o agente |\n", 153 | "\n", 154 | "Dessa forma, a cada instante recebemos uma lista da observação com o seguinte formato:" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": { 161 | "tags": [] 162 | }, 163 | "outputs": [], 164 | "source": [ 165 | "print(env.observation_space.sample())" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "Já o **Espaço de Ação** é composto por três ações: mover o jogador para cima, baixo, ou deixá-lo parado:\n", 173 | "\n", 174 | "| Ação | Significado |\n", 175 | "| :--- | :--------------- |\n", 176 | "| 0 | Ficar parado |\n", 177 | "| 1 | Mover para baixo |\n", 178 | "| 2 | Mover para cima |\n", 179 | "\n", 180 | "Por exemplo, para mover a barra para a cima, fazemos `env.step(2)`." 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "metadata": { 187 | "tags": [] 188 | }, 189 | "outputs": [], 190 | "source": [ 191 | "print(env.action_space.sample())" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": {}, 197 | "source": [ 198 | "Por fim, cada vez que tomamos uma ação, recebemos do ambiente uma **recompensa**, conforme a tabela abaixo:\n", 199 | "\n", 200 | "| Ocorrência | Recompensa |\n", 201 | "| :------------------ | ---------: |\n", 202 | "| Ponto do Agente | $+500$ |\n", 203 | "| Ponto do Oponente | $-500$ |\n", 204 | "| Vitória do Agente | $+2000$ |\n", 205 | "| Vitória do Oponente | $-2000$ |\n", 206 | "\n", 207 | "O primeiro jogador a fazer quatro pontos ganha o jogo. Além disso, as recompensas são cumulativas. Isso significa que se o oponente fizer um ponto _e_ ganhar o jogo, a recompensa é de $-2500$." 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "### ✍ Exercício de Gym\n", 215 | "\n", 216 | "Agora que você já entende como o Gym funciona, vamos tentar aplicar esse conhecimento criando uma função que roda um episódio de Pong tomando ações aleatórias!\n", 217 | "\n", 218 | "OBS: Lembrete das funções do Gym\n", 219 | "\n", 220 | "| Método | Funcionalidade |\n", 221 | "| :--------------------- |:------------------------------------------------------- |\n", 222 | "| `reset()` | Inicializa o ambiente e recebe a observação inicial |\n", 223 | "| `step(acao)` | Executa uma ação e recebe a observação e a recompensa |\n", 224 | "| `render()` | Renderiza o ambiente |\n", 225 | "| `close()` | Fecha o ambiente |" 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": null, 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [ 234 | "# Essa função deve rodar um episodio de Pong escolhendo ações aleatórias\n", 235 | "def rodar_ambiente():\n", 236 | " # Criando o ambiente 'turing_envs:pong-easy-v0'\n", 237 | " env = gym.make(\"turing_envs:pong-easy-v0\")\n", 238 | "\n", 239 | " # Resete o ambiente e receba o primeiro estado\n", 240 | " estado = env.reset()\n", 241 | "\n", 242 | " # Inicializando done como false\n", 243 | " done = False\n", 244 | "\n", 245 | " # Loop de treino\n", 246 | " while not done:\n", 247 | " # Escolha uma acao aleatoria\n", 248 | " acao = env.action_space.sample()\n", 249 | "\n", 250 | " # Tome essa acao e receba as informacoes do estado seguinte\n", 251 | " prox_estado, recompensa, done, info = env.step(acao)\n", 252 | "\n", 253 | " # Renderize o ambiente\n", 254 | " env.render()\n", 255 | "\n", 256 | " # Atualizando o estado\n", 257 | " estado = prox_estado\n", 258 | "\n", 259 | " # Fechando o ambiente\n", 260 | " env.close()" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "# Testando a função\n", 270 | "rodar_ambiente()" 271 | ] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "metadata": {}, 276 | "source": [ 277 | "## 👩‍💻 Algoritmo" 278 | ] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "metadata": {}, 283 | "source": [ 284 | "Primeiramente, precisaremos utilizar uma biblioteca chamada ***NumPy*** para auxiliar nas computações. Esta é uma biblioteca do Python capaz de manusear diversas computações matemáticas com maestria e será importante futuramente para o nosso trabalho." 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": null, 290 | "metadata": { 291 | "tags": [] 292 | }, 293 | "outputs": [], 294 | "source": [ 295 | "import numpy as np # Importando a biblioteca NumPy\n", 296 | "import gym # Importando a Biblioteca Gym\n", 297 | "\n", 298 | "# Criando o nosso Ambiente: Pong\n", 299 | "env = gym.make(\"turing_envs:pong-easy-v0\")\n", 300 | "\n", 301 | "# Número total de ações: 3\n", 302 | "# 0 = parado; 1 = baixo; 2 = cima\n", 303 | "n_acoes = env.action_space.n\n", 304 | "\n", 305 | "print('Número de ações:', n_acoes)" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "### 🔢 Discretizando o nosso Estado\n", 313 | "\n", 314 | "Como comentamos anteriormente, o estado que o nosso agente recebe consiste das distâncias horizontal e vertical da raquete controlada até a bola. Dessa forma, se a nossa tela possuir 800 unidades de largura e 600 unidades de altura, a quantidade total de diferentes estados possíveis seria aproximadamente $3 \\times 800 \\times 600 = 960000$.\n", 315 | "\n", 316 | "Como Q-Learning é um algoritmo que guarda em uma tabela as estimativas do Q de cada ação para cada estado, esse gigantesco número de estados exigiria não somente guardar como atualizar cada um desses Q. Não é uma situação ideal.\n", 317 | "\n", 318 | "Para simplificar (e agilizar) a situação, \"discretizar\" os nossos estados é razoável e esperado. Faremos com que estados similares o suficiente sejam considerados como iguais e comparilhem das mesmas estimativas, já que não faz sentido distinguir o estado (502,234) do estado (515,222)." 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": null, 324 | "metadata": {}, 325 | "outputs": [], 326 | "source": [ 327 | "def discretiza_estado(estado):\n", 328 | " return tuple(round(x/10) for x in estado)" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "metadata": {}, 334 | "source": [ 335 | "### 🔀 Escolhendo Ações\n", 336 | "\n", 337 | "Para o processo de de escolha de ação, é necessário lembrar do dilema entre **Exploração** e **Explotação**. Nosso modelo precisa estabelecer um equilíbrio entre **explorar o ambiente**, escolhendo ações que ele não costuma tomar para encontrar alguma solução que ele não havia pensado antes, e **aproveitar** o conhecimento que já possui, tomando ações que ele acredita serem as melhores para maximizar as recompensas que receberá no episódio.\n", 338 | "\n", 339 | "De forma a assegurar que o agente busque tanto novas alternativas que podem gerar melhores resultados quanto seja capaz de utilizar o aprendizado obtido de forma a maximizar seu retorno, existem diversas estratégias para a escolha de exploração e explotação. Uma das mais utilizadas, que também vamos utilizar aqui, é a seleção de ações pela estratégia do **\"$\\epsilon$-greedy\"**.\n", 340 | "\n", 341 | "#### A Estratégia **$\\epsilon$-greedy**\n", 342 | "\n", 343 | "O algoritmo \"$\\epsilon$-greedy\" é definido da seguinte forma: é retirado um número aleatório, no intervalo entre 0 e 1. caso este número tenha valor inferior ao valor do epsilon, a escolha será de uma ação aleatória, o que configura exploração. Caso este número seja superior ao epsilon, a ação a ser tomada é a que gera a maior recompensa de acordo com os valores da tabela Q.\n", 344 | "\n", 345 | "Este valor de $\\epsilon$ não é constante ao longo do treinamento. Inicialmente, este valor é alto, incentivando a maior exploração do ambiente. A medida que o treinamento ocorre, mais informação sobre o ambiente é adquirida, conseguindo uma tabela Q mais representativa da realidade. Dessa forma, quanto mais avançado no treinamento, menor a necessidade de exploração e maior a necessidade de exploitar o conhecimento adquirido para maximizar a recompensa. Esta atualização do $\\epsilon$ é chamada **\"$\\epsilon$-decay\"** (decaimento do epsilon). Também é estabelecido um valor mínimo para o $\\epsilon$, para que o agente nunca pare completamente de explorar o ambiente." 346 | ] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": null, 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [ 354 | "# Constantes da Política Epsilon Greedy\n", 355 | "# Epsilon: probabilidade de experimentar uma ação aleatória\n", 356 | "EPSILON = 0.7 # Valor inicial do epsilon\n", 357 | "EPSILON_MIN = 0.01 # Valor mínimo de epsilon\n", 358 | "DECAIMENTO = 0.98 # Fator de decaímento do epsilon (por episódio)" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": null, 364 | "metadata": {}, 365 | "outputs": [], 366 | "source": [ 367 | "def escolhe_acao(env, Q, estado, epsilon):\n", 368 | " # Se não conhecermos ainda o estado, inicializamos o Q de cada ação como 0\n", 369 | " if estado not in Q.keys(): Q[estado] = [0] * n_acoes\n", 370 | "\n", 371 | " # Escolhemos um número aleatório com \"np.random.random()\"\n", 372 | " # Se esse número for menor que epsilon, tomamos uma ação aleatória\n", 373 | " if np.random.random() < epsilon:\n", 374 | " # Escolhemos uma ação aleatória, com env.action_space.sample()\n", 375 | " acao = env.action_space.sample()\n", 376 | " else:\n", 377 | " # Escolhemos a melhor ação para o estado atual, com np.argmax()\n", 378 | " acao = np.argmax(Q[estado])\n", 379 | " return acao" 380 | ] 381 | }, 382 | { 383 | "cell_type": "markdown", 384 | "metadata": {}, 385 | "source": [ 386 | "Para rodar uma partida, são necessárias algumas etapas. Inicialmente, o ambiente é reiniciado, de forma a inicar um novo episódio. Em seguida, é necessário discretizar o estado, pelos motivos já explicados acima. Esta discretização deve ocorrer toda vez em que estamos em um novo estado.\n", 387 | "\n", 388 | "Enquanto o ambiente não chega em seu estado terminal, indicado pela variável \"done\", será feito o processo de escolha de ações e, uma vez escolhida, deve-se receber do ambiente o próximo estado, a recompensa que a ação escolhida gerou, além do sinal se estamos no estado terminal. Todo o processo é repetido novamente para o próximo estado, até o final do episódio.\n", 389 | "\n", 390 | "Como explicado na seção sobre a biblioteca \"Gym\", \"env.render()\" tem como papel mostrar o ambiente (neste caso, a partida de Pong)" 391 | ] 392 | }, 393 | { 394 | "cell_type": "code", 395 | "execution_count": null, 396 | "metadata": {}, 397 | "outputs": [], 398 | "source": [ 399 | "def roda_partida(env, Q, renderiza=True):\n", 400 | " # Resetamos o ambiente\n", 401 | " estado = env.reset()\n", 402 | "\n", 403 | " # Discretizamos o estado\n", 404 | " estado = discretiza_estado(estado)\n", 405 | " \n", 406 | " done = False\n", 407 | " retorno = 0\n", 408 | " \n", 409 | " while not done:\n", 410 | " # Escolhemos uma ação\n", 411 | " acao = escolhe_acao(env, Q, estado, epsilon=0)\n", 412 | "\n", 413 | " # Tomamos nossa ação escolhida e recebemos informações do próximo estado\n", 414 | " prox_estado, recompensa, done, info = env.step(acao)\n", 415 | "\n", 416 | " # Discretizamos o próximo estado\n", 417 | " prox_estado = discretiza_estado(prox_estado)\n", 418 | "\n", 419 | " # Renderizamos o ambiente\n", 420 | " if renderiza:\n", 421 | " env.render()\n", 422 | "\n", 423 | " retorno += recompensa\n", 424 | " estado = prox_estado\n", 425 | "\n", 426 | " print(f'retorno {retorno:.1f}, '\n", 427 | " f'placar {env.score[0]}x{env.score[1]}')\n", 428 | " \n", 429 | " env.close()" 430 | ] 431 | }, 432 | { 433 | "cell_type": "code", 434 | "execution_count": null, 435 | "metadata": { 436 | "tags": [] 437 | }, 438 | "outputs": [], 439 | "source": [ 440 | "# Rodamos uma partida de Pong\n", 441 | "Q = {}\n", 442 | "roda_partida(env, Q)" 443 | ] 444 | }, 445 | { 446 | "cell_type": "markdown", 447 | "metadata": {}, 448 | "source": [ 449 | "## 🏋️‍♀️ Treinamento\n", 450 | "\n", 451 | "Agora sim chegaremos no treinamento propriamente dito. Usando os conceitos vistos na apresentação e nas seções anteriores do notebook, podemos definir a função de treinamento que vai permitir que o agente aprenda a jogar PONG por meio de Q-Learning tabular." 452 | ] 453 | }, 454 | { 455 | "cell_type": "markdown", 456 | "metadata": {}, 457 | "source": [ 458 | "O algoritmo se baseia na atualização de estimativas dos valores Q para cada par estado-ação, de forma a chegar a uma tabela cada vez mais próxima da realidade do ambiente. Dessa forma, devemos atualizar cada entrada da tabela de acordo com a **equação do Q-Learning**:\n", 459 | "\n", 460 | "$$Q*(s,a) \\leftarrow Q*(s,a) + \\alpha \\cdot \\left[r + \\gamma \\cdot \\max_{a'} (Q(s',a')) - Q(s, a)\\right]$$\n", 461 | "\n", 462 | "Esta equação corrige o valor do Q(s,a) de acordo com os valores anteriores somados a uma parecela de correção, de forma a minimizar o erro. A recompensa é representada por r, enquanto os outros parâmetros estão explicados a seguir:" 463 | ] 464 | }, 465 | { 466 | "cell_type": "markdown", 467 | "metadata": {}, 468 | "source": [ 469 | "* `ALFA` ($\\alpha$): algoritmos de aprendizado de máquina costumam precisar de uma forma de serem otimizados. Q-learning trabalha em cima de gradientes, uma entidade matemática que indica a direção para maximizar (ou minimizar) uma função. Dispondo dessa direção, precisamos informar qual deve ser o tamanho do passo a ser dado antes de atualizar a nova \"direção ideal\".\n", 470 | "\n", 471 | "* `GAMA` ($\\gamma$): denota o quanto desejamos que nosso algoritmo considere eventos futuros. Se \"$\\gamma = 1$\", nosso algoritmo avaliará que a situação futura ser melhor que a atual é tão importante quanto a recompensa da situação atual em si, por outro lado, se \"$\\gamma = 0$\", os eventos futuros não apresentam importância alguma para nosso algoritmo. \n", 472 | "\n", 473 | "* `Q` é um dicionário, ou seja, uma estrtura de dados capaz de buscar elementos de forma rápida. Nós o usaremos para guardar valores relativos às estimativas do algoritmo." 474 | ] 475 | }, 476 | { 477 | "cell_type": "code", 478 | "execution_count": null, 479 | "metadata": {}, 480 | "outputs": [], 481 | "source": [ 482 | "# Hiperparâmetros do Q-Learning\n", 483 | "ALFA = 0.05 # Learning rate\n", 484 | "GAMA = 0.9 # Fator de desconto\n", 485 | "\n", 486 | "# Dicionário dos valores de Q\n", 487 | "# Chaves: estados; valores: qualidade Q atribuida a cada ação\n", 488 | "Q = {}" 489 | ] 490 | }, 491 | { 492 | "cell_type": "code", 493 | "execution_count": null, 494 | "metadata": {}, 495 | "outputs": [], 496 | "source": [ 497 | "def atualiza_q(Q, estado, acao, recompensa, prox_estado):\n", 498 | " # para cada estado ainda não descoberto, iniciamos seu valor como nulo\n", 499 | " if estado not in Q.keys(): Q[estado] = [0] * n_acoes\n", 500 | " if prox_estado not in Q.keys(): Q[prox_estado] = [0] * n_acoes\n", 501 | "\n", 502 | " # equação do Q-Learning\n", 503 | " Q[estado][acao] = Q[estado][acao] + ALFA*(recompensa + GAMA*np.max(Q[prox_estado]) - Q[estado][acao])" 504 | ] 505 | }, 506 | { 507 | "cell_type": "markdown", 508 | "metadata": {}, 509 | "source": [ 510 | "Pickle é uma maneira de salvar dados em um arquivo independente. Dessa forma, podemos gravar os valores da nossa tabela Q em um arquivo próprio, ficando disponível para ser acessada em outro momento. Assim, podemos efetivamente salvar o modelo treinado para ser utilizado posteriormente. Abaixo, já estão presentes as funções de salvar e de abrir as tabelas com pickle." 511 | ] 512 | }, 513 | { 514 | "cell_type": "code", 515 | "execution_count": null, 516 | "metadata": {}, 517 | "outputs": [], 518 | "source": [ 519 | "import pickle\n", 520 | "\n", 521 | "def salva_tabela(Q, nome = 'model.pickle'):\n", 522 | " with open(nome, 'wb') as pickle_out:\n", 523 | " pickle.dump(Q, pickle_out)\n", 524 | "\n", 525 | "def carrega_tabela(nome = 'model.pickle'):\n", 526 | " with open(nome, 'rb') as pickle_out:\n", 527 | " return pickle.load(pickle_out)" 528 | ] 529 | }, 530 | { 531 | "cell_type": "markdown", 532 | "metadata": {}, 533 | "source": [ 534 | "A função de treinamento tem estrutura semelhante à função roda_partida, conforme visto anteriormente. A cada episódio, o embiente deve ser reiniciado e discretizado, e deve indicar que o episódio ainda não chegou em sua condição terminal. Devemos também zerar o valor da recompensa, pois não devemos utilizar o retorno do episódio anterior.\n", 535 | "\n", 536 | "Enquanto o episódio não chega no final, o agente deve escolher uma ação e tomar a ação escolhida. Uma vez tomada a ação, o ambiente fornece o próximo estado, a recompensa recebida com a escolha, a indicação se o estado é terminal e informações sobre o ambiente.\n", 537 | "\n", 538 | "Em seguida, devemos discretizar o próximo estado e atualizar os valores de q, o retorno e o estado atual.\n", 539 | "\n", 540 | "Por fim, devemos atualizar o valor do epsilon, de acordo com o método $\\epsilon$-greedy, onde deve ocorrer o decaimento do epsilon, mas seu valor nunca deve ser inferior ao valor mínimo definido.\n", 541 | "\n" 542 | ] 543 | }, 544 | { 545 | "cell_type": "markdown", 546 | "metadata": {}, 547 | "source": [ 548 | "* `N_EPISODIOS` dita quantas vezes o agente deverá \"reviver\" o ambiente (vitórias e derrotas) antes de acabar seu treinamento." 549 | ] 550 | }, 551 | { 552 | "cell_type": "code", 553 | "execution_count": null, 554 | "metadata": {}, 555 | "outputs": [], 556 | "source": [ 557 | "N_EPISODIOS = 250 # quantidade de episódios que treinaremos" 558 | ] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": null, 563 | "metadata": {}, 564 | "outputs": [], 565 | "source": [ 566 | "def treina(env, Q):\n", 567 | " retornos = [] # retorno de cada episódio\n", 568 | " epsilon = EPSILON\n", 569 | "\n", 570 | " for episodio in range(1, N_EPISODIOS+1):\n", 571 | " # resetar o ambiente\n", 572 | " estado = env.reset()\n", 573 | " \n", 574 | " # discretizar o estado inicial\n", 575 | " estado = discretiza_estado(estado)\n", 576 | " \n", 577 | " done = False\n", 578 | " retorno = 0\n", 579 | " \n", 580 | " while not done:\n", 581 | " # politica\n", 582 | " acao = escolhe_acao(env, Q, estado, epsilon)\n", 583 | "\n", 584 | " # A ação é tomada e os valores novos são coletados\n", 585 | " # O novo estado é salvo numa nova variavel\n", 586 | " prox_estado, recompensa, done, info = env.step(acao)\n", 587 | " prox_estado = discretiza_estado(prox_estado)\n", 588 | "\n", 589 | " atualiza_q(Q, estado, acao, recompensa, prox_estado)\n", 590 | "\n", 591 | " retorno += recompensa\n", 592 | " estado = prox_estado\n", 593 | "\n", 594 | " epsilon = max(DECAIMENTO*epsilon, EPSILON_MIN)\n", 595 | " retornos.append(retorno)\n", 596 | "\n", 597 | " if episodio % 10 == 0:\n", 598 | " salva_tabela(Q)\n", 599 | "\n", 600 | " print(f'episódio {episodio}, '\n", 601 | " f'retorno {retorno:7.1f}, '\n", 602 | " f'retorno médio (últimos 10 episódios) {np.mean(retornos[-10:]):7.1f}, '\n", 603 | " f'placar {env.score[0]}x{env.score[1]}, '\n", 604 | " f'epsilon: {epsilon:.3f}')\n", 605 | " \n", 606 | " env.close()" 607 | ] 608 | }, 609 | { 610 | "cell_type": "code", 611 | "execution_count": null, 612 | "metadata": { 613 | "tags": [] 614 | }, 615 | "outputs": [], 616 | "source": [ 617 | "treina(env, Q)" 618 | ] 619 | }, 620 | { 621 | "cell_type": "markdown", 622 | "metadata": {}, 623 | "source": [ 624 | "## 🏓 Testando nosso Agente Treinado" 625 | ] 626 | }, 627 | { 628 | "cell_type": "code", 629 | "execution_count": null, 630 | "metadata": { 631 | "tags": [] 632 | }, 633 | "outputs": [], 634 | "source": [ 635 | "roda_partida(env, Q)" 636 | ] 637 | } 638 | ], 639 | "metadata": { 640 | "kernelspec": { 641 | "display_name": "Python 3", 642 | "language": "python", 643 | "name": "python3" 644 | }, 645 | "language_info": { 646 | "codemirror_mode": { 647 | "name": "ipython", 648 | "version": 3 649 | }, 650 | "file_extension": ".py", 651 | "mimetype": "text/x-python", 652 | "name": "python", 653 | "nbconvert_exporter": "python", 654 | "pygments_lexer": "ipython3", 655 | "version": "3.8.1" 656 | } 657 | }, 658 | "nbformat": 4, 659 | "nbformat_minor": 4 660 | } 661 | --------------------------------------------------------------------------------