Copyright 2023 The TF-Agents Authors.

#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

具有 Actor-Learner API 的 SAC Minitaur#

在 TensorFlow.org 上查看 在 Google Colab 运行 在 Github 上查看源代码 下载笔记本

简介#

本例介绍如何在 Minitaur 环境中训练 Soft Actor Critic 代理。

如果您使用过 DQN Colab,应该会对这个环境很熟悉。一些明显的变化包括:

  • 将代理从 DQN 改成了 SAC。

  • 在 Minitaur 中训练,与 CartPole 相比,此环境要复杂得多。Minitaur 环境旨在训练一个四足机器人向前移动。

  • 使用 TF-Agents Actor-Learner API 进行分布式强化学习。

该 API 既支持使用经验回放缓冲区和可变容器(参数服务器)的分布式数据收集,也支持跨多个设备的分布训练,其设计非常简单,并且是模块化的。对于回放缓冲区和可变容器,我们都利用 Reverb,而对于 GPU 和 TPU 上的分布训练,我们都利用 TF DistributionStrategy API

如果尚未安装以下依赖项,请运行:

!sudo apt-get update
!sudo apt-get install -y xvfb ffmpeg
!pip install 'imageio==2.4.0'
!pip install matplotlib
!pip install tf-agents[reverb]
!pip install pybullet

设置#

首先,我们将导入所需的不同工具。

import base64
import imageio
import IPython
import matplotlib.pyplot as plt
import os
import reverb
import tempfile
import PIL.Image

import tensorflow as tf

from tf_agents.agents.ddpg import critic_network
from tf_agents.agents.sac import sac_agent
from tf_agents.agents.sac import tanh_normal_projection_network
from tf_agents.environments import suite_pybullet
from tf_agents.metrics import py_metrics
from tf_agents.networks import actor_distribution_network
from tf_agents.policies import greedy_policy
from tf_agents.policies import py_tf_eager_policy
from tf_agents.policies import random_py_policy
from tf_agents.replay_buffers import reverb_replay_buffer
from tf_agents.replay_buffers import reverb_utils
from tf_agents.train import actor
from tf_agents.train import learner
from tf_agents.train import triggers
from tf_agents.train.utils import spec_utils
from tf_agents.train.utils import strategy_utils
from tf_agents.train.utils import train_utils

tempdir = tempfile.gettempdir()

超参数#

env_name = "MinitaurBulletEnv-v0" # @param {type:"string"}

# Use "num_iterations = 1e6" for better results (2 hrs)
# 1e5 is just so this doesn't take too long (1 hr)
num_iterations = 100000 # @param {type:"integer"}

initial_collect_steps = 10000 # @param {type:"integer"}
collect_steps_per_iteration = 1 # @param {type:"integer"}
replay_buffer_capacity = 10000 # @param {type:"integer"}

batch_size = 256 # @param {type:"integer"}

critic_learning_rate = 3e-4 # @param {type:"number"}
actor_learning_rate = 3e-4 # @param {type:"number"}
alpha_learning_rate = 3e-4 # @param {type:"number"}
target_update_tau = 0.005 # @param {type:"number"}
target_update_period = 1 # @param {type:"number"}
gamma = 0.99 # @param {type:"number"}
reward_scale_factor = 1.0 # @param {type:"number"}

actor_fc_layer_params = (256, 256)
critic_joint_fc_layer_params = (256, 256)

log_interval = 5000 # @param {type:"integer"}

num_eval_episodes = 20 # @param {type:"integer"}
eval_interval = 10000 # @param {type:"integer"}

policy_save_interval = 5000 # @param {type:"integer"}

环境#

在强化学习 (RL) 中,环境代表要解决的任务或问题。在 TF-Agents 中,使用 suites 可以轻松创建标准环境。我们提供了不同的 suites,只需提供一个字符串环境名称,即可从 OpenAI Gym、Atari、DM Control 等来源加载环境。

现在,我们从 Pybullet 套件加载 Minituar 环境。

env = suite_pybullet.load(env_name)
env.reset()
PIL.Image.fromarray(env.render())

在该环境中,代理的目标是训练一个控制 Minitaur 机器人的策略,让机器人以尽可能快的速度向前移动。片段会持续 1000 个步骤,回报是整个片段的奖励总和。

