阅读时间约 115 分钟

强化学习从零到RLHF

从基础概念到 RLHF 的学习笔记

Posted by LuckyE on July 3, 2025

强化学习从零到RLHF

https://www.zhihu.com/column/c_1638958028161433600

什么是强化学习

agent (智能体)通过环境交互(通过反复试验)并从环境中学习,并获得奖励作为执行动作的反馈。

强化学习是一种从行动中学习的计算方法。

强化学习是一个用于解决控制任务(也称为决策问题)的框架,通过构建智能体,通过反复试验与环境交互并从环境中学习,并获得奖励(正面或负面)作为独特的反馈。

强化学习流程

image.png

  • 智能体收到来自环境的S0 — 我们收到游戏的第一帧(环境)。
  • 基于此S0,智能体采取行动At:向右移动一格— 智能体将向右移动。
  • 环境走向新的St— 新环境。
  • 环境给了一些奖励R1给智能体 — 我们没有死(正奖励 +1)。

 RL 循环输出一系列状态、操作、奖励和下一个状态

智能体的目标是期望收益(期望累积奖励)的最大化

image.png

马尔科夫性

马尔可夫性的过程表示系统的未来状态仅依赖于其当前状态,与过去的历史状态和动作无关

马尔可夫决策过程(Markov Decision Process,简称MDP)由一个状态集合、一个动作集合、状态转移概率函数和奖励函数组成。

观测空间和状态空间

智能体可以观察到的信息集合和环境的所有可能状态集合

  • 观测空间:观测空间指的是智能体在与环境交互过程中可以获取的信息的集合。观测可以是连续的(例如机器人传感器返回的浮点数值)或离散的(例如表示当前棋盘位置的符号)。观测空间是智能体获取信息、学习和做出决策的基础。
  • 状态空间:状态空间是环境中所有可能状态的集合。一个状态是环境的完整描述,包含了智能体进行决策和预测未来状态所需的所有信息。在具有马尔可夫性质的环境中,未来状态的预测仅依赖于当前状态。

观测空间和状态空间可能是相同的,即智能体可以完全观察到环境的状态,称这个环境为完全可观测.

在部分可观测的环境中,智能体需要利用观测序列或其他方法来估计底层状态,以便做出更好的决策。

行动空间

智能体在特定环境中可以采取的所有可能动作的集合

离散行动空间

智能体可以选择有限数量的动作,例如,在一个棋类游戏中,智能体可以在棋盘上选择一个合法的位置来放置棋子,这些位置构成了一个离散的行动空间。

连续行动空间

智能体可以在某个范围内连续地选择动作。例如,对于一个控制机器人手臂的问题,智能体需要选择关节角度作为动作,这些角度可以在一个连续的范围内任意取值,构成了一个连续的行动空间。

累计奖励

奖励是 RL 的基础,是智能体的唯一反馈

γ折扣率

image.png

主流方法

策略π—如何选择最大化预期累积奖励的策略

策略π是智能体的大脑,告诉在给定状态时采取什么行动的功能

将策略视为我们代理的大脑,该功能将告诉我们在给定状态时要采取的行动

将策略视为我们代理的大脑,该功能将告诉我们在给定状态时要采取的行动

找到最优的策略π,当智能体按照它决策时,能够得到期望回报最大化的策略。

两种策略:

  • 直接地,通过教智能体了解在给定当前状态的情况下采取各操作的映射或概率基于策略的方法
  • 间接地,教智能体了解哪种状态更有价值,然后采取导致更有价值的状态的操作基于值的方法

基于策略的方法

直接学习策略函数,策略函数(policy)是一个从状态(state)到动作(action)的映射,可以定义该状态下一组可能操作的概率分布。

确定策略:处于给定状态的策略将始终返回相同的操作

image.png

随机策略:输出操作的概率分布

image.png

基于价值的方法

学习的不是策略函数,而是将状态映射到处于该状态的期望值的值函数

价值表示在给定状态(State)或状态-动作对(State-Action pair)下,智能体预期能获得的累积奖励。

image.png

两种基于值的函数

状态价值函数V

image.png

状态值函数 $V_{\pi}(s)$表示在目前时刻,在当前状态$s$下遵循策略 $\pi$的情况下,智能体预期能获得的累计奖励$G_t$期望值

动作价值函数Q

image.png

动作值函数 $Q_{\pi}(s,a)$ 表示在目前时刻 $t$,在当前状态 $s$ 下采取动作 $a$ 并随后遵循策略 $\pi$ 的情况下,智能体预期能获得的累积奖励 $G_t$ 期望值。

区别在于:

  • 对于状态值函数,计算状态的价值
  • 对于动作值函数,计算状态-动作对的价值

贝尔曼方程

根据马尔可夫决策过程(MDP)的性质,当前状态的价值应该是在当前状态下采取某个动作获得的奖励与下一个状态基于策略 $\pi$ 获得的预期回报的加权和, 这就是贝尔曼方程的基本思想。

贝尔曼方程计算的价值一般在时序差分(TD)或蒙特卡洛(MC)方法中被视为target,因为它们都是用来更新状态或状态-动作对的估计值的目标。

  • 在 MC 中,target 是从当前状态开始的实际 return,即 $G_t$,它是对状态值函数的无偏估计
  • 在 TD 中,target 是从当前状态开始的下一步 reward 加上下一步状态的估计值,它是对状态值函数的有偏估计

image.png

两种基于值的强化学习算法

我们将智能体在环境中进行交互,直到结束交互的完整过程称之为一个 episode,一个 episode 中的经验称之为 trajectory

蒙特卡洛

蒙特卡洛方法需要一个完整的episode结束,然后基于trajectory去做价值函数的更新

image.png

$G_t$ 的含义是该完整 trajectory 的 $t$ 时刻状态下实际上获得的累积奖励, $V(S_t)$ 是当前状态值函数的值,$\alpha$ 是学习率。

image.png

  • 我们的学习率(lr)是 0.1,我们的折扣率是 1(= 无折扣)
  • 我们的鼠鼠探索环境并采取随机行动
  • 鼠标走了 10 多步,所以 episode 结束了
  • 我们有一个状态、动作、奖励和下一个状态的列表,此时我们尝试更新 $V(S_0)$
  • $G_0 = 1+0+0+0+0+0+1+1+0+0 = 3$
  • 所以新的 $V(S_0) = 0 + 0.1 \times (3-0) = 0.3$

上一步的$V(S_t)$为0

TD时序差分

蒙特卡洛方法需要一个完整episode的所有trajectory才能进行更新,这就使得更新过程必须离线时序差异学习是一种在线的强化学习方法

时序差异学习只等待一次交互(一步),利用贝尔曼方程计算出当前状态值的目标值为$R_{t+1}+\gamma V(S_{t+1})$,当前的值为 $V(S_t)$

image.png

TD学习的核心概念是通过估计当前状态和下一个状态的价值差(即时序差分)来更新状态价值函数或动作价值函数。

image.png

  • 初始化值函数为每个状态返回 0 个值
  • 学习率(lr)为 0.1,折扣率为 1(无折扣)
  • 鼠标开始探索环境并采取随机动作(如向左移动),得到奖励 1
  • 现在更新 $V(S_0) = 0 + 0.1 \times (1 + 1 \times 0 - 0) = 0.1$

Q-Learning

什么是 Q-Learning?

Q-learning 是一种无模型(model-free)的强化学习算法。它通过估计动作-价值函数(action-value function),即 Q 函数(Q-function),来学习智能体在环境中采取各种动作所能获得的期望奖励。Q-learning 算法可以应用于有限 MDP(Markov Decision Process)问题。

  • Q-learning算法的核心是基于TD算法训练其动作值函数,所以可以称之为一种TD算法。
  • Q 函数由 Q 表编码,Q 表中的每个单元格对应于一个状态-动作对值。将此 Q 表视为我们 Q 函数。

image.png

Q-learning 基本流程

Q-learning 算法包含两个阶段:训练和推理

训练

不断 Exploration(探索)和 Exploitation(利用),以生成一个最优的 Q 函数。

image.png

推理

在 Q 表中搜索相应的值,然后采用最优策略(选择最大 Q 值的动作)来进行下一步动作。

image.png

Q-learning 算法具体实现

Q _learning伪代码

image.png

第1步:初始化 Q 表

