强化学习 (RL) 是三种主要机器学习范式之一,另外两种是监督学习和无监督学习。在强化学习中,智能体学习与环境互动以最大化累积奖励。它通过试错学习在不同环境条件下的最佳行动。带有人类反馈的强化学习 (RLHF) 允许智能体在每一步根据人类输入调整行为。
强化学习解决了自驾车、自动交易、视频游戏中的计算机玩家、训练机器人等问题。当深度神经网络用于应用强化学习算法时,这称为深度强化学习。
在本教程中,我将向您展示如何开始使用 Gymnasium,这是一个开源的 Python 库,用于开发和比较强化学习算法。我将演示如何设置它,探索各种 RL 环境,并使用 Python 构建一个简单的智能体来实现 RL 算法。
什么是 Gymnasium?
Gymnasium是一个开源的Python库,旨在支持强化学习算法的开发。为了促进强化学习的研究和开发,Gymnasium提供:
- 多种环境,从简单的游戏到模拟现实生活场景的问题。
- 简化的API和包装器,以便与环境进行交互。
- 创建自定义环境的能力,并利用API框架。
开发者可以构建强化学习算法,并使用API调用执行诸如:
- 将代理选择的动作传递给环境。
- 了解每个动作后环境的状态和奖励。
- 训练模型。
- 测试模型的性能。
OpenAI的Gym与Farama的Gymnasium
OpenAI并未投入大量资源来开发Gym,因为这不是公司的商业重点。 Farama基金会成立的目的是为了长期标准化和维护RL库。Gymnasium是Farama基金会对OpenAI Gym的分支。Gymnasium 0.26.2是Gym 0.26.2的替代品。通过这一分支,Farama旨在为所有API调用添加功能性(而不仅是基于类的方法),支持向量环境,并改进包装器。总体目标是使框架更清洁、更高效。
设置Gymnasium
体育馆需要特定版本(不是最新版本)的各种依赖程序,比如NumPy和PyTorch。因此,我们建议创建一个新的 Conda 或 venv 环境或一个新的笔记本来安装、使用体育馆,并运行强化学习程序。
您可以使用这个DataLab工作簿来跟随教程。
安装体育馆
要在服务器或本地机器上安装体育馆,请运行:
$ pip install gymnasium
要使用类似谷歌的Colab或DataCamp的DataLab这样的笔记本安装,请使用:
!pip install gymnasium
上述命令会安装Gymnasium和正确版本的依赖项。
探索Gymnasium环境
截至2024年11月,Gymnasium包括超过60个内置环境。要浏览可用的内置环境,请使用gym.envs.registry.all()
函数,如下例所示:
import gymnasium as gym for i in gym.envs.registry.keys(): print(i)
您也可以访问体育馆主页。左侧栏有所有环境的链接。每个环境的网页包括有关其的详细信息,例如操作、状态等。
环境被组织成经典控制、Box2D等类别。下面,我列出每个组中一些常见环境:
- 经典控制:这些是RL开发中常用的经典环境;它们构成许多教科书示例的基础。它们提供了测试和基准测试新RL算法的复杂性和简单性的正确混合。体育馆中的经典控制环境包括:
- Acrobot
- Cart Pole
- Mountain Car Discrete
- Mountain Car Continuous
- Pendulum
- Box2D: Box2D 是一个用于游戏的二维物理引擎。基于该引擎的环境包括简单的游戏,如:
- 月球着陆器
- 赛车
- 玩具文本: 这些是小型和简单的环境,通常用于调试强化学习算法。这些环境中的许多基于小型网格世界模型和简单的纸牌游戏。示例包括:
- 二十一点
- 出租车
- 冰冻湖
- MuJoCo: 多关节动态与接触 (MuJoCo) 是一个开源物理引擎,模拟用于机器人技术、生物力学、机器学习等应用的环境。Gymnasium 中的 MuJoCo 环境包括:
- 蚂蚁
- 跳跃者
- 类人
- 游泳者
- 以及更多
除了内置环境外,Gymnasium 还可以与许多 外部环境 一起使用相同的 API。
我们将在本教程中使用经典控制环境之一。要导入特定环境,请使用.make()
命令,并将环境名称作为参数传递。例如,要创建一个基于CartPole(版本1)的新环境,请使用以下命令:
import gymnasium as gym env = gym.make("CartPole-v1")
理解Gymnasium中的强化学习概念
简而言之,强化学习由一个与环境交互的代理(如机器人)组成。一个策略决定了代理的动作。根据代理的动作,环境在每个时间步给予奖励(或惩罚)。代理使用强化学习来找出最大化代理所获得的总奖励的最佳策略。
强化学习环境的组件
以下是强化学习环境的关键组成部分:
- 环境:外部系统、世界或背景。代理程序在一系列时间步中与环境互动。在每个时间步中,基于代理程序的动作,环境:
- 给出奖励(或惩罚)
- 决定下一个状态
- 状态:环境当前配置的数学表示。
- 例如,摆环境的状态可以包括每个时间步的摆动位置和角速度。
- 终止状态:不会导致新状态或其他状态的状态。
- Agent:观察环境并根据观察结果采取各种行动的算法。Agent的目标是最大化其奖励。
- 例如,Agent决定以多大力度和方向推动摆。
- 观察:代表代理人对环境的看法的数学表示,例如通过传感器获取。
- 动作:代理人在进行下一步之前做出的决定。动作会影响环境的下一个状态,并为代理人赢得奖励。
- 奖励:环境对代理人的反馈。根据动作和环境的状态,奖励可以是正面的也可以是负面的。
- 返回:未来时间步的预期累积回报。未来时间步的奖励可以使用折扣因子进行折现。
- 策略:代理在不同状态下采取何种行动的策略。通常表示为概率矩阵,P,它将状态映射到行动。
- 给定一个有限的 m 个可能状态和 n 个可能的动作,矩阵中的元素 Pmn 表示在状态 sm 中采取动作 an 的概率。
- 剧集: 从(随机化的)初始状态到代理达到终止状态的一系列时间步长。
观察空间和动作空间
观察是代理收集关于环境的信息。例如,机器人等代理可以使用传感器收集环境信息。理想情况下,代理应能观察完整状态,描述环境的所有方面。实际上,代理使用其观察作为状态的代理。因此,观察决定了代理的行动。
一个空间类似于一个数学集合。项目的空间X包含所有可能的X的实例。X的空间还定义了所有类型X项目的结构(语法和格式)。每个Gymnasium环境都有两个空间,动作空间action_space
和观察空间observation_space
。动作空间和观察空间都源自父类gymnasium.spaces.Space
。
观察空间
观察空间是包含所有可能观察的空间。它还定义了观察存储的格式。观察空间通常表示为一个数据类型 Box 的对象。这是一个 ndarray,它 描述了观察的参数。箱子指定了每个维度的边界。您可以使用 observation_space
方法查看环境的观察空间:
print("observation space: ", env.observation_space)
在 CartPole-v1
环境的情况下,输出看起来像下面的示例:
observation space: Box([-4.8 -inf -0.41887903 -inf], [4.8 inf 0.41887903 inf], (4,), float32)
在这个例子中,CartPole-v1
观测空间有4个维度。观测数组的4个元素是:
- 小车位置 – 变化范围在 -4.8 到 +4.8 之间
- 购物车速度 – 范围在 – 到 +
- 杆角度 – 变化在 -0.4189 到 +0.4189
- 杆角速度 – 范围在 – 到 +
要查看个别观测数组的示例,请使用.reset()
命令。
observation, info = env.reset() print("observation: ", observation)
在CartPole-v1
环境中,输出如下所示:
[ 0.03481963 -0.0277232 0.01703267 -0.04870504]
该数组的四个元素对应于四个观测量(车的位置、车的速度、杆的角度、杆的角速度),如前所述。
动作空间
动作空间包括代理可以采取的所有可能动作。动作空间还定义了动作的表示格式。您可以使用action_space
方法查看环境的动作空间:
print("action space: ", env.action_space)
在CartPole-v1
环境中,输出如下所示:
action space: Discrete(2)
在CartPole-v1
环境中,动作空间是离散的。代理可以执行两种动作:
- 0:将小车推向左侧
- 1:将小车推向右侧
使用 Gymnasium 构建您的第一个强化学习代理
在前几节中,我们探讨了强化学习和Gymnasium的基本概念。本节将向您展示如何使用Gymnasium来构建一个强化学习代理。
创建和重置环境
第一步是创建环境的一个实例。要创建新的环境,请使用.make()
方法。
env = gym.make('CartPole-v1')
代理的交互会改变环境的状态。.reset()
方法将环境重置为初始状态。默认情况下,环境会初始化为随机状态。您可以在.reset()
方法中使用一个SEED
参数,以便每次运行程序时将环境初始化为相同的状态。下面的代码展示了如何实现这一点:
SEED = 1111 env.reset(seed=SEED)
操作的采样也涉及随机性。为了控制这种随机性并获得完全可复制的训练路径,我们可以对 NumPy 和 PyTorch 的随机生成器设置种子:
np.random.seed(SEED) torch.manual_seed(SEED)
随机与智能动作
在马尔可夫过程中的每一步中,代理可以随机选择一个动作并探索环境,直到到达终止状态。通过随机选择动作:
- 可能需要很长时间才能到达终止状态。
- 累积奖励远低于可能达到的水平。
通过根据以往的经验(与环境的互动)来优化动作选择,训练代理更有效,以最大化长期回报。
未训练的智能体从基于随机初始化策略的随机动作开始。这个策略通常用神经网络表示。在训练过程中,智能体学习最大化奖励的最优策略。在强化学习中,训练过程也称为策略优化。
有多种策略优化的方法。贝尔曼方程描述了如何计算强化学习策略的价值并确定最优策略。在本教程中,我们将使用一种简单的技术,称为策略梯度。还有其他方法,例如近端策略优化(PPO)。
实现一个简单的策略梯度智能体
要构建一个使用策略梯度的RL代理,我们创建一个神经网络来实现策略,编写函数来计算分步奖励和动作概率的回报和损失,并使用标准的反向传播技术迭代更新策略。
设置策略网络
我们使用神经网络来实现策略。因为CartPole-v1
是一个简单的环境,我们使用具有以下特点的神经网络:
- 输入维度等于环境观察空间的维度。
- 一个包含64个神经元的单隐藏层。
- 输出维度等于环境动作空间的维度。
因此,策略网络的功能是将观察到的状态映射到动作。给定输入观察,它预测正确的动作。下面的代码实现了策略网络:
class PolicyNetwork(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim, dropout): super().__init__() self.layer1 = nn.Linear(input_dim, hidden_dim) self.layer2 = nn.Linear(hidden_dim, output_dim) self.dropout = nn.Dropout(dropout) def forward(self, x): x = self.layer1(x) x = self.dropout(x) x = F.relu(x) x = self.layer2(x) return x
奖励收集和前向传递
正如前面提到的,在马尔可夫过程的每一步中,环境根据代理的动作和状态给出奖励。强化学习的目标是最大化总回报。
- 每个时间步的回报是从开始到该步骤获得的奖励的累积和。
- 每一集的总回报是通过累积该集中所有步骤奖励得到的。因此,总回报是在最后一个时间步(当代理达到终止状态时)的回报。
在实践中,累积奖励时通常会:
- 使用折扣因子调整未来的奖励。
- 将逐步回报的数组归一化,以确保平稳和稳定的训练。
以下代码显示了如何实现这一过程:
def calculate_stepwise_returns(rewards, discount_factor): returns = [] R = 0 for r in reversed(rewards): R = r + R * discount_factor returns.insert(0, R) returns = torch.tensor(returns) normalized_returns = (returns - returns.mean()) / returns.std() return normalized_returns
前向传递包括基于当前策略运行代理,直到达到终止状态,并收集逐步奖励和动作概率。以下步骤解释了如何实现前向传递:
- 将环境重置为初始状态。
- 初始化缓冲区以存储动作概率、奖励和累积回报
- 使用
.step()
函数迭代地在环境中运行代理,直到其终止: - 获取环境状态的观察值。
- 根据观察预测策略中的动作。
- 使用
Softmax
函数来估计采取预测动作的概率。 - 基于这些估计概率模拟一个分类概率分布。
- 从这个分布中采样以获得代理的动作。
- 估计从模拟分布中采样的动作的对数概率。
- 将每一步的动作和奖励的对数概率附加到各自的缓冲区中。
- 根据奖励估计每一步的回报的归一化和折现值。
def forward_pass(env, policy, discount_factor): log_prob_actions = [] rewards = [] done = False episode_return = 0 policy.train() observation, info = env.reset() while not done: observation = torch.FloatTensor(observation).unsqueeze(0) action_pred = policy(observation) action_prob = F.softmax(action_pred, dim = -1) dist = distributions.Categorical(action_prob) action = dist.sample() log_prob_action = dist.log_prob(action) observation, reward, terminated, truncated, info = env.step(action.item()) done = terminated or truncated log_prob_actions.append(log_prob_action) rewards.append(reward) episode_return += reward log_prob_actions = torch.cat(log_prob_actions) stepwise_returns = calculate_stepwise_returns(rewards, discount_factor) return episode_return, stepwise_returns, log_prob_actions
根据奖励更新策略
损失代表我们应用梯度下降的数量。在RL中,目标是最大化回报。因此,我们使用期望回报值作为损失的代理。期望回报值被计算为逐步期望回报和逐步动作的对数概率的乘积。下面的代码计算损失:
def calculate_loss(stepwise_returns, log_prob_actions): loss = -(stepwise_returns * log_prob_actions).sum() return loss
要更新策略,您需要根据损失函数运行反向传播。下面的update_policy()
方法调用calculate_loss()
方法。然后对这个损失运行反向传播,以更新策略参数,即策略网络的模型权重。
def update_policy(stepwise_returns, log_prob_actions, optimizer): stepwise_returns = stepwise_returns.detach() loss = calculate_loss(stepwise_returns, log_prob_actions) optimizer.zero_grad() loss.backward() optimizer.step() return loss.item()
基于回报梯度更新策略被称为策略梯度方法。
训练策略
现在我们已经准备好训练和评估策略所需的所有组件。我们将实现训练循环,步骤如下:
在开始之前,我们声明超参数,实例化一个策略,并创建一个优化器:
- 将超参数声明为 Python 常量:
MAX_EPOCHS
是我们准备运行以训练策略的最大迭代次数。DISCOUNT_FACTOR
决定未来时间步骤奖励的相对重要性。折扣因子为 1 意味着所有奖励同等重要,而值为 0 则意味着只有当前时间步骤的奖励重要。N_TRIALS
是我们平均回报以评估智能体表现的剧集数量。如果在N_TRIALS
剧集中的平均回报高于阈值,我们将认为训练是成功的。REWARD_THRESHOLD
:如果策略能够实现超过阈值的回报,则被视为成功。DROPOUT
决定应该随机归零的权重比例。随机失活函数会随机将模型权重的一部分设置为零。这减少了对特定神经元的依赖,并防止过拟合,使网络更具鲁棒性。LEARNING_RATE
决定了每一步中策略参数可以被修改的程度。每次迭代中参数的更新是梯度与学习率的乘积。- 将策略定义为
PolicyNetwork
类的一个实例(之前实现的)。 - 使用Adam算法和学习率创建一个优化器。
为了训练策略,我们迭代地运行训练步骤,直到平均回报(在N_TRIALS
次试验中)大于奖励阈值为止:
- 对于每一集,运行前向传播一次。收集动作的对数概率、逐步回报以及该集的总回报。将集的回报累积在一个数组中。
- 使用对数概率和逐步返回计算损失。运行损失的反向传播。使用优化器更新策略参数。
- 检查平均回报是否超过
N_TRIALS
的奖励阈值。
以下代码实现了这些步骤:
def main(): MAX_EPOCHS = 500 DISCOUNT_FACTOR = 0.99 N_TRIALS = 25 REWARD_THRESHOLD = 475 PRINT_INTERVAL = 10 INPUT_DIM = env.observation_space.shape[0] HIDDEN_DIM = 128 OUTPUT_DIM = env.action_space.n DROPOUT = 0.5 episode_returns = [] policy = PolicyNetwork(INPUT_DIM, HIDDEN_DIM, OUTPUT_DIM, DROPOUT) LEARNING_RATE = 0.01 optimizer = optim.Adam(policy.parameters(), lr = LEARNING_RATE) for episode in range(1, MAX_EPOCHS+1): episode_return, stepwise_returns, log_prob_actions = forward_pass(env, policy, DISCOUNT_FACTOR) _ = update_policy(stepwise_returns, log_prob_actions, optimizer) episode_returns.append(episode_return) mean_episode_return = np.mean(episode_returns[-N_TRIALS:]) if episode % PRINT_INTERVAL == 0: print(f'| Episode: {episode:3} | Mean Rewards: {mean_episode_return:5.1f} |') if mean_episode_return >= REWARD_THRESHOLD: print(f'Reached reward threshold in {episode} episodes') break
最后,调用main()
函数来训练策略:
main()
U使用这个 DataLab 工作簿 直接运行上述算法并解决 CartPole 环境,使用 RL。
Gymnasium 中的高级技术
在展示如何实现 RL 算法后,我们现在讨论一些在实践中常用的高级技术。
使用预构建架构
从头实现 RL 算法是一个漫长而困难的过程,特别是对于复杂环境和最先进的策略。
一个更实用的选择是使用类似Stable Baselines3的软件。它提供了经过验证的强化学习算法实现。包括预训练代理、训练脚本、评估工具以及绘制图形和记录视频的模块。
Ray RLib是另一个流行的强化学习工具。RLib被设计为可扩展解决方案,使在多 GPU 系统上实现强化学习算法变得容易。它还支持多智能体强化学习,这开启了新的可能性,比如:
- 独立多智能体学习:每个智能体将其他智能体视为环境的一部分。
- 协作式多智能体训练:一组智能体共享相同的策略和价值函数,并并行地从彼此的经验中学习。
- 对抗训练:智能体(或智能体组)在竞争性类似游戏的环境中相互竞争。
使用 RLib 和 Stable Baselines3,您可以导入并使用来自 OpenAI Gymnasium 的环境。
自定义环境
Gymnasium打包的环境是测试新的RL策略和训练策略的正确选择。然而,对于大多数实际应用程序,您需要创建并使用一个准确反映您想要解决的问题的环境。您可以使用Gymnasium创建自定义环境。使用Gymnasium自定义环境的优势在于许多外部工具如RLib和Stable Baselines3已经配置为与Gymnasium API结构配合工作。
要在Gymnasium中创建自定义环境,您需要定义:
- 观察空间。
- 终止条件。
- 代理可以选择的动作集。
- 如何初始化环境(当调用
reset()
函数时)。 - 环境如何根据智能体的动作决定下一个状态(当调用
step()
函数时)。
要了解更多信息,请查看Gymnasium关于创建自定义环境的指南。
使用Gymnasium的最佳实践
尝试不同的环境
这个教程中的代码展示了如何在CartPole环境中实现策略梯度算法。这是一个具有离散动作空间的简单环境。为了更好地理解强化学习,我们建议您在其他环境中应用相同的策略梯度算法(以及其他算法,如PPO)。
例如,摆环境具有连续的动作空间。它由一个表示为连续变量的单一输入组成——在任何给定状态下施加于摆的扭矩(大小和方向)。这个扭矩可以取任何值,范围在 -2 到 +2 之间。
在各种环境中实验不同的算法可以帮助你更好地理解不同类型的强化学习解决方案及其挑战。
监控训练进度
RL环境通常包括机器人、摆锤、山地车、视频游戏等。在环境中可视化代理的动作有助于更直观地了解策略的表现。
在Gymnasium中,env.render()
方法可视化代理与环境的交互。它图形化显示环境的当前状态,如游戏画面、摆锤或车杆的位置等。代理动作和环境响应的视觉反馈有助于监控代理的表现,并跟踪训练过程中的进展。
有四种渲染模式:“human”、“rgb_array”、“ansi”和“rgb_array_list”。要可视化代理的表现,可使用“human”渲染模式。渲染模式在环境初始化时指定。例如:
env = gym.make(‘CartPole-v1’, render_mode=’human’)
为执行渲染操作,在代理程序执行每个动作后(通过调用.step()
方法),需要调用.render()
方法。以下伪代码说明了如何实现:
while not done: … step, reward, terminated, truncated, info = env.step(action.item()) env.render() …
故障排除常见错误
Gymnasium使得与复杂的RL环境进行接口交互变得简单。然而,它是一个包含许多依赖项的持续更新的软件。因此,注意一些常见类型的错误是至关重要的。
版本不匹配
- 健身房版本不匹配: Farama 的健身房软件包是从 OpenAI 的 Gym 版本 0.26.2 分叉而来的。在旧版本的 Gym 和新版本的健身房之间有一些重大变化。许多公开可用的实现是基于旧版 Gym 发行版,可能无法直接与最新版本兼容。在这种情况下,有必要要么将安装回滚到旧版本,要么调整代码以与新版本兼容。
- 环境版本不匹配:许多体育馆环境有不同的版本。例如,有两个CartPole环境 –
CartPole-v1
和CartPole-v0
。虽然这两个版本的环境行为是相同的,但一些参数,如回合长度、奖励阈值等可能不同。在一个版本上训练的策略在同一环境的另一个版本上可能表现不佳。您需要更新训练参数,并为每个环境版本重新训练策略。 - 依赖版本不匹配:Gymnasium 依赖于 NumPy 和 PyTorch 等库。截至 2024 年 12 月,这些依赖的最新版本是
numpy 2.1.3
和torch 2.5.1
。然而,Gymnasium 最佳兼容的版本是torch 1.13.0
和numpy 1.23.3
。如果您在一个已预安装这些依赖的环境中安装 Gymnasium,可能会遇到问题。我们建议在一个全新的 Conda 环境中安装和使用 Gymnasium。
收敛问题
- 超参数:与其他机器学习算法一样,强化学习策略对学习率、折现因子等超参数非常敏感。我们建议手动尝试和调整超参数,或者使用诸如网格搜索和随机搜索之类的自动化技术。
- 探索与利用:对于某些策略类别(例如PPO),代理采用了一种双管齐下的策略:探索环境以发现新路径,并根据迄今为止已知的路径采用贪婪方法最大化奖励。如果探索过多,策略将无法收敛。相反,如果探索不够,它将永远不会尝试最佳路径。因此,找到探索与利用之间的平衡至关重要。在训练过程中,通常会优先在早期剧集中进行探索,并在后期剧集中进行利用。
训练不稳定
- 过大的学习率: 如果学习率过高,策略参数在每一步中会经历大幅更新。这可能导致错过最佳值集。一个常见的解决方案是逐渐降低学习率,确保在训练收敛时进行更小且更稳定的更新。
- 过度探索: 在行动选择中过多的随机性(熵)会阻碍收敛,并导致后续步骤之间损失函数的大幅波动。为了实现稳定和收敛的训练过程,需要平衡探索与利用。
- 错误的算法选择:像策略梯度这样的简单算法可能会导致在具有大动作和状态空间的复杂环境中训练不稳定。在这种情况下,我们建议使用更强大的算法,如PPO和信任区域策略优化(TRPO)。这些算法在每一步中避免大规模的策略更新,从而可以更稳定。
- 随机性:强化学习算法对初始状态和动作选择中固有的随机性非常敏感。当训练过程不稳定时,有时可以通过使用不同的随机种子或重新初始化策略来实现稳定。
结论
在本教程中,我们探讨了强化学习(RL)的基本原理,讨论了Gymnasium作为一个具有干净API的软件包,以便与各种RL环境进行交互,并展示了如何编写一个Python程序来实现一个简单的RL算法并将其应用于Gymnasium环境。
在理解了本教程中的基本知识后,我建议使用Gymnasium环境将RL的概念应用于解决实际问题,例如出租车路径优化和股票交易模拟。
Source:
https://www.datacamp.com/tutorial/reinforcement-learning-with-gymnasium