¡Confirme su asistencia a su evento local de TensorFlow Everywhere hoy!
Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Entrene una red Deep Q con TF-Agents

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHubDescargar cuaderno

Introducción

Este ejemplo muestra cómo entrenar a un agente DQN (Deep Q Networks) en el entorno Cartpole usando la biblioteca TF-Agents.

Ambiente de cartpole

Lo guiará a través de todos los componentes de una canalización de aprendizaje reforzado (RL) para capacitación, evaluación y recopilación de datos.

Para ejecutar este código en vivo, haga clic en el enlace 'Ejecutar en Google Colab' arriba.

Preparar

Si no ha instalado las siguientes dependencias, ejecute:

sudo apt-get install -y xvfb ffmpeg
pip install -q 'imageio==2.4.0'
pip install -q pyvirtualdisplay
pip install -q tf-agents
from __future__ import absolute_import, division, print_function

import base64
import imageio
import IPython
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import PIL.Image
import pyvirtualdisplay

import tensorflow as tf

from tf_agents.agents.dqn import dqn_agent
from tf_agents.drivers import dynamic_step_driver
from tf_agents.environments import suite_gym
from tf_agents.environments import tf_py_environment
from tf_agents.eval import metric_utils
from tf_agents.metrics import tf_metrics
from tf_agents.networks import sequential
from tf_agents.policies import random_tf_policy
from tf_agents.replay_buffers import tf_uniform_replay_buffer
from tf_agents.trajectories import trajectory
from tf_agents.specs import tensor_spec
from tf_agents.utils import common
# Set up a virtual display for rendering OpenAI gym environments.
display = pyvirtualdisplay.Display(visible=0, size=(1400, 900)).start()
tf.version.VERSION
'2.4.1'

Hiperparámetros

num_iterations = 20000 # @param {type:"integer"}

initial_collect_steps = 100  # @param {type:"integer"} 
collect_steps_per_iteration = 1  # @param {type:"integer"}
replay_buffer_max_length = 100000  # @param {type:"integer"}

batch_size = 64  # @param {type:"integer"}
learning_rate = 1e-3  # @param {type:"number"}
log_interval = 200  # @param {type:"integer"}

num_eval_episodes = 10  # @param {type:"integer"}
eval_interval = 1000  # @param {type:"integer"}

Ambiente

En el aprendizaje por refuerzo (RL), un entorno representa la tarea o el problema a resolver. Se pueden crear entornos estándar en TF-Agents utilizando tf_agents.environments suites. TF-Agents tiene suites para cargar entornos de fuentes como OpenAI Gym, Atari y DM Control.

Cargue el entorno CartPole desde la suite OpenAI Gym.

env_name = 'CartPole-v0'
env = suite_gym.load(env_name)

Puede renderizar este entorno para ver cómo se ve. Un poste de oscilación libre está sujeto a un carro. El objetivo es mover el carro hacia la derecha o hacia la izquierda para mantener el poste apuntando hacia arriba.

env.reset()
PIL.Image.fromarray(env.render())

png

El método environment.step realiza una action en el entorno y devuelve una tupla TimeStep contiene la siguiente observación del entorno y la recompensa por la acción.

El método time_step_spec() devuelve la especificación de la tupla TimeStep . Su atributo de observation muestra la forma de las observaciones, los tipos de datos y los rangos de valores permitidos. El atributo de reward muestra los mismos detalles para la recompensa.

print('Observation Spec:')
print(env.time_step_spec().observation)
Observation Spec:
BoundedArraySpec(shape=(4,), dtype=dtype('float32'), name='observation', minimum=[-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], maximum=[4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38])

print('Reward Spec:')
print(env.time_step_spec().reward)
Reward Spec:
ArraySpec(shape=(), dtype=dtype('float32'), name='reward')

El método action_spec() devuelve la forma, los tipos de datos y los valores permitidos de las acciones válidas.

print('Action Spec:')
print(env.action_spec())
Action Spec:
BoundedArraySpec(shape=(), dtype=dtype('int64'), name='action', minimum=0, maximum=1)

En el entorno Cartpole:

  • observation es una matriz de 4 flotadores:
    • la posición y velocidad del carro
    • la posición angular y la velocidad del polo
  • reward es un valor flotante escalar
  • action es un entero escalar con solo dos valores posibles:
    • 0 - "mover a la izquierda"
    • 1 - "mover a la derecha"
