• 文档 >
  • TorchRL 模块入门
快捷键

TorchRL 模块入门

作者: Vincent Moens

注意

要在 notebook 中运行本教程,请在开头添加一个安装单元,其中包含

!pip install tensordict
!pip install torchrl

强化学习旨在创建能够有效应对特定任务的策略。策略可以采取多种形式,从观察空间到动作空间的可微分映射,到更专门的方法,例如对为每个可能的动作计算的值列表执行 argmax 操作。策略可以是确定性的或随机的,并且可能包含复杂的元素,例如循环神经网络 (RNN) 或 Transformer。

适应所有这些场景可能非常复杂。在本简洁的教程中,我们将深入探讨 TorchRL 在策略构建方面的核心功能。我们将主要关注两种常见场景中的随机策略和 Q 值策略:使用多层感知器 (MLP) 或卷积神经网络 (CNN) 作为骨干网络。

TensorDictModules

与环境如何与 TensorDict 实例交互类似,用于表示策略和价值函数的模块也执行相同的操作。核心思想很简单:将标准 Module(或任何其他函数)封装在一个类中,该类知道哪些条目需要读取并传递给模块,然后使用分配的条目记录结果。为了说明这一点,我们将使用最简单的策略:从观察空间到动作空间的确定性映射。为了最大程度的通用性,我们将使用 LazyLinear 模块以及我们在上一个教程中实例化的 Pendulum 环境。

import torch

from tensordict.nn import TensorDictModule
from torchrl.envs import GymEnv

env = GymEnv("Pendulum-v1")
module = torch.nn.LazyLinear(out_features=env.action_spec.shape[-1])
policy = TensorDictModule(
    module,
    in_keys=["observation"],
    out_keys=["action"],
)

这就是执行我们的策略所需的全部内容!使用惰性模块使我们无需获取观察空间的形状,因为模块将自动确定它。此策略现在已准备好在环境中运行

rollout = env.rollout(max_steps=10, policy=policy)
print(rollout)
TensorDict(
    fields={
        action: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
        done: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
        next: TensorDict(
            fields={
                done: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
                observation: Tensor(shape=torch.Size([10, 3]), device=cpu, dtype=torch.float32, is_shared=False),
                reward: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
                terminated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
                truncated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False)},
            batch_size=torch.Size([10]),
            device=None,
            is_shared=False),
        observation: Tensor(shape=torch.Size([10, 3]), device=cpu, dtype=torch.float32, is_shared=False),
        terminated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
        truncated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False)},
    batch_size=torch.Size([10]),
    device=None,
    is_shared=False)

专用包装器

为了简化 Actor、 # ProbabilisticActor、 # ActorValueOperator 或 # ActorCriticOperator 的集成。例如,Actorin_keysout_keys 提供默认值,从而简化了与许多常见环境的集成

from torchrl.modules import Actor

policy = Actor(module)
rollout = env.rollout(max_steps=10, policy=policy)
print(rollout)
TensorDict(
    fields={
        action: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
        done: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
        next: TensorDict(
            fields={
                done: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
                observation: Tensor(shape=torch.Size([10, 3]), device=cpu, dtype=torch.float32, is_shared=False),
                reward: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
                terminated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
                truncated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False)},
            batch_size=torch.Size([10]),
            device=None,
            is_shared=False),
        observation: Tensor(shape=torch.Size([10, 3]), device=cpu, dtype=torch.float32, is_shared=False),
        terminated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
        truncated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False)},
    batch_size=torch.Size([10]),
    device=None,
    is_shared=False)

可用专用 TensorDictModules 的列表可在 API 参考 中找到。

网络

TorchRL 还提供常规模块,可以在不使用 tensordict 功能的情况下使用。您将遇到的两个最常见的网络是 MLPConvNet (CNN) 模块。我们可以用其中一个替换我们的策略模块

from torchrl.modules import MLP

module = MLP(
    out_features=env.action_spec.shape[-1],
    num_cells=[32, 64],
    activation_class=torch.nn.Tanh,
)
policy = Actor(module)
rollout = env.rollout(max_steps=10, policy=policy)

TorchRL 还支持基于 RNN 的策略。由于这是一个更专业的技术主题,因此在 单独的教程 中进行了介绍。

概率策略

