<jupyter_start><jupyter_text>Introducción Créditos:* Documentación y repo de Stable-baselines https://stable-baselines3.readthedocs.io.    * Tutorial sobre SB3: https://github.com/araffin/rl-tutorial-jnrr19.* Documentación y repo de OpenAI Gym https://github.com/openai/gym/blob/master/docs/.    * Crear un entorno https://github.com/openai/gym/blob/master/docs/creating-environments.md.  Stable-baselines3: framework de deep RL que provee interfaces para ejecutar y adaptar algoritmos de RL "al estilo scikit-learn". Permite utilizar agentes abstrayéndonos de los detalles de bajo nivel de abstracción referentes a la implementación del algoritmo$^1$Además, ofrece herramientas muy útiles como* Monitores que permiten ver el rendimiento del agente según se desempeña en el entorno, sin tener que esperar a que finalice de entrenar.* Callbacks que permiten accionar eventos cuando se cumplen algunas condiciones en el entrenamiento de nuestro agente (por ejemplo, detenerlo si la recompensa recibida es menor a cierto umbral tras un cierto período de tiempo).Documentación https://stable-baselines3.readthedocs.ioEs un fork activamente mantenido de [OpenAI baselines](https://github.com/openai/baselines)La versión 3 cambia el framework subyacente de Tensorflow a Pytorch y está activamente en desarrollo; no obstante la versión 2 es completamente funcional$^1$ no obstante, al igual que sucede generalmente con librerías de ML: * Siempre es bueno tener en mente las características, ventajas y desventajas del algoritmo utilizado, pues de eso depende mucho la convergencia de nuestra solución, especialmente cuando se emplean entornos adaptados para nuestras necesidades. * Esta librería, al igual que demás frameworks generales de RL, están muy probadas en entornos estándares de RL como Atari o PyBullet. No obstante, es posible que nuestro entorno o nuestras necesidades difieran significativamente, lo que hace que en algunos casos haya que meter mano directo en el código de los algoritmos/librería.  Interfaz básica stable-baselines  Instalación de Stable-baselines<jupyter_code>#@title Instalación (no modificar)
!pip install stable-baselines3[extra,tests,docs]<jupyter_output><empty_output><jupyter_text>Desde Windows, además, instalar: * Microsoft Visual C++ desde https://visualstudio.microsoft.com/visual-cpp-build-tools/* PyType, mediante `conda install -c conda-forge pytype`  Instalación de RLBaselinesZoo Desde Google Colab<jupyter_code>#@title Instalación de RLBaselinesZoo (no modificar)

# Estamos en Colab?

try:
  import google.colab
  IN_COLAB = True
except:
  IN_COLAB = False

if IN_COLAB:
    !git clone --recursive https://github.com/DLR-RM/rl-baselines3-zoo
    !cd rl-baselines3-zoo/
    !apt-get install swig cmake ffmpeg
    !pip install -r /content/rl-baselines3-zoo/requirements.txt<jupyter_output><empty_output><jupyter_text>Desde Linux, ejecutando    git clone --recursive https://github.com/DLR-RM/rl-baselines3-zoo    cd rl-baselines3-zoo/    conda install swig    pip install -r requirements.txt  Ejecución de un algoritmo de RL  Importaciones/inicializaciones<jupyter_code>import os
from subprocess import Popen, PIPE

import numpy as np
import matplotlib.pyplot as plt

import gym
from gym import spaces
#from gym.envs.registration import register

from stable_baselines3 import DQN, PPO
from stable_baselines3.common.monitor import Monitor
from stable_baselines3.common.vec_env import DummyVecEnv
from stable_baselines3.common.callbacks import EvalCallback, StopTrainingOnRewardThreshold
from stable_baselines3.common.env_util import make_vec_env

os.makedirs('logs', exist_ok=True)

cwd = os.getcwd()

%matplotlib inline
%load_ext tensorboard<jupyter_output><empty_output><jupyter_text>Ejemplo básico<jupyter_code>env = gym.make('CartPole-v1')

