注意
转到底部 下载完整的示例代码。
多智能体强化学习(PPO)TorchRL教程¶
作者: Matteo Bettini
另请参阅
BenchMARL 库提供了使用 TorchRL 的最先进的多智能体强化学习 (MARL) 算法实现。
本教程演示了如何使用 PyTorch 和 torchrl
来解决多智能体强化学习 (MARL) 问题。
为方便起见,本教程将遵循现有教程的通用结构:TorchRL 强化学习(PPO)教程。建议在开始本教程之前熟悉该教程,但并非强制要求。
在本教程中,我们将使用来自 VMAS 的“导航”环境。VMAS 是一个多机器人模拟器,也基于 PyTorch,它在设备上运行并行批量模拟。
在“导航”环境中,我们需要训练多个机器人(在随机位置生成)导航到它们的目标(也在随机位置),同时使用 LIDAR 传感器来避免相互碰撞。

多智能体“导航”场景¶
主要学习内容
如何在 TorchRL 中创建多智能体环境,其规范(specs)如何工作,以及它如何与库集成;
如何在 TorchRL 中使用 GPU 向量化环境;
如何在 TorchRL 中创建不同的多智能体网络架构(例如,使用参数共享、集中式评论员)
如何使用
tensordict.TensorDict
来承载多智能体数据;如何将所有库组件(收集器、模块、回放缓冲区和损失)集成到多智能体 MAPPO/IPPO 训练循环中。
如果您在 Google Colab 中运行此代码,请确保安装以下依赖项
!pip3 install torchrl
!pip3 install vmas
!pip3 install tqdm
Proximal Policy Optimization (PPO) 是一种策略梯度算法,它收集一批数据并直接使用这些数据来最大化预期回报,同时满足邻近性约束。您可以将其视为 REINFORCE(基础策略优化算法)的复杂版本。有关更多信息,请参阅 Proximal Policy Optimization Algorithms 论文。
这类算法通常是“**在线**”训练的。这意味着在每次学习迭代中,我们都有一个 **采样** 和一个 **训练** 阶段。在迭代 \(t\) 的 **采样** 阶段,使用当前策略 \(\mathbf{\pi}_t\) 从智能体与环境的交互中收集的轨迹(rollouts)。在 **训练** 阶段,所有收集到的轨迹会立即馈送到训练过程中进行反向传播。这会产生更新后的策略,然后这些策略会再次用于采样。这个过程的循环执行构成了“**在线学习**”。