time_step = env.reset()
print('Time step:')
print(time_step)

action = np.array(1, dtype=np.int32)

next_time_step = env.step(action)
print('Next time step:')
print(next_time_step)
Time step:
TimeStep(step_type=array(0, dtype=int32), reward=array(0., dtype=float32), discount=array(1., dtype=float32), observation=array([-0.03850375, -0.01932749,  0.00336228, -0.04097027], dtype=float32))
Next time step:
TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([-0.03889029,  0.17574608,  0.00254287, -0.3325905 ], dtype=float32))

Por lo general, se crean instancias de dos entornos: uno para entrenamiento y otro para evaluación.

train_py_env = suite_gym.load(env_name)
eval_py_env = suite_gym.load(env_name)

El entorno Cartpole, como la mayoría de los entornos, está escrito en Python puro. Esto se convierte a TensorFlow mediante el contenedor TFPyEnvironment .

La API del entorno original utiliza matrices Numpy. TFPyEnvironment convierte en Tensors para que sean compatibles con los agentes y las políticas de Tensorflow.

train_env = tf_py_environment.TFPyEnvironment(train_py_env)
eval_env = tf_py_environment.TFPyEnvironment(eval_py_env)

Agente

El algoritmo utilizado para resolver un problema de RL está representado por un Agent . TF-Agents proporciona implementaciones estándar de una variedad de Agents , que incluyen:

El agente DQN se puede utilizar en cualquier entorno que tenga un espacio de acción discreto.

En el corazón de un agente DQN se encuentra un QNetwork , un modelo de red neuronal que puede aprender a predecir QValues (retornos esperados) para todas las acciones, dada una observación del entorno.

Usaremos tf_agents.networks. para crear una QNetwork . La red constará de una secuencia de tf.keras.layers.Dense , donde la capa final tendrá 1 salida para cada posible acción.

fc_layer_params = (100, 50)
action_tensor_spec = tensor_spec.from_spec(env.action_spec())
num_actions = action_tensor_spec.maximum - action_tensor_spec.minimum + 1

# Define a helper function to create Dense layers configured with the right
# activation and kernel initializer.
def dense_layer(num_units):
  return tf.keras.layers.Dense(
      num_units,
      activation=tf.keras.activations.relu,
      kernel_initializer=tf.keras.initializers.VarianceScaling(
          scale=2.0, mode='fan_in', distribution='truncated_normal'))

# QNetwork consists of a sequence of Dense layers followed by a dense layer
# with `num_actions` units to generate one q_value per available action as
# it's output.
dense_layers = [dense_layer(num_units) for num_units in fc_layer_params]
q_values_layer = tf.keras.layers.Dense(
    num_actions,
    activation=None,
    kernel_initializer=tf.keras.initializers.RandomUniform(
        minval=-0.03, maxval=0.03),
    bias_initializer=tf.keras.initializers.Constant(-0.2))
q_net = sequential.Sequential(dense_layers + [q_values_layer])

Ahora use tf_agents.agents.dqn.dqn_agent para instanciar un DqnAgent . Además de time_step_spec , action_spec y QNetwork, el constructor del agente también requiere un optimizador (en este caso, AdamOptimizer ), una función de pérdida y un contador de pasos de números enteros.

optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)

train_step_counter = tf.Variable(0)

agent = dqn_agent.DqnAgent(
    train_env.time_step_spec(),
    train_env.action_spec(),
    q_network=q_net,
    optimizer=optimizer,
    td_errors_loss_fn=common.element_wise_squared_loss,
    train_step_counter=train_step_counter)

agent.initialize()

Políticas

Una política define la forma en que un agente actúa en un entorno. Por lo general, el objetivo del aprendizaje por refuerzo es entrenar el modelo subyacente hasta que la política produzca el resultado deseado.

En este tutorial:

  • El resultado deseado es mantener el palo en posición vertical sobre el carro.
  • La política devuelve una acción (izquierda o derecha) para cada observación time_step .

Los agentes contienen dos políticas:

  • agent.policy : la política principal que se utiliza para la evaluación y la implementación.
  • agent.collect_policy : una segunda política que se utiliza para la recopilación de datos.
