注意
要下载完整的示例代码,请转到末尾。
TorchRL 模块入门¶
作者: Vincent Moens
注意
要在 notebook 中运行此教程,请在开头添加一个包含以下内容的安装单元格:
!pip install tensordict !pip install torchrl
强化学习旨在创建能够有效解决特定任务的策略。策略可以采取各种形式,从可微分的映射(将观测空间映射到动作空间),到更临时的方法(例如对每个可能动作计算的值列表执行 argmax)。策略可以是确定性的或随机的,并且可能包含复杂元素,例如循环神经网络 (RNN) 或 Transformer。
适应所有这些场景可能相当复杂。在这个简洁的教程中,我们将深入研究 TorchRL 在策略构建方面的核心功能。我们将主要关注两种常见场景下的随机策略和 Q 值策略:使用多层感知机 (MLP) 或卷积神经网络 (CNN) 作为骨干网络。
TensorDict模块(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)
专用包装器(Specialized wrappers)¶
为了简化 Actor
、# ProbabilisticActor
、# ActorValueOperator
或 # ActorCriticOperator
的集成。例如,Actor
为 in_keys
和 out_keys
提供了默认值,使得与许多常见环境的集成变得直接。
from torchrl.modules import Actor
policy = Actor(module)
rollout = env.rollout(max_steps=10, policy=policy)
print(rollout)
可用的专用 TensorDictModules 列表可在API 参考中找到。
网络(Networks)¶
TorchRL 还提供可以使用而无需依赖 TensorDict 功能的常规模块。您会遇到的两个最常见的网络是 MLP
和 ConvNet
(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 的策略。由于这是一个更技术性的主题,因此在单独的教程中进行了处理。
概率策略(Probabilistic policies)¶
像 PPO 这样的策略优化算法要求策略是随机的:与上述示例不同,模块现在编码从观测空间到参数空间的映射,该参数空间编码了对可能动作的分布。TorchRL 通过将各种操作(例如从参数构建分布、从该分布采样以及检索对数概率)分组到一个单一类中来促进此类模块的设计。在这里,我们将构建一个依赖于正则正态分布的 actor,使用三个组件:
一个
MLP
骨干网络,读取大小为[3]
的观测值,并输出一个大小为[2]
的单一张量;一个
NormalParamExtractor
模块,它将此输出分成两块,一个均值(mean)和一个标准差(standard deviation),大小均为[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)
关于这次 roll out 有几点需要注意:
正如我们在构建 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
关键字参数以了解更多信息。
探索(Exploration)¶
像这样的随机策略在一定程度上自然地权衡了探索和利用,但确定性策略则不会。幸运的是,TorchRL 也可以通过其探索模块来弥补这一点。我们将以 EGreedyModule
探索模块为例(也请查看 AdditiveGaussianModule
和 OrnsteinUhlenbeckProcessModule
)。为了看到这个模块的作用,让我们回到一个确定性策略。
from tensordict.nn import TensorDictSequential
from torchrl.modules import EGreedyModule
policy = Actor(MLP(3, 1, num_cells=[32, 64]))
我们的 \(\epsilon\)-greedy 探索模块通常会通过一些退火帧数和 \(\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-Value actors)¶
在某些设置中,策略不是一个独立的模块,而是构建在另一个模块之上。Q 值 actor 就是这种情况。简而言之,这些 actor 需要对动作值(大多数情况下是离散的)进行估计,并贪婪地选择具有最高值的动作。在某些设置中(有限的离散动作空间和有限的离散状态空间),人们可以直接存储一个状态-动作对的二维表格,并选择具有最高值的动作。DQN 带来的创新是通过利用神经网络来编码 Q(s, a)
值映射,从而将其扩展到连续状态空间。让我们考虑一个具有离散动作空间的另一个环境,以便更清楚地理解。
env = GymEnv("CartPole-v1")
print(env.action_spec)
我们构建一个值网络,当它读取环境中的状态时,为每个动作产生一个值。
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
)
我们来试一试!我们运行策略几个步骤,并查看输出。我们应该在获得的 roll out 中找到 "action_value"
和 "chosen_action_value"
条目。
rollout = env.rollout(max_steps=3, policy=policy)
print(rollout)
因为它依赖于 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(参阅教程);
将其与决策 Transformer 示例中的 Transformer 使用进行比较(请参阅 GitHub 上的
example
目录)。