ChatGPT强化学习大杀器——近端策略优化(PPO)

news2024/11/16 5:25:31

ChatGPT强化学习大杀器——近端策略优化(PPO)

近端策略优化Proximal Policy Optimization)来自 Proximal Policy Optimization Algorithms(Schulman et. al., 2017)这篇论文,是当前最先进的强化学习 (RL) 算法。这种优雅的算法可以用于各种任务,并且已经在很多项目中得到了应用,最近火爆的ChatGPT就采用了该算法。

在这里插入图片描述

网上讲解ChatGPT算法和训练流程的文章很多,但很少有人深入地将其中关键的近端策略优化算法讲清楚,本文我会重点讲解近端策略优化算法,并用PyTorch从头实现一遍。

文章目录

    • 强化学习
    • 算法
      • 策略优化(基于梯度)
    • 近端策略优化
      • CLIP项
      • 价值函数项
      • 熵奖励项
    • 算法实现
      • 工具代码
      • 核心代码
    • 结论

强化学习

近端策略优化作为一个先进的强化学习算法,我们首先要对强化学习有个了解。关于强化学习,介绍的文章很多,这里我不做过多介绍,但这里我们可以看一下ChatGPT是怎么解释的:

在这里插入图片描述

ChatGPT给出的解释比较通俗易懂,更加学术一点的说,强化学习的流程如下:

在这里插入图片描述

强化学习框架

上图中,每个时刻环境都会为代理反馈奖励,并监控当前状态。有了这些信息,代理就会在环境中采取行动,然后新的奖励和状态等会反馈给代理,以此形成循环。这个框架非常通用,可以应用于各种领域。

我们的目标是创建一个可以最大化获得奖励的代理。 通常这个最大化奖励是各时间折扣奖励的总和。
G = ∑ t = 0 T γ t r t G = \sum_{t=0}^T\gamma^tr_t G=t=0Tγtrt
这里 γ \gamma γ是折扣因子,通常在 [0.95, 0.99] 范围内, r t r_t rt 是时间 t 的奖励。

算法

那么我们如何解决强化学习问题呢? 有多种算法,可以(对于马尔可夫决策过程或 MDP)分为两类:基于模型(创建环境模型)和无模型(仅给定状态学习)。

img

强化学习算法分类

基于模型的算法创建环境模型并使用该模型来预测未来状态和奖励。 该模型要么是给定的(例如棋盘),要么是学习的。

无模型算法直接学习如何针对训练期间遇到的状态(策略优化或 PO)采取行动,哪些状态-行动会产生良好的回报(Q-Learning)。

我们今天讨论的近端策略优化算法属于 PO 算法家族。 因此,我们不需要环境模型来驱动学习。PO 和 Q-Learning 算法之间的主要区别在于 PO 算法可以用于具有连续动作空间的环境(即我们的动作具有真实值)并且即使该策略是随机策略(即按概率行事)也可以找到最优策略;而 Q-Learning 算法不能做这两件事。 这是PO 算法更受欢迎的另一个原因。 另一方面,Q-Learning 算法往往更简单、更直观且更易于训练。

策略优化(基于梯度)

策略优化算法可以直接学习策略。 为此,策略优化可以使用无梯度算法(例如遗传算法),也可以使用更常见的基于梯度的算法。

通过基于梯度的方法,我们指的是所有尝试估计学习策略相对于累积奖励的梯度的方法。 如果我们知道这个梯度(或它的近似值),我们可以简单地将策略的参数移向梯度的方向以最大化奖励。

策略梯度方法通过重复估计梯度 g : = ∇ θ E [ ∑ t = 0 ∞ r t ] g:=\nabla_\theta\mathbb{E}[\sum_{t=0}^{\infin}r_t] g:=θE[t=0rt]来最大化预期总奖励。策略梯度有几种不同的相关表达式,其形式为:

g = E [ ∑ t = 0 ∞ Ψ t ∇ θ l o g π θ ( a t ∣ s t ) ] (1) g=\mathbb{E}\Bigg\lbrack \sum_{t=0}^{\infin} \Psi_t \nabla_\theta log\pi_\theta(a_t \mid s_t) \Bigg\rbrack \tag{1} g=E[t=0Ψtθlogπθ(atst)](1)