eval_policy = agent.policy
collect_policy = agent.collect_policy

Las políticas se pueden crear independientemente de los agentes. Por ejemplo, use tf_agents.policies.random_tf_policy para crear una política que seleccionará aleatoriamente una acción para cada time_step .

random_policy = random_tf_policy.RandomTFPolicy(train_env.time_step_spec(),
                                                train_env.action_spec())

Para obtener una acción de una política, llame al policy.action(time_step) . time_step contiene la observación del entorno. Este método devuelve un PolicyStep , que es una tupla con nombre con tres componentes:

  • action - la acción que se va a realizar (en este caso, 0 o 1 )
  • state : se utiliza para políticas con estado (es decir, basadas en RNN)
  • info : datos auxiliares, como el registro de probabilidades de acciones
example_environment = tf_py_environment.TFPyEnvironment(
    suite_gym.load('CartPole-v0'))
time_step = example_environment.reset()
random_policy.action(time_step)
PolicyStep(action=<tf.Tensor: shape=(1,), dtype=int64, numpy=array([0])>, state=(), info=())

Métricas y evaluación

La métrica más común utilizada para evaluar una póliza es el rendimiento promedio. El retorno es la suma de las recompensas obtenidas al ejecutar una política en un entorno para un episodio. Se ejecutan varios episodios, lo que genera un rendimiento promedio.

La siguiente función calcula el rendimiento promedio de una política, dados la política, el entorno y una serie de episodios.

def compute_avg_return(environment, policy, num_episodes=10):

  total_return = 0.0
  for _ in range(num_episodes):

    time_step = environment.reset()
    episode_return = 0.0

    while not time_step.is_last():
      action_step = policy.action(time_step)
      time_step = environment.step(action_step.action)
      episode_return += time_step.reward
    total_return += episode_return

  avg_return = total_return / num_episodes
  return avg_return.numpy()[0]


# See also the metrics module for standard implementations of different metrics.
# https://github.com/tensorflow/agents/tree/master/tf_agents/metrics

La ejecución de este cálculo en random_policy muestra un rendimiento de referencia en el entorno.

compute_avg_return(eval_env, random_policy, num_eval_episodes)
20.7

Búfer de reproducción

El búfer de reproducción realiza un seguimiento de los datos recopilados del entorno. Este tutorial usa tf_agents.replay_buffers.tf_uniform_replay_buffer.TFUniformReplayBuffer , ya que es el más común.

El constructor requiere las especificaciones de los datos que recopilará. Está disponible en el agente mediante el método collect_data_spec . También se requieren el tamaño del lote y la longitud máxima del búfer.

replay_buffer = tf_uniform_replay_buffer.TFUniformReplayBuffer(
    data_spec=agent.collect_data_spec,
    batch_size=train_env.batch_size,
    max_length=replay_buffer_max_length)

Para la mayoría de los agentes, collect_data_spec es una tupla con nombre llamada Trajectory , que contiene las especificaciones de observaciones, acciones, recompensas y otros elementos.

agent.collect_data_spec
Trajectory(step_type=TensorSpec(shape=(), dtype=tf.int32, name='step_type'), observation=BoundedTensorSpec(shape=(4,), dtype=tf.float32, name='observation', minimum=array([-4.8000002e+00, -3.4028235e+38, -4.1887903e-01, -3.4028235e+38],
      dtype=float32), maximum=array([4.8000002e+00, 3.4028235e+38, 4.1887903e-01, 3.4028235e+38],
      dtype=float32)), action=BoundedTensorSpec(shape=(), dtype=tf.int64, name='action', minimum=array(0), maximum=array(1)), policy_info=(), next_step_type=TensorSpec(shape=(), dtype=tf.int32, name='step_type'), reward=TensorSpec(shape=(), dtype=tf.float32, name='reward'), discount=BoundedTensorSpec(shape=(), dtype=tf.float32, name='discount', minimum=array(0., dtype=float32), maximum=array(1., dtype=float32)))
agent.collect_data_spec._fields
('step_type',
 'observation',
 'action',
 'policy_info',
 'next_step_type',
 'reward',
 'discount')

Recopilación de datos