策略优化算法(如 PPO)要求策略是随机的:与上述示例不同,该模块现在编码从观察空间到参数空间的映射,该参数空间编码可能动作的分布。TorchRL 通过将各种操作(例如从参数构建分布、从该分布中采样以及检索对数概率)分组在一个类下来简化此类模块的设计。在这里,我们将构建一个依赖于常规正态分布的 actor,使用三个组件

  • 一个 MLP 骨干网络,读取大小为 [3] 的观察结果,并输出大小为 [2] 的单个张量;

  • 一个 NormalParamExtractor 模块,它将把此输出拆分为两块,大小为 [1] 的均值和标准差;

  • 一个 ProbabilisticActor,它将读取这些参数作为 in_keys,使用它们创建分布,并使用样本和对数概率填充我们的 tensordict。

from tensordict.nn.distributions import NormalParamExtractor
from torch.distributions import Normal
from torchrl.modules import ProbabilisticActor

backbone = MLP(in_features=3, out_features=2)
extractor = NormalParamExtractor()
module = torch.nn.Sequential(backbone, extractor)
td_module = TensorDictModule(module, in_keys=["observation"], out_keys=["loc", "scale"])
policy = ProbabilisticActor(
    td_module,
    in_keys=["loc", "scale"],
    out_keys=["action"],
    distribution_class=Normal,
    return_log_prob=True,
)

rollout = env.rollout(max_steps=10, policy=policy)
print(rollout)
TensorDict(
    fields={
        action: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
        done: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
        loc: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
        next: TensorDict(
            fields={
                done: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
                observation: Tensor(shape=torch.Size([10, 3]), device=cpu, dtype=torch.float32, is_shared=False),
                reward: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
                terminated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
                truncated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False)},
            batch_size=torch.Size([10]),
            device=None,
            is_shared=False),
        observation: Tensor(shape=torch.Size([10, 3]), device=cpu, dtype=torch.float32, is_shared=False),
        sample_log_prob: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
        scale: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.float32, is_shared=False),
        terminated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False),
        truncated: Tensor(shape=torch.Size([10, 1]), device=cpu, dtype=torch.bool, is_shared=False)},
    batch_size=torch.Size([10]),
    device=None,
    is_shared=False)

关于此 rollout,有几点需要注意

  • 由于我们在构建 actor 期间要求了它,因此还写入了给定当时分布的动作的对数概率。这对于像 PPO 这样的算法是必要的。

  • 分布的参数也以 "loc""scale" 条目形式在输出 tensordict 中返回。

如果您的应用程序需要,您可以控制动作的采样以使用分布的期望值或其他属性,而不是使用随机样本。这可以通过 set_exploration_type() 函数来控制

from torchrl.envs.utils import ExplorationType, set_exploration_type

with set_exploration_type(ExplorationType.DETERMINISTIC):
    # takes the mean as action
    rollout = env.rollout(max_steps=10, policy=policy)
with set_exploration_type(ExplorationType.RANDOM):
    # Samples actions according to the dist
    rollout = env.rollout(max_steps=10, policy=policy)

查看文档字符串中的 default_interaction_type 关键字参数以了解更多信息。

探索

像这样的随机策略在某种程度上自然地权衡了探索和利用,但确定性策略则不会。幸运的是,TorchRL 也可以通过其探索模块来缓解这种情况。我们将以 EGreedyModule 探索模块为例(另请查看 AdditiveGaussianModuleOrnsteinUhlenbeckProcessModule)。要查看此模块的实际效果,让我们回到确定性策略

from tensordict.nn import TensorDictSequential
from torchrl.modules import EGreedyModule

policy = Actor(MLP(3, 1, num_cells=[32, 64]))

我们的 \(\epsilon\)-贪婪探索模块通常会使用许多退火帧和 \(\epsilon\) 参数的初始值进行自定义。\(\epsilon = 1\) 的值表示采取的每个动作都是随机的,而 \(\epsilon=0\) 表示根本没有探索。要退火(即,减少)探索因子,需要调用 step()(有关示例,请参见最后的 教程)。

exploration_module = EGreedyModule(
    spec=env.action_spec, annealing_num_steps=1000, eps_init=0.5
)

要构建我们的探索策略,我们只需将确定性策略模块与 TensorDictSequential 模块中的探索模块连接起来(这类似于 tensordict 领域中的 Sequential)。

exploration_policy = TensorDictSequential(policy, exploration_module)

with set_exploration_type(ExplorationType.DETERMINISTIC):
    # Turns off exploration
    rollout = env.rollout(max_steps=10, policy=exploration_policy)
with set_exploration_type(ExplorationType.RANDOM):
    # Turns on exploration
    rollout = env.rollout(max_steps=10, policy=exploration_policy)

由于它必须能够对动作空间中的随机动作进行采样,因此 EGreedyModule 必须配备来自环境的 action_space,以了解使用什么策略来随机采样动作。