我们看看该环境提供的 observation 信息,该策略将使用这些信息生成 actions

print('Observation Spec:')
print(env.time_step_spec().observation)
print('Action Spec:')
print(env.action_spec())

该观测值非常复杂。我们收到了 28 个值,分别代表所有电机的角度、速度和扭矩。作为回应,该环境希望获得操作介于 [-1, 1] 之间的 8 个值。这些值是需要的电机角度。

通常,我们创建两个环境:一个用于在训练过程中收集数据,另一个用于评估。这些环境使用纯 Python 语言编写,并且使用 NumPy 数组(由 Actor Learner API 直接使用)。

collect_env = suite_pybullet.load(env_name)
eval_env = suite_pybullet.load(env_name)

分布策略#

我们使用 DistributionStrategy API 来支持跨多个设备(如使用数据并行的多个 GPU 或 TPU)运行训练步骤计算。该训练步骤:

  • 接收一批训练数据

  • 将数据分发给设备

  • 计算前向步骤

  • 聚合并计算损失平均值

  • 计算后向步骤并执行梯度变量更新

使用 TF-Agents Learner API 和 DistributionStrategy API,从在 GPU 上运行训练步骤(使用 MirroredStrategy)切换到 TPU(使用 TPUStrategy)非常容易,无需更改下面的任何训练逻辑。

启用 GPU#

如果您希望在 GPU 上运行,首先需要为笔记本启用 GPU:

  • 导航至 Edit→Notebook Settings

  • 从 Hardware Accelerator 下拉列表中选择 GPU

选择策略#

使用 strategy_utils 生成策略。在后台传递参数:

  • use_gpu = False 返回 tf.distribute.get_strategy(),该策略使用 CPU

  • use_gpu = True 返回 tf.distribute.MirroredStrategy(),该策略使用一台计算机上 TensorFlow 可见的所有 GPU。

use_gpu = True #@param {type:"boolean"}

strategy = strategy_utils.get_strategy(tpu=False, use_gpu=use_gpu)

您需要在 strategy.scope() 下创建所有变量和代理,如下所示。

代理#

要创建 SAC 代理,首先要创建通过该代理训练的网络。SAC 是一个 Actor-Critic 代理,所以我们需要两个网络。

Critic 会提供 Q(s,a) 的值估算结果。也就是说,它会接收一个观测值和一个操作作为输入,同时提供该操作在指定状态下的表现的估算结果。

observation_spec, action_spec, time_step_spec = (
      spec_utils.get_tensor_specs(collect_env))

with strategy.scope():
  critic_net = critic_network.CriticNetwork(
        (observation_spec, action_spec),
        observation_fc_layer_params=None,
        action_fc_layer_params=None,
        joint_fc_layer_params=critic_joint_fc_layer_params,
        kernel_initializer='glorot_uniform',
        last_kernel_initializer='glorot_uniform')

我们将使用该 Critic 来训练 actor 网络,通过该网络,我们可以利用提供的观测值生成操作。

ActorNetwork 将预测 tanh-squashed MultivariateNormalDiag 分布的参数。随后,只要我们需要生成操作,就可以对该分布进行采样,根据当前观测值建立条件。

with strategy.scope():
  actor_net = actor_distribution_network.ActorDistributionNetwork(
      observation_spec,
      action_spec,
      fc_layer_params=actor_fc_layer_params,
      continuous_projection_net=(
          tanh_normal_projection_network.TanhNormalProjectionNetwork))

凭借这些已有的网络,我们现在可以实例化代理。

with strategy.scope():
  train_step = train_utils.create_train_step()

  tf_agent = sac_agent.SacAgent(
        time_step_spec,
        action_spec,
        actor_network=actor_net,
        critic_network=critic_net,
        actor_optimizer=tf.keras.optimizers.Adam(
            learning_rate=actor_learning_rate),
        critic_optimizer=tf.keras.optimizers.Adam(
            learning_rate=critic_learning_rate),
        alpha_optimizer=tf.keras.optimizers.Adam(
            learning_rate=alpha_learning_rate),
        target_update_tau=target_update_tau,
        target_update_period=target_update_period,
        td_errors_loss_fn=tf.math.squared_difference,
        gamma=gamma,
        reward_scale_factor=reward_scale_factor,
        train_step_counter=train_step)

  tf_agent.initialize()

