Proximal Policy Optimization(PPO)是解决强化学习(RL)问题的首选算法之一。它由OpenAI的联合创始人约翰·舒曼于2017年开发。
PPO已被广泛应用于OpenAI,用于训练模型以模仿类似人类的行为。它改进了早期的方法,如信任区域策略优化(TRPO),因为它是一种稳健且高效的算法而变得流行。
在本教程中,我们深入探讨了PPO。我们涵盖了理论并演示了如何使用PyTorch实现它。
理解Proximal Policy Optimization(PPO)
传统的监督学习算法沿着最陡梯度的方向更新参数。如果此更新过度,则在之后的训练示例中进行校正,这些示例彼此独立。
然而,在强化学习中,训练示例包括代理的行动和回报。因此,训练示例彼此相关。代理探索环境以找出最佳策略。因此,大幅度改变梯度可能导致策略停留在具有次优奖励的不良区域。由于代理需要探索环境,大的策略变化会使训练过程不稳定。
基于信任区域的方法旨在通过确保策略更新位于信任区域内来避免这个问题。这个信任区域是策略空间内的一个人为限制的区域,在这个区域内允许更新。更新后的策略只能位于旧策略的信任区域内。确保策略更新是增量的可以防止不稳定性。
信任区域策略更新(TRPO)
信任区域策略更新(TRPO)算法由John Schulman于2015年提出(他也在2017年提出了PPO)。为了衡量旧策略和更新策略之间的差异,TRPO使用KL散度(Kullback-Leibler divergence)。KL散度用于衡量两个概率分布之间的差异。TRPO被证明在实现信任区域时非常有效。
TRPO的问题在于与KL散度相关的计算复杂性。应用KL散度必须通过数值方法(如泰勒展开)扩展到二阶。这在计算上是昂贵的。PPO被提出作为TRPO的更简单和更高效的替代方案。PPO通过剪切策略的比率来近似信任区域,而无需进行涉及KL散度的复杂计算。
这就是为什么在解决强化学习问题时,PPO已经成为比TRPO更受青睐的原因。由于更高效的信任区域估计方法,PPO有效地平衡了性能和稳定性。
Proximal policy optimization(PPO)
PPO通常被认为是演员-评论家方法的一个子类,它根据值函数更新策略梯度。优势演员-评论家(A2C)方法使用一个称为优势的参数。这一参数衡量了评论家预测的回报与通过实施策略获得的实际回报之间的差异。
要理解PPO,需要了解它的组成部分:
- 演员执行策略。它被实现为一个神经网络。给定一个状态作为输入,它输出要执行的动作。
- 评论家是另一个神经网络。 它以状态作为输入,并输出该状态的预期值。因此,评论家表示状态值函数。
- 基于策略梯度的方法可以选择使用不同的目标函数。 特别是,PPO使用优势函数。优势函数衡量了累积奖励(基于演员实施的策略)超过评论家预测的基准奖励的数量。PPO的目标是增加选择具有高优势的行动的可能性。PPO的优化目标使用基于此优势函数的损失函数。
- 剪切的目标函数是PPO的主要创新。它防止在单次训练迭代中进行大幅度的策略更新。它限制了在单次迭代中策略更新的幅度。为了衡量增量策略更新,基于策略的方法使用新策略对旧策略的概率比。
- 替代损失是PPO中的目标函数,它考虑了前述的创新。计算如下:
- 计算实际比率(如前所述)并将其乘以优势。
- 将比率剪裁至所需范围内。将剪裁后的比率乘以优势。
- 取上述两个量中的最小值。
- 在实践中,还会添加熵项到替代损失中。这被称为熵奖励。它基于动作概率的数学分布。熵奖励的理念是以受控的方式引入一些额外的随机性。这样做鼓励优化过程探索动作空间。高熵奖励促进探索而非开发。
理解剪裁机制
假设根据旧政策πold,在状态a中采取行动s的概率是πold(a | s)。在新政策下,从相同状态a采取相同行动的概率更新为πnew(a | s)。这些概率的比率作为政策参数θ的函数是r(θ)。当新政策使行动更有可能发生时(在相同状态下),比率大于1,反之亦然。
修剪机制限制了这个概率比例,使得新的动作概率必须位于旧的动作概率的一定百分比范围内。例如,r(θ)可以被限制在0.8和1.2之间。这可以防止大幅跳跃,从而确保稳定的训练过程。
在本文的其余部分,您将学习如何组装用于使用PyTorch实现PPO的简单组件。
1. 设置环境
在实施PPO之前,我们需要安装先决条件软件库并选择一个合适的环境来应用该策略。
安装PyTorch和所需的库
我们需要安装以下软件:
- PyTorch和其他软件库,比如
numpy
(用于数学和统计函数)和matplotlib
(用于绘制图形)。 - 开源的Gym软件包来自OpenAI,这是一个模拟不同环境和游戏的Python库,可以使用强化学习来解决。您可以使用Gym API使您的算法与环境进行交互。由于
gym
的功能有时会在升级过程中发生变化,因此在这个示例中,我们将其版本固定为0.25.2
。
要在服务器或本地机器上安装,请运行:
$ pip install torch numpy matplotlib gym==0.25.2
要使用类似Google Colab或DataLab的笔记本安装,请使用:
!pip install torch numpy matplotlib gym==0.25.2
创建CartPole环境
使用OpenAI Gym创建两个实例(一个用于训练,另一个用于测试)CartPole环境:
env_train = gym.make('CartPole-v1') env_test = gym.make('CartPole-v1')
2. 在PyTorch中实现PPO
现在,让我们使用PyTorch实现PPO。
定义策略网络
如前所述,PPO被实现为一个actor-critic模型。actor实现策略,critic预测其估计值。actor和critic神经网络都以相同的输入(每个时间步的状态)作为输入。因此,actor和critic模型可以共享一个通用神经网络,被称为骨干架构。actor和critic可以通过额外的层扩展骨干架构。
定义骨干网络
以下步骤描述了骨干网络:
- 实现一个具有3层的网络 – 输入层、隐藏层和输出层。
- 在输入和隐藏层之后,我们使用激活函数。在本教程中,我们选择ReLU因为它在计算上是有效的。
- 我们还在输入层和隐藏层之后引入了一个辍学函数来获得一个强大的网络。辍学函数会随机将一些神经元置为零。这样可以减少对特定神经元的依赖,防止过拟合,从而使网络更加强大。
下面的代码实现了主干:
class BackboneNetwork(nn.Module): def __init__(self, in_features, hidden_dimensions, out_features, dropout): super().__init__() self.layer1 = nn.Linear(in_features, hidden_dimensions) self.layer2 = nn.Linear(hidden_dimensions, hidden_dimensions) self.layer3 = nn.Linear(hidden_dimensions, out_features) self.dropout = nn.Dropout(dropout) def forward(self, x): x = self.layer1(x) x = f.relu(x) x = self.dropout(x) x = self.layer2(x) x = f.relu(x) x = self.dropout(x) x = self.layer3(x) return x
定义演员-评论家网络
现在,我们可以使用这个网络来定义演员-评论家类,ActorCritic
。演员模拟策略并预测动作。评论家模拟值函数并预测值。它们都以状态作为输入。
class ActorCritic(nn.Module): def __init__(self, actor, critic): super().__init__() self.actor = actor self.critic = critic def forward(self, state): action_pred = self.actor(state) value_pred = self.critic(state) return action_pred, value_pred
实例化演员和评论家网络
我们将使用上面定义的网络来创建一个演员和一个评论家。然后,我们将创建一个代理,包括演员和评论家。
在创建代理之前,初始化网络的参数:
- 隐藏层的维度H,这是一个可配置的参数。隐藏层的大小和数量取决于问题的复杂性。我们将使用一个64 x 64维度的隐藏层。
- 输入特征N,其中N是状态数组的大小。输入层具有N x H的维度。在CartPole环境中,状态是一个包含4个元素的数组。因此N为4。
- 演员网络的输出特征O,其中O是环境中动作的数量。演员的输出层具有H x O的维度。CartPole环境有2个动作。
- 评论家网络的输出特征。由于评论家网络只预测期望值(给定输入状态),输出特征的数量为1。
- 作为一个分数的辍学率。
以下代码展示了如何基于骨干网络声明演员和评论家网络:
def create_agent(hidden_dimensions, dropout): INPUT_FEATURES = env_train.observation_space.shape[0] HIDDEN_DIMENSIONS = hidden_dimensions ACTOR_OUTPUT_FEATURES = env_train.action_space.n CRITIC_OUTPUT_FEATURES = 1 DROPOUT = dropout actor = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, ACTOR_OUTPUT_FEATURES, DROPOUT) critic = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, CRITIC_OUTPUT_FEATURES, DROPOUT) agent = ActorCritic(actor, critic) return agent
计算回报
环境根据代理的动作,从每一步到下一步给予奖励。奖励R表示为:
回报被定义为预期未来奖励的累积价值。未来时间步骤中的奖励比即时奖励更有价值。因此,回报通常被计算为折扣回报G,定义如下:
在本教程(以及许多其他参考资料)中,回报指的是折扣回报。
要计算回报:
- 从所有未来状态的预期奖励开始。
- 将每个未来奖励乘以折扣因子的幂。例如,从现在开始的2个时间步后的预期奖励会乘以2。
- 总结所有折扣未来奖励以计算回报。
- 规范化回报的值。
函数calculate_returns()
执行这些计算,如下所示:
def calculate_returns(rewards, discount_factor): returns = [] cumulative_reward = 0 for r in reversed(rewards): cumulative_reward = r + cumulative_reward * discount_factor returns.insert(0, cumulative_reward) returns = torch.tensor(returns) # 规范化回报 returns = (returns - returns.mean()) / returns.std() return returns
实现优势函数
优势被计算为评论家预测的值与演员根据政策选择的动作的预期回报之间的差异。对于给定的动作,优势表示采取该特定动作而不是任意(平均)动作的好处。
在原始PPO论文(方程10)中,从当前时间步长向前查看到时间步长T的优势被表示为:
在编写算法时,通过批处理大小来强制查看一组时间步长的约束。因此,上述方程可以简化为值和预期回报之间的差异。预期回报在状态-动作值函数Q中进行量化。
因此,下面的简化公式表达了选择的优势:
- 在给定状态下
- 选择特定操作
- 在特定策略下
- 在特定时间步长下
这被表达为:
OpenAI也使用这个公式来实现RL。 calculate_advantages()
函数如下所示计算优势:
def calculate_advantages(returns, values): advantages = returns - values # 规范化优势 advantages = (advantages - advantages.mean()) / advantages.std() return advantages
替代损失和剪切机制
如果没有像PPO这样的特殊技术,那么政策损失将是标准政策梯度损失。标准政策梯度损失计算公式如下:
- 政策动作概率
- 优势函数,计算方式为:
- 政策回报
- 期望值
标准政策梯度损失无法纠正突然的政策变化。替代损失修改了标准损失,限制了每次迭代中政策变化的数量,取两个量中的较小值:
- 乘积:
- 政策比率。该比率表示旧动作概率和新动作概率之间的差异。
- 优势函数
- 乘积:
- 政策比率的夹值。该比率被剪切,以确保更新后的政策在旧政策的一定百分比范围内。
- 优势函数
在优化过程中,替代损失被用作实际损失的替代。
剪切机制
策略比率,R,是新旧策略之间的差异,并被定义为在新旧参数下策略的对数概率之比:
被剪切的策略比率,R’,受到约束,使得:
鉴于优势At,如前一节所示,以及如上所示的策略比率,代理损失计算如下:
下面的代码显示了如何实现剪切机制和代理损失。
def calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages): advantages = advantages.detach() policy_ratio = ( actions_log_probability_new - actions_log_probability_old ).exp() surrogate_loss_1 = policy_ratio * advantages surrogate_loss_2 = torch.clamp( policy_ratio, min=1.0-epsilon, max=1.0+epsilon ) * advantages surrogate_loss = torch.min(surrogate_loss_1, surrogate_loss_2) return surrogate_loss
3. 训练代理
现在,让我们训练代理。
计算策略和价值损失
我们现在准备计算策略和价值损失:
- 策略损失是替代损失和熵奖励之和。
- 价值损失基于评论家预测的价值与策略生成的回报(累积奖励)之间的差异。价值损失计算使用平滑L1损失函数。这有助于使损失函数平滑,减少对异常值的敏感性。
如上所计算的两种损失都是张量。梯度下降基于标量值。要获得代表损失的单个标量值,请使用.sum()
函数来对张量元素求和。以下函数显示如何做到这一点:
def calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred): entropy_bonus = entropy_coefficient * entropy policy_loss = -(surrogate_loss + entropy_bonus).sum() value_loss = f.smooth_l1_loss(returns, value_pred).sum() return policy_loss, value_loss
定义训练循环
在开始训练过程之前,请创建一组空数组作为缓冲区。训练算法将使用这些缓冲区来存储有关代理动作、环境状态和每个时间步的奖励的信息。下面的函数初始化这些缓冲区:
def init_training(): states = [] actions = [] actions_log_probability = [] values = [] rewards = [] done = False episode_reward = 0 return states, actions, actions_log_probability, values, rewards, done, episode_reward
每个训练迭代都会使用该迭代的策略参数来运行代理。代理与环境交互,以循环的方式在时间步中进行,直到达到终止条件。
在每个时间步之后,代理的动作、奖励和价值将被附加到相应的缓冲区中。当该情节终止时,该函数会返回更新后的缓冲区集,总结该情节的结果。
在运行训练循环之前:
- 使用
agent.train()
将模型设置为训练模式。 - 使用
env.reset()
将环境重置为随机状态。这是本次训练迭代的起始状态。
以下步骤解释了训练循环中每个时间步发生的情况:
- 将状态传递给代理。
- 代理返回:
- 根据策略(actor)给定状态后的预测动作。通过softmax函数将此预测动作张量传递以获得动作概率集合。
- 基于评论家的预测的状态值。
- 代理选择要采取的动作:
- 使用动作概率来估算概率分布。
- 通过从该分布中选择一个样本来随机选择一个动作。函数
dist.sample()
会执行此操作。 - 使用
env.step()
函数将此动作传递给环境,以模拟此时间步的环境响应。根据代理的动作,环境会生成: - 新状态
- 奖励
- 布尔返回值
done
(表示环境是否达到终止状态) - 将代理动作、奖励、预测值和新状态的值追加到各自的缓冲区中。
当env.step()
函数返回true
作为done
的布尔返回值时,训练集终止。
在每个事件结束后,使用每个时间步的累积值来计算从该事件中累积返回,方法是将每个时间步的奖励相加。我们使用之前描述的calculate_returns()
函数来完成这一步。该函数的输入是折扣因子和包含每个时间步奖励的缓冲区。我们使用这些返回值和每个时间步的累积值来计算优势,使用calculate_advantages()
函数。
下面的Python函数展示了如何实现这些步骤:
def forward_pass(env, agent, optimizer, discount_factor): states, actions, actions_log_probability, values, rewards, done, episode_reward = init_training() state = env.reset() agent.train() while not done: state = torch.FloatTensor(state).unsqueeze(0) states.append(state) action_pred, value_pred = agent(state) action_prob = f.softmax(action_pred, dim=-1) dist = distributions.Categorical(action_prob) action = dist.sample() log_prob_action = dist.log_prob(action) state, reward, done, _ = env.step(action.item()) actions.append(action) actions_log_probability.append(log_prob_action) values.append(value_pred) rewards.append(reward) episode_reward += reward states = torch.cat(states) actions = torch.cat(actions) actions_log_probability = torch.cat(actions_log_probability) values = torch.cat(values).squeeze(-1) returns = calculate_returns(rewards, discount_factor) advantages = calculate_advantages(returns, values) return episode_reward, states, actions, actions_log_probability, advantages, returns
更新模型参数
每个训练迭代通过完整的包含许多时间步的事件(直到达到终止条件)运行模型。在每个时间步中,我们存储策略参数、Agent的动作、返回值和优势。每次迭代后,我们根据策略在该迭代中所有时间步的表现来更新模型。
CartPole环境中的最大时间步数为500。在更复杂的环境中,时间步数会更多,甚至可能达到百万级。在这种情况下,训练结果数据集必须分批处理。每个批次中的时间步数被称为优化批次大小。
因此,更新模型参数的步骤如下:
- 将训练结果数据集分成批次。
- 对于每个批次:
- 获取代理的动作和每个状态的预测值。
- 使用这些预测动作来估计新的动作概率分布。
- 使用此分布计算熵。
- 使用此分布获取训练结果数据集中动作的对数概率。这是训练结果数据集中动作的新对数概率集。这些相同动作的旧对数概率是在前一节中训练循环中计算的。
- 使用动作的旧和新概率分布计算替代损失。
- 使用替代损失、熵和优势计算策略损失和价值损失。
- 分别在策略损失和价值损失上运行
.backward()
。这会更新损失函数上的梯度。 - 运行
.step()
来更新策略参数。在这种情况下,我们使用Adam优化器来平衡速度和鲁棒性。 - 累积策略和价值损失。
- 根据参数
PPO_STEPS
的值,在每个批次上重复反向传播(上述操作)几次。在每个批次上重复反向传播是计算效率很高的,因为它有效地增加了训练数据集的大小,而无需运行额外的前向传播。在采样和优化之间的每次交替中的环境步数称为迭代批次大小。 - 返回平均策略损失和价值损失。
以下代码实现了这些步骤:
def update_policy( agent, states, actions, actions_log_probability_old, advantages, returns, optimizer, ppo_steps, epsilon, entropy_coefficient): BATCH_SIZE = 128 total_policy_loss = 0 total_value_loss = 0 actions_log_probability_old = actions_log_probability_old.detach() actions = actions.detach() training_results_dataset = TensorDataset( states, actions, actions_log_probability_old, advantages, returns) batch_dataset = DataLoader( training_results_dataset, batch_size=BATCH_SIZE, shuffle=False) for _ in range(ppo_steps): for batch_idx, (states, actions, actions_log_probability_old, advantages, returns) in enumerate(batch_dataset): # 获取所有输入状态的动作的新对数概率 action_pred, value_pred = agent(states) value_pred = value_pred.squeeze(-1) action_prob = f.softmax(action_pred, dim=-1) probability_distribution_new = distributions.Categorical( action_prob) entropy = probability_distribution_new.entropy() # 使用旧动作估算新的对数概率 actions_log_probability_new = probability_distribution_new.log_prob(actions) surrogate_loss = calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages) policy_loss, value_loss = calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred) optimizer.zero_grad() policy_loss.backward() value_loss.backward() optimizer.step() total_policy_loss += policy_loss.item() total_value_loss += value_loss.item() return total_policy_loss / ppo_steps, total_value_loss / ppo_steps
4. 运行PPO代理
最终让我们运行PPO代理。
评估性能
为了评估代理的性能,创建一个全新的环境并计算代理在这个新环境中运行时的累积奖励。您需要使用.eval()
函数将代理设置为评估模式。步骤与训练循环相同。下面的代码片段实现了评估函数:
def evaluate(env, agent): agent.eval() rewards = [] done = False episode_reward = 0 state = env.reset() while not done: state = torch.FloatTensor(state).unsqueeze(0) with torch.no_grad(): action_pred, _ = agent(state) action_prob = f.softmax(action_pred, dim=-1) action = torch.argmax(action_prob, dim=-1) state, reward, done, _ = env.step(action.item()) episode_reward += reward return episode_reward
可视化训练结果
我们将使用Matplotlib库来可视化训练过程的进展。下面的函数展示了如何绘制来自训练和测试循环的奖励:
def plot_train_rewards(train_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(train_rewards, label='Training Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Training Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(train_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()
def plot_test_rewards(test_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(test_rewards, label='Testing Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Testing Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(test_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()
在下面的示例图中,我们展示了在训练和测试环境中应用策略所获得的训练和测试奖励。请注意,每次运行代码时,这些图形的形状会有所不同。这是由于训练过程中固有的随机性。
训练奖励(在训练环境中应用策略所获得)。作者提供的图片。
测试奖励(在测试环境中应用策略所获得)。作者提供的图片。
在上面显示的输出图中,观察训练过程的进展:
- 奖励从较低的值开始。随着训练的进行,奖励增加。
- 奖励在增加的同时会随机波动。这是因为代理在探索策略空间。
- 训练结束时,测试奖励在许多迭代中稳定在阈值(475)附近。
- 奖励上限为500。这些是环境(Gym CartPole v1)施加的约束。
同样,您可以绘制值和策略损失随迭代次数变化的情况:
def plot_losses(policy_losses, value_losses): plt.figure(figsize=(12, 8)) plt.plot(value_losses, label='Value Losses') plt.plot(policy_losses, label='Policy Losses') plt.xlabel('Episode', fontsize=20) plt.ylabel('Loss', fontsize=20) plt.legend(loc='lower right') plt.grid() plt.show()
下面的示例图显示了训练周期中跟踪的损失情况:
价值和政策损失通过培训过程。作者提供的图片
观察图并注意:
- 损失似乎是随机分布的,没有遵循任何模式。
- 这是强化学习培训的典型特征,其目标不是最小化损失,而是最大化奖励。
运行PPO算法
现在您已经拥有使用PPO训练代理的所有组件。要将所有组件整合在一起,您需要:
- 声明超参数,如折扣因子、批量大小、学习率等。
- 实例化缓冲区为null数组,用于存储每次迭代的奖励和损失。
- 使用
create_agent()
函数创建一个代理实例。 - 使用
forward_pass()
和update_policy()
函数迭代运行前向和后向传递。 - 使用
evaluate()
函数测试策略的性能。 - 将训练和评估函数的策略、价值损失和奖励追加到相应的缓冲区中。
- 计算最近几个时间步的奖励和损失的平均值。以下示例对最近的40个时间步的奖励和损失进行平均。
- 每隔一段时间打印评估结果。以下示例每隔10个步骤打印一次。
- 当平均奖励超过一定阈值时终止进程。
以下代码展示了如何在Python中声明执行此操作的函数:
def run_ppo(): MAX_EPISODES = 500 DISCOUNT_FACTOR = 0.99 REWARD_THRESHOLD = 475 PRINT_INTERVAL = 10 PPO_STEPS = 8 N_TRIALS = 100 EPSILON = 0.2 ENTROPY_COEFFICIENT = 0.01 HIDDEN_DIMENSIONS = 64 DROPOUT = 0.2 LEARNING_RATE = 0.001 train_rewards = [] test_rewards = [] policy_losses = [] value_losses = [] agent = create_agent(HIDDEN_DIMENSIONS, DROPOUT) optimizer = optim.Adam(agent.parameters(), lr=LEARNING_RATE) for episode in range(1, MAX_EPISODES+1): train_reward, states, actions, actions_log_probability, advantages, returns = forward_pass( env_train, agent, optimizer, DISCOUNT_FACTOR) policy_loss, value_loss = update_policy( agent, states, actions, actions_log_probability, advantages, returns, optimizer, PPO_STEPS, EPSILON, ENTROPY_COEFFICIENT) test_reward = evaluate(env_test, agent) policy_losses.append(policy_loss) value_losses.append(value_loss) train_rewards.append(train_reward) test_rewards.append(test_reward) mean_train_rewards = np.mean(train_rewards[-N_TRIALS:]) mean_test_rewards = np.mean(test_rewards[-N_TRIALS:]) mean_abs_policy_loss = np.mean(np.abs(policy_losses[-N_TRIALS:])) mean_abs_value_loss = np.mean(np.abs(value_losses[-N_TRIALS:])) if episode % PRINT_INTERVAL == 0: print(f'Episode: {episode:3} | \ Mean Train Rewards: {mean_train_rewards:3.1f} \ | Mean Test Rewards: {mean_test_rewards:3.1f} \ | Mean Abs Policy Loss: {mean_abs_policy_loss:2.2f} \ | Mean Abs Value Loss: {mean_abs_value_loss:2.2f}') if mean_test_rewards >= REWARD_THRESHOLD: print(f'Reached reward threshold in {episode} episodes') break plot_train_rewards(train_rewards, REWARD_THRESHOLD) plot_test_rewards(test_rewards, REWARD_THRESHOLD) plot_losses(policy_losses, value_losses)
运行程序:
run_ppo()
输出应类似于下面的示例:
Episode: 10 | Mean Train Rewards: 22.3 | Mean Test Rewards: 30.4 | Mean Abs Policy Loss: 0.37 | Mean Abs Value Loss: 0.39 Episode: 20 | Mean Train Rewards: 38.6 | Mean Test Rewards: 69.8 | Mean Abs Policy Loss: 0.46 | Mean Abs Value Loss: 0.37 . . . Episode: 100 | Mean Train Rewards: 289.5 | Mean Test Rewards: 427.3 | Mean Abs Policy Loss: 1.73 | Mean Abs Value Loss: 0.21 Episode: 110 | Mean Train Rewards: 357.7 | Mean Test Rewards: 461.4 | Mean Abs Policy Loss: 1.86 | Mean Abs Value Loss: 0.22 Reached reward threshold in 116 episodes
您可以在这个 DataLab 笔记本上查看并运行工作程序!
5. 超参数调整和优化
在机器学习中,超参数控制着训练过程。下面,我将解释一些在 PPO 中使用的重要超参数:
- 学习率: 学习率决定了每次迭代中策略参数可以变化的程度。在随机梯度下降中,策略参数在每次迭代中更新的幅度由学习率和梯度的乘积决定。
- 剪切参数: 这也被称为 epsilon,ε。 它决定了对策略比例进行剪切的程度。允许新旧策略的比例在范围 [1-ε, 1+ε]变动。当超出此范围时,将被人为剪切至该范围内。
- 批量大小:这指的是每次梯度更新考虑的步数。在PPO中,批量大小是需要应用策略和计算替代损失以更新策略参数所需的时间步数。在本文中,我们使用了批量大小为64。
- 迭代步数:这是每个批次被重复使用进行反向传播的次数。本文中的代码将其称为
PPO_STEPS
。在复杂环境中,多次运行前向传播是计算昂贵的。一个更高效的替代方案是多次重新运行每个批次。通常建议使用介于5和10之间的值。 - 折现因子: 这也被称为γ(gamma)。 它表达了即时奖励比未来奖励更有价值的程度。这类似于计算货币时间价值时利率的概念。当接近0时,意味着未来奖励较不重要,代理应优先考虑即时奖励。当接近1时,意味着未来奖励很重要。
- 熵系数:熵系数决定了熵奖励,其计算结果为熵系数与分布的熵的乘积。熵奖励的作用是引入更多的随机性到策略中。这鼓励代理探索策略空间。然而,当这种随机性过高时,训练无法收敛到最佳策略。
- 培训的成功标准:您需要设定决定培训成功的标准。一个常见的做法是设置过去N次试验(回合)的平均奖励超过特定阈值的条件。在上面的示例代码中,这是通过变量
N_TRIALS
来表示的。当将其设置为较高值时,培训时间会更长,因为政策必须在更多的回合中实现阈值奖励。这也会导致更健壮的政策,但计算成本更高。请注意,PPO是一种随机策略,代理有时不会达到阈值奖励。因此,如果N_TRIALS
的值过高,您的培训可能不会终止。
优化PPO性能的策略
优化训练PPO算法的性能涉及尝试和错误以及尝试不同的超参数值。然而,有一些广泛的指导原则:
- 折扣因子: 当长期奖励很重要时,如在CartPole环境中,杆需要随着时间保持稳定,可以从中等的gamma值开始,例如0.99。
- 熵奖励: 在复杂环境中,代理必须探索动作空间以找到最优策略。熵奖励促进探索。 熵奖励添加到代理损失中。 在决定熵系数之前,请检查代理损失的大小和分布的熵。 在本文中,我们使用了熵系数为0.01。
- 剪切参数: 剪切参数决定了更新后的策略与当前策略的差异程度。较大的剪切参数值鼓励更好地探索环境,但也会增加训练的不稳定风险。您需要一个剪切参数,既能够逐渐探索环境,又能防止不稳定的更新。在本文中,我们使用了剪切参数为0.2。
- 学习率: 当学习率过高时,策略会以较大步长进行更新,每次迭代训练过程可能会变得不稳定。当学习率过低时,训练时间会变得过长。本教程使用了学习率为0.001,在该环境下效果良好。在许多情况下,建议使用学习率为1e-5。
在PPO中的挑战和最佳实践
在解释了PPO的概念和实现细节之后,让我们讨论挑战和最佳实践。
训练PPO时常见的挑战
尽管PPO被广泛使用,但您需要了解潜在挑战,以成功使用这种技术解决现实世界的问题。一些挑战包括:
- 收敛速度慢:在复杂环境中,PPO可能在样本效率上存在问题,需要与环境进行许多交互才能收敛到最优策略。这使得训练变得缓慢且昂贵。
- 超参数的敏感性: PPO依赖于有效地探索策略空间。训练过程的稳定性和收敛速度对于超参数的值非常敏感。通常只能通过试错来确定这些超参数的最佳值。
- 过拟合: RL环境通常使用随机参数初始化。PPO训练是基于找到基于代理环境的最佳策略。有时,训练过程会收敛到针对特定环境的一组最佳参数,但并不适用于任何随机化环境。通常通过进行许多迭代,每个迭代使用一个不同随机化的训练环境来解决这个问题。
- 动态环境:简单的强化学习环境,如CartPole环境,是静态的 – 规则始终保持不变。许多其他环境,比如机器人学习在不稳定的移动表面上行走,是动态的 – 环境的规则随时间变化。为了在这样的环境中表现良好,PPO通常需要额外的微调。
- 探索与利用:PPO的修剪机制确保策略更新在可信区域内。然而,这也阻止了代理程序探索动作空间。这可能导致在复杂环境中收敛到局部最优解。另一方面,允许代理程序过度探索可能会阻止其收敛到任何最优策略。
训练PPO模型的最佳实践
要在PPO中获得良好的结果,我建议采用一些最佳实践,例如:
- 归一化输入特征:对回报和优势的值进行归一化可以减少数据的变异性,并导致稳定的梯度更新。归一化数据将所有值带到一致的数值范围内。这有助于减少离群值和极端值的影响,否则可能扭曲梯度更新并减慢收敛速度。
- 使用适当大的批量大小:小批量可以加快更新和训练速度,但可能导致收敛到局部最优解和训练过程不稳定。较大的批量大小可以使代理程序学习到稳健的策略,从而导致稳定的训练过程。然而,过大的批量大小也是次优的。除了增加计算成本外,它们使策略更新对值函数的响应变得较弱,因为梯度更新是基于大批量估计的平均值。此外,它可能导致过拟合更新到特定批次。
- 迭代步骤:通常建议每个批次重复使用 5-10 次迭代。这样可以使训练过程更加高效。过多地重复使用同一批次会导致过拟合。代码中将这个超参数称为
PPO_STEPS
。 - 进行定期评估:为了检测过拟合,必须定期监控政策的有效性。如果在某些场景中发现政策无效,可能需要进一步训练或微调。
- 调整超参数: 如前所述,PPO训练对超参数的值敏感。尝试不同的超参数值,以确定适合您特定问题的正确值集。
- 共享骨干网络: 如本文所示,使用共享骨干网络可防止演员和评论者网络之间的不平衡。在演员和评论者之间共享骨干网络有助于共享特征提取和对环境的共同理解。这使学习过程更高效稳定。还有助于减少算法的计算空间和时间复杂度。
- 隐藏层的数量和大小: 增加隐藏层的数量和维度可处理更复杂的环境。像CartPole这样简单的问题可以用单个隐藏层解决。本文中使用的隐藏层有64个维度。将网络设计得比必要的更大会造成计算浪费并可能导致不稳定。
- 提前停止: 当满足评估指标时停止训练有助于防止过度训练并避免资源浪费。一个常见的评估指标是当代理在过去的N个事件中超过阈值奖励时。
结论
在本文中,我们讨论了PPO作为解决RL问题的一种方法。然后,我们详细介绍了使用PyTorch实现PPO的步骤。最后,我们提出了一些关于PPO的性能提示和最佳实践。
学习的最佳方式是自己实现代码。您也可以修改代码以适用于Gym中的其他经典控制环境。要了解如何使用Python和OpenAI的Gymnasium实现RL代理,请关注课程使用Python在Gymnasium中进行强化学习!
Source:
https://www.datacamp.com/tutorial/proximal-policy-optimization