其中 Ψ t \Psi_t Ψt 可以是如下几个:

  1. ∑ t = 0 ∞ r t \sum_{t=0}^\infin r_t t=0rt: 轨迹的总奖励
  2. ∑ t ′ = t ∞ r t ′ \sum_{t'=t}^\infin r_{t'} t=trt: 下一个动作 a t a_t at 的奖励
  3. ∑ t = 0 ∞ r t − b ( s t ) \sum_{t=0}^\infin r_t - b(s_t) t=0rtb(st): 上面公式的基线版本
  4. Q π ( s t , a t ) Q^\pi(s_t, a_t) Qπ(st,at): 状态-动作价值函数
  5. A π ( s t , a t ) A^\pi(s_t, a_t) Aπ(st,at): 优势函数
  6. r t + V π ( s t + 1 ) + V π ( s t ) r_t+V^\pi(s_{t+1})+V^\pi(s_{t}) rt+Vπ(st+1)+Vπ(st): TD残差

后面3个公式的具体定义如下:
V π ( s t ) : = E s t + 1 : ∞ , a t : ∞ [ ∑ l = 0 ∞ r t + l ] Q π ( s t , a t ) : = E s t + 1 : ∞ , a t + 1 : ∞ [ ∑ l = 0 ∞ r t + l ] (2) V^\pi(s_t) := \mathbb{E}_{s_{t+1:\infin}, a_{t:\infin}}\Bigg\lbrack\sum_{l=0}^\infin r_{t+l} \Bigg\rbrack \\ Q^\pi(s_t, a_t) := \mathbb{E}_{s_{t+1:\infin}, a_{t+1:\infin}}\Bigg\lbrack\sum_{l=0}^\infin r_{t+l} \Bigg\rbrack \tag{2} Vπ(st):=Est+1:,at:[l=0rt+l]Qπ(st,at):=Est+1:,at+1:[l=0rt+l](2)

A π ( s t , a t ) : = Q π ( s t , a t ) − V π ( s t ) (3) A^\pi(s_t, a_t) := Q^\pi(s_t, a_t) - V^\pi(s_t) \tag{3} Aπ(st,at):=Qπ(st,at)Vπ(st)(3)

请注意,有多种方法可以估计梯度。 在这里,我们列出了 6 个不同的值:总奖励、后继动作的奖励、减去基线版本的奖励、状态-动作价值函数、优势函数(在原始 PPO 论文中使用)和时间差 (TD) 残差。我们可以选择这些值作为我们的最大化目标。 原则上,它们都提供了我们所关注的真实梯度的估计。

近端策略优化

近端策略优化简称PPO,是一种(无模型)基于策略优化梯度的算法。 该算法旨在学习一种策略,可以根据训练期间的经验最大化获得的累积奖励。

它由一个参与者(actor) π θ ( . ∣ s t ) \pi\theta(. \mid st) πθ(.st) 和一个评估者(critic) V ( s t ) V(st) V(st)组成。前者在时间 t t t 处输出下一个动作的概率分布,后者估计该状态的预期累积奖励(标量)。 由于 actor 和 critic 都将状态作为输入,因此可以在提取高级特征的两个网络之间共享骨干架构。

PPO 旨在使策略选择具有更高“优势”的行动,即具有比评估者预测的高得多的累积奖励。 同时,我们也不希望一次更新策略太多,这样很可能会出现优化问题。 最后,如果策略具有高熵,我们会倾向于给与额外奖励,以激励更多探索。

总损失函数由三项组成:CLIP项、价值函数 (VF) 项和熵奖励项。最终目标如下:
L t C L I P + V F + S ( θ ) = E ^ t [ L t C L I P ( θ ) − c 1 L t V F ( θ ) + c 2 S [ π θ ] ( s t ) ] L_t^{CLIP+VF+S}(\theta) = \hat{\mathbb{E}}_t \Big\lbrack L_t^{CLIP}(\theta) - c_1L_t^{VF}(\theta)+c_2S[\pi_\theta](s_t)\Big\rbrack LtCLIP+VF+S(θ)=E^t[LtCLIP(θ)c1LtVF(θ)+c2S[πθ](st)]
其中 c 1 c_1 c1 c 2 c_2 c2 是超参,分别衡量策略评估(critic)和探索(exploration)准确性的重要性。

CLIP项

