Авторские права 2021 Авторы TF-Agents.
Посмотреть на 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
метод , который применяется действие на окружающую среду, и возвращает следующую информацию о следующем шаге:
-
observation
: Это часть состояния окружающей среды , что агент может наблюдать , чтобы выбрать свои действия на следующем шаге. -
reward
: Агент обучения , чтобы максимизировать сумму этих наград через несколько этапов. -
step_type
: Взаимодействие с окружающей средой , как правило , является частью последовательности / эпизода. например, несколько ходов в шахматной игре. step_type может быть либоFIRST
,MID
илиLAST
, чтобы указать на этот раз шаг является ли первым, средним или последним шагом в последовательности. -
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 до 10.
- На каждом ходу агент может сделать 2 вещи: получить новую случайную карту или остановить текущий раунд.
- Цель состоит в том, чтобы получить сумму ваших карт как можно ближе к 21 в конце раунда, не переходя больше.
Окружение, представляющее игру, может выглядеть так:
- Действия: У нас есть 2 действия. Действие 0: получить новую карту, Действие 1: завершить текущий раунд.
- Наблюдения: сумма карт в текущем раунде.
- Награда: цель состоит в том, чтобы максимально приблизиться к 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
. Например:
-
ActionDiscretizeWrapper
: Преобразует непрерывное пространство действия в дискретном пространстве действия. -
RunStats
: Захватывает запустить статистику окружающей среды , такие как число шагов , принятое, количество эпизодов завершения и т.д. -
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