Среды

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

Вступление

Цель обучения с подкреплением (RL) - разработать агентов, которые обучаются, взаимодействуя с окружающей средой. В стандартной настройке RL агент получает наблюдение на каждом временном шаге и выбирает действие. Действие применяется к окружению, и оно возвращает награду и новое наблюдение. Агент обучает политику выбирать действия, чтобы максимизировать сумму вознаграждений, также известную как возврат.

В TF-Agents среды могут быть реализованы либо на Python, либо на TensorFlow. Среды Python обычно проще реализовать, понять и отладить, но среды TensorFlow более эффективны и допускают естественное распараллеливание. Наиболее распространенный рабочий процесс - реализовать среду на Python и использовать одну из наших оболочек для ее автоматического преобразования в TensorFlow.

Давайте сначала посмотрим на среды Python. Среды TensorFlow следуют очень похожему API.

Настраивать

Если вы еще не установили tf-agent или тренажерный зал, запустите:

pip install "gym>=0.21.0"
pip install tf-agents
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import abc
import tensorflow as tf
import numpy as np

from tf_agents.environments import py_environment
from tf_agents.environments import tf_environment
from tf_agents.environments import tf_py_environment
from tf_agents.environments import utils
from tf_agents.specs import array_spec
from tf_agents.environments import wrappers
from tf_agents.environments import suite_gym
from tf_agents.trajectories import time_step as ts

Среды Python

Среда Python есть step(action) -> next_time_step метод , который применяется действие на окружающую среду, и возвращает следующую информацию о следующем шаге:

  1. observation : Это часть состояния окружающей среды , что агент может наблюдать , чтобы выбрать свои действия на следующем шаге.
  2. reward : Агент обучения , чтобы максимизировать сумму этих наград через несколько этапов.
  3. step_type : Взаимодействие с окружающей средой , как правило , является частью последовательности / эпизода. например, несколько ходов в шахматной игре. step_type может быть либо FIRST , MID или LAST , чтобы указать на этот раз шаг является ли первым, средним или последним шагом в последовательности.
  4. discount : Это поплавок , представляющий сколько веса награду на следующий шаг по времени по отношению к награде на текущем временном шаге.

Они сгруппированы в именованный кортеж TimeStep(step_type, reward, discount, observation) .

Интерфейс , что все окружение Python должны реализовать в environments/py_environment.PyEnvironment . Основные методы:

class PyEnvironment(object):

  def reset(self):
    """Return initial_time_step."""
    self._current_time_step = self._reset()
    return self._current_time_step

  def step(self, action):
    """Apply action and return new time_step."""
    if self._current_time_step is None:
        return self.reset()
    self._current_time_step = self._step(action)
    return self._current_time_step

  def current_time_step(self):
    return self._current_time_step

  def time_step_spec(self):
    """Return time_step_spec."""

  @abc.abstractmethod
  def observation_spec(self):
    """Return observation_spec."""

  @abc.abstractmethod
  def action_spec(self):
    """Return action_spec."""

  @abc.abstractmethod
  def _reset(self):
    """Return initial_time_step."""

  @abc.abstractmethod
  def _step(self, action):
    """Apply action and return new time_step."""

В дополнение к step() метод, среды также обеспечивают reset() метод , который начинает новую последовательность , и обеспечивает начальное TimeStep . Не надо называть reset метод явно. Мы предполагаем, что среды сбрасываются автоматически, либо когда они доходят до конца эпизода, либо когда step () вызывается в первый раз.

Обратите внимание , что подклассы не реализуют step() или reset() непосредственно. Вместо этого они переопределить _step() и _reset() методы. Шаги времени , возвращаемые из этих методов будет кэшировать и экспонируют через current_time_step() .

observation_spec и action_spec методы возвращают гнездо (Bounded)ArraySpecs , которые описывают имя, форму, тип данных и диапазоны наблюдений и действий соответственно.

В TF-Agents мы неоднократно ссылаемся на гнезда, которые определяются как любая древовидная структура, состоящая из списков, кортежей, именованных кортежей или словарей. Они могут быть составлены произвольно, чтобы поддерживать структуру наблюдений и действий. Мы обнаружили, что это очень полезно для более сложных сред, где у вас есть много наблюдений и действий.

Использование стандартных сред

TF Agents имеет встроенный оберток для многих стандартных сред , таких как OpenAI тренажерного зал, DeepMind-контроль и Atari, так что они следуют за наш py_environment.PyEnvironment интерфейса. Эти обернутые среды можно легко загрузить с помощью наших наборов сред. Давайте загрузим среду CartPole из тренажерного зала OpenAI и посмотрим на действие и time_step_spec.