正如我们所说,损失函数激发行为概率最大化(或最小化),从而导致行为正面优势(或负面优势)
L C L I P ( θ ) = E ^ t [ m i n ( r t ( θ ) A t ^ , c l i p ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) A ^ t ) ] L^{CLIP}(\theta) = \hat{\mathbb{E}}_t\Big\lbrack min \Big\lparen r_t(\theta)\hat{A_t},clip \big\lparen r_t(\theta),1-\epsilon, 1+\epsilon\big\rparen \hat{A}_t \Big\rparen \Big\rbrack LCLIP(θ)=E^t[min(rt(θ)At^,clip(rt(θ),1ϵ,1+ϵ)A^t)]
其中:
r t ( θ ) = π θ ( a t ∣ s t ) π θ o l d ( a t ∣ s t ) r_t(\theta) = \frac{\pi_\theta(a_t \mid s_t)}{\pi_{\theta_{old}}(a_t \mid s_t)} rt(θ)=πθold(atst)πθ(atst)
是衡量我们现在(更新后的策略)相对于之前执行该先前操作的可能性的比率。 原则上,我们不希望这个系数太大,因为太大意味着策略突然改变。这就是为什么我们采用其最小值和 [ 1 − ϵ , 1 + ϵ ] [1-\epsilon, 1+\epsilon] [1ϵ,1+ϵ] 之间的裁剪版本,其中 ϵ \epsilon ϵ 是个超参。

优势(advantage)的计算公式如下:
A ^ t = − V ( s t ) + r t + γ r t + 1 + γ 2 r t + 2 + ⋯ + γ ( T − t + 1 ) r T − 1 + γ T − t V ( s T ) \hat{A}_t = -V(s_t)+r_t+\gamma r_{t+1}+\gamma^2 r_{t+2}+\dots+\gamma^{(T-t+1)} r_{T-1} + \gamma^{T-t}V(s_T) A^t=V(st)+rt+γrt+1+γ2rt+2++γ(Tt+1)rT1+γTtV(sT)
其中: A t ^ \hat{A_t} At^ 是估计的优势, − V ( s t ) -V(s_t) V(st) 是估计的初始状态值, γ T − t V ( s T ) \gamma^{T-t}V(s_T) γTtV(sT) 是估计的终末状态值,中间部分是过程中观测到的累计奖励。

我们看到它只是衡量评估者对给定状态 s t s_t st 的错误程度。 如果我们获得更高的累积奖励,则优势估计将为正,我们将更有可能在这种状态下采取行动。 反之亦然,如果我们期望更高的奖励但我们得到的奖励更小,则优势估计将为负,我们将降低在此步骤中采取行动的可能性。

请注意,如果我们一直走到终末状态 s T s_T sT,我们就不再需要依赖评估者了,我们可以简单地将评估者与实际累积奖励进行比较。 在这种情况下,优势的估计就是真正的优势。

价值函数项

为了对优势有一个良好的估计,我们需要一个可以预测给定状态值的评估者。 该模型为具有简单 MSE 损失的监督学习:
L t V F = M S E ( r t + γ r t + 1 + ⋯ + γ ( T − t + 1 ) r T − 1 + V ( s T ) , V ( s t ) ) = ∣ ∣ A ^ t ∣ ∣ 2 L_t^{VF} = MSE(r_t+\gamma r_{t+1}+\dots+\gamma^{(T-t+1)} r_{T-1}+V(s_T),V(s_t)) = ||\hat{A}_t||_2 LtVF=MSE(rt+γrt+1++γ(Tt+1)rT1+V(sT),V(st))=∣∣A^t2
每次迭代中,我们也会更新评估者,以便随着训练的进行,它会为我们提供越来越准确的状态值。

熵奖励项

最后,我们鼓励对策略输出分布的熵进行少量奖励的探索。 标准熵为:
S [ π θ ] ( s t ) = − ∫ π θ ( a t ∣ s t ) l o g ( π θ ( a t ∣ s t ) ) d a t S[\pi_\theta](s_t) = -\int \pi_\theta(a_t \mid s_t) log(\pi_\theta(a_t \mid s_t))da_t S[πθ](st)=πθ(atst)log(πθ(atst))dat

算法实现

如果上面的讲解不够清晰,不用担心,下面将带大家从零开始一步一步实现近端策略优化算法。

工具代码

首先先导入所需的库

from argparse import ArgumentParser

import gym
import numpy as np
import wandb

import torch
import torch.nn as nn
from torch.optim import Adam
from torch.optim.lr_scheduler import LinearLR
from torch.distributions.categorical import Categorical

import pytorch_lightning as pl

PPO 的重要超参有 actor 数量、horizon、epsilon、每个优化阶段的 epoch 数量、学习率、折扣因子gamma以及权衡不同损失项的常数 c1 和 c2。 这些超参我们通过参数传入进来。

