Copyright 2021 Die Autoren der TF-Agenten.
![]() | ![]() | ![]() |
Einführung
Das Ziel von Reinforcement Learning (RL) ist es, Agenten zu entwerfen, die durch Interaktion mit einer Umgebung lernen. In der Standardeinstellung RL erhält der Agent bei jedem Zeitschritt eine Beobachtung und wählt eine Aktion aus. Die Aktion wird auf die Umgebung angewendet und die Umgebung gibt eine Belohnung und eine neue Beobachtung zurück. Der Agent trainiert eine Richtlinie zur Auswahl von Aktionen, um die Summe der Belohnungen zu maximieren, die auch als Rendite bezeichnet wird.
In TF-Agents können Umgebungen entweder in Python oder TensorFlow implementiert werden. Python-Umgebungen sind normalerweise einfacher zu implementieren, zu verstehen und zu debuggen, aber TensorFlow-Umgebungen sind effizienter und ermöglichen eine natürliche Parallelisierung. Der häufigste Workflow besteht darin, eine Umgebung in Python zu implementieren und sie mithilfe eines unserer Wrapper automatisch in TensorFlow zu konvertieren.
Betrachten wir zunächst die Python-Umgebungen. TensorFlow-Umgebungen folgen einer sehr ähnlichen API.
Konfiguration
Wenn Sie noch keine tf-Agenten oder ein Fitnessstudio installiert haben, führen Sie Folgendes aus:
pip install -q 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
tf.compat.v1.enable_v2_behavior()
Python-Umgebungen
Python-Umgebungen verfügen über eine step(action) -> next_time_step
Methode, die eine Aktion auf die Umgebung anwendet und die folgenden Informationen zum nächsten Schritt zurückgibt:
-
observation
: Dies ist der Teil des Umgebungszustands, den der Agent beobachten kann, um seine Aktionen im nächsten Schritt auszuwählen. -
reward
: Der Agent lernt, die Summe dieser Belohnungen über mehrere Schritte zu maximieren. -
step_type
: Interaktionen mit der Umgebung sind normalerweise Teil einer Sequenz / Episode. zB mehrere Züge in einer Schachpartie. step_type kann entwederFIRST
,MID
oderLAST
um anzugeben, ob dieser Zeitschritt der erste, mittlere oder letzte Schritt in einer Sequenz ist. -
discount
: Dies ist ein Float, der angibt, wie viel die Belohnung im nächsten Zeitschritt im Verhältnis zur Belohnung im aktuellen Zeitschritt gewichtet werden soll.
Diese sind in einem benannten Tupel TimeStep(step_type, reward, discount, observation)
.
Die Schnittstelle, die alle Python-Umgebungen implementieren müssen, befindet sich in den environments/py_environment.PyEnvironment
. Die Hauptmethoden sind:
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."""
Zusätzlich zur step()
-Methode bieten Umgebungen auch eine reset()
-Methode, die eine neue Sequenz startet und einen anfänglichen TimeStep
. Es ist nicht erforderlich, die reset
explizit aufzurufen. Wir gehen davon aus, dass Umgebungen automatisch zurückgesetzt werden, entweder wenn sie das Ende einer Episode erreichen oder wenn step () zum ersten Mal aufgerufen wird.
Beachten Sie, dass Unterklassen step()
oder reset()
direkt implementieren. Sie überschreiben stattdessen die _step()
und _reset()
. Die von diesen Methoden zurückgegebenen Zeitschritte werden zwischen current_time_step()
zwischengespeichert und current_time_step()
.
Die Methoden observation_spec
und action_spec
geben ein Nest von (Bounded)ArraySpecs
, die den Namen, die Form, den Datentyp und die Bereiche der Beobachtungen bzw. Aktionen beschreiben.
In TF-Agenten beziehen wir uns wiederholt auf Nester, die als jede baumartige Struktur definiert sind, die aus Listen, Tupeln, benannten Tupeln oder Wörterbüchern besteht. Diese können beliebig zusammengesetzt werden, um die Struktur der Beobachtungen und Aktionen aufrechtzuerhalten. Wir haben festgestellt, dass dies für komplexere Umgebungen mit vielen Beobachtungen und Aktionen sehr nützlich ist.
Verwenden von Standardumgebungen
TF Agents verfügt über integrierte Wrapper für viele Standardumgebungen wie OpenAI Gym, DeepMind-Control und Atari, sodass sie unserer Schnittstelle py_environment.PyEnvironment
folgen. Diese verpackten Umgebungen können einfach mit unseren Umgebungssuiten geladen werden. Laden wir die CartPole-Umgebung aus dem OpenAI-Fitnessstudio und sehen uns die Aktion und time_step_spec an.
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')
Wir sehen also, dass die Umgebung Aktionen vom Typ int64
in [0, 1] erwartet und TimeSteps
wobei die Beobachtungen ein float32
Vektor der Länge 4 sind und der Abzinsungsfaktor ein float32
in [0.0, 1.0] ist. Versuchen wir nun, eine feste Aktion (1,)
für eine ganze Episode auszuführen.
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(step_type=array(0, dtype=int32), reward=array(0., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.01285449, 0.04769544, 0.01983412, -0.00245379], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.0138084 , 0.24252741, 0.01978504, -0.2888134 ], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.01865895, 0.43736172, 0.01400878, -0.57519126], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.02740618, 0.6322845 , 0.00250495, -0.8634283 ], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.04005187, 0.82737225, -0.01476362, -1.1553226 ], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.05659932, 1.0226836 , -0.03787007, -1.452598 ], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.07705299, 1.2182497 , -0.06692202, -1.7568679 ], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.10141798, 1.4140631 , -0.10205939, -2.069591 ], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.12969925, 1.6100639 , -0.1434512 , -2.3920157 ], dtype=float32)) TimeStep(step_type=array(1, dtype=int32), reward=array(1., dtype=float32), discount=array(1., dtype=float32), observation=array([ 0.16190052, 1.8061239 , -0.19129153, -2.725115 ], dtype=float32)) TimeStep(step_type=array(2, dtype=int32), reward=array(1., dtype=float32), discount=array(0., dtype=float32), observation=array([ 0.198023 , 2.002027 , -0.24579382, -3.0695074 ], dtype=float32))
Erstellen Sie Ihre eigene Python-Umgebung
Für viele Kunden besteht ein häufiger Anwendungsfall darin, einen der Standardagenten (siehe Agenten /) in TF-Agenten auf ihr Problem anzuwenden. Dazu müssen sie ihr Problem als Umgebung gestalten. Schauen wir uns also an, wie eine Umgebung in Python implementiert wird.
Angenommen, wir möchten einen Agenten darin schulen, das folgende (von Black Jack inspirierte) Kartenspiel zu spielen:
- Das Spiel wird mit einem unendlichen Kartenspiel von 1 ... 10 gespielt.
- In jeder Runde kann der Agent zwei Dinge tun: eine neue Zufallskarte erhalten oder die aktuelle Runde stoppen.
- Das Ziel ist es, die Summe Ihrer Karten am Ende der Runde so nahe wie möglich an 21 zu bringen, ohne darüber hinwegzugehen.
Eine Umgebung, die das Spiel darstellt, könnte folgendermaßen aussehen:
- Aktionen: Wir haben 2 Aktionen. Aktion 0: Holen Sie sich eine neue Karte und Aktion 1: Beenden Sie die aktuelle Runde.
- Beobachtungen: Summe der Karten in der aktuellen Runde.
- Belohnung: Das Ziel ist es, so nahe wie möglich an 21 heranzukommen, ohne darüber hinwegzugehen. Dies können wir am Ende der Runde mit der folgenden Belohnung erreichen: sum_of_cards - 21 if sum_of_cards <= 21, else -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)
Stellen wir sicher, dass wir alles richtig gemacht haben, um die obige Umgebung zu definieren. Wenn Sie Ihre eigene Umgebung erstellen, müssen Sie sicherstellen, dass die generierten Beobachtungen und Zeitschritte den richtigen Formen und Typen entsprechen, wie sie in Ihren Spezifikationen definiert sind. Diese werden zum Generieren des TensorFlow-Diagramms verwendet und können daher zu schwer zu debuggenden Problemen führen, wenn wir sie falsch verstehen.
Um unsere Umgebung zu validieren, verwenden wir eine zufällige Richtlinie, um Aktionen zu generieren, und wir werden über 5 Episoden iterieren, um sicherzustellen, dass die Dinge wie beabsichtigt funktionieren. Ein Fehler wird ausgelöst, wenn wir einen Zeitschritt erhalten, der nicht den Umgebungsspezifikationen entspricht.
environment = CardGameEnv()
utils.validate_py_environment(environment, episodes=5)
Nachdem wir nun wissen, dass die Umgebung wie beabsichtigt funktioniert, führen wir diese Umgebung mit einer festen Richtlinie aus: Bitten Sie um 3 Karten und beenden Sie die Runde.
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(step_type=array(0, dtype=int32), reward=array(0., dtype=float32), discount=array(1., dtype=float32), observation=array([0], dtype=int32)) TimeStep(step_type=array(1, dtype=int32), reward=array(0., dtype=float32), discount=array(1., dtype=float32), observation=array([2], dtype=int32)) TimeStep(step_type=array(1, dtype=int32), reward=array(0., dtype=float32), discount=array(1., dtype=float32), observation=array([7], dtype=int32)) TimeStep(step_type=array(1, dtype=int32), reward=array(0., dtype=float32), discount=array(1., dtype=float32), observation=array([8], dtype=int32)) TimeStep(step_type=array(2, dtype=int32), reward=array(-13., dtype=float32), discount=array(0., dtype=float32), observation=array([8], dtype=int32)) Final Reward = -13.0
Umweltverpackungen
Ein Umgebungs-Wrapper verwendet eine Python-Umgebung und gibt eine geänderte Version der Umgebung zurück. Sowohl die ursprüngliche Umgebung als auch die geänderte Umgebung sind Instanzen von py_environment.PyEnvironment
, und mehrere Wrapper können miteinander verkettet werden.
Einige gängige Wrapper finden Sie in environments/wrappers.py
. Zum Beispiel:
-
ActionDiscretizeWrapper
: Konvertiert einen kontinuierlichen Aktionsraum in einen diskreten Aktionsraum. -
RunStats
:RunStats
der Umgebung, z. B. Anzahl der durchgeführten Schritte, Anzahl der abgeschlossenen Episoden usw. -
TimeLimit
: Beendet die Episode nach einer festgelegten Anzahl von Schritten.
Beispiel 1: Aktion Wrapper diskretisieren
InvertedPendulum ist eine PyBullet-Umgebung, die kontinuierliche Aktionen im Bereich [-2, 2]
akzeptiert. Wenn wir einen diskreten Aktionsagenten wie DQN in dieser Umgebung trainieren möchten, müssen wir den Aktionsraum diskretisieren (quantisieren). Genau das macht der ActionDiscretizeWrapper
. Vergleichen Sie die action_spec
vor und nach dem action_spec
:
env = suite_gym.load('Pendulum-v0')
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)
Das py_environment.PyEnvironment
discrete_action_env
ist eine Instanz von py_environment.PyEnvironment
und kann wie eine normale Python-Umgebung behandelt werden.
TensorFlow-Umgebungen
Die Schnittstelle für TF-Umgebungen ist in environments/tf_environment.TFEnvironment
und sieht den Python-Umgebungen sehr ähnlich. TF-Umgebungen unterscheiden sich in mehreren Punkten von Python-Umgebungen:
- Sie erzeugen Tensorobjekte anstelle von Arrays
- TF-Umgebungen fügen den generierten Tensoren im Vergleich zu den Spezifikationen eine Batch-Dimension hinzu.
Durch die Konvertierung der Python-Umgebungen in TFEnvs kann Tensorflow Operationen parallelisieren. Beispielsweise könnte man ein collect_experience_op
definieren, das Daten aus der Umgebung sammelt und zu einem replay_buffer
, und ein train_op
, das aus dem replay_buffer
liest und den Agenten trainiert und diese natürlich parallel in TensorFlow ausführt.
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`."""
Die Methode current_time_step current_time_step()
gibt den aktuellen time_step zurück und initialisiert die Umgebung bei Bedarf.
Die Methode reset()
erzwingt ein Zurücksetzen in der Umgebung und gibt den aktuellen Schritt zurück.
Wenn die action
nicht vom vorherigen time_step
tf.control_dependency
wird im Graph
eine tf.control_dependency
benötigt.
Lassen Sie uns zunächst untersuchen, wie TFEnvironments
erstellt werden.
Erstellen Sie Ihre eigene TensorFlow-Umgebung
Dies ist komplizierter als das Erstellen von Umgebungen in Python, daher werden wir es in dieser Spalte nicht behandeln. Ein Beispiel finden Sie hier . Der häufigste Anwendungsfall besteht darin, Ihre Umgebung in Python zu implementieren und mit unserem TFPyEnvironment
Wrapper in TensorFlow zu TFPyEnvironment
(siehe unten).
Umschließen einer Python-Umgebung in TensorFlow
Mit dem TFPyEnvironment
Wrapper können wir jede Python-Umgebung problemlos in eine TensorFlow-Umgebung 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(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)), 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 Specs: BoundedTensorSpec(shape=(), dtype=tf.int64, name='action', minimum=array(0), maximum=array(1))
Beachten Sie, dass die Spezifikationen jetzt vom Typ sind: (Bounded)TensorSpec
.
Anwendungsbeispiele
Einfaches Beispiel
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(step_type=array([0], dtype=int32), reward=array([0.], dtype=float32), discount=array([1.], dtype=float32), observation=array([[-0.03501577, -0.04957427, 0.00623939, 0.03762257]], dtype=float32)), array([0], dtype=int32), TimeStep(step_type=array([1], dtype=int32), reward=array([1.], dtype=float32), discount=array([1.], dtype=float32), observation=array([[-0.03600726, -0.24478514, 0.00699184, 0.33226755]], dtype=float32))] [TimeStep(step_type=array([1], dtype=int32), reward=array([1.], dtype=float32), discount=array([1.], dtype=float32), observation=array([[-0.03600726, -0.24478514, 0.00699184, 0.33226755]], dtype=float32)), array([1], dtype=int32), TimeStep(step_type=array([1], dtype=int32), reward=array([1.], dtype=float32), discount=array([1.], dtype=float32), observation=array([[-0.04090296, -0.0497634 , 0.01363719, 0.04179767]], dtype=float32))] [TimeStep(step_type=array([1], dtype=int32), reward=array([1.], dtype=float32), discount=array([1.], dtype=float32), observation=array([[-0.04090296, -0.0497634 , 0.01363719, 0.04179767]], dtype=float32)), array([0], dtype=int32), TimeStep(step_type=array([1], dtype=int32), reward=array([1.], dtype=float32), discount=array([1.], dtype=float32), observation=array([[-0.04189822, -0.24507822, 0.01447314, 0.33875188]], dtype=float32))] Total reward: [3.]
Ganze Episoden
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: 138 avg_length 27.6 avg_reward: 27.6