environment = suite_gym.load('CartPole-v0')
print('action_spec:', environment.action_spec())
print('time_step_spec.observation:', environment.time_step_spec().observation)
print('time_step_spec.step_type:', environment.time_step_spec().step_type)
print('time_step_spec.discount:', environment.time_step_spec().discount)
print('time_step_spec.reward:', environment.time_step_spec().reward)
action_spec: BoundedArraySpec(shape=(), dtype=dtype('int64'), name='action', minimum=0, maximum=1)
time_step_spec.observation: 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])
time_step_spec.step_type: ArraySpec(shape=(), dtype=dtype('int32'), name='step_type')
time_step_spec.discount: BoundedArraySpec(shape=(), dtype=dtype('float32'), name='discount', minimum=0.0, maximum=1.0)
time_step_spec.reward: ArraySpec(shape=(), dtype=dtype('float32'), name='reward')

Итак , мы видим , что среда ожидает действия типа int64 в [0, 1] и возвращает TimeSteps , где наблюдения являющиеся float32 вектор длины 4 и коэффициент дисконтирования является float32 в [0,0, 1,0]. Теперь давайте попробуем фиксирует действие (1,) для всего эпизода.

action = np.array(1, dtype=np.int32)
time_step = environment.reset()
print(time_step)
while not time_step.is_last():
  time_step = environment.step(action)
  print(time_step)
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.0138565 , -0.03582913,  0.04861612, -0.03755046], dtype=float32),
 'reward': array(0., dtype=float32),
 'step_type': array(0, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.01313992,  0.15856317,  0.0478651 , -0.3145069 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.01631118,  0.35297176,  0.04157497, -0.5917188 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.02337062,  0.54748774,  0.02974059, -0.87102115], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.03432037,  0.74219286,  0.01232017, -1.1542072 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.04916423,  0.93715197, -0.01076398, -1.4430016 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.06790727,  1.1324048 , -0.03962401, -1.7390285 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.09055536,  1.327955  , -0.07440457, -2.04377   ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.11711447,  1.523758  , -0.11527998, -2.3585167 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.14758962,  1.7197047 , -0.16245031, -2.6843033 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(0., dtype=float32),
 'observation': array([ 0.18198372,  1.9156038 , -0.21613638, -3.0218334 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(2, dtype=int32)})

Создание собственной среды Python

Для многих клиентов обычным вариантом использования является применение одного из стандартных агентов (см. «Агенты /») в TF-Agents для решения их проблемы. Для этого они должны представить свою проблему как среду. Итак, давайте посмотрим, как реализовать среду на Python.

Допустим, мы хотим научить агента играть в следующую карточную игру (вдохновленную Блэк Джеком):

  1. В игре используется бесконечная колода карт с номерами от 1 до 10.
  2. На каждом ходу агент может сделать 2 вещи: получить новую случайную карту или остановить текущий раунд.
  3. Цель состоит в том, чтобы получить сумму ваших карт как можно ближе к 21 в конце раунда, не переходя больше.

Окружение, представляющее игру, может выглядеть так:

  1. Действия: У нас есть 2 действия. Действие 0: получить новую карту, Действие 1: завершить текущий раунд.
  2. Наблюдения: сумма карт в текущем раунде.
  3. Награда: цель состоит в том, чтобы максимально приблизиться к 21, не переходя дальше, поэтому мы можем достичь этого, используя следующую награду в конце раунда: sum_of_cards - 21, если sum_of_cards <= 21, иначе -21
class CardGameEnv(py_environment.PyEnvironment):

  def __init__(self):
    self._action_spec = array_spec.BoundedArraySpec(
        shape=(), dtype=np.int32, minimum=0, maximum=1, name='action')
    self._observation_spec = array_spec.BoundedArraySpec(
        shape=(1,), dtype=np.int32, minimum=0, name='observation')
    self._state = 0
    self._episode_ended = False

  def action_spec(self):
    return self._action_spec

  def observation_spec(self):
    return self._observation_spec

  def _reset(self):
    self._state = 0
    self._episode_ended = False
    return ts.restart(np.array([self._state], dtype=np.int32))

  def _step(self, action):

    if self._episode_ended:
      # The last action ended the episode. Ignore the current action and start
      # a new episode.
      return self.reset()

    # Make sure episodes don't go on forever.
    if action == 1:
      self._episode_ended = True
    elif action == 0:
      new_card = np.random.randint(1, 11)
      self._state += new_card
    else:
      raise ValueError('`action` should be 0 or 1.')

    if self._episode_ended or self._state >= 21:
      reward = self._state - 21 if self._state <= 21 else -21
      return ts.termination(np.array([self._state], dtype=np.int32), reward)
    else:
      return ts.transition(
          np.array([self._state], dtype=np.int32), reward=0.0, discount=1.0)

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