image.png

第2步:使用 epsilon 贪婪策略选择操作

image.png

image.png

epsilon 贪婪策略是一种权衡 Exploration(探索)和 Exploitation(利用)的策略。

初始值 $\epsilon = 1.0$:

  • 概率为 $1-\epsilon$ 时,进行利用(选择具有最高状态-动作对值的操作)。
  • 概率为 $\epsilon$ 时,进行探索(尝试随机动作)。

在训练开始时,由于 ɛ 非常高,→进行探索

但随着训练的进行,Q表在训练中变得越来越好,逐渐降低ε值,需要的探索越来越少→进行利用

第3步:执行动作,获得奖励 $R_{t+1}$ 和下一个状态 $S_{t+1}$

第4步:更新 $Q(S_t, a_t)$

在 TD 学习中,会在每个交互步骤后更新价值函数,推理的策略是找Q表最大值,所以公式通过查找在下一个状态下最大化当前 Q 函数的动作来计算。

image.png

⬇️

image.png

每个 episode 结束后不断开始新的 episode 进行训练,经过足够多次迭代,Q 表会收敛,智能体可通过 Q 表确定每个状态下的最优动作。

Q-learning 代码

https://gist.github.com/a1024053774/0daa4bc8db2496dde441972b051ff474

https://github.com/a1024053774/RL_Boot/blob/master/FrozenLake.ipynb

Gymnasium Documentation

Q-Learning的不足

Q 表中的每个单元格对应于一个状态-操作对值,推理阶段,智能体只要依据Q表最大值的策略就能得到最优决策

缺陷

  • Q-Learning 是表格方法,状态和动作空间太大无法用数组/表表示,难以扩展。
  • 仅适用于小型状态空间,如冰湖环境(16 状态,4 动作)。

DQN(Deep Q-Network)

基本公式

DQN 利用深度神经网络来近似每个状态下动作的$Q$值

DQN 通过设定损失函数,对比 Q 值预测和 Q-target(使用 TD 算法得到):$\text{Loss} = (Q_{\text{expected}} - Q_{\text{target}})^2$ 目标:让两者接近。用梯度下降更新 Q-Network 权重,更好地近似 Q 值。

image.png

算法两阶段

  • 采样:沿用 Q-Learning 的探索-利用采样流程。前者是采样一次更新一次Q表, DQN 在缓存 $\mathcal{D}$ 中累积足够多轨迹,再以一定概率批量进行梯度更新。
  • 训练:在缓存 $\mathcal{D}$ 中随机选一小批数据,用梯度下降更新学习。

Atari 游戏案例

以 Atari Breakout 游戏举例:

  • 每帧都是观测空间(如 84×84)。
  • 有些观测空间重复但处于不同状态(因惯性)。
  • 用 4 帧堆叠组成状态空间($4 \times 84 \times 84$)。

一般流程:

  • $x$:原始观测(像素)
  • $s$:预处理后状态(如缩放并帧堆叠)
  • $Φ$:状态特征值(如神经网络输出的 768 维特征)

智能体基于 $s$(堆叠帧)选动作, $\Phi$ 作为神经网络学习到的特征辅助决策。

算法伪代码

image.png

  • 采样阶段:用 $\epsilon$-贪婪策略采样并存入缓冲区 $\mathcal{D}$。
  • 训练阶段:随机抽样 $\mathcal{D}$ 里的数据做 MSE loss 优化。

$Q_{\text{expected}} = Q(\Phi_j, a_j; \Theta)$

$Q_network$ 估计状态 $\Phi_j$下动作 $a_j$ 的 Q 值


$Q_{\text{target}} = r_j + \gamma \max_{a’} Q^*(\Phi_{j+1}, a’; \Theta^-)$

$Q_target$ 网络估计下一状态最大动作的 Q 值,折扣加奖励

经验回放(Experience Replay)

初始化容量 $N$ 的回放缓存 $\mathcal{D}$,经验存入内存,训练时对一批体验采样。

经验回放的优点:

  • 去相关性:随机抽取样本减少相关性,数据更稳定。
  • 数据利用率高:历史数据多次利用,加快学习。
  • 结合在线与离线学习:交互和学习并行,适应性强。
  • 平衡探索与利用:重用历史经验,平衡性能。

Q_target 网络

DQN 里 Q_target 网络的参数定期用 Q_network 的参数更新。这样避免两者同步改变带来的不稳定,训练更平滑

Double DQN

DQN 存在 Q 值高估问题:因为最大化操作总是取最大 Q 值,可能有正偏差。

引入双重 DQN 解决

用 Q_network 选动作,用 Q_target 网络评估该动作的 Q 值,解耦动作选择和估值过程,减轻高估。

DQN 的目标:$Q_{\text{target}} = r_j + \gamma \max_{a’} Q^*(\Phi_{j+1}, a’; \Theta^-)$

使用 $Q^*$ 目标网络评估下一状态下所有动作的最大 Q 值,并加入奖励和折扣因子。


Double DQN 的目标:$Q_{\text{target}} = r_j + \gamma Q^*\left(\Phi_{j+1}, \arg\max_{a’} Q(\Phi_{j+1}, a’; \Theta); \Theta\right)$

使用当前网络选择动作,目标网络评估对应 Q 值,缓解过估计问题。

DQN代码—平衡杆为例

https://nbviewer.org/github/a1024053774/RL_Boot/blob/master/DQN_CartPole.ipynb

https://github.com/a1024053774/RL_Boot/blob/master/DQN_CartPole.ipynb

Loss.png

Reward.png

基于策略的RL

什么是策略

基于值的强化学习算法,他们的策略 $\pi$ 都可以归纳为执行最大价值的动作。

image.png

而使用基于策略的方法,可以直接优化策略,无需学习价值函数的中间步骤。

策略 $\pi$(Policy) 是一个状态到动作的映射,定义了智能体在特定状态下该采取的动作, 从状态到动作概率分布

状态 $s$,动作 $a$

image.png

基于策略方法的优点

  1. 采样过程有随机性,相比基于值的贪婪策略更能探索整个状态空间,减轻“感知混叠”问题(某些状态观测上相似,实际属性不同)。
  2. 策略梯度方法对连续动作空间更有效,不用离散化动作空间,直接输出动作概率分布。
  3. 基于值的方法使用最大值采样,值函数微小变化会极大改变采样空间,基于策略方法则更平滑。

当然,基于策略也有缺点,例如训练效率低、方差大。

Policy Gradient目标函数

直觉上的优化对象

Policy Gradient 是最经典的基于策略的强化学习算法,通过计算 policy gradient 来优化参数,得到最佳策略 $\pi$。

其目标是通过调整策略 $\pi$ 控制动作的概率分布,使决策最优:能获得更高累计奖励的策略就是最优策略

定义目标函数如下:

image.png

令 $\tau$ 表示从时刻 $t$ 后所有的 trajectory 轨迹,包括所有后续状态和动作。

  • $\mathbb{E}_{\tau\sim\pi}$:$\tau$ 必须服从$\pi$情况下的数学期望;
  • $R(\tau)$:在轨迹 $\tau$ 下获得的累计奖励。

目标函数:$J(\theta) = \mathbb{E}{\tau \sim \pi_\theta} [R(\tau)]$ 在环境时刻t下,$\tau$服从$\pi$的情况下,智能体能够获得的预期累计奖励

策略梯度中的目标是最大化采样轨迹 $\tau$ 下的期望累计回报 $R(\tau)$,其中策略 $\pi_\theta$ 由参数 $\theta$ 控制。

优化方向

用神经网络参数 $\theta$ 表示 $\pi$ 的概率分布,最大化期望累计奖励—即优化 $\theta$ 使目标函数最大。

image.png

策略梯度的期望形式——$J(\theta) = \sum_{\tau} P(\tau; \theta) R(\tau)$

此表达形式表示对所有可能轨迹 $\tau$ 的加权求和,权重为该轨迹在策略 $\pi_\theta$ 下的概率 $P(\tau; \theta)$,目标是最大化累计回报 $R(\tau)$ 的期望。

期望累计奖励可以表示为各 $\tau$ 概率与 $\tau$ 的累计奖励乘积之和

轨迹概率 $P(\tau; \theta)$ 为状态分布和策略分布的累乘。

image.png

策略梯度定理