Ahora ejecute la política aleatoria en el entorno durante algunos pasos, registrando los datos en el búfer de reproducción.

def collect_step(environment, policy, buffer):
  time_step = environment.current_time_step()
  action_step = policy.action(time_step)
  next_time_step = environment.step(action_step.action)
  traj = trajectory.from_transition(time_step, action_step, next_time_step)

  # Add trajectory to the replay buffer
  buffer.add_batch(traj)

def collect_data(env, policy, buffer, steps):
  for _ in range(steps):
    collect_step(env, policy, buffer)

collect_data(train_env, random_policy, replay_buffer, initial_collect_steps)

# This loop is so common in RL, that we provide standard implementations. 
# For more details see the drivers module.
# https://www.tensorflow.org/agents/api_docs/python/tf_agents/drivers

El búfer de reproducción ahora es una colección de trayectorias.

# For the curious:
# Uncomment to peel one of these off and inspect it.
# iter(replay_buffer.as_dataset()).next()

El agente necesita acceso al búfer de reproducción. Esto se proporciona mediante la creación de una canalizacióntf.data.Dataset iterable que proporcionará datos al agente.

Cada fila del búfer de reproducción solo almacena un paso de observación. Pero dado que el agente DQN necesita tanto la observación actual como la siguiente para calcular la pérdida, la canalización del conjunto de datos tomará muestras de dos filas adyacentes para cada elemento del lote ( num_steps=2 ).

Este conjunto de datos también se optimiza mediante la ejecución de llamadas paralelas y la obtención previa de datos.

# Dataset generates trajectories with shape [Bx2x...]
dataset = replay_buffer.as_dataset(
    num_parallel_calls=3, 
    sample_batch_size=batch_size, 
    num_steps=2).prefetch(3)