Чтобы проверить нашу среду, мы будем использовать случайную политику для генерации действий, и мы будем повторять более 5 эпизодов, чтобы убедиться, что все работает так, как задумано. Ошибка возникает, если мы получаем time_step, который не соответствует спецификациям среды.

environment = CardGameEnv()
utils.validate_py_environment(environment, episodes=5)

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

get_new_card_action = np.array(0, dtype=np.int32)
end_round_action = np.array(1, dtype=np.int32)

environment = CardGameEnv()
time_step = environment.reset()
print(time_step)
cumulative_reward = time_step.reward

for _ in range(3):
  time_step = environment.step(get_new_card_action)
  print(time_step)
  cumulative_reward += time_step.reward

time_step = environment.step(end_round_action)
print(time_step)
cumulative_reward += time_step.reward
print('Final Reward = ', cumulative_reward)
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([0], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(0, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([9], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([12], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(0., dtype=float32),
 'observation': array([21], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(2, dtype=int32)})
TimeStep(
{'discount': array(0., dtype=float32),
 'observation': array([21], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(2, dtype=int32)})
Final Reward =  0.0

Окружающие оболочки

Оболочка среды принимает среду Python и возвращает измененную версию среды. И оригинальная среда и модифицированная среда являются экземплярами py_environment.PyEnvironment , и несколько оберток могут быть соединены друг с другом.

Некоторые общие обертки можно найти в environments/wrappers.py . Например:

  1. ActionDiscretizeWrapper : Преобразует непрерывное пространство действия в дискретном пространстве действия.
  2. RunStats : Захватывает запустить статистику окружающей среды , такие как число шагов , принятое, количество эпизодов завершения и т.д.
  3. TimeLimit : Завершает эпизод после фиксированного числа шагов.

Пример 1: оболочка Action Discretize

InvertedPendulum это среда PyBullet , которая принимает непрерывные действия в диапазоне [-2, 2] . Если мы хотим обучить дискретного агента действия, такого как DQN, в этой среде, мы должны дискретизировать (квантовать) пространство действий. Это именно то , что ActionDiscretizeWrapper делает. Сравните action_spec до и после упаковки:

env = suite_gym.load('Pendulum-v1')
print('Action Spec:', env.action_spec())

discrete_action_env = wrappers.ActionDiscretizeWrapper(env, num_actions=5)
print('Discretized Action Spec:', discrete_action_env.action_spec())
Action Spec: BoundedArraySpec(shape=(1,), dtype=dtype('float32'), name='action', minimum=-2.0, maximum=2.0)
Discretized Action Spec: BoundedArraySpec(shape=(), dtype=dtype('int32'), name='action', minimum=0, maximum=4)

Завернутый discrete_action_env является экземпляром py_environment.PyEnvironment и может рассматриваться как обычная среда Python.

Среды TensorFlow

Интерфейс для сред TF определяется в environments/tf_environment.TFEnvironment и внешний вид очень похож на среду Python. Среды TF отличаются от окружений Python несколькими способами:

  • Они генерируют тензорные объекты вместо массивов
  • Среды TF добавляют размерность пакета к тензорам, сгенерированным по сравнению со спецификациями.

Преобразование сред Python в TFEnvs позволяет тензорному потоку распараллеливать операции. Например, можно было бы определить collect_experience_op , который собирает данные из окружающей среды и добавляет к replay_buffer , и train_op , который читает из replay_buffer и тренирует агента, и запускать их параллельно , естественно , в TensorFlow.

class TFEnvironment(object):

  def time_step_spec(self):
    """Describes the `TimeStep` tensors returned by `step()`."""

  def observation_spec(self):
    """Defines the `TensorSpec` of observations provided by the environment."""

  def action_spec(self):
    """Describes the TensorSpecs of the action expected by `step(action)`."""

  def reset(self):
    """Returns the current `TimeStep` after resetting the Environment."""
    return self._reset()

  def current_time_step(self):
    """Returns the current `TimeStep`."""
    return self._current_time_step()

  def step(self, action):
    """Applies the action and returns the new `TimeStep`."""
    return self._step(action)

  @abc.abstractmethod
  def _reset(self):
    """Returns the current `TimeStep` after resetting the Environment."""

  @abc.abstractmethod
  def _current_time_step(self):
    """Returns the current `TimeStep`."""

  @abc.abstractmethod
  def _step(self, action):
    """Applies the action and returns the new `TimeStep`."""

current_time_step() метод возвращает текущий time_step и инициализирует среду , если это необходимо.

В reset() метод силы сброса в окружающую среду и возвращает CURRENT_STEP.

Если action не зависит от предыдущего time_step tf.control_dependency необходимо в Graph режиме.

На данный момент, давайте посмотрим на то, как TFEnvironments созданы.

Создание собственной среды TensorFlow

Это сложнее, чем создание сред в Python, поэтому мы не будем рассматривать его в этой статье. Пример доступен здесь . Более распространенный случай использования для реализации среды в Python и завернуть его в TensorFlow с помощью нашего TFPyEnvironment оболочки (см . Ниже)

Обертка среды Python в TensorFlow

Мы можем легко обернуть любую среду Python в среду TensorFlow с использованием TFPyEnvironment обертки.

env = suite_gym.load('CartPole-v0')
tf_env = tf_py_environment.TFPyEnvironment(env)

print(isinstance(tf_env, tf_environment.TFEnvironment))
print("TimeStep Specs:", tf_env.time_step_spec())
print("Action Specs:", tf_env.action_spec())
True
TimeStep Specs: TimeStep(
{'discount': BoundedTensorSpec(shape=(), dtype=tf.float32, name='discount', minimum=array(0., dtype=float32), maximum=array(1., dtype=float32)),
 '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)),
 'reward': TensorSpec(shape=(), dtype=tf.float32, name='reward'),
 'step_type': TensorSpec(shape=(), dtype=tf.int32, name='step_type')})
Action Specs: BoundedTensorSpec(shape=(), dtype=tf.int64, name='action', minimum=array(0), maximum=array(1))

Обратите внимание , что данные теперь типа: (Bounded)TensorSpec .

Примеры использования

Простой пример

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

tf_env = tf_py_environment.TFPyEnvironment(env)
# reset() creates the initial time_step after resetting the environment.
time_step = tf_env.reset()
num_steps = 3
transitions = []
reward = 0
for i in range(num_steps):
  action = tf.constant([i % 2])
  # applies the action and returns the new TimeStep.
  next_time_step = tf_env.step(action)
  transitions.append([time_step, action, next_time_step])
  reward += next_time_step.reward
  time_step = next_time_step

np_transitions = tf.nest.map_structure(lambda x: x.numpy(), transitions)
print('\n'.join(map(str, np_transitions)))
print('Total reward:', reward.numpy())
[TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.0078796 , -0.04736348, -0.04966116,  0.04563603]],
      dtype=float32),
 'reward': array([0.], dtype=float32),
 'step_type': array([0], dtype=int32)}), array([0], dtype=int32), TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.00882687, -0.24173944, -0.04874843,  0.32224613]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)})]
[TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.00882687, -0.24173944, -0.04874843,  0.32224613]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)}), array([1], dtype=int32), TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.01366166, -0.04595843, -0.04230351,  0.01459712]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)})]
[TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.01366166, -0.04595843, -0.04230351,  0.01459712]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)}), array([0], dtype=int32), TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.01458083, -0.24044897, -0.04201157,  0.2936384 ]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)})]
Total reward: [3.]

Целые серии

env = suite_gym.load('CartPole-v0')
tf_env = tf_py_environment.TFPyEnvironment(env)

time_step = tf_env.reset()
rewards = []
steps = []
num_episodes = 5

for _ in range(num_episodes):
  episode_reward = 0
  episode_steps = 0
  while not time_step.is_last():
    action = tf.random.uniform([1], 0, 2, dtype=tf.int32)
    time_step = tf_env.step(action)
    episode_steps += 1
    episode_reward += time_step.reward.numpy()
  rewards.append(episode_reward)
  steps.append(episode_steps)
  time_step = tf_env.reset()

num_steps = np.sum(steps)
avg_length = np.mean(steps)
avg_reward = np.mean(rewards)

print('num_episodes:', num_episodes, 'num_steps:', num_steps)
print('avg_length', avg_length, 'avg_reward:', avg_reward)
num_episodes: 5 num_steps: 131
avg_length 26.2 avg_reward: 26.2