在线学习¶
在 PPO 算法的训练阶段,**评论员 (critic)** 用于估计策略所采取动作的“好坏”。评论员学习近似特定状态的价值(平均折扣回报)。PPO 损失然后比较策略获得的实际回报与评论员估计的回报,以确定所采取动作的优势,并指导策略优化。
在多智能体环境中,情况有点不同。我们现在有多个策略 \(\mathbf{\pi}\),每个智能体一个。策略通常是局部的和分散的。这意味着单个智能体的策略将仅根据其观察输出来自该智能体的动作。在 MARL 文献中,这被称为 **分散式执行**。另一方面,评论员存在不同的形式,主要是
在 MAPPO 中,评论员是集中式的,并接收系统全局状态作为输入。这可以是全局观察,也可以是智能体观察的简单拼接。MAPPO 可用于执行 **集中式训练** 的场景,因为它需要访问全局信息。
在 IPPO 中,评论员仅接收相应智能体的观察作为输入,与策略完全相同。这允许 **分散式训练**,因为评论员和策略都只需要本地信息来计算它们的输出。
集中式评论员有助于克服多个智能体并发学习带来的非平稳性,但另一方面,它们可能会受到其庞大输入空间的影响。在本教程中,我们将能够训练这两种形式,并且还将讨论参数共享(跨智能体共享网络参数的做法)对它们各自的影响。
本教程结构如下
首先,我们将定义一组将要使用的超参数。
接下来,我们将创建一个向量化多智能体环境,使用 TorchRL 对 VMAS 模拟器的封装。
接下来,我们将设计策略和评论员网络,讨论各种选择对参数共享和评论员集中化的影响。
接下来,我们将创建采样收集器和回放缓冲区。
最后,我们将运行训练循环并分析结果。
如果您在 Colab 或具有 GUI 的机器上运行此教程,您还可以选择在训练前后渲染并可视化您自己训练的策略。
让我们导入我们的依赖项
# Torch
import torch
# Tensordict modules
from tensordict.nn import set_composite_lp_aggregate, TensorDictModule
from tensordict.nn.distributions import NormalParamExtractor
from torch import multiprocessing
# Data collection
from torchrl.collectors import SyncDataCollector
from torchrl.data.replay_buffers import ReplayBuffer
from torchrl.data.replay_buffers.samplers import SamplerWithoutReplacement
from torchrl.data.replay_buffers.storages import LazyTensorStorage
# Env
from torchrl.envs import RewardSum, TransformedEnv
from torchrl.envs.libs.vmas import VmasEnv
from torchrl.envs.utils import check_env_specs
# Multi-agent network
from torchrl.modules import MultiAgentMLP, ProbabilisticActor, TanhNormal
# Loss
from torchrl.objectives import ClipPPOLoss, ValueEstimators
# Utils
torch.manual_seed(0)
from matplotlib import pyplot as plt
from tqdm import tqdm
定义超参数¶
我们为本教程设置了超参数。根据可用资源,可以选择在 GPU 或其他设备上执行策略和模拟器。您可以调整其中一些值以调整计算要求。
# Devices
is_fork = multiprocessing.get_start_method() == "fork"
device = (
torch.device(0)
if torch.cuda.is_available() and not is_fork
else torch.device("cpu")
)
vmas_device = device # The device where the simulator is run (VMAS can run on GPU)
# Sampling
frames_per_batch = 6_000 # Number of team frames collected per training iteration
n_iters = 5 # Number of sampling and training iterations
total_frames = frames_per_batch * n_iters
# Training
num_epochs = 30 # Number of optimization steps per training iteration
minibatch_size = 400 # Size of the mini-batches in each optimization step
lr = 3e-4 # Learning rate
max_grad_norm = 1.0 # Maximum norm for the gradients
# PPO
clip_epsilon = 0.2 # clip value for PPO loss
gamma = 0.99 # discount factor
lmbda = 0.9 # lambda for generalised advantage estimation
entropy_eps = 1e-4 # coefficient of the entropy term in the PPO loss
# disable log-prob aggregation
set_composite_lp_aggregate(False).set()
环境¶
多智能体环境模拟多个智能体与世界进行交互。TorchRL API 允许集成各种类型的多智能体环境。例如,具有共享或单独的智能体奖励、完成标志和观察的环境。有关 TorchRL 中多智能体环境 API 如何工作的更多信息,您可以查看专门的 文档部分。
特别是 VMAS 模拟器,它模拟具有单独奖励、信息、观察和动作的智能体,但具有一个集合的完成标志。此外,它使用*向量化*在批处理中执行模拟。这意味着其所有状态和物理都是 PyTorch 张量,第一个维度表示批次中的并行环境数量。这允许利用 GPU 的单指令多数据(SIMD)范例,并通过利用 GPU 线程块的并行性显著加快并行计算。它还意味着,当在 TorchRL 中使用它时,模拟和训练都可以设备上进行,而无需将数据传递到 CPU。
今天我们将解决的多智能体任务是“导航”(见上面的动画图)。在“导航”任务中,随机生成的智能体(带有点状外环的圆圈)需要导航到随机生成的(目标)圆圈。智能体需要使用 LIDARs(它们周围的点)来避免相互碰撞。智能体在一个 2D 连续世界中行动,具有阻力和弹性碰撞。它们的动作是 2D 连续力,决定了它们的加速度。奖励由三个部分组成:碰撞惩罚、基于与目标距离的奖励,以及当所有智能体到达目标时给予的最终共享奖励。基于距离的部分计算为智能体与其目标之间的相对距离在两个连续时间步长之间的差值。每个智能体观察其位置、速度、激光雷达读数以及与其目标的相对位置。
我们现在将实例化环境。在本教程中,我们将情节限制在 max_steps
,之后将设置完成标志。此功能已在 VMAS 模拟器中提供,但 TorchRL 的 StepCount
变换也可以使用。我们还将使用 num_vmas_envs
个向量化环境,以利用批处理模拟。
max_steps = 100 # Episode steps before done
num_vmas_envs = (
frames_per_batch // max_steps
) # Number of vectorized envs. frames_per_batch should be divisible by this number
scenario_name = "navigation"
n_agents = 3
env = VmasEnv(
scenario=scenario_name,
num_envs=num_vmas_envs,
continuous_actions=True, # VMAS supports both continuous and discrete actions
max_steps=max_steps,
device=vmas_device,
# Scenario kwargs
n_agents=n_agents, # These are custom kwargs that change for each VMAS scenario, see the VMAS repo to know more.
)
环境不仅由其模拟器和变换定义,还由一系列元数据定义,这些元数据描述了在执行期间可以预期什么。为了效率起见,TorchRL 在环境规范方面非常严格,但您可以轻松检查您的环境规范是否足够。在我们的示例中,VmasEnv
负责为您的环境设置正确的规范,因此您不必担心这一点。
有四个规范需要关注
action_spec
定义动作空间;reward_spec
定义奖励域;done_spec
定义完成域;observation_spec
,它定义了环境步骤所有其他输出的域;
print("action_spec:", env.full_action_spec)
print("reward_spec:", env.full_reward_spec)
print("done_spec:", env.full_done_spec)
print("observation_spec:", env.observation_spec)
使用上面显示的命令,我们可以访问每个值的域。通过这样做,我们可以看到,除了 done 之外,所有规范都有一个前导形状 (num_vmas_envs, n_agents)
。这表示这些值对于每个独立环境中的每个智能体都将存在。另一方面,done 规范具有前导形状 num_vmas_envs
,表示 done 是在智能体之间共享的。
TorchRL 有一种方法可以跟踪哪些 MARL 规范是共享的,哪些不是。事实上,具有额外智能体维度(即,它们因智能体而异)的规范将包含在一个内部“agents”键中。
正如您所看到的,奖励和动作规范都存在“agent”键,这意味着属于这些规范的 tensordict 中的条目将被嵌套在“agents” tensordict 中,对所有每个智能体的值进行分组。
为了快速访问 tensordict 中这些值各自的键,我们可以简单地向环境询问相应的键,我们就能立即理解哪些是每个智能体特有的,哪些是共享的。这些信息对于告知所有其他 TorchRL 组件在哪里找到每个值非常有用
print("action_keys:", env.action_keys)
print("reward_keys:", env.reward_keys)
print("done_keys:", env.done_keys)
变换 (Transforms)¶
我们可以将任何需要的 TorchRL 变换附加到我们的环境中。这些变换将以所需的某种方式修改其输入/输出。我们强调,在多智能体环境中,明确提供要修改的键至关重要。
例如,在这种情况下,我们将实例化一个 RewardSum
变换,它将对整个情节的奖励进行求和。我们将告诉此变换奖励键的位置以及累加的情节奖励的写入位置。变换后的环境将继承被包装环境的设备和元数据,并根据其包含的变换序列来变换它们。
env = TransformedEnv(
env,
RewardSum(in_keys=[env.reward_key], out_keys=[("agents", "episode_reward")]),
)
check_env_specs()
函数运行一个小的回滚,并将其输出与环境规范进行比较。如果没有引发错误,我们可以确信规范已正确定义。
check_env_specs(env)
回滚 (Rollout)¶
为了好玩,让我们看看一个简单的随机回滚是什么样的。您可以调用 env.rollout(n_steps) 并获得环境输入和输出外观的概述。动作将自动从动作规范域中随机抽取。
n_rollout_steps = 5
rollout = env.rollout(n_rollout_steps)
print("rollout of three steps:", rollout)
print("Shape of the rollout TensorDict:", rollout.batch_size)
我们可以看到我们的回滚具有 batch_size
为 (num_vmas_envs, n_rollout_steps)
。这意味着其中的所有张量都将具有这些前导维度。
更深入地看,我们可以看到输出的 tensordict 可以这样划分
在根目录(可以通过运行
rollout.exclude("next")
访问)我们将找到在第一次时间步长调用 reset 后可用的所有键。我们可以通过索引n_rollout_steps
维度来查看它们在回滚步骤中的演变。在这些键中,我们将找到在rollout["agents"]
tensordict 中因智能体而异的键,它将具有批次大小(num_vmas_envs, n_rollout_steps, n_agents)
,表示它存储了额外的智能体维度。不在这个智能体 tensordict 中的是共享的键(在本例中只有 done)。在 next(可以通过运行
rollout.get("next")
访问)。我们将找到与根目录相同的结构,但对于仅在步之后可用的键。
在 TorchRL 中,约定是 done 和 observations 将同时存在于 root 和 next 中(因为它们在 reset 时间和步之后都可用)。Action 只在 root 中可用(因为步没有产生 action),reward 只在 next 中可用(因为 reset 时间没有 reward)。这种结构遵循 **Reinforcement Learning: An Introduction (Sutton and Barto)** 中的结构,其中 root 表示时间 \(t\) 的数据,next 表示世界步时间 \(t+1\) 的数据。
渲染随机回滚¶
如果您正在使用 Google Colab 或拥有 OpenGL 和 GUI 的机器,您实际上可以渲染一个随机回滚。这将让您了解随机策略在此任务中会取得什么成就,以便与您自己训练的策略进行比较!
要渲染回滚,请按照本教程末尾“渲染”部分的说明进行操作,只需从 env.rollout()
中删除 policy=policy
行即可。
策略 (Policy)¶
PPO 使用随机策略来处理探索。这意味着我们的神经网络将不得不输出一个分布的参数,而不是对应于所采取动作的单个值。
由于数据是连续的,我们使用 Tanh-Normal 分布来尊重动作空间的边界。TorchRL 提供了这种分布,我们唯一需要关心的是构建一个输出正确数量参数的神经网络。
在这种情况下,每个智能体的动作将由一个 2 维的独立正态分布表示。为此,我们的神经网络将不得不为每个动作输出均值和标准差。因此,每个智能体将有 2 * n_actions_per_agents
个输出。
另一个我们需要做出的重要决定是是否希望我们的智能体**共享策略参数**。一方面,共享参数意味着它们将共享相同的策略,这将使它们能够从彼此的经验中受益。这也将导致更快的训练。另一方面,它将使它们在行为上*同质化*,因为它们实际上将共享相同的模型。在本例中,我们将启用共享,因为我们不介意同质化并且可以受益于计算速度,但始终考虑您自己问题中的这一决定非常重要!
我们分三个步骤设计策略。
第一步:定义一个神经网络 n_obs_per_agent
-> 2 * n_actions_per_agents
为此,我们使用 MultiAgentMLP
,这是一个 TorchRL 模块,专为多个智能体设计,并提供大量自定义选项。
share_parameters_policy = True
policy_net = torch.nn.Sequential(
MultiAgentMLP(
n_agent_inputs=env.observation_spec["agents", "observation"].shape[
-1
], # n_obs_per_agent
n_agent_outputs=2
* env.full_action_spec[env.action_key].shape[-1], # 2 * n_actions_per_agents
n_agents=env.n_agents,
centralised=False, # the policies are decentralised (ie each agent will act from its observation)
share_params=share_parameters_policy,
device=device,
depth=2,
num_cells=256,
activation_class=torch.nn.Tanh,
),
NormalParamExtractor(), # this will just separate the last dimension into two outputs: a loc and a non-negative scale
)
第二步:将神经网络包装在 TensorDictModule
中
这只是一个模块,它将从 tensordict 读取 in_keys
,将它们馈送到神经网络,并将输出就地写入 out_keys
。
请注意,我们使用 ("agents", ...)
键,因为这些键表示带有额外 n_agents
维度的数据。
policy_module = TensorDictModule(
policy_net,
in_keys=[("agents", "observation")],
out_keys=[("agents", "loc"), ("agents", "scale")],
)
第三步:将 TensorDictModule
包装在 ProbabilisticActor
中
我们现在需要根据正态分布的位置和尺度构建一个分布。为此,我们指示 ProbabilisticActor
类使用位置和尺度参数构建一个 TanhNormal
。我们还提供了此分布的最小值和最大值,这些值是从环境规范中收集的。
我们将 in_keys
(因此也包括上面 TensorDictModule
的 out_keys
)的名称必须以 TanhNormal
分布构造函数的关键字参数(loc 和 scale)结尾。
policy = ProbabilisticActor(
module=policy_module,
spec=env.action_spec_unbatched,
in_keys=[("agents", "loc"), ("agents", "scale")],
out_keys=[env.action_key],
distribution_class=TanhNormal,
distribution_kwargs={
"low": env.full_action_spec_unbatched[env.action_key].space.low,
"high": env.full_action_spec_unbatched[env.action_key].space.high,
},
return_log_prob=True,
) # we'll need the log-prob for the PPO loss
评论员网络 (Critic network)¶
评论员网络是 PPO 算法的关键组成部分,尽管它在采样时不会被使用。此模块将读取观察并返回相应的价值估计。
与之前一样,应该仔细考虑**共享评论员参数**的决定。总的来说,参数共享将加快训练收敛速度,但有几个重要的考虑因素:
当智能体具有不同的奖励函数时,不建议共享,因为评论员需要学习为相同的状态分配不同的值(例如,在混合合作-竞争环境中)。
在分散式训练场景中,如果没有额外的基础设施来同步参数,则无法进行共享。
在所有其他情况下,当所有智能体的奖励函数(与奖励本身区分开)相同(如当前场景)时,共享可以提供改进的性能。但这可能会以智能体策略同质化为代价。总的来说,了解哪种选择更优的最佳方法是快速尝试这两种选项。
这也是我们必须在**MAPPO 和 IPPO**之间做出选择的地方
使用 MAPPO,我们将获得一个具有完全可观察性的集中式评论员(即,它将接收所有连接的智能体观察作为输入)。由于我们处于模拟器中并且训练是集中式的,因此我们可以这样做。
使用 IPPO,我们将拥有一个局部的分散式评论员,就像策略一样。
无论哪种情况,评论员的输出形状都将是 (..., n_agents, 1)
。如果评论员是集中式且共享的,则沿 n_agents
维度的所有值都将是相同的。
share_parameters_critic = True
mappo = True # IPPO if False
critic_net = MultiAgentMLP(
n_agent_inputs=env.observation_spec["agents", "observation"].shape[-1],
n_agent_outputs=1, # 1 value per agent
n_agents=env.n_agents,
centralised=mappo,
share_params=share_parameters_critic,
device=device,
depth=2,
num_cells=256,
activation_class=torch.nn.Tanh,
)
critic = TensorDictModule(
module=critic_net,
in_keys=[("agents", "observation")],
out_keys=[("agents", "state_value")],
)
让我们尝试我们的策略和评论员模块。正如前面提到的,TensorDictModule
的使用使得可以直接读取环境的输出来运行这些模块,因为它们知道要读取什么信息以及在哪里写入。
从这一点开始,多智能体特定的组件已经实例化,我们只需要使用与单智能体学习相同的组件。这不是很棒吗?
print("Running policy:", policy(env.reset()))
print("Running value:", critic(env.reset()))
数据收集器 (Data collector)¶
TorchRL 提供了一组数据收集器类。简而言之,这些类执行三个操作:重置环境,使用策略和最新观察计算动作,在环境中执行一个步骤,并重复最后两个步骤,直到环境发出停止信号(或达到完成状态)。
我们将使用最简单的数据收集器,它的输出与环境回滚相同,唯一的区别是它将自动重置完成状态,直到收集到所需的帧数。
collector = SyncDataCollector(
env,
policy,
device=vmas_device,
storing_device=device,
frames_per_batch=frames_per_batch,
total_frames=total_frames,
)
回放缓冲区 (Replay buffer)¶
回放缓冲区是离策略 RL 算法的常见构建模块。在策略环境中,每当收集一批数据时,回放缓冲区就会被重新填充,并且其数据会在一定数量的 epoch 中被重复消耗。
为 PPO 使用回放缓冲区不是强制性的,我们可以直接在线使用收集到的数据,但使用这些类可以让我们轻松地以可重现的方式构建内部训练循环。
replay_buffer = ReplayBuffer(
storage=LazyTensorStorage(
frames_per_batch, device=device
), # We store the frames_per_batch collected at each iteration
sampler=SamplerWithoutReplacement(),
batch_size=minibatch_size, # We will sample minibatches of this size
)
损失函数 (Loss function)¶
PPO 损失可以直接从 TorchRL 导入以方便使用,使用 ClipPPOLoss
类。这是利用 PPO 最简单的方法:它隐藏了 PPO 的数学运算以及与之相关的控制流。
PPO 需要计算一些“优势估计”。简而言之,优势是反映预期回报值的值,同时处理偏差/方差权衡。要计算优势,您只需(1)构建优势模块,它使用我们的价值算子,然后(2)在每个 epoch 之前将每个数据批次通过该模块。GAE 模块将使用新的 "advantage"
和 "value_target"
条目更新输入 TensorDict
。 "value_target"
是一个无梯度张量,它表示价值网络应使用输入观察表示的经验值。两者都将由 ClipPPOLoss
用于返回策略和价值损失。
loss_module = ClipPPOLoss(
actor_network=policy,
critic_network=critic,
clip_epsilon=clip_epsilon,
entropy_coef=entropy_eps,
normalize_advantage=False, # Important to avoid normalizing across the agent dimension
)
loss_module.set_keys( # We have to tell the loss where to find the keys
reward=env.reward_key,
action=env.action_key,
value=("agents", "state_value"),
# These last 2 keys will be expanded to match the reward shape
done=("agents", "done"),
terminated=("agents", "terminated"),
)
loss_module.make_value_estimator(
ValueEstimators.GAE, gamma=gamma, lmbda=lmbda
) # We build GAE
GAE = loss_module.value_estimator
optim = torch.optim.Adam(loss_module.parameters(), lr)
训练循环¶
现在我们有了编写训练循环所需的所有组件。步骤包括
- 收集数据
- 计算优势
- 循环遍历 epoch
- 循环遍历 mini-batches 以计算损失值
反向传播
优化
重复
重复
重复
重复
pbar = tqdm(total=n_iters, desc="episode_reward_mean = 0")
episode_reward_mean_list = []
for tensordict_data in collector:
tensordict_data.set(
("next", "agents", "done"),
tensordict_data.get(("next", "done"))
.unsqueeze(-1)
.expand(tensordict_data.get_item_shape(("next", env.reward_key))),
)
tensordict_data.set(
("next", "agents", "terminated"),
tensordict_data.get(("next", "terminated"))
.unsqueeze(-1)
.expand(tensordict_data.get_item_shape(("next", env.reward_key))),
)
# We need to expand the done and terminated to match the reward shape (this is expected by the value estimator)
with torch.no_grad():
GAE(
tensordict_data,
params=loss_module.critic_network_params,
target_params=loss_module.target_critic_network_params,
) # Compute GAE and add it to the data
data_view = tensordict_data.reshape(-1) # Flatten the batch size to shuffle data
replay_buffer.extend(data_view)
for _ in range(num_epochs):
for _ in range(frames_per_batch // minibatch_size):
subdata = replay_buffer.sample()
loss_vals = loss_module(subdata)
loss_value = (
loss_vals["loss_objective"]
+ loss_vals["loss_critic"]
+ loss_vals["loss_entropy"]
)
loss_value.backward()
torch.nn.utils.clip_grad_norm_(
loss_module.parameters(), max_grad_norm
) # Optional
optim.step()
optim.zero_grad()
collector.update_policy_weights_()
# Logging
done = tensordict_data.get(("next", "agents", "done"))
episode_reward_mean = (
tensordict_data.get(("next", "agents", "episode_reward"))[done].mean().item()
)
episode_reward_mean_list.append(episode_reward_mean)
pbar.set_description(f"episode_reward_mean = {episode_reward_mean}", refresh=False)
pbar.update()
结果 (Results)¶
让我们绘制每集获得的平均奖励
为了延长训练时间,请增加 n_iters
超参数。
plt.plot(episode_reward_mean_list)
plt.xlabel("Training iterations")
plt.ylabel("Reward")
plt.title("Episode reward mean")
plt.show()
渲染 (Render)¶
如果您在具有 GUI 的机器上运行此教程,可以通过运行以下命令来渲染训练好的策略:
with torch.no_grad():
env.rollout(
max_steps=max_steps,
policy=policy,
callback=lambda env, _: env.render(),
auto_cast_to_device=True,
break_when_any_done=False,
)
如果您正在使用 Google Colab 运行此教程,您可以通过运行以下命令来渲染训练好的策略:
!apt-get update
!apt-get install -y x11-utils
!apt-get install -y xvfb
!pip install pyvirtualdisplay
import pyvirtualdisplay
display = pyvirtualdisplay.Display(visible=False, size=(1400, 900))
display.start()
from PIL import Image
def rendering_callback(env, td):
env.frames.append(Image.fromarray(env.render(mode="rgb_array")))
env.frames = []
with torch.no_grad():
env.rollout(
max_steps=max_steps,
policy=policy,
callback=rendering_callback,
auto_cast_to_device=True,
break_when_any_done=False,
)
env.frames[0].save(
f"{scenario_name}.gif",
save_all=True,
append_images=env.frames[1:],
duration=3,
loop=0,
)
from IPython.display import Image
Image(open(f"{scenario_name}.gif", "rb").read())
结论和后续步骤¶
在本教程中,我们看到了:
如何在 TorchRL 中创建多智能体环境,其规范(specs)如何工作,以及它如何与库集成;
如何在 TorchRL 中使用 GPU 向量化环境;
如何在 TorchRL 中创建不同的多智能体网络架构(例如,使用参数共享、集中式评论员)
如何使用
tensordict.TensorDict
来承载多智能体数据;如何将所有库组件(收集器、模块、回放缓冲区和损失)集成到多智能体 MAPPO/IPPO 训练循环中。
现在您已经熟练掌握了多智能体 DDPG,您可以查看 GitHub 存储库中所有的 TorchRL 多智能体实现。这些是许多流行的 MARL 算法的代码实现,例如本教程中看到的 QMIX、MADDPG、IQL 等!
您还可以查看我们关于如何在 PettingZoo/VMAS 中使用多个智能体组训练竞争性 MADDPG/IDDPG 的多智能体教程:TorchRL 竞争性多智能体强化学习(DDPG)教程。
如果您有兴趣创建或包装自己的 TorchRL 多智能体环境,您可以查看专门的 文档部分。
最后,您可以修改本教程的参数来尝试更多配置和场景,成为 MARL 大师。以下是一些 VMAS 中可能场景的视频。

VMAS 中可用的场景¶