Google I / O возвращается 18-20 мая! Зарезервируйте место и составьте свое расписание Зарегистрируйтесь сейчас
Эта страница переведена с помощью Cloud Translation API.
Switch to English

Обучите сеть Deep Q с помощью агентов TF

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHubСкачать блокнот

Вступление

В этом примере показано, как обучить агент DQN (Deep Q Networks) в среде Cartpole с помощью библиотеки TF-Agents.

Окружающая среда Cartpole

Он проведет вас через все компоненты конвейера обучения с подкреплением (RL) для обучения, оценки и сбора данных.

Чтобы запустить этот код в реальном времени, щелкните ссылку «Запустить в Google Colab» выше.

Настраивать

Если вы не установили следующие зависимости, запустите:

sudo apt-get update
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.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'

Гиперпараметры

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"}

Среда

В обучении с подкреплением (RL) среда представляет собой задачу или проблему, которую необходимо решить. Стандартные среды могут быть созданы в TF-Agents с tf_agents.environments пакетов tf_agents.environments . У TF-Agents есть комплекты для загрузки сред из таких источников, как OpenAI Gym, Atari и DM Control.

Загрузите среду CartPole из пакета OpenAI Gym.

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

Вы можете визуализировать эту среду, чтобы увидеть, как она выглядит. К тележке прикреплен свободно качающийся шест. Цель состоит в том, чтобы переместить тележку вправо или влево, чтобы столб оставался направленным вверх.

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

PNG

Метод environment.step выполняет action в среде и возвращает кортеж TimeStep содержащий следующее наблюдение за средой и награду за действие.

Метод time_step_spec() возвращает спецификацию кортежа TimeStep . Его атрибут observation показывает форму наблюдений, типы данных и диапазоны допустимых значений. Атрибут reward показывает те же сведения о награде.

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')

Метод action_spec() возвращает форму, типы данных и допустимые значения допустимых действий.

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

В среде Cartpole:

  • observation представляет собой массив из 4-х поплавков:
    • положение и скорость тележки
    • угловое положение и скорость полюса
  • reward - это скалярное значение с плавающей запятой
  • action - это скалярное целое число, имеющее только два возможных значения:
    • 0 - «двигаться влево»
    • 1 - «двигаться вправо»
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.01730286,  0.04666085,  0.01145206, -0.0473538 ], 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.01823608,  0.24161673,  0.01050499, -0.3364016 ], dtype=float32))

Обычно создаются две среды: одна для обучения, а другая - для оценки.

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

Среда Cartpole, как и большинство сред, написана на чистом Python. Он преобразуется в TensorFlow с TFPyEnvironment оболочки TFPyEnvironment .

API исходной среды использует массивы Numpy. TFPyEnvironment преобразует их в Tensors чтобы сделать его совместимым с агентами и политиками Tensorflow.

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

Агент

Алгоритм, используемый для решения проблемы RL, представлен Agent . TF-Agents предоставляет стандартные реализации различных Agents , в том числе:

Агент DQN можно использовать в любой среде с дискретным пространством действий.

В основе агента DQN лежит QNetwork , модель нейронной сети, которая может научиться предсказывать QValues (ожидаемые результаты) для всех действий с учетом наблюдения из окружающей среды.

Мы будем использовать tf_agents.networks. для создания QNetwork . Сеть будет состоять из последовательности слоев tf.keras.layers.Dense , где последний слой будет иметь 1 выход для каждого возможного действия.

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])

Теперь используйте tf_agents.agents.dqn.dqn_agent для создания экземпляра DqnAgent . В дополнение к time_step_spec , action_spec и QNetwork конструктору агента также требуется оптимизатор (в данном случае AdamOptimizer ), функция потерь и целочисленный счетчик шагов.

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()

Политики

Политика определяет способ действий агента в среде. Обычно цель обучения с подкреплением состоит в том, чтобы тренировать базовую модель до тех пор, пока политика не приведет к желаемому результату.

В этом руководстве:

  • Желаемый результат - удерживать шест в вертикальном положении над тележкой.
  • Политика возвращает действие (влево или вправо) для каждого наблюдения time_step .

Агенты содержат две политики:

  • agent.policy - основная политика, используемая для оценки и развертывания.
  • agent.collect_policy - вторая политика, которая используется для сбора данных.
eval_policy = agent.policy
collect_policy = agent.collect_policy

Политики можно создавать независимо от агентов. Например, используйте tf_agents.policies.random_tf_policy для создания политики, которая будет случайным образом выбирать действие для каждого time_step .

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