def parse_args():
    """解析参数"""
    parser = ArgumentParser()

    parser.add_argument("--max_iterations", type=int, help="训练迭代次数", default=100)
    parser.add_argument("--n_actors", type=int, help="actor数量", default=8)
    parser.add_argument("--horizon", type=int, help="每个actor的时间戳数量", default=128)
    parser.add_argument("--epsilon", type=float, help="Epsilon", default=0.1)
    parser.add_argument("--n_epochs", type=int, help="每次迭代的训练轮数", default=3)
    parser.add_argument("--batch_size", type=int, help="Batch size", default=32 * 8)
    parser.add_argument("--lr", type=float, help="学习率", default=2.5 * 1e-4)
    parser.add_argument("--gamma", type=float, help="折扣因子gamma", default=0.99)
    parser.add_argument("--c1", type=float, help="损失函数价值函数的权重", default=1)
    parser.add_argument("--c2", type=float, help="损失函数熵奖励的权重", default=0.01)
    parser.add_argument("--n_test_episodes", type=int, help="Number of episodes to render", default=5)
    parser.add_argument("--seed", type=int, help="随机种子", default=0)

    return vars(parser.parse_args())

请注意,默认情况下,参数是按照论文中的描述设置的。 理想情况下,我们的代码应该尽可能在 GPU 上运行,因此我们需要设置一下torch的设备。

def get_device():
    if torch.cuda.is_available():
        device = torch.device("cuda")
        print(f"Found GPU device: {torch.cuda.get_device_name(device)}")
    else:
        device = torch.device("cpu")
        print("No GPU found: Running on CPU")
    return device

当我们执行强化学习时,通常会设一个缓冲区来存储当前模型遇到的状态、动作和奖励,用于更新我们的模型。 我们创建一个函数 run_timestamps,它将在给定的环境中运行给定的模型并获得固定数量的时间戳(如果episode 结束则重新设置环境)。 我们还使用选项 render=False 以便我们只想查看训练模型的表现。

@torch.no_grad()
def run_timestamps(env, model, timestamps=128, render=False, device="cpu"):
    """针对给定数量的时间戳在给定环境中运行给定策略。 
    返回具有状态、动作和奖励的缓冲区。"""
    buffer = []
    state = env.reset()[0]

    # 运行时间戳并收集状态、动作、奖励和终止
    for ts in range(timestamps):
        model_input = torch.from_numpy(state).unsqueeze(0).to(device).float()
        action, action_logits, value = model(model_input)
        new_state, reward, terminated, truncated, info = env.step(action.item())

        # (s, a, r, t)渲染到环境或存储到buffer
        if render:
            env.render()
        else:
            buffer.append([model_input, action, action_logits, value, reward, terminated or truncated])

        # 更新当前状态
        state = new_state

        # 如果episode终止或被截断,则重置环境
        if terminated or truncated:
            state = env.reset()[0]

    return buffer

该函数的返回值(未渲染时)是一个缓冲区,其中包含状态、采取的行动、行动概率(logits)、评估者价值、奖励以及每个时间戳所提供策略的终止状态。 请注意,该函数使用装饰器@torch.no_grad(),因此我们不需要为与环境交互期间采取的操作存储梯度。

核心代码

有了上面的工具函数,我们就可以开发近端策略优化的核心代码了。首先新搭建main函数流程:

def main():
    # 解析参数
    args = parse_args()
    print(args)

    # 设置种子
    pl.seed_everything(args["seed"])

    # 获取设备
    device = get_device()

    # 创建环境
    env_name = "CartPole-v1"
    env = gym.make(env_name)

    # TODO 创建模型,训练模型,输出结果
    model = MyPPO(env.observation_space.shape, env.action_space.n).to(device)
    training_loop(env, model, args)
    model = load_best_model()
    testing_loop(env, model)

上面就是整体程序的流程框架。接下来,我们只需要定义 PPO 模型、训练和测试函数。

PPO 模型的架构这里不过多阐述,我们只需要两个在环境中起作用的模型(actor和critic)。 当然,模型架构在更复杂的任务中作用至关重要,但有我们这个简单任务中,MLP 就可以完成这项工作。

因此,我们可以创建一个包含actor和critic模型的 MyPPO 类。当对某些状态运行前向方法时,我们返回actor的采样动作、每个可能动作的相对概率 (logits) 以及critic对每个状态的估计值。