dataset
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/autograph/operators/control_flow.py:1218: ReplayBuffer.get_next (from tf_agents.replay_buffers.replay_buffer) is deprecated and will be removed in a future version.
Instructions for updating:
Use `as_dataset(..., single_deterministic_pass=False) instead.

<PrefetchDataset shapes: (Trajectory(step_type=(64, 2), observation=(64, 2, 4), action=(64, 2), policy_info=(), next_step_type=(64, 2), reward=(64, 2), discount=(64, 2)), BufferInfo(ids=(64, 2), probabilities=(64,))), types: (Trajectory(step_type=tf.int32, observation=tf.float32, action=tf.int64, policy_info=(), next_step_type=tf.int32, reward=tf.float32, discount=tf.float32), BufferInfo(ids=tf.int64, probabilities=tf.float32))>
iterator = iter(dataset)
print(iterator)
<tensorflow.python.data.ops.iterator_ops.OwnedIterator object at 0x7fd45c8c1d30>

# For the curious:
# Uncomment to see what the dataset iterator is feeding to the agent.
# Compare this representation of replay data 
# to the collection of individual trajectories shown earlier.

# iterator.next()

Entrenando al agente

Deben suceder dos cosas durante el ciclo de entrenamiento:

  • recopilar datos del medio ambiente
  • usar esos datos para entrenar las redes neuronales del agente

Este ejemplo también evalúa periódicamente la política e imprime la puntuación actual.

Lo siguiente tardará ~ 5 minutos en ejecutarse.

try:
  %%time
except:
  pass

# (Optional) Optimize by wrapping some of the code in a graph using TF function.
agent.train = common.function(agent.train)

# Reset the train step
agent.train_step_counter.assign(0)

# Evaluate the agent's policy once before training.
avg_return = compute_avg_return(eval_env, agent.policy, num_eval_episodes)
returns = [avg_return]

for _ in range(num_iterations):

  # Collect a few steps using collect_policy and save to the replay buffer.
  collect_data(train_env, agent.collect_policy, replay_buffer, collect_steps_per_iteration)

  # Sample a batch of data from the buffer and update the agent's network.
  experience, unused_info = next(iterator)
  train_loss = agent.train(experience).loss

  step = agent.train_step_counter.numpy()

  if step % log_interval == 0:
    print('step = {0}: loss = {1}'.format(step, train_loss))

  if step % eval_interval == 0:
    avg_return = compute_avg_return(eval_env, agent.policy, num_eval_episodes)
    print('step = {0}: Average Return = {1}'.format(step, avg_return))
    returns.append(avg_return)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/util/dispatch.py:201: calling foldr_v2 (from tensorflow.python.ops.functional_ops) with back_prop=False is deprecated and will be removed in a future version.
Instructions for updating:
back_prop=False is deprecated. Consider using tf.stop_gradient instead.
Instead of:
results = tf.foldr(fn, elems, back_prop=False)
Use:
results = tf.nest.map_structure(tf.stop_gradient, tf.foldr(fn, elems))
step = 200: loss = 7.1376953125
step = 400: loss = 10.289593696594238
step = 600: loss = 19.545475006103516
step = 800: loss = 36.43363952636719
step = 1000: loss = 5.419467449188232
step = 1000: Average Return = 54.20000076293945
step = 1200: loss = 34.57895278930664
step = 1400: loss = 46.99689483642578
step = 1600: loss = 40.32734298706055
step = 1800: loss = 47.736488342285156
step = 2000: loss = 49.739967346191406
step = 2000: Average Return = 190.0
step = 2200: loss = 844.77099609375
step = 2400: loss = 4119.0439453125
step = 2600: loss = 255.4365234375
step = 2800: loss = 143.8880157470703
step = 3000: loss = 225.29849243164062
step = 3000: Average Return = 200.0
step = 3200: loss = 599.4447021484375
step = 3400: loss = 1019.076171875
step = 3600: loss = 1293.42919921875
step = 3800: loss = 2320.72216796875
step = 4000: loss = 41184.2109375
step = 4000: Average Return = 200.0
step = 4200: loss = 4963.77880859375
step = 4400: loss = 5559.41552734375
step = 4600: loss = 5049.33203125
step = 4800: loss = 78073.1328125
step = 5000: loss = 36796.953125
step = 5000: Average Return = 200.0
step = 5200: loss = 9767.94140625
step = 5400: loss = 22039.0625
step = 5600: loss = 22485.873046875
step = 5800: loss = 388653.09375
step = 6000: loss = 216484.203125
step = 6000: Average Return = 200.0
step = 6200: loss = 25190.736328125
step = 6400: loss = 34655.4296875
step = 6600: loss = 46708.58984375
step = 6800: loss = 53557.33984375
step = 7000: loss = 32926.9296875
step = 7000: Average Return = 200.0
step = 7200: loss = 45613.109375
step = 7400: loss = 37991.77734375
step = 7600: loss = 69465.1796875
step = 7800: loss = 82083.4140625
step = 8000: loss = 45056.90234375
step = 8000: Average Return = 200.0
step = 8200: loss = 82084.65625
step = 8400: loss = 123847.4375
step = 8600: loss = 78031.5
step = 8800: loss = 67286.640625
step = 9000: loss = 227665.625
step = 9000: Average Return = 200.0
step = 9200: loss = 189473.8125
step = 9400: loss = 175909.5625
step = 9600: loss = 219817.296875
step = 9800: loss = 402358.21875
step = 10000: loss = 295174.6875
step = 10000: Average Return = 200.0
step = 10200: loss = 297499.125
step = 10400: loss = 328237.8125
step = 10600: loss = 6439973.5
step = 10800: loss = 196471.09375
step = 11000: loss = 412917.0
step = 11000: Average Return = 200.0
step = 11200: loss = 224290.40625
step = 11400: loss = 215624.25
step = 11600: loss = 428307.0625
step = 11800: loss = 979577.5625
step = 12000: loss = 192212.125
step = 12000: Average Return = 200.0
step = 12200: loss = 395367.75
step = 12400: loss = 24781564.0
step = 12600: loss = 10260239.0
step = 12800: loss = 540727.875
step = 13000: loss = 638093.25
step = 13000: Average Return = 200.0
step = 13200: loss = 774659.4375
step = 13400: loss = 725100.875
step = 13600: loss = 759670.625
step = 13800: loss = 650109.0625
step = 14000: loss = 714944.5
step = 14000: Average Return = 200.0
step = 14200: loss = 765532.1875
step = 14400: loss = 559825.3125
step = 14600: loss = 1272693.375
step = 14800: loss = 1612590.75
step = 15000: loss = 1986240.25
step = 15000: Average Return = 200.0
step = 15200: loss = 1077601.125
step = 15400: loss = 1231381.125
step = 15600: loss = 2455884.5
step = 15800: loss = 23788086.0
step = 16000: loss = 1422684.0
step = 16000: Average Return = 200.0
step = 16200: loss = 2394509.0
step = 16400: loss = 4006896.0
step = 16600: loss = 1646453.625
step = 16800: loss = 1340689.125
step = 17000: loss = 26381168.0
step = 17000: Average Return = 200.0
step = 17200: loss = 2301365.75
step = 17400: loss = 589999.5
step = 17600: loss = 1951961.5
step = 17800: loss = 717706.3125
step = 18000: loss = 1537313.875
step = 18000: Average Return = 200.0
step = 18200: loss = 2864821.0
step = 18400: loss = 1825217.5
step = 18600: loss = 2375406.75
step = 18800: loss = 4396603.0
step = 19000: loss = 2847116.0
step = 19000: Average Return = 200.0
step = 19200: loss = 2379508.5
step = 19400: loss = 785688.625
step = 19600: loss = 2396494.0
step = 19800: loss = 3612146.75
step = 20000: loss = 3842961.75
step = 20000: Average Return = 200.0

Visualización

Parcelas

Utilice matplotlib.pyplot para trazar cómo mejoró la política durante el entrenamiento.

Una iteración de Cartpole-v0 consta de 200 pasos de tiempo. El entorno otorga una recompensa de +1 por cada paso que la pole se mantiene, por lo que el rendimiento máximo para un episodio es 200. Los gráficos muestran el rendimiento aumentando hacia ese máximo cada vez que se evalúa durante el entrenamiento. (Puede ser un poco inestable y no aumentar monótonamente cada vez).

iterations = range(0, num_iterations + 1, eval_interval)
plt.plot(iterations, returns)
plt.ylabel('Average Return')
plt.xlabel('Iterations')
plt.ylim(top=250)
(1.3400002002716054, 250.0)

png

Videos

Los gráficos son agradables. Pero lo más emocionante es ver a un agente realizando una tarea en un entorno.

Primero, cree una función para incrustar videos en el cuaderno.

def embed_mp4(filename):
  """Embeds an mp4 file in the notebook."""
  video = open(filename,'rb').read()
  b64 = base64.b64encode(video)
  tag = '''
  <video width="640" height="480" controls>
    <source src="data:video/mp4;base64,{0}" type="video/mp4">
  Your browser does not support the video tag.
  </video>'''.format(b64.decode())

  return IPython.display.HTML(tag)

Ahora repite algunos episodios del juego Cartpole con el agente. El entorno de Python subyacente (el que está "dentro" del contenedor de entorno de TensorFlow) proporciona un método render() , que genera una imagen del estado del entorno. Estos se pueden recopilar en un video.

def create_policy_eval_video(policy, filename, num_episodes=5, fps=30):
  filename = filename + ".mp4"
  with imageio.get_writer(filename, fps=fps) as video:
    for _ in range(num_episodes):
      time_step = eval_env.reset()
      video.append_data(eval_py_env.render())
      while not time_step.is_last():
        action_step = policy.action(time_step)
        time_step = eval_env.step(action_step.action)
        video.append_data(eval_py_env.render())
  return embed_mp4(filename)




create_policy_eval_video(agent.policy, "trained-agent")
WARNING:root:IMAGEIO FFMPEG_WRITER WARNING: input image is not divisible by macro_block_size=16, resizing from (400, 600) to (400, 608) to ensure video compatibility with most codecs and players. To prevent resizing, make your input image divisible by the macro_block_size or set the macro_block_size to None (risking incompatibility). You may also see a FFMPEG warning concerning speedloss due to data not being aligned.

Para divertirse, compare el agente entrenado (arriba) con un agente que se mueve al azar. (No funciona tan bien).

create_policy_eval_video(random_policy, "random-agent")
WARNING:root:IMAGEIO FFMPEG_WRITER WARNING: input image is not divisible by macro_block_size=16, resizing from (400, 600) to (400, 608) to ensure video compatibility with most codecs and players. To prevent resizing, make your input image divisible by the macro_block_size or set the macro_block_size to None (risking incompatibility). You may also see a FFMPEG warning concerning speedloss due to data not being aligned.