Чтобы получить действие из политики, вызовите метод policy.action(time_step) . time_step содержит наблюдение из окружающей среды. Этот метод возвращает PolicyStep , который представляет собой именованный кортеж с тремя компонентами:

  • action - действие, которое нужно предпринять (в данном случае 0 или 1 )
  • state - используется для политик с отслеживанием состояния (то есть на основе RNN)
  • info - вспомогательные данные, такие как лог вероятностей действий
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([1])>, state=(), info=())

Метрики и оценка

Наиболее распространенный показатель, используемый для оценки политики, - это средний доход. Возврат - это сумма вознаграждений, полученных при выполнении политики в среде для эпизода. Запускается несколько серий, что дает средний доход.

Следующая функция вычисляет средний доход от политики, учитывая политику, среду и количество эпизодов.

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

Выполнение этого вычисления в random_policy показывает базовую производительность в среде.

compute_avg_return(eval_env, random_policy, num_eval_episodes)
16.1

Буфер воспроизведения

Буфер воспроизведения отслеживает данные, собранные из среды. В этом руководстве используется tf_agents.replay_buffers.tf_uniform_replay_buffer.TFUniformReplayBuffer , поскольку он является наиболее распространенным.

Конструктору требуются спецификации данных, которые он будет собирать. Это доступно из агента с collect_data_spec метода collect_data_spec . Также требуются размер пакета и максимальная длина буфера.

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)

Для большинства агентов collect_data_spec представляет собой именованный кортеж, называемый Trajectory , содержащий спецификации для наблюдений, действий, наград и других элементов.

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')

Сбор информации

Теперь выполните в среде несколько шагов случайной политики, записав данные в буфер воспроизведения.

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 tutorial 4 or the drivers module.
# https://github.com/tensorflow/agents/blob/master/docs/tutorials/4_drivers_tutorial.ipynb 
# https://www.tensorflow.org/agents/api_docs/python/tf_agents/drivers

Буфер воспроизведения теперь представляет собой набор траекторий.

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

Агенту нужен доступ к буферу воспроизведения. Это обеспечивается созданиемtf.data.Dataset конвейераtf.data.Dataset который будет передавать данные агенту.

Каждая строка буфера воспроизведения хранит только один шаг наблюдения. Но поскольку агенту DQN для вычисления потерь требуется и текущее, и следующее наблюдение, конвейер набора данных будет производить выборку двух соседних строк для каждого элемента в пакете ( num_steps=2 ).

Этот набор данных также оптимизирован за счет выполнения параллельных вызовов и предварительной выборки данных.

# 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 0x7fc0c5abc780>
# 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()

Обучение агента

Во время цикла обучения должны произойти две вещи:

  • собирать данные из окружающей среды
  • использовать эти данные для обучения нейронной сети (сетей) агента

В этом примере также периодически оценивается политика и выводится текущая оценка.