# MlpPolicy es una política "estándar" que aprende con perceptron multicapa
# (es decir sin capas convolucionales o demás variantes),
# 2 capas ocultas con 64 neuronas cada una
model = DQN('MlpPolicy', env)
model.learn(total_timesteps=10000)<jupyter_output><empty_output><jupyter_text>Renderización<jupyter_code>if not IN_COLAB:

    obs = env.reset()
    for i in range(1000):
        action, _states = model.predict(obs, deterministic=True)
        obs, reward, done, info = env.step(action)
        env.render()
        if done:
          obs = env.reset()

    env.close()<jupyter_output><empty_output><jupyter_text>Logging  Ver rendimiento del agente en tensorboard<jupyter_code>venv = make_vec_env(lambda: gym.make('CartPole-v1'), n_envs=1)

model = DQN('MlpPolicy', venv, tensorboard_log='tensorboard/')
model.learn(total_timesteps=100000)<jupyter_output><empty_output><jupyter_text>Para verlo en tensorboard, correr `tensorboard --logdir=tensorboard/`<jupyter_code>%tensorboard --logdir=tensorboard<jupyter_output><empty_output><jupyter_text>MonitorVamos a crear un monitor para loguear nuestro agente en la carpeta logs. Nuestro monitor guardará datos de recompensa (r), duración (l) y tiempo total (t)<jupyter_code>env = gym.make('CartPole-v1')
env = Monitor(env, 'logs/')  # reemplazamos env por su monitor

model = DQN('MlpPolicy', env, )
model.learn(total_timesteps=10000)<jupyter_output><empty_output><jupyter_text>Callbacks<jupyter_code>env = gym.make('CartPole-v1')

callbacks = []  # lista de callbacks a usar, pueden ser varios

# callback para detener entrenamiento al alcanzar recompensa de 9.8
# (es una recompensa muy baja, pero la establecemos a fines demostrativos)
stop_training_callback = StopTrainingOnRewardThreshold(reward_threshold=9.8)

# al crear EvalCallback, se asocia el mismo con stop_training_callback
callbacks.append(EvalCallback(Monitor(env, 'logs/'), 
                              eval_freq=1000,
                              callback_on_new_best=stop_training_callback))

# la semilla aleatoria hace que las ejecuciones sean determinísticas
model = DQN('MlpPolicy', env, seed=42)
model.learn(total_timesteps=10000, callback=callbacks)<jupyter_output>Eval num_timesteps=1000, episode_reward=9.60 +/- 0.80
Episode length: 9.60 +/- 0.80
New best mean reward!
Eval num_timesteps=2000, episode_reward=9.20 +/- 0.75
Episode length: 9.20 +/- 0.75
Eval num_timesteps=3000, episode_reward=9.80 +/- 0.75
Episode length: 9.80 +/- 0.75
New best mean reward!<jupyter_text>Ejecutar agente RL en múltiples ambientes Esta librería provee una interfaz para ejecutar agentes en varias instancias de un mismo entorno a la vez (*vectorized environments*), de modo tal que se habilite la ejecución paralela y de otras funcionalidades útiles.Para ello, varios de sus algoritmos implementan cambios que consideren la posibilidad de que haya múltiples entornos subyacentes, por ejemplo `step(accion)` cambia a `step(lista_acciones)`, aplicando acciones a todos los entornos, recibiendo ahora múltiples observaciones y recompensas.Otro cambio: se aplica `reset()` automáticamente a cada entorno que llega a un estado final. SB brinda dos formas de utilizar entornos vectorizados:* **DummyVecEnv**, el cuál consiste en un *wrapper* de varios entornos, los cuáles funcionarán en un sólo hilo. Este wrapper es útil como entrada de algoritmos que requieren los entornos de esta forma, y habilita los procesamientos y operaciones comunes de los entornos vectorizados.* **SubprocVecEnv**, el cuál paraleliza multiples entornos pero en procesos de ejecucíon separados. Cada proceso tiene su propia memoria y puede adquirir derechos sobre las CPUs de la computadora donde se ejecuta. Se utiliza cuando el entorno del agente es computacionalemente complejo. Atención! **Puede comer mucha RAM**.Vemos un ejemplo:<jupyter_code># ejemplo de ambiente dummy
venv = DummyVecEnv([lambda: gym.make('CartPole-v1')]*4)

model = PPO('MlpPolicy', venv, )
model.learn(total_timesteps=10000)<jupyter_output><empty_output><jupyter_text>También puede hacerse con un una función de SB a tal efecto<jupyter_code>venv = make_vec_env(lambda: env, n_envs=4)

model = PPO('MlpPolicy', venv, )
model.learn(total_timesteps=10000)<jupyter_output><empty_output><jupyter_text>Ejecutar agente con políticas personalizadas<jupyter_code># Creamos una clase con una red neuronal de 128x128 neuronas