回放缓冲区#

为了跟踪从环境收集的数据,我们将使用 Reverb——Deepmind 出品的一款高效、可扩展且易于使用的回放系统。它会存储 Actor 收集的经验数据,供 Learner 在训练时使用。

在本教程中,其重要性不如 max_size。但是,在使用异步收集和训练的分布设置中,您可能希望使用位置在 2 到 1000 之间的 samples_per_insert 来尝试执行 rate_limiters.SampleToInsertRatio。例如:

rate_limiter=reverb.rate_limiters.SampleToInsertRatio(samples_per_insert=3.0, min_size_to_sample=3, error_buffer=3.0)
table_name = 'uniform_table'
table = reverb.Table(
    table_name,
    max_size=replay_buffer_capacity,
    sampler=reverb.selectors.Uniform(),
    remover=reverb.selectors.Fifo(),
    rate_limiter=reverb.rate_limiters.MinSize(1))

reverb_server = reverb.Server([table])

回放缓冲区使用描述要存储的张量的规范构造,这些张量可从使用 tf_agent.collect_data_spec 的代理获取。

由于 SAC 代理同时需要当前和下一个观察值才能计算损失,因此,我们设置 sequence_length=2

reverb_replay = reverb_replay_buffer.ReverbReplayBuffer(
    tf_agent.collect_data_spec,
    sequence_length=2,
    table_name=table_name,
    local_server=reverb_server)

现在,我们创建一个驱动器来积累经验,利用这些经验设置回放缓冲区的种子。驱动器提供了一种简单的方法,让我们可以使用特定策略在环境中收集 n 个步骤或片段的数据。

dataset = reverb_replay.as_dataset(
      sample_batch_size=batch_size, num_steps=2).prefetch(50)
experience_dataset_fn = lambda: dataset

策略#

在 TF-Agents 中,策略就是 RL 中的标准概念策略:给定 time_step 来产生操作或操作的分布。主要方法是 policy_step = policy.step(time_step),其中 policy_step 是指定元祖 PolicyStep(action, state, info)policy_step.action 是要应用到环境的 actionstate 表示有状态 (RNN) 策略的状态,而 info 可能包含辅助信息(如操作的对数几率)。

代理包含两项策略:

  • agent.policy — 用于评估和部署的主策略。

  • agent.collect_policy — 用于数据收集的第二策略。

tf_eval_policy = tf_agent.policy
eval_policy = py_tf_eager_policy.PyTFEagerPolicy(
  tf_eval_policy, use_tf_function=True)
tf_collect_policy = tf_agent.collect_policy
collect_policy = py_tf_eager_policy.PyTFEagerPolicy(
  tf_collect_policy, use_tf_function=True)

策略可以独立于代理进行创建。例如,使用 tf_agents.policies.random_py_policy 创建策略,将为每个 time_step 随机选择一项操作。

random_policy = random_py_policy.RandomPyPolicy(
  collect_env.time_step_spec(), collect_env.action_spec())

Actor#

Actor 用于管理策略与环境之间的交互。

  • Actor 组件包含环境的一个实例( 作为 py_environment)和策略变量的一个副本。

  • 给定策略变量的本地值,每个 Actor 工作进程运行一系列数据收集步骤。

  • 在调用 actor.run() 之前,使用训练脚本中的可变容器客户端实例明确完成变量更新。

  • 在每个数据收集步骤中将观察到的经验写入回放缓冲区。

当 Actor 运行数据收集步骤时,它们会将(状态、操作、奖励)的轨迹传递给观察器,而观察器将缓存轨迹并将其写入 Reverb 回放系统。

由于 stride_length=1,因此,我们存储框架 [(t0,t1) (t1,t2) (t2,t3), …]。

rb_observer = reverb_utils.ReverbAddTrajectoryObserver(
  reverb_replay.py_client,
  table_name,
  sequence_length=2,
  stride_length=1)

我们使用随机策略创建一个 Actor,并收集经验以设置回放缓冲区的种子。

initial_collect_actor = actor.Actor(
  collect_env,
  random_policy,
  train_step,
  steps_per_run=initial_collect_steps,
  observers=[rb_observer])
initial_collect_actor.run()