梯度上升优化目标函数,对 $J(\theta)$ 求导。但无法枚举所有轨迹的概率,只能采样部分轨迹进行估算

策略梯度算法🌟

策略网络

image.png

image.png

image.png

image.png

  • 如何计算$q_t$ $≈$$Q_π(s_t,a_t)$?

image.png

image.png

蒙特卡洛方法

用蒙特卡洛思想计算梯度,$使用整条 episode 的实际累计奖励更新参数$。

步骤如下:

  1. 用当前策略 $\pi$ 收集一个 episode,得到一条轨迹 $\tau$,倒序计算每一步的累计奖励。
  2. 用每个时刻的状态、动作和累计奖励计算 $\nabla_\theta J(\theta)$。

image.png

  • 黄色部分说明这是基于给定的缓存的$\tau$预估的梯度
  • 绿色部分说明这是$\tau$中t时刻的累计奖励(实际上由于动作已经确定,此处的累计奖励就是Q值)
  • 红色部分说明这是$\tau$中t时刻状态和动作的概率
  • 紫色部分说明这是对数似然概率
  • 最后沿梯度方向更新权重即可

收集多个episode估计梯度

image.png

损失函数

实际代码中的损失函数表达式

$L(\theta) = -\frac{1}{m} \sum_{i=1}^{m} \sum_{t} \log \pi_{\theta}(a_{t}^{(i)} \mid s_{t}^{(i)}) \cdot G_{t}(\tau^{(i)})$

这是策略梯度法中常用的损失函数形式:对所有样本 $i$ 和时间步 $t$ 进行求和,使用当前策略对每个动作的对数概率进行加权,其中权重为从该时间步开始的累计回报 $G_t$。负号表示我们希望最大化期望回报。

其中 $G_t(\tau^{(i)})$ 从后往前计算获得累计回报(return)。

  • $\log\pi_\theta(a_t^{(i)} s_t^{(i)})$:时刻 $t$ 采样动作 $a$ 的对数概率;
  • $G_t$:对应动作后的累计回报。

用梯度下降沿梯度更新会降低损失函数值:

  • 当 $G_t > 0$,会增加 $\log\pi_\theta(a_t s_t)$,即增加该动作概率,且 $G$ 越大,更新“权重”越大;
  • 当 $G_t < 0$,会减少 $\log\pi_\theta(a_t s_t)$,即减少该动作概率。

代码

定义 REINFORCE 算法:输入策略网络、优化器、训练回合数、最大步数、折扣因子、打印频率,进行迭代。

  • 对每个 episode:
    • 初始化状态、对数概率和奖励列表。
    • 采样每一步:策略网络选择动作及对数概率,执行动作,存储相关数据。
    • 计算每步折扣回报。
    • 计算策略损失,对数概率和回报乘积求和取负。
    • 优化更新参数。

https://nbviewer.org/github/a1024053774/RL_Boot/blob/master/REINFORCE_CartPole.ipynb

image.png

https://github.com/a1024053774/RL_Boot/blob/master/REINFORCE_CartPole.ipynb

Actor-Critic,A2C,A3C

Policy Gradient缺陷

  1. 方差大:在Policy Gradient方法中,通常使用蒙特卡洛方法(从实际经验中抽样)来估计期望的累积奖励。这种基于随机抽样的估计具有一定随机性,在不同抽样过程中可能产生不同结果。这种随机性就是”方差”。高方差使得估计的累积奖励在不同抽样过程中差异很大,导致策略更新步骤不稳定,从而影响算法的收敛性和性能。
  2. 离线学习:Policy Gradient不能进行在线学习,因为它使用蒙特卡洛方法估计期望累积奖励G值。这种方法要求等待整个episode完成才能获得所有奖励,然后从尾到头计算每个时间步的累积奖励。因此,基本的Policy Gradient方法只能在每个episode结束后更新策略。

Actor-Critic 经典架构

是否可以用神经网络拟合的方法来替代蒙特卡洛估计的 $G_t$ 值呢?我们先回顾一下 policy gradient 的损失函数:

$L(\Theta) = -\frac{1}{m} \sum_{i=1}^{m} \sum_{t=0}^{T} \log \pi_{\Theta} (a_{t}^{(i)} \mid s_{t}^{(i)}) \, G_{t} (\tau^{(i)})$

我们使用一个神经网络估计 $G_{t} (\tau^{(i)})$ 的值,那么此刻 $G_{t} (\tau^{(i)})$ 的含义就

从“该轨迹时刻的实际回报” ➡️ 变成了“该轨迹时刻采取动作 $a$ 后的期望后续累计回报”这等效于 $Q_{t} (a, s)$。

于是损失函数变为:$L(\Theta) = - \log \pi_{\Theta} (a_{t} \mid s_{t}) \cdot Q_{w} (a_{t}, s_{t})$

只简化到了环境中一个时刻的状态和动作。此处的 $Q$ 指动作价值函数。

在这里需要维护两个训练参数 $\Theta$ 与 $w$ ,以学习两个函数近似:

  • Actor:由参数 $\Theta$ 控制的策略函数。
  • Critic:由参数 $w$ 控制,用来评估动作的 $Q$ 值,协助策略更新。

由于只需要预估动作的价值函数而非实际的累计回报,可以使用 TD 方法替代蒙特卡洛方法

这样可以在每一次迭代后进行梯度更新,使 Actor-Critic 架构$具备在线更新和更高稳定性$。

例子:

损失函数—从 Q 值到 V 值的拟合策略:优化 Policy Gradient 损失函数

根据如下的策略梯度损失函数:

$L(\Theta) = -\log \pi_{\Theta}(a_t \mid s_t) \, Q_w(a_t, s_t)$

我们的优化目标是求解 $Q_w(a_t, s_t)$ 并对其进行优化。

由于当前环境中的策略为概率分布非确定性策略 $μ$,不能直接将状态 $s$ 映射为唯一动作 $a$,因此直接优化 Q 值较为复杂。

为简化操作,我们常转而拟合状态值函数 $V$

根据 Q 与 V 的定义关系,有:$Q(a_t, s_t) = r_{t+1} + \gamma V(s_{t+1})$

于是,我们可以改写 Actor 的损失函数为:$L(\Theta) = -\log \pi_{\Theta}(a_t \mid s_t) \, (r_{t+1} + \gamma V_w(s_{t+1}))$

接着我们构造 Critic 的 TD 损失函数:$L_w = \left( r_{t+1} + \gamma V_w(s_{t+1}) - V_w(s_t) \right)^2$

最终,将两个损失结合用于更新 Actor-Critic 架构中的策略与价值函数。

Advantage Actor Critic(A2C)—使用优势函数稳定 Actor-Critic 学习策略

可以使用优势函数作为 Critic 来进一步稳定学习过程。实际上,A2C(Advantage Actor-Critic) 是 Actor-Critic 架构中更常用的形式。

优势函数衡量某状态下选择某一动作相较于其他可能动作的表现,即:

$A(s_t, a_t) = Q(a_t, s_t) - V(s_t)$

这表示:该状态下执行该动作所获得的奖励,与该状态期望奖励的差距


Actor 的损失函数为:

$L(\Theta) = -\log \pi_{\Theta}(a_t \mid s_t) \cdot A(s_t, a_t)$

  • 若 $A(s_t, a_t) > 0$:梯度推动策略向该动作靠拢
  • 若 $A(s_t, a_t) < 0$:梯度推动策略远离该动作

如何简化 Advantage 的计算?

定义关系为:

$Q(a_t, s_t) = r_{t+1} + \gamma V(s_{t+1})$

代入$A(s_t, a_t) = Q(a_t, s_t) - V(s_t)$后有:

$A(s_t, a_t) = r_{t+1} + \gamma V(s_{t+1}) - V(s_t)$

即:只要拟合 $V$ 函数即可计算 Advantage


Critic 的 TD 误差损失函数为:

$L(w) = \left( r_{t+1} + \gamma V_w(s_{t+1}) - V_w(s_t) \right)^2$

最终,可将 Actor 与 Critic 两个损失联合训练,从而优化策略与价值函数。

A3C—Asynchronous advantage actor-critic