Выполнение следующего займет ~ 5 минут.

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 = 28.54922866821289
step = 400: loss = 11.265779495239258
step = 600: loss = 118.93780517578125
step = 800: loss = 6.945855140686035
step = 1000: loss = 81.30548095703125
step = 1000: Average Return = 100.5
step = 1200: loss = 583.738525390625
step = 1400: loss = 84.12023162841797
step = 1600: loss = 1195.0528564453125
step = 1800: loss = 798.727294921875
step = 2000: loss = 1214.359619140625
step = 2000: Average Return = 80.5
step = 2200: loss = 119.32345581054688
step = 2400: loss = 89.71794128417969
step = 2600: loss = 77.56394958496094
step = 2800: loss = 200.96832275390625
step = 3000: loss = 687.2619018554688
step = 3000: Average Return = 200.0
step = 3200: loss = 273.20257568359375
step = 3400: loss = 1348.410400390625
step = 3600: loss = 7291.123046875
step = 3800: loss = 9693.671875
step = 4000: loss = 11049.0078125
step = 4000: Average Return = 200.0
step = 4200: loss = 19630.55078125
step = 4400: loss = 162683.890625
step = 4600: loss = 188355.25
step = 4800: loss = 1085961.625
step = 5000: loss = 28136.2421875
step = 5000: Average Return = 200.0
step = 5200: loss = 41078.0703125
step = 5400: loss = 463938.15625
step = 5600: loss = 37489.6875
step = 5800: loss = 85520.1875
step = 6000: loss = 54998.30078125
step = 6000: Average Return = 200.0
step = 6200: loss = 57353.96875
step = 6400: loss = 82274.921875
step = 6600: loss = 480124.1875
step = 6800: loss = 125073.3515625
step = 7000: loss = 109867.765625
step = 7000: Average Return = 200.0
step = 7200: loss = 95796.765625
step = 7400: loss = 84803.3828125
step = 7600: loss = 174417.65625
step = 7800: loss = 145947.21875
step = 8000: loss = 857481.8125
step = 8000: Average Return = 200.0
step = 8200: loss = 198140.53125
step = 8400: loss = 184674.125
step = 8600: loss = 284932.15625
step = 8800: loss = 140546.625
step = 9000: loss = 1301324.125
step = 9000: Average Return = 200.0
step = 9200: loss = 2169464.0
step = 9400: loss = 192097.03125
step = 9600: loss = 95835.109375
step = 9800: loss = 362408.4375
step = 10000: loss = 234460.84375
step = 10000: Average Return = 200.0
step = 10200: loss = 2636405.0
step = 10400: loss = 406090.9375
step = 10600: loss = 218874.421875
step = 10800: loss = 314460.375
step = 11000: loss = 313773.625
step = 11000: Average Return = 200.0
step = 11200: loss = 235539.46875
step = 11400: loss = 230301.625
step = 11600: loss = 195967.515625
step = 11800: loss = 529188.25
step = 12000: loss = 559874.75
step = 12000: Average Return = 200.0
step = 12200: loss = 517803.1875
step = 12400: loss = 379510.1875
step = 12600: loss = 192517.515625
step = 12800: loss = 254534.40625
step = 13000: loss = 247311.53125
step = 13000: Average Return = 200.0
step = 13200: loss = 467653.5625
step = 13400: loss = 266234.1875
step = 13600: loss = 254158640.0
step = 13800: loss = 273959.4375
step = 14000: loss = 1116587.125
step = 14000: Average Return = 200.0
step = 14200: loss = 556657.3125
step = 14400: loss = 657635.75
step = 14600: loss = 637587.5625
step = 14800: loss = 518682.375
step = 15000: loss = 427786.5
step = 15000: Average Return = 200.0
step = 15200: loss = 393602.625
step = 15400: loss = 470813.375
step = 15600: loss = 49263600.0
step = 15800: loss = 18822354.0
step = 16000: loss = 1389915.625
step = 16000: Average Return = 200.0
step = 16200: loss = 179496144.0
step = 16400: loss = 3241569.5
step = 16600: loss = 12142488.0
step = 16800: loss = 922398.3125
step = 17000: loss = 1751954.625
step = 17000: Average Return = 200.0
step = 17200: loss = 70321296.0
step = 17400: loss = 2027174.0
step = 17600: loss = 103143216.0
step = 17800: loss = 71887584.0
step = 18000: loss = 1041745.375
step = 18000: Average Return = 200.0
step = 18200: loss = 899747.5
step = 18400: loss = 1508126.375
step = 18600: loss = 18633292.0
step = 18800: loss = 1058055.25
step = 19000: loss = 2446145.0
step = 19000: Average Return = 200.0
step = 19200: loss = 2081777.875
step = 19400: loss = 1023876.875
step = 19600: loss = 1734647.25
step = 19800: loss = 1467883.75
step = 20000: loss = 90669328.0
step = 20000: Average Return = 200.0

Визуализация

Сюжеты

Используйте matplotlib.pyplot чтобы matplotlib.pyplot как политика улучшилась во время обучения.

Одна итерация Cartpole-v0 состоит из 200 временных шагов. Окружающая среда дает вознаграждение в размере +1 за каждый шаг, когда полюс остается вверх, поэтому максимальная отдача для одного эпизода составляет 200. Графики показывают, что отдача увеличивается до этого максимума каждый раз, когда она оценивается во время тренировки. (Он может быть немного нестабильным и не увеличиваться монотонно каждый раз.)

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

PNG

Видео

Графики хороши. Но еще интереснее видеть, как агент действительно выполняет задачу в среде.

Сначала создайте функцию для встраивания видео в блокнот.

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)

Теперь пройдите через несколько эпизодов игры Cartpole с агентом. Базовая среда Python (та, что находится «внутри» оболочки среды TensorFlow) предоставляет метод render() , который выводит изображение состояния среды. Их можно собрать в видео.

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.

Для развлечения сравните обученного агента (см. Выше) с агентом, перемещающимся случайным образом. (Это не так.)

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.