在训练过程中使用收集策略实例化 Actor 以收集更多经验。

env_step_metric = py_metrics.EnvironmentSteps()
collect_actor = actor.Actor(
  collect_env,
  collect_policy,
  train_step,
  steps_per_run=1,
  metrics=actor.collect_metrics(10),
  summary_dir=os.path.join(tempdir, learner.TRAIN_DIR),
  observers=[rb_observer, env_step_metric])

创建一个可用于在训练过程中评估策略的 Actor。我们传入 actor.eval_metrics(num_eval_episodes),以便随后记录指标。

eval_actor = actor.Actor(
  eval_env,
  eval_policy,
  train_step,
  episodes_per_run=num_eval_episodes,
  metrics=actor.eval_metrics(num_eval_episodes),
  summary_dir=os.path.join(tempdir, 'eval'),
)

Learner#

Learner 组件包含代理,并使用回放缓冲区的经验数据执行策略变量的梯度步骤更新。在经过一个或多个训练步骤后,Learner 可以将一组新的变量值推送到可变容器。

saved_model_dir = os.path.join(tempdir, learner.POLICY_SAVED_MODEL_DIR)

# Triggers to save the agent's policy checkpoints.
learning_triggers = [
    triggers.PolicySavedModelTrigger(
        saved_model_dir,
        tf_agent,
        train_step,
        interval=policy_save_interval),
    triggers.StepPerSecondLogTrigger(train_step, interval=1000),
]

agent_learner = learner.Learner(
  tempdir,
  train_step,
  tf_agent,
  experience_dataset_fn,
  triggers=learning_triggers,
  strategy=strategy)

指标和评估#

我们使用上面的 actor.eval_metrics 实例化评价 Actor,这会创建在策略评估期间最常用的指标:

  • 平均回报。回报是在某一片段的环境中运行策略时获得的回报总和,我们通常会求几个片段的平均值。

  • 平均片段长度。

我们运行 Actor 以生成这些指标。

def get_eval_metrics():
  eval_actor.run()
  results = {}
  for metric in eval_actor.metrics:
    results[metric.name] = metric.result()
  return results

metrics = get_eval_metrics()
def log_eval_metrics(step, metrics):
  eval_results = (', ').join(
      '{} = {:.6f}'.format(name, result) for name, result in metrics.items())
  print('step = {0}: {1}'.format(step, eval_results))

log_eval_metrics(0, metrics)

有关不同指标的其他标准实现,请查看指标模块

训练代理#

训练循环包括从环境收集数据和优化代理的网络。在训练过程中,我们偶尔会评估代理的策略,看看效果如何。

#@test {"skip": true}
try:
  %%time
except:
  pass

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

# Evaluate the agent's policy once before training.
avg_return = get_eval_metrics()["AverageReturn"]
returns = [avg_return]

for _ in range(num_iterations):
  # Training.
  collect_actor.run()
  loss_info = agent_learner.run(iterations=1)

  # Evaluating.
  step = agent_learner.train_step_numpy

  if eval_interval and step % eval_interval == 0:
    metrics = get_eval_metrics()
    log_eval_metrics(step, metrics)
    returns.append(metrics["AverageReturn"])

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

rb_observer.close()
reverb_server.stop()

可视化#

绘图#

我们可以通过绘制回报与全局步骤的图形来了解代理的性能。在 Minitaur 中,奖励函数基于 Minitaur 在 1000 个步骤中行走了多长的距离,并扣除能量消耗。

#@test {"skip": true}

steps = range(0, num_iterations + 1, eval_interval)
plt.plot(steps, returns)
plt.ylabel('Average Return')
plt.xlabel('Step')
plt.ylim()

视频#

渲染每个步骤的环境有助于可视化代理的性能。在此之前,我们先创建一个函数,在该 Colab 中嵌入视频。

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)

以下代码可将代理策略可视化多个片段:

num_episodes = 3
video_filename = 'sac_minitaur.mp4'
with imageio.get_writer(video_filename, fps=60) as video:
  for _ in range(num_episodes):
    time_step = eval_env.reset()
    video.append_data(eval_env.render())
    while not time_step.is_last():
      action_step = eval_actor.policy.action(time_step)
      time_step = eval_env.step(action_step.action)
      video.append_data(eval_env.render())

embed_mp4(video_filename)