Q 值 actor

在某些设置中,策略不是独立的模块,而是在另一个模块之上构建的。Q 值 actor 就是这种情况。简而言之,这些 actor 需要估计动作值(大多数情况下是离散的),并将贪婪地选择具有最高值的动作。在某些设置中(有限离散动作空间和有限离散状态空间),可以只存储状态-动作对的 2D 表,并选择具有最高值的动作。DQN 带来的创新是将此扩展到连续状态空间,方法是利用神经网络对 Q(s, a) 值映射进行编码。让我们考虑另一个具有离散动作空间的环境,以便更清楚地理解

env = GymEnv("CartPole-v1")
print(env.action_spec)
OneHot(
    shape=torch.Size([2]),
    space=CategoricalBox(n=2),
    device=cpu,
    dtype=torch.int64,
    domain=discrete)

我们构建一个值网络,当它从环境中读取状态时,为每个动作生成一个值

num_actions = 2
value_net = TensorDictModule(
    MLP(out_features=num_actions, num_cells=[32, 32]),
    in_keys=["observation"],
    out_keys=["action_value"],
)

通过在我们的值网络之后添加 QValueModule,我们可以轻松构建我们的 Q 值 actor

from torchrl.modules import QValueModule

policy = TensorDictSequential(
    value_net,  # writes action values in our tensordict
    QValueModule(spec=env.action_spec),  # Reads the "action_value" entry by default
)

让我们看看!我们运行策略几个步骤,并查看输出。我们应该在获得的 rollout 中找到 "action_value" 以及 "chosen_action_value" 条目

rollout = env.rollout(max_steps=3, policy=policy)
print(rollout)
TensorDict(
    fields={
        action: Tensor(shape=torch.Size([3, 2]), device=cpu, dtype=torch.int64, is_shared=False),
        action_value: Tensor(shape=torch.Size([3, 2]), device=cpu, dtype=torch.float32, is_shared=False),
        chosen_action_value: Tensor(shape=torch.Size([3, 1]), device=cpu, dtype=torch.float32, is_shared=False),
        done: Tensor(shape=torch.Size([3, 1]), device=cpu, dtype=torch.bool, is_shared=False),
        next: TensorDict(
            fields={
                done: Tensor(shape=torch.Size([3, 1]), device=cpu, dtype=torch.bool, is_shared=False),
                observation: Tensor(shape=torch.Size([3, 4]), device=cpu, dtype=torch.float32, is_shared=False),
                reward: Tensor(shape=torch.Size([3, 1]), device=cpu, dtype=torch.float32, is_shared=False),
                terminated: Tensor(shape=torch.Size([3, 1]), device=cpu, dtype=torch.bool, is_shared=False),
                truncated: Tensor(shape=torch.Size([3, 1]), device=cpu, dtype=torch.bool, is_shared=False)},
            batch_size=torch.Size([3]),
            device=None,
            is_shared=False),
        observation: Tensor(shape=torch.Size([3, 4]), device=cpu, dtype=torch.float32, is_shared=False),
        terminated: Tensor(shape=torch.Size([3, 1]), device=cpu, dtype=torch.bool, is_shared=False),
        truncated: Tensor(shape=torch.Size([3, 1]), device=cpu, dtype=torch.bool, is_shared=False)},
    batch_size=torch.Size([3]),
    device=None,
    is_shared=False)

由于它依赖于 argmax 运算符,因此此策略是确定性的。在数据收集期间,我们将需要探索环境。为此,我们再次使用 EGreedyModule

policy_explore = TensorDictSequential(policy, EGreedyModule(env.action_spec))

with set_exploration_type(ExplorationType.RANDOM):
    rollout_explore = env.rollout(max_steps=3, policy=policy_explore)

这就是我们关于使用 TorchRL 构建策略的简短教程的全部内容!

您可以使用该库做更多事情。一个好的起点是查看 模块的 API 参考

后续步骤

  • 当动作是复合动作时(例如,环境需要离散动作和连续动作),检查如何将复合分布与 CompositeDistribution 一起使用;

  • 查看如何在策略中使用 RNN(教程);

  • 将此与 Decision Transformer 示例中 Transformer 的用法进行比较(请参阅 GitHub 上的 example 目录)。

脚本的总运行时间:(0 分钟 46.376 秒)

估计内存使用量: 320 MB

由 Sphinx-Gallery 生成的图库

文档

访问 PyTorch 的全面开发者文档

查看文档

教程

获取面向初学者和高级开发者的深入教程

查看教程

资源

查找开发资源并获得问题解答

查看资源