神经网络训练时,需要的数据是独立同分布的

  • 为了打破数据之间的相关性,DQN等方法都采用了经验回放的技巧。
  • 然而经验回放需要大量的内存,打破数据的相关性,经验回放并非是唯一的方法。
  • 另外一种是异步的方法,所谓异步的方法是指数据并非同时产生,A3C的方法便是其中表现非常优异的异步强化学习算法。

A3C模型如下图所示,每个Worker直接从Global Network中拿参数,自己与环境互动输出行为。

利用每个Worker的梯度,对Global Network的参数进行更新。每一个Worker都是一个A2C

image.png

代码

https://nbviewer.org/github/a1024053774/RL_Boot/blob/master/Actor_Critic%E6%95%99%E7%A8%8B.ipynb

https://github.com/a1024053774/RL_Boot/blob/master/A3C.py

image.png

评估结果

– Starting Evaluation — Evaluation Episode 1, Total Reward: 10.0 Evaluation Episode 2, Total Reward: 9.0 Evaluation Episode 3, Total Reward: 10.0 Evaluation Episode 4, Total Reward: 9.0 Evaluation Episode 5, Total Reward: 9.0 — Evaluation Finished —

连续动作空间

$之前的都是离散的动作空间$,在离散动作空间中,智能体只能选择有限数量的动作。

例如,如果一个智能体正在玩一个游戏,它可能能向上、向下、向左或向右移动,或者执行跳跃动作。每一个可能的动作都可以被独立地选择。

许多问题需要更细粒度的控制,比如控制一个机器人的关节角度,或者设定汽车的油门和刹车程度。

在这些情况下,我们可能会有一个连续的动作空间,也就是说,智能体可以选择无数种可能的动作。

在连续动作空间中,动作通常由一个或多个实数值表示,每一个实数值都代表了某种程度的动作。

例如,在控制机器人的关节角度时,每一个角度都可以是任意的实数值。

这种情况下的动作选择和优化比离散动作空间要复杂得多,因此需要使用更复杂的算法,如 DPG 或 DDPG,来解决。

DPG(Deterministic Policy Gradient)

AC策略的两个损失函数

🎭 Actor Loss$L(\Theta) = -\log \pi_{\Theta}(a_t s_t)Q_w(a_t, s_t)$

🎯 Critic Loss$L(w) = (r_{t+1} + \gamma V_w(s_{t+1}) - V_w(s_t))^2$

Actor Loss 的目标是提升当前动作的 Q 值,但在连续动作空间中,动作是数值型而非概率分布

DPG 算法通过直接最大化预期奖励,让 actor 网络学会在每个状态下选择最优动作以获取最大累计奖励。


DPG损失函数⚙️ Deterministic Policy Gradient (DPG) 算法简介

DPG 是用于连续动作空间的强化学习算法。在此类空间中,策略为每个状态返回一个具体动作,而非一个概率分布。

  • DPG 使用确定性策略:对每个状态 $s$,策略 $\pi$ 输出一个动作 $a = \pi(s)$。
  • 不再依赖采样近似,可以直接求策略梯度。
  • Actor 网络目标:最大化动作价值函数 $Q$。

🧠 Actor Loss 在 DPG 中

$L(\Theta) = -Q_w(s_t, \mu_{\Theta}(s_t))$

其中:

  • $\mu_{\Theta}(s_t)$ 表示在状态 $s_t$ 下,Actor 网络输出的连续动作值;
  • $Q_w$ 是 Critic 网络,用于估计该状态与动作的价值;
  • 注意:在计算 Actor Loss 时,$Q_w$ 不对 Actor 网络反向传播梯度。

📉 Critic 网络优化

为了拟合 $Q_w$,我们通过 TD 方法优化 Critic Loss:

$L(w) = (r_{t+1} + \gamma Q_w(s_{t+1}, \mu_{\Theta}(s_{t+1})) - Q_w(s_t, \mu_{\Theta}(s_t)))^2$

这样就能更新 Critic 网络的参数以获得更精确的状态-动作价值估计。

DPG代码

代码主要分为环境创建、网络结构、采样、学习四块:

以下是伪代码:

  1. 初始化:导入必要的库,定义超参数,创建环境和网络结构,定义优化器。
  2. 开始训练:对于每个episode,执行以下步骤:
    • 初始化环境:重置环境,得到初始状态。
    • 执行动作:对于每个时间步,执行以下步骤:
    • 采样
      • 选择动作:使用策略网络选择动作,并添加一些探索噪声。
      • 执行动作:在环境中执行动作,得到下一个状态、奖励和是否终止的标志。
      • 存储转移:将当前的状态、动作、奖励、下一个状态和是否终止的标志存储在转移列表中。
      • 更新状态:将下一个状态设置为当前状态。
      • 检查是否终止:如果回合结束,跳出循环。
    • 学习:如果转移列表的长度大于批量大小,执行以下步骤:
      • 取数据:从转移列表中随机采样一个批量。
      • 计算目标Q值:使用下一个状态的最大Q值乘以折扣因子,再加上当前的奖励。如果是终止状态,就只用当前的奖励。
      • 计算当前Q值:使用当前的状态和动作。
      • 计算值函数网络的损失:使用critic loss。
      • 优化值函数网络的参数
      • 计算策略网络的损失:使用actor loss,即负的Q值作为目标。
      • 优化策略网络的参数

3.结束训练:当达到最大回合数时,结束训练。

https://gist.github.com/a1024053774/40d45d7200b880924add79602272c85c

critic中Q与V的选择

在 Actor-Critic(AC)结构中,Critic 的目标是估计值函数,而 Actor 则使用这些估计来更新策略。

值函数可以是状态值函数 V(s),也可以是动作值函数 Q(s, a)。

  • 使用 V(s):在某些 AC 方法中,如 Advantage Actor-Critic (A2C) 或其异步版本 Asynchronous Advantage Actor-Critic (A3C),Critic 估计的是状态值函数 V(s)。这种方法需要计算每个动作的优势函数,这是实际回报与状态值函数 V(s) 的差值。优势函数用来指示选择特定动作比遵循当前策略的平均表现更好或更差。因此,优势函数为正时,增加这个动作的概率;为负时,减少这个动作的概率。
  • 使用 Q(s, a):在另一些 AC 方法中,如 DPG、DDPG中,Critic 估计的是动作值函数 Q(s, a)。这种方法主要用于连续动作空间。与离散动作空间不同,连续动作空间的大小可能是无限的,因此不能对每个可能的动作求和或取平均。在这种情况下,使用 Q(s, a) 更为合适,因为我们可以在给定的状态动作对 (s, a) 上直接估计 Q 值

DDPG (Deep Deterministic Policy Gradient)

🤖 DDPG(Deep Deterministic Policy Gradient)算法的损失函数解析

DPG 在高维或复杂观测空间中存在较大困难:观察到的状态可能很复杂,不易直接学习有效策略。

DDPG 结合了 DPG 与 DQN 的优势,引入深度神经网络来逼近策略与价值函数,从而提升了在连续动作空间中的表现。

1. 网络架构

  • DPG: 通常使用较简单的函数逼近器
  • DDPG: 使用深度神经网络来处理高维状态空间

2. 目标网络(Target Networks)

  • DPG: 没有目标网络概念
  • DDPG: 引入了Actor和Critic的目标网络,使用软更新:

    θ' ← τθ + (1-τ)θ' # τ通常取0.001

3. 经验回放(Experience Replay)

  • DPG: 通常使用在线学习
  • DDPG: 借鉴DQN的经验回放机制,从replay buffer中随机采样训练

4. 探索策略

  • DPG: 探索机制相对简单
  • DDPG: 在确定性动作上添加噪声(如Ornstein-Uhlenbeck噪声)进行探索

5. 稳定性改进

  • DDPG: 通过目标网络和经验回放显著提升了训练稳定性

🎯 Actor Loss(策略网络损失)

$L(\Theta) = -Q_w(s_t, \mu_\Theta(s_t))$

解释:

  • $\mu_\Theta(s_t)$:表示当前策略网络在状态 $s_t$ 下输出的具体动作;
  • $Q_w(s_t, a_t)$:Critic 网络估计该状态-动作对的价值;
  • 损失函数通过最大化动作价值引导策略网络优化。

📉 Critic Loss(价值网络损失)

$L(w) = \left( r_{t+1} + \gamma Q_w(s_{t+1}, \mu_\Theta(s_{t+1})) - Q_w(s_t, \mu_\Theta(s_t)) \right)^2$