class MyPPO(nn.Module):
    """
    PPO模型的实现。 
    相同的代码结构即可用于actor,也可用于critic。
    """

    def __init__(self, in_shape, n_actions, hidden_d=100, share_backbone=False):
        # 父类构造函数
        super(MyPPO, self).__init__()

        # 属性
        self.in_shape = in_shape
        self.n_actions = n_actions
        self.hidden_d = hidden_d
        self.share_backbone = share_backbone

        # 共享策略主干和价值函数
        in_dim = np.prod(in_shape)

        def to_features():
            return nn.Sequential(
                nn.Flatten(),
                nn.Linear(in_dim, hidden_d),
                nn.ReLU(),
                nn.Linear(hidden_d, hidden_d),
                nn.ReLU()
            )

        self.backbone = to_features() if self.share_backbone else nn.Identity()

        # State action function
        self.actor = nn.Sequential(
            nn.Identity() if self.share_backbone else to_features(),
            nn.Linear(hidden_d, hidden_d),
            nn.ReLU(),
            nn.Linear(hidden_d, n_actions),
            nn.Softmax(dim=-1)
        )

        # Value function
        self.critic = nn.Sequential(
            nn.Identity() if self.share_backbone else to_features(),
            nn.Linear(hidden_d, hidden_d),
            nn.ReLU(),
            nn.Linear(hidden_d, 1)
        )

    def forward(self, x):
        features = self.backbone(x)
        action = self.actor(features)
        value = self.critic(features)
        return Categorical(action).sample(), action, value

请注意,Categorical(action).sample() 创建了一个分类分布,其中包含一个动作(针对每个状态)的动作 logits 和样本。

最后,我们可以处理 training_loop 函数中的实际算法。 正如我们从论文中了解到的,该函数的实际签名应如下所示:

def training_loop(env, model, max_iterations, n_actors, horizon, gamma, 
epsilon, n_epochs, batch_size, lr, c1, c2, device, env_name=""):
  # TODO...

以下是论文中为 PPO 训练程序的伪代码:

img

PPO 的伪代码相对简单:我们只需通过策略模型(称为actor)的多个副本收集与环境的交互,并使用先前定义的目标来优化actor和critic网络。

由于我们需要衡量我们真正获得的累积奖励,因此需要创建一个函数,给定一个缓冲区,用累积奖励替换每个时间的奖励:

def compute_cumulative_rewards(buffer, gamma):
    """
    给定一个包含状态、策略操作逻辑、奖励和终止的缓冲区,计算每个时间的累积奖励并将它们代入缓冲区。
    """
    curr_rew = 0.

    # 反向遍历缓冲区
    for i in range(len(buffer) - 1, -1, -1):
        r, t = buffer[i][-2], buffer[i][-1]

        if t:
            curr_rew = 0
        else:
            curr_rew = r + gamma * curr_rew

        buffer[i][-2] = curr_rew

    # 在规范化之前获得平均奖励(用于日志记录和检查点)
    avg_rew = np.mean([buffer[i][-2] for i in range(len(buffer))])

    # 规范化累积奖励
    mean = np.mean([buffer[i][-2] for i in range(len(buffer))])
    std = np.std([buffer[i][-2] for i in range(len(buffer))]) + 1e-6
    for i in range(len(buffer)):
        buffer[i][-2] = (buffer[i][-2] - mean) / std

    return avg_rew

请注意,最后我们将累积奖励归一化处理。 这是使优化问题更容易和训练更顺利的标准技巧。

现在我们可以获得包含状态、采取的动作、动作概率和累积奖励的缓冲区,可以编写一个函数,在给定缓冲区的情况下,为我们的最终目标计算三个损失项:

def get_losses(model, batch, epsilon, annealing, device="cpu"):
    """给定模型、给定批次和附加参数返回三个损失项"""
    # 获取旧数据
    n = len(batch)
    states = torch.cat([batch[i][0] for i in range(n)])
    actions = torch.cat([batch[i][1] for i in range(n)]).view(n, 1)
    logits = torch.cat([batch[i][2] for i in range(n)])
    values = torch.cat([batch[i][3] for i in range(n)])
    cumulative_rewards = torch.tensor([batch[i][-2] for i in range(n)]).view(-1, 1).float().to(device)

    # 使用新模型计算预测
    _, new_logits, new_values = model(states)

    # 状态动作函数损失(L_CLIP)
    advantages = cumulative_rewards - values
    margin = epsilon * annealing
    ratios = new_logits.gather(1, actions) / logits.gather(1, actions)

    l_clip = torch.mean(
        torch.min(
            torch.cat(
                (ratios * advantages,
                 torch.clip(ratios, 1 - margin, 1 + margin) * advantages),
                dim=1),
            dim=1
        ).values
    )

    # 价值函数损失(L_VF)
    l_vf = torch.mean((cumulative_rewards - new_values) ** 2)

    # 熵奖励
    entropy_bonus = torch.mean(torch.sum(-new_logits * (torch.log(new_logits + 1e-5)), dim=1))

    return l_clip, l_vf, entropy_bonus