model = PPO('MlpPolicy', policy_kwargs=dict(net_arch=[128,128]), env='CartPole-v1', verbose=1)
model.learn(total_timesteps=10000)<jupyter_output>Using cpu device
Creating environment from the given name 'CartPole-v1'
Wrapping the env with a `Monitor` wrapper
Wrapping the env in a DummyVecEnv.
---------------------------------
| rollout/           |          |
|    ep_len_mean     | 22.4     |
|    ep_rew_mean     | 22.4     |
| time/              |          |
|    fps             | 3998     |
|    iterations      | 1        |
|    time_elapsed    | 0        |
|    total_timesteps | 2048     |
---------------------------------
-----------------------------------------
| rollout/                |             |
|    ep_len_mean          | 27          |
|    ep_rew_mean          | 27          |
| time/                   |             |
|    fps                  | 2737        |
|    iterations           | 2           |
|    time_elapsed         | 1           |
|    total_timesteps      | 4096        |
| train/                  |             |
|    approx_kl            | 0.008256847 |
|    clip_fraction        | 0.0804      |
|    cl[...]<jupyter_text>[Buen post](https://medium.com/aureliantactics/understanding-ppo-plots-in-tensorboard-cbc3199b9ba2) en donde se explica qué significan varias de estas métricas. Para verlas en detalle podemos consultar directamente el [código fuente](https://github.com/DLR-RM/stable-baselines3/blob/master/stable_baselines3/ppo/ppo.py), que está bien documentado y no es muy difícil de seguir.  Utilizar un entorno personalizadoAntes que nada, además de la interfaz que ya vimos de Gym, hay otras nociones que tenemos que tener en cuenta en este contexto:* Los entornos definen un espacio de estados y de acciones, a partir de los cuáles los modelos asumen y respetan la "forma" de observaciones y acciones. Por ejemplo, algunos algoritmos están diseñados para espacios de acciones discretos (DQN), continuos (DDPG) o bien poseen implementaciones particulares pueden usarse en ambos (PPO, en el repo de SB3). En cuanto a los espacios, algunos algoritmos asumen explícitamente un espacio discreto (y pequeño), como Q-Learning, mientras que otros como PPO asumen cualquier tipo de espacio.* Los dos tipos más comunes de estados o acciones son los espacios discretos `gym.spaces.Discrete` y los continuos `gym.spaces.Box`.* Los espacios discretos definen un conjunto de $n$ estados/acciones $\{ 0, 1, \dots, n-1 \}$, mientras que los espacios continuos definen un espacio $\mathbb{R}^d$, de una de las siguientes 4 formas: $[a, b], (-\infty, b], [a, \infty), (-\infty, \infty)$, en donde $a,b$ son las cotas superior e inferior (de existir).* Ejemplos: un espacio de acciones `Discrete(4)` tiene 4 acciones: $\{0,1,2,3\}$; un espacio de estados `Discrete(16)` tiene 16 estados. Un espacio de estados ALTURA, ANCHO, N_CANALES que represente una imagen RGB acotada en $[a=0, b=255]$ se puede crear como`observation_space = spaces.Box(low=0, high=255, shape=(HEIGHT, WIDTH, N_CHANNELS), dtype=np.uint8)` Para usar un entorno compatible por esta librería, el mismo tiene que heredar de *gym.Env*. Vemos un ejemplo (crédito: <NAME> https://colab.research.google.com/github/araffin/rl-tutorial-jnrr19/blob/sb3/5_custom_gym_env.ipynb)<jupyter_code>class GoLeftEnv(gym.Env):
  """
  Ambiente personalizado que sigue la interfaz de gym.
  Es un entorno simple en el cuál el agente debe aprender a ir siempre 
  hacia la izquierda.
  """
  # Dado que podemos estar en colab, no podemos implementar la salida por interfaz 
  # gráfica ('human' render mode) 
  metadata = {'render.modes': ['console']}
  # Definimos las constantes
  LEFT = 0
  RIGHT = 1

  def __init__(self, grid_size=10):
    super(GoLeftEnv, self).__init__()

    # Tamaño de la grilla de 1D
    self.grid_size = grid_size
    # Inicializamos en agente a la derecha de la grilla
    self.agent_pos = grid_size - 1

    # Definimos el espacio de acción y observaciones
    # Los mismos deben ser objetos gym.spaces
    # En este ejemplo usamos dos acciones discretas: izquierda y derecha
    n_actions = 2
    self.action_space = spaces.Discrete(n_actions)
    # La observación será la coordenada donde se encuentra el agente
    # puede ser descrita tanto por los espacios Discrete como Box
    self.observation_space = spaces.Box(low=0, high=self.grid_size,
                                        shape=(1,), dtype=np.float32)

  def reset(self):
    """
    Importante: la observación devuelta debe ser un array de numpy
    :return: (np.array) 
    """
    # Se inicializa el agente a la derecha de la grilla
    self.agent_pos = self.grid_size - 1
    # convertimos con astype a float32 (numpy) para hacer más general el agente
    # (en caso de que querramos usar acciones continuas)
    return np.array([self.agent_pos]).astype(np.float32)

  def step(self, action):
    if action == self.LEFT:
      self.agent_pos -= 1
    elif action == self.RIGHT:
      self.agent_pos += 1
    else:
      raise ValueError("Se recibió una acción inválida={} que no es parte del espacio de acciones".format(action))

    # Evitamos que el agente se salga de los límites de la grilla
    self.agent_pos = np.clip(self.agent_pos, 0, self.grid_size)

    # Llegó el agente a su estado objetivo (izquierda) de la grilla?
    done = bool(self.agent_pos == 0)

    # Asignamos recompensa sólo cuando el agente llega a su objetivo
    # (recompensa = 0 en todos los demás estados)
    reward = 1 if self.agent_pos == 0 else 0

    # gym también nos permite devolver información adicional, ej. en Atari: 
    # las vidas restantes del agente (no usaremos esto por ahora)
    info = {}

    return np.array([self.agent_pos]).astype(np.float32), reward, done, info

  def render(self, mode='console'):
    if mode != 'console':
      raise NotImplementedError()
    # en nuestra interfaz de consola, representamos el agente como una cruz, y 
    # el resto como un punto
    print("." * self.agent_pos, end="")
    print("x", end="")
    print("." * (self.grid_size - self.agent_pos))

  def close(self):
    pass
env = GoLeftEnv(grid_size=10)
env = make_vec_env(lambda: env, n_envs=1)

model = PPO('MlpPolicy', env, verbose=1).learn(20000)<jupyter_output>Using cpu device
---------------------------------
| rollout/           |          |
|    ep_len_mean     | 107      |
|    ep_rew_mean     | 1        |
| time/              |          |
|    fps             | 3601     |
|    iterations      | 1        |
|    time_elapsed    | 0        |
|    total_timesteps | 2048     |
---------------------------------
-----------------------------------------
| rollout/                |             |
|    ep_len_mean          | 61.5        |
|    ep_rew_mean          | 1           |
| time/                   |             |
|    fps                  | 2447        |
|    iterations           | 2           |
|    time_elapsed         | 1           |
|    total_timesteps      | 4096        |
| train/                  |             |
|    approx_kl            | 0.019907912 |
|    clip_fraction        | 0.348       |
|    clip_range           | 0.2         |
|    entropy_loss         | -0.675      |
|    explained_variance   | 0.134       |
|    learning[...]<jupyter_text>Ver agente entrenado<jupyter_code>obs = env.reset()
for i in range(10):
    action, _states = model.predict(obs)
    obs, rewards, dones, info = env.step(action)
    env.render(mode='console')
    if dones[0]:
        obs = env.reset()<jupyter_output>........x..
.......x...
......x....
.....x.....
....x......
...x.......
..x........
.x.........
.........x.
........x..<jupyter_text>Normalización de features y recompensas Stable-Baselines3 tiene predefinidos un conjunto de **wrappers** genericos que pueden utilizarse para preprocesar las observaciones que llegan al agente RL, desacoplando del mismo el prepocesamiento.Entre las funcionalidades disponibles tenemos:* **VecFrameStack**: Se utiliza cuando la observación que percibe el agente es una imagen. Sirve para expandir el espacio de estados apilando N frames de manera conjunta (ver [paper de DQN](https://arxiv.org/abs/1312.5602) para más detalle).* **VecNormalize**: Se utiliza para normalizar las observaciónes y/o las recompenzas que percibe el agente, a $\mu=0$ y $\sigma=1$. También permite cortar valores de observaciones y/o recompensas que excedan un rango establecido. * **VecCheckNan**: Se utiliza para trackear los estados del entorno que generan que los gradientes de la RNN se hagan NaN.* **VecVideoRecorder**: Se utiliza para exportar el funcionamiento de la política aprendida por el agente a un video (MP4).Ademas, se pueden crear **wrappers** personalizados extendiendo la clase **VecEnvWrapper**:```class MiWrapper(VecEnvWrapper):    [...]``` Ejemplo (VecNormalize)<jupyter_code>from stable_baselines3.common.vec_env import DummyVecEnv, VecNormalize

env = gym.make('CartPole-v1')
env = DummyVecEnv([lambda: env])  # Multiples entornos vectorizados
# Normalizamos tanto observaciones como recompensas, y recortamos ambas cuando exceden 10
env = VecNormalize(env, norm_obs=True, norm_reward=True, clip_obs=10.0, clip_reward=10.0)

model = DQN('MlpPolicy', env)
model.learn(total_timesteps=10000)<jupyter_output><empty_output><jupyter_text>RL-baselines3-zooColección de agentes RL y herramientas útiles para ejecutarlos, evaluarlos e incluso hacer videos con ellos. Los agentes de este repo están preparados con la configuración requerida para los distintos tipos de entornos, incluyendo Atari, PyBullet y entornos clásicos, incluyendo configuraciones e híper-parámetros que producen buenas políticas para tales entornos.Esta librería ofrece un muy buen punto de partida para utilizar agentes / entornos personalizados, ya que ofrece una [interfaz](https://github.com/DLR-RM/rl-baselines3-zoo/blob/master/train.py) fácilmente adaptable a nuestras necesidades.Si se usan entornos personalizados con rl-baselines3-zoo, debe tenerse en cuenta que se deben definir todos los híper-parámetros de antemano, sea al instanciar el agente, o en la carpeta */rl-baselines3-zoo/hyperparams*; de lo contrario arrojará error por no encontrar qué híper-parámetro usar.  EjecuciónLos agentes pueden ser llamados desde la consola mediante comandos como`python train.py --algo algo_name --env env_id`Los cuales pueden ser llamados usando<jupyter_code>os.chdir('rl-baselines3-zoo/')

args = [
    '-n', str(100000),
    '--algo', 'ppo',
    '--env', 'CartPole-v1'
]

p = Popen(['python', 'train.py'] + args,
                               stdin=PIPE, stdout=PIPE, stderr=PIPE)
output, err = p.communicate()
rc = p.returncode
os.chdir(cwd)
assert rc == 0<jupyter_output><empty_output><jupyter_text>Ver en acción el agente entrenado (nota: no disponible en Google Colab, requiere ffmpeg)<jupyter_code>if not IN_COLAB:
    os.chdir('rl-baselines3-zoo/')

    args = [
        '--algo', 'ppo',
        '--env', 'CartPole-v1',
        '--folder', 'logs/'
    ]

    p = Popen(['python', 'enjoy.py'] + args,
                                   stdin=PIPE, stdout=PIPE, stderr=PIPE)
    output, err = p.communicate()
    rc = p.returncode
    os.chdir(cwd)
    assert rc == 0<jupyter_output><empty_output><jupyter_text>También es posible grabar un video! Ver https://stable-baselines3.readthedocs.io/en/master/guide/examples.htmlrecord-a-video Ver curva de aprendizaje obtenida por el agente desde *utils.plot*<jupyter_code>os.chdir('rl-baselines3-zoo/')

args = [
    '--algo', 'ppo',
    '--env', 'CartPole-v1',
    '--exp-folder', 'logs/'
]

p = Popen(['python', '-m', 'scripts.plot_train'] + args, stdout=PIPE)
output, err = p.communicate()
rc = p.returncode
os.chdir(cwd)

assert rc == 0
print(output)<jupyter_output>b'Figure(640x480)\n'
{'fraction_non_alphanumeric': 0.10491967871485944, 'fraction_numerical': 0.018524096385542168, 'mean_word_length': 3.4645898700134468, 'pattern_counts': {'":': 0, '<': 69, '<?xml version=': 0, '>': 69, 'https://': 15, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '213619', 'license': 'MIT', 'n_tokens_mistral': 6902, 'n_tokens_neox': 6545, 'n_words': 1977, 'path': 'lab_2_stable_baselines.ipynb', 'repo_name': 'jbarsce/AprendizajePorRefuerzos', 'size': '19928'}