解释:

  • $r_{t+1}$:当前动作获得的奖励;
  • $\gamma$:折扣因子,权衡未来奖励的重要性;
  • 目标是通过时间差分(TD)方法拟合动作价值函数 $Q_w$,从而提升对状态-动作对的估计准确性。

DDPG代码

DDPG代码与DPG代码几乎一致,只是添加了经验回放和目标网络。

伪代码:

代码主要分为环境创建、网络结构、采样、学习四块:

  1. 初始化:导入必要的库,解析命令行参数,设置随机种子,定义设备,创建环境和网络,定义优化器,初始化ReplayBuffer
  2. 开始训练:对于每个episode,执行以下步骤:
    • 初始化环境:重置环境,得到初始状态。
    • 执行动作:对于每个时间步,执行以下步骤:
    • 采样
      • 选择动作:如果全局步骤小于学习开始步骤,随机选择动作;否则,使用策略网络选择动作,并添加一些探索噪声。
      • 执行动作:在环境中执行动作,得到下一个状态、奖励、是否终止的标志和信息。
      • 保存数据到ReplayBuffer:将当前的状态、动作、奖励、下一个状态和是否终止的标志存储在ReplayBuffer中。
      • 更新状态:将下一个状态设置为当前状态。
    • 学习:如果全局步骤大于学习开始步骤,执行以下步骤:
      • 抽样:从ReplayBuffer中随机采样一个批量。
      • 计算目标Q值:使用下一个状态的最大Q值乘以折扣因子,再加上当前的奖励。如果是终止状态,就只用当前的奖励。使用critic中的Q_target网络
      • 计算当前Q值:使用当前的状态和动作,使用critic中的Q网络
      • 计算值函数网络的损失:使用critic loss。
      • 优化值函数网络的参数
      • 计算策略网络的损失:使用actor loss,即负的Q值作为目标。
      • 优化策略网络的参数
      • 更新目标网络:每隔一定步数,使用软更新策略用Q网络的更新目标网络Q_target中的参数。

3.结束训练:当达到最大步骤数时,结束训练。

https://gist.github.com/a1024053774/676f26180027922505588c2f00aa7078

PPO

A2C采取的是单步更新的方法,即每采样一步进行一次梯度的更新。

单步更新中,我们只考虑当前步骤的奖励下一步的状态值

缺点是忽略了未来的奖励信息,可能导致策略更新不够准确

在多步更新中,我们考虑多步的奖励未来的状态值。这种方法的优点是能够更准确地估计未来的奖励,从而得到更准确的策略更新。

🧠 广义优势估计(GAE, Generalized Advantage Estimation)

GAE 是强化学习中用于估计优势函数的一种方法,结合了偏差小的 Monte Carlo 方法和方差小的时间差分(TD)方法。

它通过引入衰减因子 $\lambda$ 来平衡偏差和方差。

GAE将TD误差组合成一个加权和,权重由一个衰减因子λ决定。

当λ=0时,GAE就退化为普通的优势函数估计;

当λ=1时,GAE就变成了一种名为”蒙特卡洛”的方法。


🎯 单步优势函数定义:

$A(s_t, a_t) = Q(a_t, s_t) - V(s_t) = r_{t+1} + \gamma V(s_{t+1}) - V(s_t)$

该公式基于单步 TD 误差,表示当前动作与状态的即时优势。


📐 GAE 多步估计公式:

$A_t^{GAE} = \sum_{l=0}^{k-1} (\lambda \gamma)^l \delta_{t+l}$

其中:

  • $\lambda$ 是 GAE 的衰减系数(通常在 0~1 之间);
  • $\gamma$ 是奖励折扣因子;
  • $\delta_{t+l}$ 是第 $t+l$ 步的 TD 误差→$\delta_{t+l} = r_{t+l+1} + \gamma V(s_{t+l+1}) - V(s_{t+l})$
    • 该项衡量某一步对状态价值的误差,用于更新 Critic 网络或估计优势函数。

通过调整 $\lambda$,GAE 可以在高方差和高偏差之间找到最优平衡,提高策略梯度估计的稳定性和学习效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
with torch.no_grad():
    # 获取下一个观察的值函数输出
    next_value = agent.get_value(next_obs).reshape(1, -1)

    # 初始化优势张量
    advantages = torch.zeros_like(rewards).to(device)

    # 初始化上一步的优势值
    lastgaelam = 0

    # 反向遍历每个步骤
    for t in reversed(range(args.num_steps)):
        # 如果是最后一个步骤
        if t == args.num_steps - 1:
            # 设置下一个非终止状态的标志
            nextnonterminal = 1.0 - next_done
            # 设置下一个值函数的输出
            nextvalues = next_value
        else:
            # 设置下一个非终止状态的标志
            nextnonterminal = 1.0 - dones[t + 1]
            # 设置下一个值函数的输出
            nextvalues = values[t + 1]

        # 计算TD误差(Advantage)
        delta = rewards[t] + args.gamma * nextvalues * nextnonterminal - values[t]

        # 计算GAE(Generalized Advantage Estimation)
        advantages[t] = lastgaelam = delta + args.gamma * args.gae_lambda * nextnonterminal * lastgaelam

PPO

损失函数

PPO的损失函数是基于策略梯度方法的改进,

策略梯度方法的基本思想是,如果一个动作导致了好的结果(即有高的优势函数值),那么我们应该增加在相同情况下采取这个动作的概率。

在标准的A2C方法中:

$L(\Theta) = -\log \pi_{\Theta}(a_t \mid s_t) \hat{A}_t$

该损失函数增加那些导致好结果的动作的概率, $\hat{A}_t$是优势函数,表示动作$a_t$相比于平均情况的趋势

问题:→它可能会导致策略的大幅度更新,可能会从一个好的策略跳到一个非常糟糕的策略


PPO(Proximal Policy Optimization)引入了剪切损失函数

限制新策略和旧策略之间的差异,防止策略更新过大,导致训练不稳定,同时还能保证策略的改进。这使得PPO在许多任务中都能取得良好的性能。

$L_{\text{CLIP}}(\Theta) = \min \left( r_t(\Theta) \hat{A}_t,\ \text{clip}(r_t(\Theta),\ 1 - \epsilon,\ 1 + \epsilon)\ \hat{A}_t \right)$

其中:

  • $r_t(\Theta) = \frac{\pi_{\Theta}(a_t \mid s_t)}{\pi_{\text{old}}(a_t \mid s_t)}$ 为当前策略和旧策略的比率;
  • $\hat{A}_t$ 是优势函数;
  • $\epsilon$ 是剪切阈值,超参数(一般在 0.1 到 0.2 之间);
  • clip 是裁剪函数,将$r_t(\Theta)$限制在$[1 - \epsilon ,1 + \epsilon ]$范围内更新幅度,避免剧烈变动导致性能退化。

🔥策略熵

熵衡量随机变量的不确定性。在强化学习中,策略熵用于鼓励策略在训练初期进行更广泛探索


熵公式定义:

$\mathcal{H}(\pi(\cdot \mid s_t)) = - \sum_{a_t} \pi(a_t \mid s_t) \log(\pi(a_t \mid s_t))$