请注意,在实践中,我们使用初值为 1 并在整个训练过程中线性衰减至 0 的退火参数。 因为随着训练的进行,我们希望我们的策略变化越来越少。 另外,与 new_logitsnew_values 不同,我们不不跟踪advantages 变量的梯度,只是张量之差。

现在我们有了环境交互和存储缓冲区、计算(真实)累积奖励并获得损失项的方法,可以着手编写最终训练代码了:

def training_loop(env, model, max_iterations, n_actors, horizon, gamma, epsilon, n_epochs, batch_size, lr,
                  c1, c2, device, env_name=""):
    """使用最多n个时间戳的多个actor在给定环境中训练模型。"""

    # 开始运行新的权重和偏差
    wandb.init(project="Papers Re-implementations",
               entity="peutlefaire",
               name=f"PPO - {env_name}",
               config={
                   "env": str(env),
                   "number of actors": n_actors,
                   "horizon": horizon,
                   "gamma": gamma,
                   "epsilon": epsilon,
                   "epochs": n_epochs,
                   "batch size": batch_size,
                   "learning rate": lr,
                   "c1": c1,
                   "c2": c2
               })

    # 训练变量
    max_reward = float("-inf")
    optimizer = Adam(model.parameters(), lr=lr, maximize=True)
    scheduler = LinearLR(optimizer, 1, 0, max_iterations * n_epochs)
    anneals = np.linspace(1, 0, max_iterations)

    # 训练循环
    for iteration in range(max_iterations):
        buffer = []
        annealing = anneals[iteration]

        # 使用当前策略收集所有actor的时间戳
        for actor in range(1, n_actors + 1):
            buffer.extend(run_timestamps(env, model, horizon, False, device))

        # 计算累积奖励并刷新缓冲区
        avg_rew = compute_cumulative_rewards(buffer, gamma)
        np.random.shuffle(buffer)

        # 运行几轮优化
        for epoch in range(n_epochs):
            for batch_idx in range(len(buffer) // batch_size):
                start = batch_size * batch_idx
                end = start + batch_size if start + batch_size < len(buffer) else -1
                batch = buffer[start:end]

                # 归零优化器梯度
                optimizer.zero_grad()

                # 获取损失
                l_clip, l_vf, entropy_bonus = get_losses(model, batch, epsilon, annealing, device)

                # 计算总损失并反向传播
                loss = l_clip - c1 * l_vf + c2 * entropy_bonus
                loss.backward()

                # 优化
                optimizer.step()
            scheduler.step()

        # 记录输出
        curr_loss = loss.item()
        log = f"Iteration {iteration + 1} / {max_iterations}: " \
              f"Average Reward: {avg_rew:.2f}\t" \
              f"Loss: {curr_loss:.3f} " \
              f"(L_CLIP: {l_clip.item():.1f} | L_VF: {l_vf.item():.1f} | L_bonus: {entropy_bonus.item():.1f})"
        if avg_rew > max_reward:
            torch.save(model.state_dict(), MODEL_PATH)
            max_reward = avg_rew
            log += " --> Stored model with highest average reward"
        print(log)

        # 将信息记录到 W&B
        wandb.log({
            "loss (total)": curr_loss,
            "loss (clip)": l_clip.item(),
            "loss (vf)": l_vf.item(),
            "loss (entropy bonus)": entropy_bonus.item(),
            "average reward": avg_rew
        })

    # 完成 W&B 会话
    wandb.finish()

最后,为了查看模型的最终效果,我们使用以下 testing_loop 函数:

def testing_loop(env, model, n_episodes, device):
    for _ in range(n_episodes):
        run_timestamps(env, model, timestamps=128, render=True, device=device)

这样,我们的主程序就会变得很简单:

def main():
    # 解析参数
    args = parse_args()
    print(args)

    # 设置种子
    pl.seed_everything(args["seed"])

    # 获取设备
    device = get_device()

    # 创建环境
    env_name = "CartPole-v1"
    env = gym.make(env_name)

    # 创建模型(actor和critic)
    model = MyPPO(env.observation_space.shape, env.action_space.n).to(device)

    # 训练
    training_loop(env, model, args["max_iterations"], args["n_actors"], args["horizon"], args["gamma"], args["epsilon"],
                  args["n_epochs"], args["batch_size"], args["lr"], args["c1"], args["c2"], device, env_name)

    # 加载最佳模型
    model = MyPPO(env.observation_space.shape, env.action_space.n).to(device)
    model.load_state_dict(torch.load(MODEL_PATH, map_location=device))

    # 测试
    env = gym.make(env_name, render_mode="human")
    testing_loop(env, model, args["n_test_episodes"], device)
    env.close()

以上这就是全部的实现! 如果你理解上面的代码,恭喜你,你已经理解PPO算法了。

结论

近端策略优化是最先进的策略强化学习优化算法,它几乎可以在任何环境中使用。 此外,近端策略优化具有相对简单的目标函数和相对较少的需要调整的超参。

ChatGPT依赖PPO在第三步获得了超出预期的效果,大家可以在自己的强化学习任务中予以采用,可能会获得意想不到的效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/391050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【学习笔记】深入理解JVM之垃圾回收机制

【学习笔记】深入理解JVM之垃圾回收机制 更多文章首发地址&#xff1a;地址 参考&#xff1a; 《深入理解JAVA虚拟机》第三版 第三章尚硅谷 第134 - 203 集参考文章&#xff1a;https://blog.csdn.net/qq_48435252/article/details/123697193 1、概念 &#x1f33b; 首先我们…

Mock.js介绍及使用总结

1 什么是Mock.js Mock.js用于生成随机的模拟数据&#xff0c;拦截 Ajax 请求&#xff0c;返回伪造的数据。因此在前端开发阶段就可以通过这个工具进行沉浸式开发&#xff0c;实现数据自产自销&#xff0c;降低和后端的沟通成本&#xff0c;实现真正意义上的前后端开发解耦合。…

2023年轻资产创业怎么做?哪些项目容易创业?

2023年&#xff0c;越来越多的创业者开始考虑轻资产创业&#xff0c;但是&#xff0c;多数人在选择项目的时候&#xff0c;没有一个标准&#xff0c;不知道怎么选择轻资产创业项目&#xff0c;那么&#xff0c;在这里给大家讲讲&#xff0c;轻资产创业怎么做&#xff0c;哪些轻…

反思当下所处的环境,有没有让你停滞不前、随波逐流

环境对人的影响真的很大&#xff0c;小时候的环境、长大后的环境、工作环境、生活环境、好的环境、差的环境......我们都生活在一定的环境中所以既是环境的产物&#xff0c;又是环境的创造者与改造者。我们与环境的关系是相辅相成的我们的生活和工作当中接触到的人或事或物&…

emac接口与phy交互

nuc970的emac接口 nuc970的EMAC以太网接口与PHY芯片之间的数据交换是通过MII&#xff08;Media Independent Interface&#xff09;或RMII&#xff08;Reduced Media Independent Interface&#xff09;接口实现的。 在MII接口中&#xff0c;EMAC和PHY之间通过4对数据线&…

C语言:当scanf语句中有转义字符时,你该这样输入

最近在重新学习一下C语言的有关知识&#xff0c;突然发现现在来看过去所学的知识&#xff0c;自己的感受又有很大的不同&#xff0c;就拿输入语句scanf来说吧&#xff01;看到CSDN问答上有一个关于scanf语句的问题&#xff0c;当时题主代码很简单&#xff0c;可是就是没有输出语…

idea:地址被占用

问题启动idea报&#xff1a;java.net.BindException: Address already in use: bind&#xff0c;具体截图如下&#xff1a;解决步骤1、首先想到的是改idea端口&#xff0c;但按网上方法试下了几个4位数和5位数的端口&#xff0c;没啥作用2、根据idea抛异常的弹出框提示&#xf…

在函数中,用指针接收就可以改变相应的内容吗??

作者&#xff1a;小树苗渴望变成参天大树 作者宣言&#xff1a;认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; 我们在不管指针那篇博客&#xff0c;还是在函数那篇博客中&#xff0c;我都给大家讲解过…

Unity 打包代码到 DLL

Unity 打包代码到 DLL 使用Unity API PlayerBuildInterface.CompilePlayerScripts 将项目中的代码生成为 DLL 程序集 在 Editor 文件夹下新建脚本 CompileDll using UnityEngine; using UnityEditor; using UnityEditor.Build.Player; using System.IO;public class Compile…

【springmvc】获取请求参数

SpringMVC获取请求参数 1、通过ServletAPI获取 将HttpServletRequest作为控制器方法的形参&#xff0c;此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象 RequestMapping("/testParam") public String testParam(HttpServletRequest request)…

Allegro如何输出ODB文件操作指导

Allegro如何输出ODB文件操作指导 在PCB设计完成之后,需要输出生产文件用于生产加工,除了gerber文件可以用生产制造,ODB文件同样也可以用于生产,如下图 用Allegro如何输出ODB文件,具体操作如下 首先确保电脑上已经安装了ODB这个插件,版本不受限制点击File

开学季准备哪些电容笔好?apple pencil一代平替笔推荐

一支简单而又易用的电容笔&#xff0c;配上我们的ipad&#xff0c;将会为我们的工作、学习带来更多的乐趣。在画画时&#xff0c;也不能忽略电容笔的重要作用。关于电容笔的相关知识&#xff0c;本人也略有知不少&#xff0c;以下是我今天要带来的几款具有极高性价比的电容笔&a…

【C#进阶】C# 特性

序号系列文章10【C#基础】C# 正则表达式11【C#基础】C# 预处理器指令12【C#基础】C# 文件与IO文章目录前言1&#xff0c;特性的概念1.1 特性的属性1.2 特性的用途2&#xff0c;特性的定义2.1 特性参数2.2 特性目标3&#xff0c;预定义特性3.1 AttributeUsage3.2 Conditional3.2…

【springmvc】java bean 的区分

bean JavaBean分为两类&#xff1a; 一类称为实体类Bean&#xff1a;专门存储业务数据的&#xff0c;如 Student、User 等一类称为业务处理 Bean&#xff1a;指 Service 或 Dao 对象&#xff0c;专门用于处理业务逻辑和数据访问。 在一个java的springboot中&#xff0c;一般…

day59-day60【代码随想录】二刷数组

文章目录前言一、移动零&#xff08;力扣283&#xff09;【双指针】二、轮转数组&#xff08;力扣189&#xff09;三、寻找数组的中心下标&#xff08;力扣728&#xff09;四、和为 K 的子数组&#xff08;力扣560&#xff09;五、按奇偶排序数组 II&#xff08;力扣922&#x…

【SPSS】多因素方差分析详细操作教程(附案例实战)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【微信小程序-原生开发】实用教程14 - 列表的分页加载,触底加载更多(含无更多数据的提醒和显示,自定义组件)

此页可在动态列表的基础上完善&#xff0c;也可以单独学习 【微信小程序-原生开发】实用教程10 - 动态列表的新增、修改、删除 https://blog.csdn.net/weixin_41192489/article/details/128835069 效果预览 核心技术 列表的分页加载 skip 跳跃到指定下标开始查询limit 限制返…

Vulnhub系列:SickOS1.1

靶机地址&#xff1a;SickOS1.1渗透思路&#xff1a;信息收集对于目标靶机进行扫描&#xff0c;可以利用nmap或arp-scan -l进行查询&#xff0c;查询到靶机后&#xff0c;探测其开放的端口&#xff0c;常见的端口21、22、80、3306、445等等&#xff0c;对于不同的端口进行不同的…

博途PLC开放式以太网通信之TRCV_C指令编程应用

博途PLC开放式以太网通信TSENG_C指令应用,请参看下面的文章链接: 博途PLC 1200/1500PLC开放式以太网通信TSEND_C通信(UDP)_plc的udp通信_RXXW_Dor的博客-CSDN博客开放式TSEND_C通信支持TCP 、UDP等,关于TSEND_C的TCP通信可以参看下面这篇文章:博途PLC 1200/1500PLC开放式…

存储器分类

存储器(Memory)包括&#xff1a; RAM(Random Access Memory)&#xff08;计算机运行内存、CPU的L1/L2 Cache等&#xff09;、 ROM(Read Only Memory)&#xff08;用于BIOS等固化程序/数据的存储&#xff09;和 Flash&#xff08;可用于机械硬盘等&#xff09;。 存储器&#x…