$=$期望形式:$\mathbb{E}_{a_t \sim \pi}[-\log(\pi(a_t \mid s_t))$


  • 在 PPO 中,加入熵项可提高策略探索性;
  • 熵系数(entropy coefficient)用于权衡熵奖励与主要目标之间的影响;
  • 较高的熵系数 → 更多探索;
  • 较低的熵系数 → 更快收敛但可能陷入局部最优。

在PPO中,为了提高算法的探索能力,我们一般在actor的loss中增加一项策略熵,并乘以一个系数entropy_coef,使得在优化actor_loss的同时,让策略的熵尽可能大。一般我们设置entropy_coef=0.01

设置这个是因为如果策略总是倾向于选择某个确定的动作,那么它可能会错过一些其他动作带来的更好的奖励。通过增加熵的项,可以使策略在选择动作时保持一定的随机性,从而有更多的机会去探索那些可能带来更好奖励的动作。

KL 散度阈值在 PPO 中的作用


为什么使用 KL 散度?

KL 散度用于衡量新旧策略之间的相似度

在 PPO 中,它被用作约束项,以防止策略更新过程中过度偏离旧策略,从而导致性能骤降。


KL 散度约束公式: $KL[\pi_{\theta_{\text{old}}}(\cdot \mid s_t),\ \pi_{\theta}(\cdot \mid s_t)] \leq \delta$

  • $\pi_{\theta_{\text{old}}}$:表示旧策略;
  • $\pi_{\theta}$:表示当前更新后的新策略;
  • $\pi_{\theta}(\cdot \mid s_t)$表示在状态$s_t$下,由参数θ确定的策略产生的动作的概率分布。
  • $\delta$:KL 散度阈值,手动设定;
  • 如果实际 KL 散度超过 $\delta$,优化将提前停止,避免策略剧烈变化。
    • KL 散度过大 → 策略偏离过猛,有过拟合风险;
    • KL 散度过小 → 学习进展缓慢;
    • 动态调整 $\delta$可提高 PPO 的稳定性。

PPO代码

PPO伪代码

  1. 初始化:导入必要的库,定义超参数,创建环境和网络结构,定义优化器。
  2. 环境设置:创建多个并行的游戏环境,每个环境都会进行相同的训练过程,但是会有不同的随机种子。
  3. 网络结构定义:定义一个代理网络,包括一个用于特征提取的卷积神经网络,一个用于输出动作的actor网络,和一个用于输出状态价值的critic网络。
  4. 开始训练:对于每个更新周期,执行以下步骤:
    1. 采样阶段:对于每个时间步,执行以下步骤:
      1. 使用当前的策略网络选择动作。
      2. 在环境中执行动作,得到下一个状态、奖励和是否终止的标志。
      3. 将当前的状态、动作、奖励、下一个状态和是否终止的标志存储在缓冲区中。
      4. 更新状态:将下一个状态设置为当前状态。
      5. 如果回合结束,跳出循环。
    2. 计算优势函数和回报:使用GAE算法和折扣回报算法计算每个时间步的优势函数和回报。
    3. 学习阶段:对于每个更新周期,执行以下步骤:
      1. 对缓冲区中的数据进行随机采样。
      2. 计算新的动作概率和旧的动作概率之间的比率。
      3. 计算策略损失:使用clip函数限制比率的范围,然后计算优势函数和比率的乘积,最后取最大值作为策略损失。
      4. 计算值函数损失:如果使用clip值函数损失,那么计算clip后的值函数和回报之间的均方误差,否则直接计算值函数和回报之间的均方误差。
      5. 计算总损失:策略损失减去熵正则项,再加上值函数损失。
      6. 使用优化器更新网络参数。
      7. 计算新旧策略网络的概率分布的KL散度,当大于特定阈值时,跳出该更新周期。

5.结束训练:当达到最大更新周期数时,结束训练。

https://github.com/a1024053774/RL_Boot/blob/master/PPO_CartPole.py

https://nbviewer.org/github/a1024053774/RL_Boot/blob/master/PPO_CartPole_Tutorial.ipynb

image.png

RLHF中的PPO

1、SFT,也就是 Supervised Fine-Tuning,是 GPT-3 以及 ChatGPT 训练过程中的一个重要步骤。在这个步骤中,采用有监督的方式对预训练的语言模型进行微调。这又被称为行为克隆(Behavioral Cloning,简称BC),即直接使用专家的行为数据(例如,专家在特定情况下采取的动作)来训练模型。在行为克隆中,模型的目标是尽可能地复制专家的行为,而不是尝试优化某种奖励函数,所以它可能无法处理那些专家数据中没有覆盖到的情况,因为它完全依赖于专家的行为数据。

2、RM,奖励模型(Reward Model)是ChatGPT训练过程的第二阶段,它的目标是训练一个模型来适应人类的偏好。在这个阶段,首先从提示库中进行采样,并使用大型语言模型生成多个响应。然后,人工对这些响应进行排名,根据这些排名训练一个奖励模型。奖励模型的目标是学习人类对于不同响应的偏好,并将这些偏好编码到模型中。这样,奖励模型可以用来为模型生成的新响应打分,从而在后续的训练中引导模型生成更符合人类偏好的内容。这种方式不仅能帮助模型处理训练数据中未覆盖的情况,也能减少模型生成不确定或模棱两可的回答,从而打破行为克隆的影响。

3、PPO,PPO(Proximal Policy Optimization)是一种强化学习算法,它通过引入奖励信号来调整模型的行为,使模型生成的内容更符合人类的偏好。具体来说,PPO通过最大化预期奖励来调整模型的策略,使模型在选择行为时更倾向于选择可以得到更高奖励的行为。在这个阶段中,我们首先使用在第一阶段训练的有监督微调模型和第二阶段训练的奖励模型来生成一个初始的策略。然后,我们使用PPO算法来调整这个策略,使模型在生成内容时更考虑人类的偏好。通过这个阶段的训练,模型不仅可以理解人类的语言,还可以理解人类的偏好,并生成更符合人类偏好的内容。

PPO整体架构

  1. [Rollout and Evaluation](https://zhida.zhihu.com/search?content_id=229410969&content_type=Article&match_order=1&q=Rollout+and+Evaluation&zhida_source=entity):在这个阶段,我们从prompt库里抽样,使用语言模型生成response,然后使用奖励模型(Reward Model, RM)给出奖励得分。这个得分反映了生成的response的质量,比如它是否符合人类的偏好,是否符合任务的要求等。
  2. Make experience:在这个阶段,我们收集了一系列的“经验”,即模型的行为和对应的奖励。这些经验包括了模型生成的response以及对应的奖励得分。这些经验将被用于下一步的优化过程。
  3. Optimization:在这个阶段,我们使用收集到的经验来更新模型的参数。具体来说,我们使用PPO算法来调整模型的参数,使得模型生成的response的奖励得分能够增加。PPO算法的一个关键特性是它尝试保持模型的行为不会发生太大的改变,这有助于保证模型的稳定性。

image.png

十个步骤依次是:

  1. Rollout:根据策略(LM)生成轨迹(文本)。
  2. Evaluate:对生成的轨迹进行评估(RM)。
  3. Old Policy Sampling:从旧的策略(initial actor)中采样概率等信息。
  4. KL Penalty:计算当前策略和原始LM之间的KL散度,用作对策略改变过快的惩罚项。
  5. Generalized Advantage Estimation (GAE):GAE是一种优势函数的估计方法,它结合了所有可能的n-step 进行advantage估计。
  6. New Policy Sampling:从新的策略中采样概率等信息。
  7. Critic Loss:Critic的目标是估计状态的价值函数,Critic loss就是价值函数预测值和实际回报之间的差距。
  8. Actor Loss:Actor的目标是优化策略,Actor loss就是基于优势函数的策略梯度。
  9. Entropy Loss:为了增加探索性,通常会添加一个基于策略熵的正则项,它鼓励策略保持多样性。
  10. Policykl:这是对策略迭代过程的一个度量,它度量新策略和旧策略之间的差距。

步骤1:Rollout

在强化学习中,Rollout是指在给定的策略下模拟环境的过程

在PPO中,Rollout的过程对应于根据当前的语言模型(策略)生成文本(轨迹)。

这个过程依赖于在prompt库中抽取的一个batch的数据:Batch Prompt和当前的语言模型LM。

语言模型接收一个prompt作为输入,并生成一个Response。

输入输出

输入:Batch Prompt、LM

输出:Prompt+Response

huggingface的官方强化学习库TRL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 创建PPOTrainer实例。需要提供的参数包括:配置信息,模型,引用模型,分词器,数据集,数据整理器,优化器。
ppo_trainer = PPOTrainer(
    config,  # 配置信息
    model,  # 要训练的模型
    ref_model=None,  # 引用模型,通常是微调之前的预训练模型
    tokenizer=tokenizer,  # 用于文本编码和解码的分词器
    dataset=dataset,  # 用于训练的数据集
    data_collator=collator,  # 用于批量处理数据的数据整理器
    optimizer=optimizer,  # 用于模型优化的优化器
)

# 定义生成模型响应时的参数
generation_kwargs = {
    # "min_length": -1,  # 最小生成长度
    "top_k": 0.0,  # 在生成时,仅考虑前k个最可能的词
    "top_p": 1.0,  # 在生成时,仅考虑概率累计到某个阈值的词
    "do_sample": True,  # 是否进行抽样
    "pad_token_id": tokenizer.pad_token_id,  # 填充词的词ID
    "eos_token_id": 100_000,  # 句子结束词的词ID
}

# 使用定义好的参数生成模型的响应
response_tensors = ppo_trainer.generate(
        question_tensors,  # 输入的问题
        return_prompt=False,  # 是否返回提示
        length_sampler=output_length_sampler,  # 输出长度的抽样器
        **generation_kwargs,  # 生成参数
    )

# 将生成的响应从张量转换为文本,并存储在batch字典中
batch["response"] = tokenizer.batch_decode(response_tensors, skip_special_tokens=True)

# 这里将问题和相应的回答拼接起来,然后准备对拼接后的文本进行情感打分
texts = [q + r for q, r in zip(batch["query"], batch["response"])]

步骤2:Evaluate

Evaluate是在强化学习中对生成的轨迹(在我们的例子中就是文本)进行评估的步骤。

在PPO中,这个评估过程由一个RM模型来完成,来为每一对Prompt+Response产生一个标量奖励值,这个值表示生成的轨迹的好坏,优化过程会试图最大化这个值。

输入输出

输入:Prompt+Response、RM

输出:Reward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建一个情感分析pipeline,也就是RM模型。需要提供的参数包括:模型类型,模型名称,设备映射,模型参数,分词器。
sentiment_pipe = pipeline(
    "sentiment-analysis",  # 模型类型,这里是情感分析
    model=reward_model_name,  # 模型名称
    device_map={"": current_device},  # 设备映射,这里将模型加载到当前设备
    model_kwargs={"load_in_8bit": True},  # 模型参数,这里是加载8位模型
    tokenizer=tokenizer,  # 用于文本编码和解码的分词器
)

# 使用情感分析对文本进行打分
pipe_outputs = sentiment_pipe(texts, **sent_kwargs)  # 'texts'是之前拼接好的问题和响应文本,'sent_kwargs'是情感分析的参数

# 计算奖励值。这里的奖励值是情感分析的得分减去一个基线值。
rewards = [torch.tensor(output[0]["score"] - script_args.reward_baseline) for output in pipe_outputs]  

步骤3:Old Policy Sampling

Old Policy Sampling 这个步骤是make experience的过程,计算并存储旧策略的概率、价值等值,来为后面更新的过程服务。

  • Old Logprobs 从“旧的”策略,即在这个batch数据中初始的LM(initial actor)中计算每个token在旧的策略下的概率Old Logprobs。 actor:从策略网络拷贝来的模拟整个智能体在环境中行动的网络 在优化策略的时候,需要比较新旧策略下动作的概率,以此来更新—所以需要存储旧策略的动作概率—> 为了算ratio的值,用这个值更新策略梯度,能限制更新率,
  • Old Values 旧策略中每个时间步(每个token的预测结果)的价值,这个值由critic网络进行预测,critic网络就是actor上加几个线性层能够给每个token预测一个值。需要这个值的原因是advantage的计算依赖于Old Values。 critic:专门用来预测actor轨迹每一步价值的网络
  • Ref Logprobs 最原始的LM对于每个时间步的概率预测,一般就是固定不变的gpt3,计算这个值的目的是限制actor的更新,防止其偏离原始gpt3太远,他的实现在下一个步骤中。

输入输出

输入:Ref_model、Actor、Critic、Prompt+Response

输出:Ref Logprobs、Old Logprobs、Old Values

1
2
all_logprobs, _, values, masks = self.batched_forward_pass(self.model, queries, responses, model_inputs)
ref_logprobs, _, _, _ = self.batched_forward_pass(self.ref_model, queries, responses, model_inputs)

步骤4:KL Penalty

KL Penalty是在模型优化过程中添加的一个惩罚项,保证强化学习后的模型(新策略actor)不会过于偏离原始预训练模型(ref model)

  • 使用微调过程中的模型(新策略actor)和预训练模型(ref model)来计算序列中每个词的对数概率。
  • 计算两个模型输出之间的 Kullback-Leibler (KL) 散度,这是一种衡量两个概率分布差异的方法。该KL散度被用作一个额外的奖励信号,并作为优化过程中的惩罚项,用于确保微调后的模型生成的响应不会偏离太远于预训练模型。这样可以保证模型在微调的过程中不会丢失预训练模型学习到的有用的知识和模式。

输入输出

输入:Ref Logprobs、Old Logprobs、Reward

输出:Token Reward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 初始化两个列表来分别存储奖励和非得分奖励
rewards, non_score_rewards = [], []

# 使用 zip 函数并行遍历输入的得分、对数概率、参考模型的对数概率以及mask
for score, logprob, ref_logprob, mask in zip(scores, logprobs, ref_logprobs, masks):
    # 计算 KL 散度,即模型的对数概率与参考模型的对数概率之间的差值
    kl = logprob - ref_logprob

    # 计算非得分奖励,即 KL 散度乘以 KL 控制器值的负值
    non_score_reward = -self.kl_ctl.value * kl
    non_score_rewards.append(non_score_reward)

    # 复制非得分奖励为新的奖励
    reward = non_score_reward.clone()

    # 找到mask中最后一个非零元素的索引,这表示输入序列的实际长度
    last_non_masked_index = mask.nonzero()[-1]

    # 对于最后一个非mask部分的token,其奖励是偏好模型的得分加上 KL 散度
    reward[last_non_masked_index] += score

    # 将计算的奖励添加到奖励列表中
    rewards.append(reward)

# 返回包含所有奖励的张量以及包含所有非得分奖励的张量
return torch.stack(rewards), torch.stack(non_score_rewards)

步骤5:Generalized Advantage Estimation (GAE)

使用advantage来衡量每个时间步动作的含金量,本质含义是当前采样到的动作(生成的文本)的价值比平均的数学期望价值高的部分。

PPO中一般使用GAE来进行advantage的计算。

GAE是一种多步优势估计方法。

它通过引入一个权衡参数λ,在单步TD误差和多步TD误差之间进行权衡,从而减小估计的方差,提高学习的稳定性。


GAE 的目标是估计策略在当前状态下未来能获得的累积优势,从而优化策略更新的方向。

💡 TD 误差定义$\delta_t = r_{t+1} + \gamma V(s_{t+1}) - V(s_t)$

  • $r_{t+1}$ :第 $t+1$ 步的奖励;
  • $V(s_{t+1})$:下一状态的价值;
  • $V(s_t$) :当前状态的价值;
  • 用于衡量一步的状态价值估计误差。

🔁 GAE 累积形式$A_t^{GAE} = \sum_{l=0}^{k-1} (\gamma \lambda)^l \delta_{t+l}$

  • $\gamma$ :折扣因子,控制未来奖励影响;
  • $\lambda$ :平衡因子,用于控制偏差与方差;
  • $k$ :回溯步数;
  • $A_t^{GAE}$ :优势函数估计,用于策略梯度更新。

🎲 特殊情况:Monte Carlo 方法

当 $\lambda = 1$ 时,GAE 退化为蒙特卡洛估计,即考虑全部未来回报而不依赖 bootstrapping。这种方式偏差小但方差大


输入输出

输入:Token Reward、Old Values

输出:Advantages、Returns

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 从后往前遍历整个生成的序列
for t in reversed(range(gen_len)):
    # 计算下一个状态的价值,如果当前状态已经是最后一个状态,则下一个状态的价值为0
    nextvalues = values[:, t + 1] if t < gen_len - 1 else 0.0

    # 计算 δ,它是奖励加上衰减后的下一个状态的价值,然后减去当前状态的价值
    delta = rewards[:, t] + self.config.gamma * nextvalues - values[:, t]

    # 使用 δ 更新 lastgaelam,这是 GAE 公式的一部分
    lastgaelam = delta + self.config.gamma * self.config.lam * lastgaelam

    # 将计算的优势值添加到优势值列表中
    advantages_reversed.append(lastgaelam)

# 将优势值列表反向并转换为张量
advantages = torch.stack(advantages_reversed[::-1]).transpose(0, 1)

# 计算回报值,它是优势值加上状态值
returns = advantages + values

步骤6:New Policy Sampling

New Policy Sampling是PPO算法中的一个关键步骤。

在PPO中,策略优化的过程涉及到两个策略:

一个是”旧的”策略,这是我们在开始每次优化迭代时使用的策略

另一个是”新的”策略,这是我们在优化过程中不断更新的策略

New Policy Sampling就是在新的策略(更新后的actor)下对轨迹(文本)计算概率的过程。这个信息会被用于计算”Actor Loss”,也就是策略梯度的损失。在我们的步骤中,Old Logprobs是一次性一个batch的数据计算的,这是因为在一个batch中旧策略都是不变的;而New Logprobs是一个mini batch计算一次,这是因为新策略每个mini batch变一次。

输入输出

输入:Ref_model、Actor、Critic

输出:New Logprobs、New Values、Logits

1
2
3
logprobs, logits, vpreds, _ = self.batched_forward_pass(
                        self.model, batch["queries"], batch["responses"], model_inputs, return_logits=True
                    )

Critic Loss

在强化学习中,Critic是一个模型,其任务是估计状态的价值函数,预测从当前状态开始,通过遵循某个策略,期望能得到的总回报。

Critic的训练目标是最小化它的预测价值与实际回报之间的差距。这个差距被称为Critic Loss。

Critic Loss通过均方误差(Mean Squared Error, MSE)来计算。

对于每一个状态,由Critic预测出的预期回报值V(s),以及一个真实的回报值G(returns)。

Critic Loss就是这两个值之间差的平方,在一个批量的数据中,Critic Loss是所有状态的这个差的平方的平均值。 $\text{CriticLoss} = \mathbb{E}[(V(s) - G)^2]$

  • $V(s)$ :Critic 对状态 ( s ) 的价值预测(New Values);
  • $G$ :实际获得的回报(Returns);
  • $\mathbb{E}[.]$ :期望值,表示对整个批次的平均;

通过最小化Critic Loss,Critic的预测能力会逐渐提升。

Critic的预测结果会被用来估计每个行动的优势(Advantage),这个优势值又会被用来计算策略的更新(Actor Loss)。

输入输出

输入:New Values、Returns

输出:梯度更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 将价值函数的预测值裁剪到一个范围内
vpredclipped = clip_by_value(
            vpreds, values - self.config.cliprange_value, values + self.config.cliprange_value
        )

# 计算裁剪前和裁剪后的价值函数损失
vf_losses1 = (vpreds - returns) ** 2
vf_losses2 = (vpredclipped - returns) ** 2

# 最终的价值函数损失是裁剪前和裁剪后损失的最大值的平均值的一半
vf_loss = 0.5 * masked_mean(torch.max(vf_losses1, vf_losses2), mask)

# 计算裁剪操作实际发生的频率
vf_clipfrac = masked_mean(torch.gt(vf_losses2, vf_losses1).double(), mask)

代码的作用是将vpreds裁剪到一个范围内,由values - self.config.cliprange_value和values + self.config.cliprange_value确定,

  • 其中values是原始的价值函数预测值,self.config.cliprange_value是裁剪的范围。目的是为了避免value的变化太快。

步骤8:Actor Loss——PPO剪裁目标函数

PPO 算法通过引入剪裁机制,约束策略更新幅度,以保持训练稳定性。 $L^{CLIP}(\Theta) = \mathbb{E}_t\left[\min\left(r_t(\Theta)\hat{A_t}, \text{clip}\left(r_t(\Theta), 1 - \epsilon, 1 + \epsilon\right)\hat{A_t}\right)\right]$

其中:

  • $r_t(\Theta) = \frac{\pi_\Theta(a_t s_t)}{\pi_{\Theta_{\text{old}}}(a_t s_t)}$ :新旧策略概率比值;
  • $\hat{A_t}$:优势函数,衡量当前动作的相对好坏;
  • $\epsilon$ :剪裁阈值,通常设为 0.1 或 0.2;
  • clip 函数用于限制比值 $r_t(\Theta)$ 超出$[1 - \epsilon, 1 + \epsilon]$的范围。

  • 保持策略稳定性:防止更新过大导致策略崩坏;
  • 强化表现良好动作:在优势为正时鼓励增大概率;
  • 抑制表现差动作:在优势为负时削弱其概率;
  • 剪裁后的损失函数选择较小值,避免策略朝错误方向大步更新。

输入输出

输入:Old Logprobs,New Logprobs、Advantages

输出:梯度更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 计算新旧策略下概率的比值
ratio = torch.exp(logprobs - old_logprobs)

# 计算未截断的策略梯度损失
pg_losses = -advantages * ratio

# 计算截断的策略梯度损失
pg_losses2 = -advantages * torch.clamp(ratio, 1.0 - self.config.cliprange, 1.0 + self.config.cliprange)

# 选择两者中较大的作为最终的策略梯度损失
pg_loss = masked_mean(torch.max(pg_losses, pg_losses2), mask)

# 计算因为截断导致策略梯度损失改变的比例
pg_clipfrac = masked_mean(torch.gt(pg_losses2, pg_losses).double(), mask)

步骤9:Entropy Loss

策略熵衡量当前策略对状态的选择不确定性——在策略优化中引入熵项有助于提升探索性,从而避免陷入次优解。 $\mathcal{H}(\pi(\cdot \mid s_t)) = - \sum_{a_t} \pi(a_t \mid s_t) \log(\pi(a_t \mid s_t))$ $= \mathbb{E}_{a_t \sim \pi}[-\log(\pi(a_t \mid s_t))]$

  • 在策略目标中加入熵奖励项;
  • 控制系数如 entropy_coef = 0.01
  • 熵系数越大 → 鼓励更多探索;
  • 熵系数越小 → 提高稳定性但探索减少。

输入输出

输入:Logits

输出:梯度更新

1
entropy = -torch.sum(logits* torch.log(logits + 1e-9), dim=-1).mean()

步骤10:PolicyKL —散度约束机制

在优化策略目标函数的同时,PPO 引入 KL 散度作为约束项,用于防止新策略偏离旧策略过远,避免性能骤降。 $\text{KL}\left[ \pi_{\theta_{\text{old}}}(\cdot \mid s_t),\ \pi_{\theta}(\cdot \mid s_t) \right] \leq \delta$


  • $\pi_{\theta_{\text{old}}}$ :旧策略;
  • $\pi_{\theta}$ :更新后的新策略;
  • $\delta$ :预设的 KL 阈值;
  • 如果 KL 散度超过$\delta$ ,将提前终止本轮优化(early stop),保证新策略不会剧烈偏移。
  • 每个 mini batch 计算当前策略与旧策略之间的 KL 散度;
  • 若超出设定阈值 $\delta$ ,则本轮更新被终止;
  • 此机制可有效降低策略更新的不稳定性。

输入输出

输入:Old Logprobs,New Logprobs

输出:是否early stop

1
2
3
4
5
6
7
8
9
# 计算旧策略和新策略之间的KL散度
policykl = masked_mean(old_logprobs - logprobs, mask) 
# old_logprobs 是旧策略下行为的概率的对数,logprobs 是新策略下的对数概率
# masked_mean 函数计算差异(old_logprobs - logprobs)的平均值,但只考虑mask中对应元素为True的元素

# 检查计算出的KL散度(policykl)是否大于目标KL散度(self.config.target_kl)的1.5倍
if policykl > 1.5 * self.config.target_kl: 
    self.optimizer.zero_grad()  # 如果实际的KL散度超过了目标的1.5倍,那么策略改变过多,这步的梯度也不更新了。
    early_stop = True  # 并设置early_stop标志为True,表示应提前停止优化,以防止策略从旧策略进一步偏离

推荐强化学习代码库

  1. Stable Baselines3:Stable Baselines3是一个提供各种深度强化学习算法实现的库,包括PPO,DQN,A2C等。所有的算法都采用了相同的API,使得在不同的算法之间进行切换变得非常方便。此外,它还提供了许多实用的功能,如模型保存/加载,向量化的环境,多种预处理选项等。
  2. Spinning Up in Deep RL:这是OpenAI开发的一个库,其中包含了诸如PPO,A2C,SAC等算法的实现。该库的目标是提供一个清晰、简洁、易于理解的深度强化学习算法实现。
  3. Ray’s Rllib:Rllib是一个高效的分布式强化学习库,支持PPO,A3C,DQN等算法。它可以很容易地与Ray的其他组件(如Tune和Serve)集成,从而提供了一个全面的深度学习和强化学习的解决方案。