Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

RL——强化学习中的方差与偏差


本文主要介绍强化学习中的方差和偏差,以及相关的解法如lambda-return、TD(lambda)、GAE等

  • 本文关键词:
    • Multi-step TD,n-step TD,多步TD,n步TD
    • lambda-return, \(\lambda\)-return
    • TD(lambda),TD-lambda,TD(\(\lambda\))
    • GAE

MC 和 TD 的方差分析

  • MC(Monte-Carlo):方差大,偏差小
  • TD(Temporal-Difference):偏差大,方差小
  • 理解:
    • 与模型训练类似,方差与偏差是指同一个模型(bagging中所有模型共同组成一个模型)的输出是随着数据集/时间变化的
    • 方差大表达的是多次评估结果之间差别大
    • 偏差大则表示多次评估结果的均值与真实值差别大
    • MC采样每次都需要重新采样不同路径集合,在不同路径集合下,相同状态价值的评估结果差别大,但是结果的期望是符合真实情况的(甚至是无偏的)
    • TD方案则每次都使用下一个状态的相关估值,方差不会太大(依赖的随机变量更少),但是下个状态的估值不一定符合真实值,所以偏差较大
  • 参考链接:
    • 强化学习,方差比较大是说什么的方差大,为啥方差比较大? - Discover的回答 - 知乎
    • 强化学习,方差比较大是说什么的方差大,为啥方差比较大? - 无非尔耳的回答 - 知乎
    • 强化学习入门 第四讲 时间差分法(TD方法)-知乎-天津包子馅儿
  • 如何权衡方差和偏差?
    • Multi-step TD、 \(\lambda\)-return、TD(\(\lambda\))和GAE均可用于权衡方差和偏差问题

强化学习中 Reward 的方差为什么不为0

  • 策略随机性会导致出现方差: \(a \sim \pi(\cdot|s)\)
  • 执行策略后状态转移随机性会导致出现方差: \(s’ \sim P(\cdot|s,a)\)

Multi-step TD

  • Multi-step TD,也称为n-step TD,中文名多步TD或n步TD
  • 将TD中的即时奖励替换成采用未来多个时间片的奖励和,同时 \(\gamma V_\theta(s_{t+1})\) 值替换成 \(\gamma^n V_\theta(s_{t+n})\)
  • 形式化定义:自 \(t\) 时间步开始, \(n\) -step TD的 \(G_t^{(n)}\) 的值定义如下:
    $$
    \begin{align}
    G_t^{(n)} &= \sum_{l=0}^{n-1} \gamma^l R_{t+l} + \gamma^n V_\theta(S_{t+n}) \\
    G_t^{(n)} &= R_t+\gamma R_{t+1}+\gamma^2 R_{t+2}+\cdots+\gamma^n V_{\theta}(S_{t+n})
    \end{align}
    $$
  • 展开来看,不同的步长下对应的n-step TD形式化定义如下
    $$
    \begin{align}
    G_t^{(1)}&=R_t+\gamma V_{\theta}(S_{t+1})\\
    G_t^{(2)}&=R_t+\gamma R_{t+1}+\gamma^2 V_{\theta}(S_{t+2})\\
    &\vdots\\
    G_t^{(n)}&=R_t+\gamma R_{t+1}+\gamma^2 R_{t+2}+\cdots+\gamma^n V_{\theta}(S_{t+n})\\
    &\cdots\\
    G_t^{(N)}&=\sum_{l=0}^N \gamma^{l} R_{t+l}\\
    \end{align}
    $$
    • \(G_t^{(N)}\) 表示包含终止状态(即包含最后一个时间步)的情况

\(\lambda\)-return

  • 回顾 Multi-step TD,自 \(t\) 时间步开始, \(n\)-step TD 的 \(G_t^{(n)}\) 的各种值如下
    $$
    \begin{align}
    G_t^{(1)}&=R_t+\gamma V_{\theta}(S_{t+1})\\
    G_t^{(2)}&=R_t+\gamma R_{t+1}+\gamma^2 V_{\theta}(S_{t+2})\\
    &\vdots\\
    G_t^{(n)}&=R_t+\gamma R_{t+1}+\gamma^2 R_{t+2}+\cdots+\gamma^n V_{\theta}(S_{t+n})\\
    &\cdots\\
    G_t^{(N)}&=\sum_{l=0}^N \gamma^{l} R_{t+l}\\
    \end{align}
    $$
  • \(\lambda\)-return 是 Multi-step TD 的加权平均(加权平均意味着权重和为1)
    $$
    \begin{align}
    G^{\lambda}_t&=(1-\lambda)[G_t^{(1)}+\lambda G_t^{(2)}+\cdots+\lambda^{N-2} G_t^{(N-1)}] + \lambda^{N-1} G_t^{(N)}\\
    G^{\lambda}_t&=(1-\lambda)\sum_{n=1}^{N-1}\lambda^{n-1}G_{t}^{(n)} + \lambda^{N-1} G_t^{(N)} \\
    \end{align}
    $$
    • 其中 \((1-\lambda)\) 是一个配平因子
      • 因为等比数列的和为 \(S_n = a+ar+ar^2+\cdots+ar^{n-1} = \frac{a(1-r^n)}{1-r}\)
      • 在我们场景中,系数部分有: \(S_n = 1+\lambda+\lambda^2+\cdots+\lambda^{n-1} = \frac{1-\lambda^n}{1-\lambda}\)
      • 所以最后一项不需要乘以 \((1-r)\),即:
        $$ 1 = (1-\lambda)(\frac{1-\lambda^N}{1-\lambda}) + \lambda^N = (1-\lambda)(1+\lambda+\lambda^2+\cdots+\lambda^{N-1}) + \lambda^N $$
      • 当 \(n\rightarrow \infty\) 时,有 \(1+\lambda+\lambda^2+\cdots+\lambda^{\infty} = \frac{1}{1-\lambda}\),此时不需要再配平,所以此时有:
        $$G^{\lambda}_t=(1-\lambda)\sum_{n=1}^{\infty}\lambda^{n-1}G_{t}^{(n)} \quad \quad \quad \quad \quad \quad$$
  • \(\lambda\)-return方法可用于平衡偏差与方差
  • \(\lambda\)-return方法是off-line的值更新方法,因为它必须用到当前时刻之后直到到达终止状态的所有数据(forward view),也就是说只有在一条episode结束后才能计算出对应的状态估计量
    • GAE也是这样吧,很多更新算法都是完成一整局游戏再更新一次模型,比如PPO,似乎也没有什么问题
  • 实践中,常常用TD(\(\lambda\))来近似拟合 \(\lambda\)-return

TD(\(\lambda\))

  • TD(\(\lambda\))方法是在Backward视角下,对n-step TD的各个值进行加权求和
    • TD(\(\lambda\))在每个时间步骤后,马上就可以调整预测
    • TD(\(\lambda\))是on-line的值更新方法,可以解决 \(\lambda\)-return需要等到 episode 结束才能获得状态估计量的缺点
  • TD(\(\lambda\))算法是强化学习中的一种重要方法,它结合了蒙特卡罗(MC)方法和时间差分(TD)方法的优点,同时引入了一个新的参数 \(\lambda\),用于平衡一步预测和多步预测之间的关系。TD(\(\lambda\))的计算公式涉及到如何更新状态或动作-状态的价值函数,并且利用了所谓的“资格迹”(eligibility trace)来追踪哪些权重对当前的 TD 误差负有责任

表格型学习下的 TD(\(\lambda\))

  • 对于表格型强化学习(Tabular RL)下的 TD(\(\lambda\)),其价值函数更新公式可以表示为:
    $$ V(S_t) \leftarrow V(S_t) + \alpha \delta_t E_t $$
  • 其中:
    • \(V(S_t)\) 是状态 \(S_t\) 的价值估计
    • \(\alpha\) 是学习率,决定了每次更新的步伐大小
    • \(\delta_t = R_{t} + \gamma V(S_{t+1}) - V(S_t)\) 是TD误差,衡量了实际观察到的结果与预测结果之间的差异,这里将这个差异用于更新当前episode上遇到的所有的相关状态的价值
    • \(E_t\) 是资格迹向量,在每个时间点 \(t\) 上根据之前的状态访问情况累积而成
      • 每次访问任意状态 \(\hat{s}\),都会更新所有状态的资格迹,假设在第 \(t\) 轮次,访问到状态 \(s_t\) 后,任意状态 \(s\) 对应的资格迹的更新规则是:
        $$ E_t(s) = \gamma \lambda E_{t-1}(s) + \mathbf{1}(s_t = s) $$
      • 当前轮次更新所有状态时,首先所有状态都会在上一轮的资格迹基础上乘以 \(\gamma \lambda \),然后,对于这轮遇到的状态 \(s = s_t\) 上资格迹加1,否则不加( \(\mathbf{1}(s_t = s)\) ),如果展开资格迹,则有如下表示:
        $$ E_t(s) = \begin{cases}
        \gamma \lambda E_{t-1}(s), & \text{if } s \neq S_t \\
        \gamma \lambda E_{t-1}(s) + 1, & \text{if } s = S_t
        \end{cases} $$
      • 注意:这里的 \(t\) 不是episode里面的时间片, \(t\) 是表示当前更新轮次 \(t\),这个更新轮次遇到的状态则表示为 \(s_t\), \(E_{t-1}(s)\) 则表示上一轮次访问到状态 \(\hat{s}\) (这里的状态 \(\hat{s}\) 可以是任意状态)以后,状态 \(s\) 更新得到的结果
      • 这意味着每当访问到某个状态 \(s\) 时,它的资格迹会被重置为1加上前一时刻该状态的资格迹乘以 \(\gamma\lambda\) 。如果当前不是访问这个状态,则仅按比例衰减
    • TD(\(\lambda\))是在访问完一个时间片以后,立刻用当前时间片访问得到的信息更新之前遇到过的其他状态,且离当前时间片越近、历史访问次数越多的状态被更新的幅度越大(实现时,资格迹默认置0,未被访问过的状态是不会被更新的)
  • 说明:虽然上述操作使得TD- \(\lambda\) 在计算上显得更优雅,但显然它的合理性弱于 \(\lambda\)-return,实际效果也可能会弱于 \(\lambda\)-return
  • TD \(\lambda\) 的代码实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class TDLambda:
    def __init__(self, alpha, gamma, lambda_, num_states):
    self.alpha = alpha
    self.gamma = gamma
    self.lambda_ = lambda_
    self.V = np.zeros(num_states) # 存储所有状态价值函数V值
    self.E = np.zeros(num_states) # 存储所有状态的资格迹
    def update(self, state, next_state, reward, done):
    delta = reward + (0 if done else self.gamma * self.V[next_state]) - self.V[state]
    self.E *= self.gamma * self.lambda_ # 更新所有状态的资格迹
    self.E[state] += 1 # 对当前状态增加踪迹
    self.V += self.alpha * delta * self.E # 更新所有状态的价值函数,资格迹为0的(不在当前episode的状态资格迹都为0)
    if done:
    self.E[:] = 0 # 如果episode结束,重置资格迹,保证新的回合开始时所有状态资格迹为0

    td_lambda = TDLambda(alpha=0.1, gamma=0.9, lambda_=0.8, num_states=env.observation_space.n)
    state = env.reset()
    for t in range(max_steps):
    action = policy(state) # 根据某种策略选择行动
    next_state, reward, done, _ = env.step(action) # 执行动作
    td_lambda.update(state, next_state, reward, done) # 更新用TD(\lambda)更新所有状态的价值函数和资格迹
    state = next_state
    if done:
    break

函数近似型学习下的资格迹

  • 在函数近似强化学习(Function Approximation RL)情况下,由于状态往往无法通过表格完全枚举,所以资格迹是针对记录参数的,而不是记录状态的,但本质都是对状态价值函数的估计
  • 这种资格迹出现在 Sutton 的《Reinforcement Learning: An Introduction》中,此时的资格迹(Eligibility Traces)定义与上述表格型资格迹有所不同,其更侧重于从权重更新的角度进行定义
    • 注:详情见第二版书籍《Reinforcement Learning: An Introduction Second Edition》的 第 12.2 章节
  • 对于状态价值函数的估计,资格迹向量 \(z_t\) 的更新公式为:
    $$
    \begin{align}
    z_{-1} &\doteq 0 \\
    z_t &\doteq \gamma\lambda z_{t - 1}+\nabla\hat{v}(S_t,w_t)
    \end{align}
    $$
    • 其中 \(\gamma\) 是折扣因子,
    • \(\lambda\) 是资格迹衰减参数,
    • \(\nabla\hat{v}(S_t,w_t)\) 是状态价值函数 \(\hat{v}(S_t,w_t)\) 关于权重 \(w_t\) 的梯度
  • 权重 \(w_t\) 的更新公式为:
    $$w_{t + 1} \doteq w_t+\alpha\delta_t z_t$$
    • 其中 \(\alpha\) 是学习率
    • \(\delta_t\) 是时序差分误差,定义为
      $$ \delta_t \doteq R_{t + 1}+\gamma\hat{v}(S_{t + 1},w_t)-\hat{v}(S_t,w_t) $$
  • 这种定义方式强调了资格迹与价值函数梯度的关系,通过资格迹来记录权重向量的各个维度对最近的状态值函数估计的贡献程度,从而在权重更新时能够更有效地利用多步的时序差分信息
  • 在强化学习中,上述提到的两种“资格迹”定义并非本质冲突,而是同一核心思想在不同价值函数表示框架下的具体实现 ,其差异源于对“状态价值函数 \( V(s) \)”的建模方式不同(表格型 vs 函数近似型),这也正是 Sutton《Reinforcement Learning: An Introduction》中从基础到进阶的核心逻辑脉络

核心区别:表格型方法 vs 函数近似方法

  • 两种资格迹的本质差异,根植于“是否用参数化模型拟合价值函数”,具体可从适用场景、表示形式、更新逻辑三个维度对比:
    对比维度 第一种定义(表格型资格迹) 第二种定义(Sutton 书的函数近似型资格迹)
    适用场景 表格型强化学习:状态空间 有限且离散(如网格世界、 Blackjack),每个状态 \( s \) 有独立的价值 \( V(s) \),无需参数拟合 函数近似强化学习:状态空间 无限或高维(如连续动作机器人、图像输入游戏),需用参数化模型 \( \hat{v}(s, \boldsymbol{w}) \) 拟合 \( V(s) \)(\( \boldsymbol{w} \) 为模型参数)
    资格迹的表示形式 针对“单个状态”的迹值:\( e_t(s) \) 是标量,每个状态 \( s \) 对应一个独立的资格迹(可理解为“状态级迹”) 针对“模型参数”的迹向量:\( \boldsymbol{z}_t \) 是与参数 \( \boldsymbol{w} \) 同维度的向量(如神经网络的权重向量),每个参数维度对应一个迹值(可理解为“参数级迹”)
    核心更新逻辑 直接更新“状态价值”:通过 \( e_t(s) \) 记录“状态 \( s \) 近期被访问的程度”,更新时直接调整 \( V(s) \) 间接更新“参数权重”:通过 \( \boldsymbol{z}_t \) 记录“每个参数对近期状态价值估计的贡献程度”,更新时先调整参数 \( \boldsymbol{w} \),再通过 \( \hat{v}(s, \boldsymbol{w}) \) 间接体现状态价值的变化
    梯度项的角色 无梯度项:因每个状态价值独立,无需对参数求导,用“指示函数 \( \mathbb{I}(s=s_t) \)”标记当前访问的状态 核心依赖梯度:\( \nabla_{\boldsymbol{w} } \hat{v}(S_t, \boldsymbol{w}) \)(价值函数对参数的梯度)决定“哪些参数对当前状态 \( S_t \) 的价值估计有贡献”,是资格迹更新的核心输入
  • 两种资格迹具体公式对比:从“状态迹”到“参数迹”的映射
    • 为更直观理解,将两种定义的核心公式并列,可清晰看到“函数近似型”是“表格型”的泛化与扩展 :
      公式类型 表格型资格迹(第一种定义) 函数近似型资格迹(Sutton 书定义)
      资格迹初始化 \( e_0(s) = 0 \)(所有状态的迹值初始为 0) \( \boldsymbol{z}_{-1} = \boldsymbol{0} \)(参数向量维度的迹向量初始为 0 向量)
      资格迹更新 \( e_t(s) = \gamma\lambda \cdot e_{t-1}(s) + \mathbb{I}(s = S_t) \)
      (当前状态迹值+1,其余衰减,所有状态都更新)
      \( \boldsymbol{z}_t = \gamma\lambda \cdot \boldsymbol{z}_{t-1} + \nabla_{\boldsymbol{w} } \hat{v}(S_t, \boldsymbol{w}) \)
      (参数迹向量衰减后,叠加当前状态的价值梯度)
      TD 误差 \( \delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t) \)
      (基于真实状态价值 \( V(s) \))
      \( \delta_t = R_{t+1} + \gamma \hat{v}(S_{t+1}, \boldsymbol{w}) - \hat{v}(S_t, \boldsymbol{w}) \)
      (基于参数化估计 \( \hat{v}(s, \boldsymbol{w}) \))
      价值/参数更新 \( V(s) \leftarrow V(s) + \alpha \cdot \delta_t \cdot e_t(s) \)
      (直接更新每个状态的价值)
      \( \boldsymbol{w}_{t+1} \leftarrow \boldsymbol{w}_t + \alpha \cdot \delta_t \cdot \boldsymbol{z}_t \)
      (更新参数向量,间接优化价值估计)
  • 核心思考:两种资格迹本质是统一的,资格迹的核心思想从未改变,尽管形式差异明显,但两种定义都遵循资格迹的核心逻辑 都是
    • 用“衰减的痕迹”记录“近期相关性” ,让后续的 TD 误差能高效回溯并更新“对当前奖励有贡献的状态/参数”
      • \(\mathbb{I}(s = S_t)\) 和 \(\nabla_{\boldsymbol{w} } \hat{v}(S_t, \boldsymbol{w})\) 表示的都是针对状态 \(S_t\) 更新的,记录的是当前状态 \(S_t\) 的相关度
      • 等价性:在表格型方法下,可以将表格位置看做参数,则参数维度为表格位置数量(与状态数量一致),此时状态价值与其他位置参数无关,仅仅与指定当前状态 \(S_t\) 对应的位置(唯一位置,假定对应参数为 \(w_i\))有关,此时的梯度:
        • 对 \(S_t\) 对应的位置参数 \(w_i\) 有: \(\nabla_{\boldsymbol{w_i} } \hat{v}(S_t, \boldsymbol{w_i}) = 1\)
        • 对其他所有 \(S_t\) 无关的位置参数 \(w_{\neg i}\)有:\(\nabla_{\boldsymbol{w_{\neg i}} } \hat{v}(S_t, \boldsymbol{w_{\neg i}}) = 0\)
    • 简言之,两种定义是“同一思想在不同问题复杂度下的适配”,函数近似型是表格型的自然延伸,而非替代
    • 对 \(\boldsymbol{w}_{t+1} = \boldsymbol{w}_t + \alpha \cdot \delta_t \cdot \boldsymbol{z}_t\) 的更新,相当于是对价值函数 \(\hat{v}(s,\boldsymbol{w}_{t+1})\) 的更新
  • 总结:一致性体现在(可能还有其他):
    • 1)衰减机制一致 :均通过 \( \gamma\lambda \) 实现“远期痕迹的指数衰减”(\( \gamma \) 折扣未来奖励,\( \lambda \) 控制多步信息的范围);
    • 2)更新触发一致 :均以 TD 误差 \( \delta_t \) 为“更新信号”,痕迹值越高的状态/参数,获得的更新幅度越大;
    • 3)目标一致 :都是为了平衡 TD(0)(单步、低方差高偏差)和 MC(全步、低偏差高方差),高效利用多步信息
    • 4)资格迹都是用于更新权重,且最终都是用于对价值函数的估计,对参数的更新会自动更新到状态价值网络
  • 何时用哪种定义?
    • 在处理简单离散状态问题(如 10x10 网格世界)时,表格型资格迹(第一种定义)更直观,无需考虑参数拟合,直接操作状态价值即可;
    • 在处理复杂高维/连续状态问题(如自动驾驶、Atari 游戏)时,必须采用 Sutton 书的函数近似型资格迹,通过参数向量 \( \boldsymbol{w} \) 和梯度 \( \nabla_{\boldsymbol{w} } \hat{v} \),才能用有限参数拟合无限状态的价值函数

GAE

  • 原始论文:(GAE)High-Dimensional Continuous Control Using Generalized Advantage Estimation, ICLR 2016, UC Berkeley

  • GAE,Generalized advantage estimation,平衡了强化学习中的方差与偏差,常用于 TRPO 和 PPO 中,用于评估优势函数

  • 推导过程(基于多步时序差分的思想)

    • \(\lambda\) 是一个介于 0 和 1 之间的参数,用于控制偏差-方差的权衡
      • 当 \(\lambda = 0\) 时,GAE 退化为单步 TD 误差
      • 当 \(\lambda = 1\) 时,GAE 等价于无限步回报(详情见下文的推导)
    • \(\gamma\) 是折扣因子
  • GAE 本质上是对不同步数的优势函数进行加权求和,最终得到一个的优势函数估计值

  • GAE 可通过参数调整控制方差和偏差的关系

GAE 中 \(\lambda=1\) 时的结论和证明

  • 当 GAE 中的 \(\lambda=1\) 时,优势估计退化为纯粹的蒙特卡洛形式 :
    • 不再使用任何 TD 自举(bootstrap),完全依赖实际采样的回报
    • 估计无偏,但方差最大
    • 对应 GAE 偏差-方差权衡谱的“高方差、零偏差”极端
  • 注意:不能直接从定义上证明,因为定义中包含 \(1-\lambda\),会导致结果看起来是 0,忽略前面又无法得到精确解,所以要从上面的结论形式出发推导
  • 回顾:写出 GAE(\(\lambda\)) 的一般定义:即 GAE(\(\lambda\)) 对优势函数的估计为:
    $$
    \hat{A}^{(\lambda)}_t = \sum_{l=0}^{\infty}(\lambda\gamma)^l\delta_{t+l},
    \qquad
    \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t).
    $$
    • 其中
      • \(\delta_t\) 是 \(t\) 时刻的 TD 误差;
      • \(\gamma\) 是折扣因子;
      • \(\lambda\) 是 GAE 平滑参数,\(0\le\lambda\le1\)
  • 令 \(\lambda=1\),得到
    $$
    \hat{A}^{(1)}_t = \sum_{l=0}^{\infty}\gamma^l\delta_{t+l}.
    $$
  • 把 \(\delta\) 的定义代入并做级数求和:即将 \(\delta_{t+l}=r_{t+l}+\gamma V(s_{t+l+1})-V(s_{t+l})\)代入,展开:
    $$
    \begin{aligned}
    \hat{A}^{(1)}_t
    &= \sum_{l=0}^{\infty}\gamma^l\bigl[r_{t+l}+\gamma V(s_{t+l+1})-V(s_{t+l})\bigr] \\
    &= \sum_{l=0}^{\infty}\gamma^l r_{t+l}
    + \sum_{l=0}^{\infty}\gamma^{l+1}V(s_{t+l+1})
    - \sum_{l=0}^{\infty}\gamma^l V(s_{t+l}).
    \end{aligned}
    $$
  • 把第二项的指标平移\(l\to l-1\),得到
    $$
    \sum_{l=0}^{\infty}\gamma^{l+1}V(s_{t+l+1})
    = \sum_{l=1}^{\infty}\gamma^{l}V(s_{t+l}).
    $$
  • 于是
    $$
    \hat{A}^{(1)}_t
    = \sum_{l=0}^{\infty}\gamma^l r_{t+l}
    + \sum_{l=1}^{\infty}\gamma^{l}V(s_{t+l})
    - \sum_{l=0}^{\infty}\gamma^l V(s_{t+l}).
    $$
  • 后两项相减,仅留下\(-V(s_t)\):
    $$
    \hat{A}^{(1)}_t = \sum_{l=0}^{\infty}\gamma^l r_{t+l} - V(s_t).
    $$
  • 右侧\(\sum_{l=0}^{\infty}\gamma^l r_{t+l}\)正是从 \(t\) 时刻开始的折扣回报\(R_t\),因此
    $$
    \hat{A}^{(1)}_t = R_t - V(s_t).
    $$
  • 这就是蒙特卡洛优势估计(Monte-Carlo advantage estimate)

GAE 实现

  • GAE 递归形式推导
    $$
    \begin{align}
    A^{\text{GAE}}_t &= \sum_{l=0}^\infty (\gamma\lambda)^l \delta_{t+l} \\
    A^{\text{GAE}}_t &= \delta_t + \sum_{l=1}^\infty (\gamma\lambda)^l \delta_{t+l} \\
    A^{\text{GAE}}_t &= \delta_t + (\gamma\lambda)\sum_{l=1}^\infty (\gamma\lambda)^{l-1} \delta_{(t+1)+(l-1)} \\
    A^{\text{GAE}}_t &= \delta_t + (\gamma\lambda)\sum_{k=0}^\infty (\gamma\lambda)^{k} \delta_{(t+1)+k} \\
    A^{\text{GAE}}_t &= \delta_t + (\gamma\lambda)A^{\text{GAE}}_{t+1} \\
    \end{align}
    $$
    • 实现代码(来自《动手学强化学习》):
      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
      35
      # 从td_deltas计算各个状态的GAE
      def compute_advantage(gamma, lmbda, td_deltas):
      td_deltas = td_deltas.detach().numpy()
      advantage_list = []
      advantage = 0.0
      for delta in td_deltas[::-1]:
      advantage = gamma * lmbda * advantage + delta # 对应推导的递归形式
      advantage_list.append(advantage)
      advantage_list.reverse()
      return torch.tensor(advantage_list, dtype=torch.float)
      # compute_advantage函数的使用
      td_target = rewards + self.gamma * self.critic(next_states) * (1 - dones)
      td_deltas = td_target - self.critic(states)
      advantage = rl_utils.compute_advantage(self.gamma, self.lmbda, td_deltas.cpu()).to(self.device)

      # 利用advantage更新PPO Agent
      mu, std = self.actor(states)
      action_dists = torch.distributions.Normal(mu.detach(), std.detach())
      # 动作是正态分布
      old_log_probs = action_dists.log_prob(actions)
      for _ in range(self.epochs):
      mu, std = self.actor(states)
      action_dists = torch.distributions.Normal(mu, std)
      log_probs = action_dists.log_prob(actions)
      ratio = torch.exp(log_probs - old_log_probs)
      surr1 = ratio * advantage
      surr2 = torch.clamp(ratio, 1 - self.eps, 1 + self.eps) * advantage
      actor_loss = torch.mean(-torch.min(surr1, surr2))
      critic_loss = torch.mean(F.mse_loss(self.critic(states), td_target.detach()))
      self.actor_optimizer.zero_grad()
      self.critic_optimizer.zero_grad()
      actor_loss.backward()
      critic_loss.backward()
      self.actor_optimizer.step()
      self.critic_optimizer.step()

讨论:GAE 中 \(\lambda\) 的作用(平滑作用)

  • GAE 的优势优势函数计算如下:
    $$ \hat{A}_t = \delta_t + (\gamma\lambda)\delta_{t+1} + (\gamma\lambda)^2\delta_{t+2} + \dots $$
  • 当 \(\lambda = 1\) 时(Monte Carlo) :
    • 中间的 \(V\) 项会相互抵消(Telescoping Sum),最后 \(\hat{A}_t = R_{final} - V(s_t)\)
    • 此时中间的 \(\delta_t\) 确实在数学形式上被“合并”了,退化为单纯的蒙特卡洛回报减去基线
    • 这会导致方差极大,因为 Critic 在中间步骤的判断被忽略了
  • 当 \(\lambda < 1\) 时(GAE 的标准用法) :
    • \(\hat{A}_t\) 变成了 \(\delta_t\) 的加权和,意味着当前的优势函数高度依赖于当前的 \(\delta_t\)
    • 如果模型在第 \(t\) 步犯错,导致 \(V(s_{t+1})\) 下降,\(\delta_t\) 会是巨大的负数
    • 由于 \(\lambda\) 的衰减,这个巨大的负数主要惩罚的是当前动作 \(a_t\),而对 \(a_{t-1}\) 的惩罚会衰减
    • 这正是我们想要的:谁犯错,谁负责
      • 如果没有中间的 \(\delta_t\),模型就不知道到底是哪一步导致了最后的失败

讨论:只有终止状态有非 0 奖励的情况下,GAE 的作用

  • 疑问:在只有最后一个 Token 有奖励时,GAE 中前面的 \(\delta_t\) 实际上都是 \(r_t = 0\),请问这种情况下,我们的 前面的 \(\delta_t\) 是不是没有什么意义?
    • 回答:前面的 \(\delta_t\) 绝对是有意义的,而且很有意义,在稀疏奖励场景下起到了至关重要的“信用分配”(Credit Assignment)作用
  • \(\delta_t\) 的定义是:
    $$ \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) $$
    • 在 \(r_t=0\) 且 \(\gamma=1\) 的情况下,它简化为:
      $$ \delta_t = V(s_{t+1}) - V(s_t) $$
    • 这意味着 \(\delta_t\) 代表了价值函数(Critic)对当前这一步动作带来的“预期改变”
  • Critic 充当了“密集奖励”的代理人:在只有最终奖励(Sparse Reward)的任务中,环境不说话(\(r_t=0\)),但 Critic(价值网络)一直在说话
    • \(V(s_t)\) :代表在 \(t\) 时刻,Critic 认为最终能拿多少分
    • \(V(s_{t+1})\) :代表在执行了动作 \(a_t\) 后,Critic 认为最终能拿多少分
  • \(\delta_t\) 实际上衡量了动作 \(a_t\) 是“好”还是“坏”
    • 如果 \(a_t\) 是一个关键的好步骤(比如在推理题中迈出了正确的一步),Critic 会觉得“稳了”,\(V(s_{t+1})\) 会比 \(V(s_t)\) 高,此时 \(\delta_t > 0\)
    • 如果 \(a_t\) 是一个致命错误(比如开始胡言乱语),Critic 会觉得“完了”,\(V(s_{t+1})\) 会暴跌,此时 \(\delta_t < 0\)
    • 如果 \(a_t\) 是废话(不影响结果),\(V(s_{t+1}) \approx V(s_t)\),此时 \(\delta_t \approx 0\)
    • \(\delta_t\) 将稀疏的最终奖励,通过 Critic 的预测能力,回传到了每一个具体的 Token 上
一个直观的例子
  • 假设题目是:“1+1 等于几?”
    • Step 1 : 生成 “The”
      • \(V(s_1) \approx 0.9\) (Critic 觉得大概率能答对)
      • \(V(s_2) \approx 0.9\) (生成 “The” 很正常)
      • \(\delta_1 \approx 0\) (无功无过)
    • Step 2 : 生成 “answer”
      • \(V(s_3) \approx 0.95\)
      • \(\delta_2 \approx 0.05\) (稍微有点进展)
    • Step 3 : 生成 “is”
      • \(V(s_4) \approx 0.95\)
      • \(\delta_3 \approx 0\)
    • Step 4 (关键步) : 生成 “three” (错误答案)
      • \(V(s_5) \approx 0.0\) (Critic 意识到这局要输了,因为答案错了)
      • \(\delta_4 = 0 + 1 \cdot 0.0 - 0.95 = -0.95\)
      • 注意:这里出现了巨大的负信号!
    • Step 5 : 生成 <EOS>
      • 最终奖励 \(r=0\) (因为答错了)
      • \(\delta_5 \approx 0\)
  • 注意:正是 Step 4 的 \(\delta_4\) 提供了极强的负反馈,告诉 Policy 网络:“是你把事情搞砸的”
    • 如果没有 \(\delta_t\),模型就只能收到最后那个 0 分,却不知道是哪个词出了问题
  • 在 \(r_t=0\) 的阶段,\(\delta_t\) 的意义在于 “信息增益” 或 “预期的修正”,\(\delta_t\) 是 Critic 告诉 Actor 的一句话:“这一步走完,我对结局的看法改变了多少”:
    • \(\delta_t \approx 0\) : 符合预期,按部就班
    • \(\delta_t \neq 0\) : 出现了转折点(惊喜或惊吓)
  • 理解:正是这些非零的 \(\delta_t\),让 PPO 能够在数千个 Token 的长序列中,精准地找到那些决定成败的关键 Token 进行强化或惩罚!

几种方法辨析

\(\lambda\)-return和TD(\(\lambda\))对比

  • 相同点:
    • \(\lambda\)-return和TD(\(\lambda\))的目标都是在单步TD和完整的蒙特卡罗回报之间找到一个折衷方案。通过调整 \(\lambda\) 值,可以在不同的时间尺度上分配信用,从而实现更有效的学习
    • 当 \(\lambda=0\) 时, \(\lambda\)-return和TD(\(\lambda\))都退化为TD(0),即只考虑一步奖励;而当 \(\lambda=1\) 时,则等价于使用整个episode的回报进行更新
  • 不同点:
    • \(\lambda\)-return是forward的,而TD(\(\lambda\))是backward的,两种视角的对比如下:
    • \(\lambda\)-return是off-line的值更新方法,而TD(\(\lambda\))是on-line的值更新方法
      • 估计值函数时,需要等到episode结束才能开始计算的算法为off-line方法(如 \(\lambda\)-return)
      • 估计值函数时,不需要等到episode结束就可以开始计算的算法称为on-line方法(如TD(\(\lambda\)))
  • 一些总结:
    • TD(\(\lambda\))是 \(\lambda\)-return的一种近似实现( \(\lambda\)-return需要采样到整个episode才可以更新一版)
    • 实际上几乎所有的on-policy方法训练时(比如许多开源实现都是这样)都需要完整完成一次episode再更新,此时直接使用( \(\lambda\)-return更好
    • TD \(\lambda\) 更多地应用于实际问题解决当中,尤其是在那些需要快速响应和持续学习的任务上。由于它可以实时更新模型参数,因此非常适合处理非终止型任务或者非常长的 episodes,在这些情况下等待整个 episode 完成后再做更新可能是不切实际的
      • 问题:这些任务上 GAE 是否也不适用?
  • 其他参考: \(\lambda\)-return和TD(\(\lambda\))的关系

GAE 和 \(\lambda\)-return

  • GAE 和 \(\lambda\)-return的思路基本一致,区别在于 GAE 关注的是优势函数, \(\lambda\)-return关注的是累计回报

附录:对 TD(\(\lambda\))和资格迹的深入理解

  • 整体描述 :
    • TD(\(\lambda\)) 使用当前时间片的 TD error 更新其他状态的能力源于其引入的资格迹(eligibility traces)机制。资格迹本质上是一个短期记忆向量,它记录了每个权重对于预测当前状态价值的贡献程度。当一个新的 TD error 被计算出来时,这个误差不仅用于直接更新当前状态的价值估计,还会根据资格迹的影响范围去调整之前的状态或动作的价值估计。这种机制允许 TD(\(\lambda\)) 在每一步都进行在线更新,而不需要等待整个 episode 结束
  • 对于资格迹的理解 :
    • 资格迹的关键在于它可以跟踪哪些状态或状态-动作对在最近被访问过,并且根据这些迹来决定如何分配新的信息。具体来说,每当智能体从一个状态转移到另一个状态并获得奖励时,资格迹会为该状态增加一定的值,同时以 \(\gamma\lambda\) 的速率衰减其他所有状态的资格迹。这里的 \(\gamma\) 是折扣因子, \(\lambda\) 是控制资格迹衰减速率的参数。因此,资格迹提供了一种方式来衡量不同状态之间的关联性,使得较早出现的状态也能受益于后来发生的事件
  • 为何可以用当前时间片的 TD error 更新其他状态?
    • 考虑一个简单的情况:假设我们有一个序列 \(S_0, A_0, R_1, S_1, A_1, R_2, …\),其中 \(S_t\) 表示第 t 步的状态, \(A_t\) 表示采取的动作,而 \(R_{t+1}\) 则是紧接着收到的即时奖励。现在假设我们在时间步 t 处得到了一个新的 TD error:
      $$ \delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t) $$
    • 这里 \(\delta_t\) 反映了当前状态下实际观察到的结果与之前的预期之间的差异。接下来,我们将使用这个误差来更新所有之前状态的价值估计。具体地,如果某个状态 \(S_i\) ( \(i < t\) )曾经出现在这一序列中,并且它的资格迹不为零,那么我们可以按照以下规则更新它的价值:
      $$ V(S_i) \leftarrow V(S_i) + \alpha \delta_t E_i $$
    • 其中 \(\alpha\) 是学习率, \(E_i\) 是状态 \(S_i\) 的资格迹。这样做的效果是,那些更接近当前时间点并且资格迹较高的状态将会受到更大的影响,因为它们被认为对当前的结果负有更多的责任。相反,距离较远或者资格迹较低的状态则只会得到较小幅度的调整
  • 为什么需要TD(\(\lambda\))?
    • TD(\(\lambda\)) 通常会在每次转移后立即更新所有相关状态的价值估计。这意味着算法可以在每一步都利用最新的反馈来改进自己的预测,而不是等到整个 episode 完成后再做一次性的修正。此外,由于资格迹的存在,即使某些状态可能不会再次出现在同一 episode 中,它们仍然有机会基于后续的发展情况进行适当的调整
    • 通过引入资格迹,TD(\(\lambda\)) 不仅能够有效地利用当前时间片的 TD error 来更新其他状态,而且还能够在不显著增加计算复杂度的情况下综合考虑所有步数的预测
    • 这种方法既保持了 TD(0) 的快速响应特性,又继承了 MC 方法在长序列上的准确性优势

RL——约束强化学习之PDO

本文主要介绍使用约束强化学习(Constraint RL)中的PDO(Primal-Dual Optimization)方法相关内容

  • 参考链接:
    • 【深度强化学习】Constraint RL for safe exploration:primal-dual optimization 算法 - XuanAxuan的文章 - 知乎

PDO 方法流程

  • PDO 方法主要用于解决解决CMDP问题
  • PDO 方法基本思路是构造拉格朗日函数后,使用梯度下降/上升方法依次迭代更新原始变量和对偶变量(更新原始变量时,对偶变量固定不变,反之依然)
  • 原始问题定义:建模为带约束最优化问题
  • 拉格朗日函数构造:引入拉格朗日变量
  • 按照如下流程迭代求解:
    • 固定对偶变量\(\lambda\)(可能有多个对偶变量,此时分别更新即可),使用梯度下降/上升法更新原始变量(参数)
      • 注:拉格朗日变量时可以分别独立更新的,拉格朗日函数关于他们各自的梯度均不会包含其他对偶变量
    • 固定原始变量(参数),使用梯度上升/下降法更新对偶变量

另一类镜像方法:CPO

  • CPO(Constraint Policy Optimization)是一类在每一步迭代中都求解一个精心设计的优化问题的方法,可以确保满足约束
  • TRPO在每一步中都在解一个约束优化问题,CPO则可以和TRPO求解融合,每一步迭代参数都确保满足指定约束
  • CPO vs PDO:CPO一般不如PDO效果好,且仅适配TRPO方法,PDO则可适配任意强化学习方法

其他说明

  • RCPO也属于一种PDO方法

RL——自然策略梯度法


整体描述

  • 自然策略梯度法(Natural Policy Gradient,NPG)本质是一种策略优化算法 ,主要应用于强化学习领域中

传统梯度的局限性与改进方向

传统策略梯度的问题

  • 传统策略梯度算法(如REINFORCE)直接沿梯度方向更新策略参数,公式为:
    $$\theta_{t+1} = \theta_t + \alpha \nabla_\theta J(\theta)$$
  • 但该方法更新时可能不稳定(步长敏感):不同参数的尺度差异可能导致更新步长不合理(如某些参数微小变化就会显著影响策略,而另一些则不然)

自然梯度的核心改进

  • 自然策略梯度通过引入Fisher信息矩阵(Fisher Information Matrix, FIM) 对梯度进行“几何修正”
  • 自然策略梯度的优点:
    • 稳定性 :保证每次更新的策略变化幅度可控(如约束在固定步长内),提升优化稳定性;
    • 加速收敛 :加速收敛速度,尤其在复杂任务中效果更显著

从梯度到自然梯度

策略梯度与KL散度的几何关系

  • 设策略分布为 \(\pi_\theta(a|s)\) ,目标函数 \(J(\theta)\) 的梯度可表示为:
    $$\nabla_\theta J(\theta) = \mathbb{E}_{\pi_\theta} \left[ \nabla_\theta \log \pi_\theta(a|s) Q^\pi(s,a) \right]$$
    • 其中 \(Q^\pi(s,a)\) 为状态-动作价值函数
  • 自然梯度的关键在于考虑参数更新 \(\delta\theta\) 引起的KL散度变化:
    $$KL(\pi_\theta || \pi_{\theta+\delta\theta}) \approx \frac{1}{2} (\delta\theta)^T F(\theta) \delta\theta$$
    • 其中 \(F(\theta)\) 为Fisher信息矩阵 ,定义为:
      $$F_{ij}(\theta) = \mathbb{E}_{\pi_\theta} \left[ \nabla_{\theta_i} \log \pi_\theta(a|s) \nabla_{\theta_j} \log \pi_\theta(a|s) \right]$$

自然梯度的定义与求解

  • 自然梯度 \(\nabla^{nat}_\theta J(\theta)\) 是在约束 \(KL(\pi_\theta || \pi_{\theta+\delta\theta}) \leq \epsilon\) 下,使目标函数增长最快的方向,数学上等价于:
    $$\nabla^{nat}_\theta J(\theta) = F(\theta)^{-1} \nabla_\theta J(\theta)$$
  • 该式通过求解线性方程组 \(F(\theta) \delta\theta = \nabla_\theta J(\theta)\) 得到更新方向

算法流程与实现要点

标准自然策略梯度算法步骤

  • 第一步-数据收集 :用当前策略 \(\pi_\theta\) 与环境交互,采集轨迹数据 \(\{(s_t, a_t, r_t)\}\) ;
  • 第二步-计算策略梯度 :根据数据估计 \(\nabla_\theta J(\theta)\) ;
  • 第三步-构建Fisher信息矩阵 :通过采样或近似方法计算 \(F(\theta)\) ;
  • 第四步-求解自然梯度 :解线性方程组 \(F(\theta) \delta\theta = \nabla_\theta J(\theta)\) ,得到更新方向;
  • 第五步-参数更新 :按约束步长(如 \(\delta\theta^T F(\theta) \delta\theta \leq \epsilon\) )更新参数 \(\theta\) ;
  • 第六步-迭代优化 :重复上述过程直至收敛

关键实现技巧

  • Fisher信息矩阵的近似 :直接计算 \(F(\theta)^{-1}\) 在高维场景中计算量极大,常用方法包括:
    • 共轭梯度法(TRPO的做法):迭代求解线性方程组,避免显式求逆;
    • KL散度约束 :用线搜索(Line Search)或信任区域(Trust Region)方法限制更新步长,保证稳定性(如TRPO算法即基于此思想)
  • 价值函数估计 :通常结合优势函数(Advantage Function) \(A^\pi(s,a)\) 优化,减少方差(如 \(Q^\pi(s,a) \approx V^\pi(s) + A^\pi(s,a)\) )

一些相关 RL 方法的对比

算法 关键点 复杂度 稳定性
REINFORCE 直接使用梯度更新,步长依赖学习率 低 差
自然策略梯度 引入 Fisher 信息矩阵修正梯度方向 高 高
TRPO(信任区域策略优化) 是自然梯度的工程化实现,用共轭梯度法近似求解 Fisher 信息矩阵的逆 中高 极高
PPO(近端策略优化) 自然梯度思想的简化实用版本,是 TRPO 的简化版本,计算更高效 中 高

总结

  • 自然梯度法有迭代次数少、稳定性强(KL散度限制范围)、理论完善等优点
  • 自然梯度法有计算复杂度高(Fisher信息矩阵的求解和存储)、样本需求大(准确估计Fisher矩阵需要大量样本)、工程实现困难(共轭梯度法等近似技巧需要精细调参,对算法实现要求较高)等缺点
  • 注:自然策略梯度法的核心思路(信任区域约束)在 PPO、SAC 等主流算法中被广泛应用

RL——强化学习相关笔记


为什么 off-policy 的方法在于不与环境交互时效果不好?

  • 注:理论上,off-policy 的方法对收集样本的行为策略没有要求,但实际上,行为策略与目标策略不同可能导致外推误差问题,具体包含下面三个部分
  • Absent Data: 数据集中可能存在某些状态是从来没有出现过的
    • 某些 state-action 对的缺失会导致在该状态和动作上的错误估计
  • Training Mismatch: 离线训练数据的分布和强化模型线上serving时的分布不一致
    • 用于更新网络的状态分布需要满足当前策略对应的分布(即使所有状态动作都见过,没有Absent Data问题,也可能因为分布不同而导致问题),否则会导致线上线下状态分布不一致。可能导致训练效率低下,甚至模型关注一些不重要的状态动作(可类比CTR模型样本分布最好是线上线下一致的)
  • Model Bias: 数据集本身可能分布与MDP分布不一致
    • 这里主要指在随机MDP中,数据集本身与MDP分布不一致(这里主要指与策略无关的部分,比如数据集中 \(P(s’|s,a)\) 无法准确表达真实环境)

强化学习中外推误差和OOD问题的区别【TODO:待补充】

  • 外推误差(Extrapolation Error)问题在强化学习场景中的定义:off-policy值学习中,当前策略真实状态动作访问分布和数据集中的状态动作分布不匹配导致的一种误差,具体来说,包括Absent Data(状态动作对缺失),Training Mismatch(训练预测分布不一致),Model Bias(随机MDP的状态转移概率有偏差)等问题
  • OOD(Out of Distribution)问题在强化学习场景中,更倾向于数据缺失(Absent Data)问题
  • 总结:外推误差可以是Absent Data,Training Mismatch,Model Bias等原因共同导致的,而OOD倾向于特指其中最常见的一种问题(Absent Data问题)

为什么Online RL中不能使用BN?

  • 参考链接:
    • 强化学习需要批归一化(Batch Norm) 或归一化吗?
    • [5分钟深度学习] #06 批量归一化 Batch Normalization
  • BN能work的主要原因是,每个Batch的数据是从整体数据中随机采样得到的,每个Batch的均值和方差不会差别太大
  • 在Online RL中,随着Agent的探索,数据分布一直在变化,进入模型的不同Batch无法保证能代表整体样本,随着Agent的迭代数据分布逐步变化,模型来不及学到固定的均值和方差,导致无法收敛
  • Offline RL中,数据是提前固定的,则没有这个问题,所以Offline RL中,可以使用BN
  • 实际上,可以分场景尝试,在一些状态变化不剧烈的场景中,BN也是可以使用的,大部分场景中则建议使用LN

为什么DQN/Q-learning不需要重要性采样?

  • 事实 :DQN有Reply Buffer,使用了其他策略收集到的样本,但是不需要重要性采样
  • 解释1 :DQN是是Q-learning的神经网络实现,Q-learning方法是基于贝尔曼最优方程的,最优价值函数是关于状态转移分布的期望值,而不是关于策略分布的期望值(默认是按照当前最优策略进行决策),所以不需要重要性采样
  • 解释2 :因为DQN是贝尔曼最优公式推导得到的,在推导和收敛性证明过程中,不涉及任何的策略数据采样分布要求,只要求计算下个状态Q值用的Q网络是上次迭代的值即可
  • 其他说明 :对于状态-动作分布的问题,实际上DQN也最好是使用当前策略采样的数据,原因是更能保证线上线下一致性;此外,在Offline RL场景中直接使用DQN可能因为没有探索而出现OOD等问题
  • 注 :如果使用n-step奖励进行学习,实际上不能满足中间步骤都是当前最优策略(可能有随机探索概率存在),但一般也忽略这个问题

D-Learning中,Q值高估现象及其导致的问题

  • Q-learning(以及 DQN 等基于 Q-learning 的算法)中的最大化操作(maximization operator),简单可以理解为以下:
    • 最大化操作本身的偏置(Maximization Bias)导致高估
    • 函数逼近误差(Function Approximation Error)进一步加剧高估
  • 这种高估往往服从不均匀的误差分布(Non-uniform Error Distribution),即部分动作高估,部分动作低估
    • 离线强化学习中,越没有见过的动作越容易被高估,因为其他动作会被数据集中的数据更新
    • 理解:如果是所有动作都相同程度的高估,实际上问题不大,依然能选到最优动作
    • 在线强化学习中,不均匀的高估可能降低收敛速度,导致策略往次优动作上尝试
  • 实践中常用 Double DQN 或 Twin Q 的思想来减少高估问题;离线强化学习中则常常会特别关注Q值高估问题(比如CQL)

Q网络的更新应该是软更新还是硬更新?

  • DDPG,BCQ连续版本等连续动作模型的Q值:软更新
    • DDPG论文中提到,使用这种软更新的方式,模型参数和对应的Target值都会缓慢的被更新,有利于保持学习的稳定性
  • DQN,BCQ离散版本等离散动作模型的Q值:硬更新
  • 问题:是因为BCQ本身就是基于以上两个模型实现的吗?(TODO)
  • 在DQN中目标网络两种常见更新方法对比有针对硬更新和软更新在DQN上分别尝试效果:

    实验结果表明,当目标网络更新间隔 \(C\) 和软间隔更新系数 \(\tau\) 满足 \(\tau C = 1\) 时,DQN算法使用这两种目标网路更新方式的性能是相当的


on-policy和off-policy方法简单对比?

  • 下面是网上经常有人引用的一句话:

    on-policy优点是直接了当,速度快,劣势是不一定找到最优策略。off-policy劣势是曲折,收敛慢,但优势是更为强大和通用

  • 个人理解,这句话前半段关于on-policy本身需要在某些特定场景下才准确,分析如下:
  • “on-policy优点是直接了当,速度快,劣势是不一定找到最优策略。”
    • 为什么说速度快?对训练数据利用不足本身就会导致收敛慢吧?:这里说的速度快是训练时能够直接训练当前策略收集到的状态动作下的值(Q值或策略),直接更新当前策略收集到的状态动作对,可能导致策略快速收敛到局部最优,特别是当探索不充分时,off-policy则不然
    • 不一定找到最优策略的理解:1)由于 on-policy 要求收集数据的行为策略(需要权衡探索和利用)和待优化策略是同一个策略,所以不能过于探索,否则最终学习到的策略不好,从而需要降低探索,减少探索以后导致容易陷入局部最优;2)仅更新当前策略收集到的状态动作对,导致模型快速收敛到某一个局部环境中,再也出不来
    • 理解:on-policy采用初期增加探索,后期减少探索的方式会更好,比如SARSA的更新过程中 \(\epsilon\) 越来越小
  • 实际上,从探索和利用的视角看,无论是off-policy和on-policy方法,在训练足够的时间以后,策略都会逐步利用收集到的知识收敛到一个特定的策略,而不再重视探索:
    • 也就是说在训练过程中 ,策略会越来越倾向于利用而不是探索 ,所以容易陷入局部最优解
    • 一种思路是将策略的熵添加到目标中或损失函数中,从而尽可能的保证策略始终在探索(比如SAC)

为什么DDPG/SAC是off-policy的?

  • DDPG/SAC是off-policy的,原因如下:
    • Q函数的学习 :DQN、DDPG和SAC三者本质都是基于贝尔曼最优公式迭代Q值的,其中DDPG的策略迭代是最大化Q值,SAC的策略迭代则是最大化Q值和策略对应的熵;他们是off-policy的原因一样的,是因为Q函数都是在学习某个策略 \(\pi_0\) 下对应的Q值,只要TD-Error中的Q值目标 \(r(s,a)+\gamma Q(s’,a’) - Q(s, a)\) 中的 \(a’\) 是策略 \(\pi_0\) 采样的 ,即目标值动作服从策略 \(a\sim\pi_0(\cdot|s’)\)的 即可保证学到的Q值是 \(Q^{\pi_0}(s,a)\),与 \((s,a,s’)\) 本身的采样策略无关(但是最好与当前策略的状态分布差距不要太大,否则容易出现线上线下分布不一致等问题)。随着策略逐步提升到最优策略,Q函数也会收敛到最优策略对应的Q值
      • 注:SAC的Q函数是包含了熵信息的,但这不影响Q值本身的学习策略
    • 策略的学习 :在保证Q函数预估的是当前策略(即截止到目前的最优策略)对应的Q值以后,我们只需要求解一个策略,保证当前策略对应的动作分布下的Q值最大即可,此时有:
      • DDPG :目标是找到一个确定性策略 \(\pi_\theta(s)\),最大化 \(Q(s,\pi_\theta(s))\) (本质可以理解为Q值在策略下的期望 \(\pi_\theta(s)\),确定性策略的期望就是这个策略对应的Q值),可采用梯度直接回传的方式链式求导;
      • SAC-连续动作 :通过随机采样生成动作 \(\tilde{a}\) (重参数法),然后将基于状态动作生成 \(Q(s,\tilde{a})\),从最大化Q值实现梯度传到到策略参数上(本质上是最大化Q值在策略分布上的期望)
      • SAC-离散动作 :则通过玻尔兹曼分布直接得到一个Q值下最优的随机策略(离线动作),具体实现时先生成不同动作下的Q值 \(Q(s,a)\),求Q值在策略 \(\pi(a|s)\) (本质是一个分布)上的期望 \(\sum_a \pi(a|s)Q(s,a) \) (与连续版本本质一致),然后最大化这个期望以实现梯度传导到策略参数上
      • 注意:DDPG/SAC等方法中 ,策略学习的目标是找到使得当前 \(Q(s,a)|a\sim\pi_\theta(s)\) 值最大的策略 \(\pi_\theta\),学习时状态 \(s\) 是从什么策略采样来的无关,即任意给我一个状态 \(s\),目标策略都能使得该状态下的 \(Q\) 值最大化(这里的Q值在学习中拟合目标是在当前策略下的Q值);但普通的AC方法中 ,Actor是直接对Reward的期望求梯度来更新的,这个期望是需要在当前策略下的期望,故而状态和动作都需要是当前状态下采样得到的,否则梯度有偏,而DDPG/SAC中则因为直接最大化Q值(该Q值是当前策略下的Q值)来实现了,绕过了必须保证状态 \(s\) 是当前策略采样得到的这一步了
  • PPO/TRPO是on-policy的,原因则是因为他们每一步的迭代目标是直接找到一个能提升RL代理函数 \(J\) 的更优策略 ,每次迭代都保证策略优于当前策略,在推导过程中,使用MM方法构造代理函数时,要求状态分布于新策略的状态分布差异不能太大 ,最好是当前策略采样的数据更新,生成新策略;(注意,动作分布部分有重要性采样保证,只要能保证准确记录每个动作采样的分布即可通过重要性采样方式恢复)

为什么SARSA和DQN都可以收敛到最优解?

  • 两者的Q值目标都可以表述为: \(r(s,a)+\gamma Q(s’,a’) - Q(s, a)\),两者的 \(Q(s’,a’)\) 都是上一轮的 \(Q\) 值(一般也会使用Q的目标网络 \(Q_{\bar{\theta}}(s’,a’)\),但含义就是上一轮的Q的含义),但两者的 \(a’\) 值对应的策略不同
  • SARSA:
    • 更新目标 : \(a’\) 是按照当前Q值对应的 \(\epsilon\) -Greedy策略采样的(该动作大多以大概率取 \(a’\sim \mathop{\arg\max}_{a’} Q(s’,a’)\),并以一定的小概率随机采样)
    • 样本状态动作分布 :当前策略采样到的状态分布 \(s,a,s’\)
    • 在随机采样概率越来越小的情况下,SARSA最终可以收敛到最优策略
  • DQN:
    • 更新目标 : \(a’\sim \mathop{\arg\max}_{a’} Q(s’,a’)\),相当于没有 \(\epsilon\) -Greedy策略
    • 样本状态动作分布 :历史行为策略采样到的状态分布 \(s,a,s’\)
  • 注意: \(Q(s’,a’)\) 需要是上一轮的Q,才能收敛,不动点定理证明时, \(Q(s’,a’)\) 是上一轮的Q的情况下,可得到最终 \(Q(s’,a’)\) 的Q和 \(Q(s,a)\) 的Q收敛到同一个Q

V值更新如何保证学习到指定策略?

  • V值更新的目标都可以归纳为: \(r+\gamma V(s’) - V(s)\),其中 \(V(s’)\) 是上一轮的V网络,一般使用目标网络 \(V_{\bar{\theta}}(s’)\)
  • \(r\) 来源于什么策略 \(r(s,a)\vert_{a\sim \pi(\cdot|s)}\),V值学习的就是什么策略

对奖励函数进行仿射变换会影响强化学习最优策略吗?

  • 对奖励函数进行放射变化,不影响最优策略(最优价值函数会跟着发生相应的仿射变换),即最优策略不变(Optimal Policy Invariance)
  • 证明:若奖励仿射变换为\(r_t = k \cdot r_t+b\),则最优价值函数仿射变换为
    $$ Q^* = k\cdot Q^* + \frac{b}{1-\gamma} $$
    • 显然Q值对应的最优策略不会发生改变

离散动作、连续动作的使用场景辨析

  • 一般来说,Q-Learning相关方法(DQN等为代表)仅支持离散动作,策略梯度相关大部分方法(SAC、PPO为代表)支持连续动作和离散动作,有部分(DDPG等为代表)仅支持连续动作
  • DDPG本质是不支持离散动作的,但是如果基于DDPG学到的Q网络做离散化动作搜索,也可以作为一种策略(次优策略)
  • PPO和SAC中均需要使用某个动作下对应的概率,连续动作下实际上概率值是0,连续动作的概率值使用的是概率密度值
    • 注:概率密度值(Probability Density Value)是连续概率分布中的一个核心概念,用于描述随机变量在某个特定点附近的相对可能性
  • Google 2018年提出的QT-Opt方法,在连续动作场景中学习Q值,然后使用CEM(Cross Entropy Method)从Q值中提取连续动作

随机策略、确定性策略的使用场景辨析

  • 随机性动作 :策略效果会有波动、能防止某些特定场景(状态观测不全面时)陷入死循环的问题、可解释性和安全性会偏低
  • 确定性动作 :策略效果更稳定、在状态观测不全面时可能陷入死循环、可解释性和安全性也更高些、更利于探索(确定性策略在训练时往往也需要加入随机探索)
    • 状态观测不全面导致陷入死循环的示例:
      • 环境状态情况:
        • o o o o o
        • x x ✓ x x
      • 对应的最优策略应该如下(第二和第四个点观察到的周围状态环境相同,但是最优策略不同):
        • → → ↓ ← ←
        • x x ✓ x x
      • 以上状态中,假设每个点只能观察到上下左右随机相邻点和自己的情况,那么第一行的第二和第四个点状态相同,此时按照确定性策略决策一定陷入循环(因为第一行的第二和第四个点策略必须相同,如论向左还是向右都会出现循环)
      • 这个例子可以用来帮助解释LLM中使用确定性策略(取概率最大的动作)容易陷入循环,产生大量重复的token,LLM中原始token被压缩后不算是状态观测完全的了,此时按照决策确定性容易陷入循环(此时会在相似的压缩空间下输出完全相同的token),加入随机反而使得生成的文本更正常
      • 其他理解:Diffusion中生成是需要采样是不是也是类似LLM的原理?
  • 二者的转换(所有转换方法均会不同程度的影响最优策略,不再是最优决策):
    • 随机策略转确定性策略:1)不直接采用Actor,通过搜索期望奖励最大的动作(连续动作可按照一定的间隔来搜索);2)直接使用Actor,找概率最大的动作(连续动作也有概率最大的动作,高斯分布对应取均值)
    • 确定性策略转随机策略: \(\epsilon\) -Greedy(离散动作)或者加一个方差扰动(连续动作)即可
  • 强化学习的最优解应该是随机策略还是确定性策略?
    • 清华李升波书籍Reinforcement Learning for Sequential Decision and Optimal Control中第六章确定性策略最优的证明和策略建模选型表述(见清华大学李升波教授强化学习书籍《Reinforcement Learning for Sequential Decision and Optimal Control》读书笔记——U6函数近似间接RL)

      在训练阶段,应该使用随机策略,因为这样可以更好地探索环境;而在评估阶段,应该使用确定性策略,因为最优策略总是一个确定的策略

      • 最优策略是确定性策略 ,证明:对于任意给定的状态 \(s\),有 \(\int_a \pi(a|s) q^*(s,a) d a \le q^*(s,a^*) \),其中 \(a^* = \mathop{\arg\max}_a q^*(s,a)\)
    • 个人理解:以上描述基本没有问题,但是加一些限定说明会更准确:
      • 状态观测完全的情况下,才可以说最优策略是确定性策略 :假设状态不是完全观测的,可能出现观测值相同,但实际上状态不同的情况,此时同一个部分观测状态对应的最优动作不再是唯一的)
      • 最优动作不唯一时,最优策略也可以是随机策略 :可能存在两个同时最优的确定性策略(决策的不同动作拿到相同的Q值),此时最优策略也可以是随机策略(即最优策略的混合策略)
      • 对于约束优化问题,可能最优策略是随机策略 :如果需要保证动作决策符合某个约束,该约束不在Reward中体现(即Q值无法体现约束),此时可能需要使用随机策略才能保证约束被满足(比如要求决策动作的期望满足一定均值约束,则不能总是执行Q值最大的策略;这里类似于状态不完全观测的情况,假如约束以及约束满足情况可以建模在Reward或者状态中,则可以看成是状态完全观测的情况,此时最优策略也是确定性策略)
      • 理论上,随机策略的搜索空间大于等于确定性策略 :理论上,随机策略可以学出来以概率1执行某个动作,按照随机策略建模也可以学到确定性策略;实际上,随机策略很难真正学到以概率1执行某个动作
      • 对于on-policy场景,需要保证行为策略和学习策略相同,故而无法实现学习策略是确定性策略,行为策略是随机性策略,实际操作中,可以开始引入随机策略,后续逐步变成确定性策略,比如SARSA策略
      • 对于Reward中包含策略熵的方法,无法使用确定性策略(加入熵是避免过拟合,增加探索,确定性策略容易过拟合,其探索性不足)
      • 为何许多优秀的模型都是随机策略模型?因为:1)建模时难以保证观测完全;2)随机性策略能探索到更多状态;3)随机策略不容易过拟合(在对Q估值无法很准确的时候,确定性策略容易过拟合)
  • 补充:关于书籍《Reinforcement Learning for Sequential Decision and Optimal Control》的博客
    • CSDN-StarMelt-清华大学李升波教授强化学习书籍《Reinforcement Learning for Sequential Decision and Optimal Control》系列读书笔记
    • 知乎-StarMelt-清华大学李升波教授强化学习书籍《Reinforcement Learning for Sequential Decision and Optimal Control》读书笔记
  • 在SARSA和Q-learning中,为了确保智能体能够在学习过程中充分地探索环境,并且最终收敛到最优策略,策略更新方法需要满足GLIE(Greedy in the Limit with Infinite Exploration,在无限探索极限下的贪心)策略,该策略包括两个特性:
    • 无限探索 :这意味着对于任何状态-动作对(s, a),智能体都有机会以非零的概率去尝试它(不会错过最优策略)
    • 渐进贪心 :随着学习过程的推进,智能体逐渐减少对非最优动作的选择
    • GLIE的一个实现是 \(\epsilon\) 逐渐减小的 \(\epsilon\)-Greedy ,比如可以设定 \(\epsilon = \frac{1}{t}\)

off-policy AC目标和on-policy AC目标等价吗?

  • off-policy AC方法的目标都是类似形式: \(\max_\pi \quad \mathbb{E}_{s\sim \rho^{\beta}(s)}[V^{\pi}(s)]\)
    • 基本含义就是,找一个最优的策略 \(\pi\),使得 \(V^{\pi}(s)\) 在行为策略的状态访问分布下期望最大
  • 实际上,on-policy AC方法的目标是类似形式: \(\max_\pi \quad \mathbb{E}_{s\sim \rho^{\pi}(s)}[V^{\pi}(s)]\)
    • 基本含义就是,找一个最优的策略 \(\pi\),使得 \(V^{\pi}(s)\) 在 策略 \(\pi\) 的状态访问分布下期望最大
  • 答案是如果策略空间包含所有策略,则两者等价,否则不等价 :
    • 如果策略模型空间是无限大的,一定存在最优的策略 \(\pi^*\) ,使得任何一个状态 \(s\) 下都有 \(V^{\pi^*}(s) \ge V^{\pi}(s), \forall{\pi}\) 成立,证明是:对于任意给定的状态 \(s\),有 \(\int_a \pi(a|s) q^*(s,a) d a \le q^*(s,a^*) \),其中 \(a^* = \mathop{\arg\max}_a q^*(s,a)\),此时,我们定义一个策略为:对任意状态 \(s\),都取该状态下的 \(a^*\) 即可
    • 如果能找到一个最优的策略 \(\pi^*\),使得在任何一个状态 \(s\) 下都有 \(V^{\pi^*}(s) \ge V^{\pi}(s)\) 成立,那么,我们找到策略 \(\pi^*\) 就同时是off-policy和on-policy的最优解
      • 这种情况下,如果能找到 \(\pi^*\),则两者等价(注意:即使存在 \(\pi^*\),模型也不一定能学到,如果在off-policy和on-policy设定下学到的难度不同,甚至策略模型空间受限,拟合不了最优的策略 \(\pi^*\),也不能说两者目标等价)
    • 如果模型空间限制导致模型空间中找不到一个最优的策略 \(\pi^*\),使得在任何一个状态 \(s\) 下都有 \(V^{\pi^*}(s) \ge V^{\pi}(s)\) 成立,那么,策略的选择就会根据状态访问分布有所取舍,更倾向于状态分布概率高的那部分状态,off-policy AC方法的目标与on-policy AC目标一定不等价,在状态分布不同的情况下,找不到一个共同的最优解 \(\pi^*\)
      • 此时,两者不等价,行为策略中出现越多的状态,越收到重视
    • 现实生活中,SAC和DDPG为什么可以正常运行且拿到不错的收益呢?
      • off-policy方法本身样本利用率更高些
      • SAC和DDPG都是在off-policy中使用,如果即时丢弃过早的数据,那行为策略分布和最优策略差异不会太大,且online的训练确保了模型见过几乎所有状态
      • 大部分场景中,只要策略模型空间够大,应当都可以认为存在最优的策略 \(\pi^*\),使得在任何一个状态 \(s\) 下都有 \(V^{\pi^*}(s) \ge V^{\pi}(s)\) 成立,或近似成立
  • 如果这里模型找不到最优的策略 \(\pi^*\),则这个问题和DQN的问题还不等价,DQN主要问题是训练模型时存在off-policy导致的线上线下分布不一致的问题,off-policy AC从目标上就存在线上线下不一致的问题,同时训练模型时也有线上线下不一致的问题(都是线上线下不一致,但是off-policy AC还多了目标上的不一致,是从根本上定义导致的偏差,理论上更严重些)

TRPO可以用确定性策略建模吗?

  • 不可以,理由是无法进行重要性采样了,无法推导出TRPO的更新公式,从这个视角看,PPO和TRPO都不可以

为什么A2C算法不需要目标网络(Target网络)?

Target Critic网络的作用和成本

  • Target Q 或 Target V 网络的引入主要优点是:防止网络波动
    • DQN初始的Target Q网络 :用于固定网络参数,防止Q网络更新的时候目标也一直发生改变,训练不收敛
    • 注:Double DQN(DDQN)中还有其他作用 :具体做法是从当前策略选动作,从Target网络取Q-value,缓解DQN的Q值高估问题(注意:DDQN相对普通DQN不需要增加网络)
  • Target Q 或 Target V 网络的引入主要有两个成本 :模型结构复杂,存储和计算成本增加、收敛速度慢
    • 模型结构复杂,存储和计算成本增加 :需要多维护一个网络,存储和计算成本都会增加
    • 收敛速度慢 :Target网络意味着延迟更新,让模型更稳定的同时,也会导致模型收敛速度变慢
  • 一般来说,需要Target Q/V 网络的地方都是为了防止网络波动 ,这种网络波动一般来自于自举(bootstrap)(比如来自贝尔曼方程更新中存在自举现象)
    • SAC(2V+2Q+1策略)的版本中,V网络更新依赖Q网络,Q网络更新依赖V网络,所以增加一个Target V网络,防止学习目标频繁波动导致Q网络波动
      • 注:Q网络是Twin Q网络,不是Target Q网络
    • SAC(4Q+1策略)的版本中,Q网络更新依赖Q网络自身,所以增加一个Target Q网络,防止学习目标频繁波动导致Q网络波动
      • 注:4个Q网络是2个Twin Q网络及他们两各自对应的Target Q网络
    • DDPG中,Q网络的更新依赖Q网络自身

为什么A2C/PPO算法存在自举也不需要Target Critic网络?

  • PPO的Critic网络更新分为几种
    • 仅使用累计收益的场景:V网络的更新不依赖V网络,V网络的学习是个普通的、基于收集到的Reward标签的监督学习,不存在自举现象,所以不需要使用Target V网络。如果使用Target V网络反而会增加存储和计算成本并降低收敛速度
    • 使用TD-Error的场景:将TD-Error \(r_t + \gamma V(s_{t+1}).detach() - V(s_t)\) 作为V网络的损失函数,此时则会出现自举现象,此时可以考虑使用Target V网络,但收敛速度会变慢
      • 注:经测试,此时使用Target V网络会导致训练速度变慢。说明在PPO中,即使存在自举现象,V网络也不会频繁波动(这不仅仅是因为on-policy,还跟PPO算法有关,因为SAC和DDPG在on-policy场景下也需要Target Q网络)
  • TODO:为什么A2C/PPO即使使用了TD-Error作为损失函数(存在自举现象),也不需要Target Critic网络?
    • 猜想1:因为V网络更新不敏感?Q网络更敏感?(注:SVC的V网络版本,本质也是为了防止Q值目标波动太快?)
    • 猜想2:因为DQN、DDPG和SAC都包含贝尔曼最优算子/公式的思想,而PPO/A2C不包含?

需要/不需要Target网络的本质原因

  • 结论:因为DQN、DDPG和SAC都包含贝尔曼最优算子/公式的思想,而PPO/A2C则不包含贝尔曼最优算子/公式的思想
  • 贝尔曼最优算子/方程下,自举更容易导致波动,目前需要使用Target网络的模型有,DQN,DDPG,SAC
    • DQN :相当于每一轮训练完Q网络以后,自动存在一个最大化当前Q值的最优策略,即使得当前策略下,Q值最大化;
    • DDPG :本质与DQN类似,也是学习一个Q网络以后,用策略网络学习一个确定性策略使得当前策略对应的Q值最大化,没有直接使用,但隐含了贝尔曼最优算子/方程的思想;
    • SAC :可以看做是DDPG的随机策略版本,本质也是一样的,学习到一个Q以后,使用策略网络去学习一个随机性策略使得当前随机策略下,Q值最大化;
    • 以上三者的Target目标 \(Q(s’,a’)\) 都隐含了最优策略对应的动作 \(a’ \approx \arg\max_{a’} Q(s’,a’)\)
  • 而PPO和A2C方法都没有贝尔曼最优策略的思想,他们的更新都是通过策略梯度法推导的梯度公式得到的,策略更新的目标也不是直接最大化当前Q值,而是借助Q值计算梯度,从而提升策略自己
    • 注:其实PPO和A2C策略的更新都会跟Q值或者A值(与Q值相关)有关,但PPO和A2C方法更新策略时不是直接最大化Q值的,而是借用Q值作为一个梯度权重,所以不能算是隐含了贝尔曼最优算子/公式的思想

其他说明

  • 一些文章也会提到PPO中也可以使用Target V网络来实现慢更新,或者使用Clip操作,以帮助稳定训练

一些相关的实验测试现象及结论

  • 现象1 :on-policy场景+PPO/A2C,使用 Target V 网络后,最终结果也可以收敛(且足够长的步骤后能收敛到最优策略),但是收敛速度变慢了(\(\tau\)值越小,收敛越慢)
    • 注意 :PPO不能直接改成 off-policy 训练 ,计算GAE需要每次得到的都是有序的一个episode才可以!off-policy 的样本状态之间不是同一个episode的有序结果(off-policy算法一般仅需要考虑一步 return 即可)
  • 现象2 :off-policy场景+SAC/DDPG,如果不使用 Target Q 网络(或 Target Q 网络更新速度(\(\tau\))值过大),都会导致DDPG效果非常差(其中SAC稍好一些)
  • 现象3 :on-policy场景+SAC/DDPG,如果包含 Target Q 网络 ,使用 on-policy 方法训练能收敛到好的模型(10-100倍episode内,不如off-policy的效果,但有收敛趋势),但需要的 episode 数量远远大于 off-policy 的训练方式;
  • 现象4 :on-policy场景+SAC/DDPG,如果去掉Target Q网络(或者调大Target Q网络更新速度) ,使用 on-policy 方法训练都无法收敛到好的模型(比随机效果好一些,然后一直在一个比较差的为止波动,学不到更多东西)
    • 注:此时还能观察到SAC和DDPG的target_value值是收敛到一个较差的值,持续在较差的位置波动,不收敛,方差较大(注:理解是因为Q网络波动,导致策略一直没学好,进而导致收集不到好的样本,Q网络也就学不好了);相比之下,正常的强化学习模型训练下,target_value值的值都应该是缓慢上升并最终收敛的过程(注:中间可能会先掉下来,但是最终肯定是缓慢收敛的,且收敛后方差较小)

Actor Critic框架中,网络参数应该共享吗?

  • 本质上可以将Actor和Critic两个任务类比为监督学习中多任务学习的模型,共享层能互相利用对方的信息,也容易被两个任务的梯度互相干扰,下面是一些通用的经验:
    • 应该共享的:稀疏高维的参数层,比如 Embedding 层可以共享,此时两者可以互相弥补在高维上的稀疏
    • 不应该共享的:稠密的参数层
  • 实际使用中还是以实验结果为准,不同场景效果不同

为什么强化学习训练总是不稳定?

  • 强化学习中的一个不稳定来源是:因为强化学习训练过程中策略一直在变化,状态分布也会随着策略不断变化
  • 特别地,强化学习中同时出现下面三个因素(也称为强化学习稳定性死亡三角(Deadly Triad Issue))时,更容易导致强化学习训练不稳定
    • 函数近似(Function Approximation) :函数近似能够处理大规模或连续状态空间的问题,但它会引入估计误差,导致值函数偏离真实值
    • 自举(Bootstrapping) :自举提高了计算和样本效率,但会导致估计值依赖于其他估计值,从而可能产生不一致性和震荡
    • 异策略训练(Off-policy Learning) :离策略学习可以利用历史数据(如经验回放),提高样本利用率,但可能导致目标策略与行为策略的分布不匹配,从而引入方差增大和收敛性问题
    • 总结:函数近似引入误差,自举放大误差,异策略训练使误差在策略不匹配的情况下进一步恶化,当三者同时出现时,误差会不断累积,导致值函数估计偏离真实值,最终使训练过程崩溃
  • 如何缓解“死亡三角”问题?
    • 目标网络(Target Networks) :在DQN中引入目标网络,稳定Q值的更新
    • 经验回放(Experience Replay) :减少样本间的相关性,使训练更稳定
    • 同策略学习(On-policy Methods) :如PPO、A2C,避免离策略带来的分布偏移问题
    • 正则化方法 :如保守Q学习(CQL),约束Q值估计范围,防止过度外推
    • 多步TD学习(n-step TD) :平衡自举和蒙特卡罗方法的优缺点,提高稳定性

为什么DDPG/TD3有Target Actor网络,而PPO/SAC没有?

副标题:什么场景下需要Target Actor网络?

  • DDPG/TD3需要用到当前策略来取下一个状态的动作来评估Q网络TD目标,此时使用的是Target Actor网络;
    • DDPG原始论文中提到,DDPG中Target Q和Target Actor网络都是必须的,有利于保证训练不发散(即训练稳定,直观理解是,如果策略被更新的同时也用来计算Q网络的TD目标,可能导致目标来回波动,网络不收敛的现象);当然,论文中提到这种设置可能导致训练收敛慢一些,但是稳定性的收益超过了这个地方
  • PPO/SAC也需要用到当前策略来取下一个状态的动作来评估Q网络TD目标,为什么不需要Target Actor网络呢?
    • SAC离散版本下,是通过采样动作来影响目标Q值的,不直接影响目标Q值;
    • SAC连续版本下,Actor网络看似是直接输出动作,且参与目标Q值计算的,但实际上是经过采样的(重参数化采样 ,可以回传梯度的采样方式)
    • PPO离散版本和连续版本下,均只需要学习V值,理论上Actor网络仅参与采样动作与环境交互,不参与生成目标V值
  • 总结:增加Target Actor网络的成本是学习会变慢;优点是学习会更稳定

什么场景下需要Twin Q网络?

  • Q值/V值更新时,如果用到Q网络,则需要使用Twin Q网络,可以降低对Q值的高估
  • Actor更新时也需要吗?什么时候需要呢?为什么SAC原作者开源代码没有使用,而其他实现使用了?
    • SAC(2V+2Q+1策略)版本:更新Q网络时用了Twin Q;更新Actor网络时仅使用其中一个网络(SAC(V)论文作者开源实现)
    • SAC(4Q+1策略)版本:更新Q网络时用了Twin Q;更新Actor网络时使用两个Q的均值(SAC论文作者开源实现)
    • 理解:Actor网络更新时使用均值可能更好些,相当于Bagging的思想,Actor更新的方向同时要让两个Q都更大

如何理解Bellman算子?

  • 函数、泛函、算子对比:
    • 函数:从一个集合的元素映射到另一个集合的元素
    • 泛函:从一个函数空间映射到数值空间(如实数或复数集)
    • 算子:从一个函数空间映射到另一个函数空间
  • Bellman算子:
    • 一般指从Q函数 \(Q(s,a)\) 映射到另一个Q函数 \(Q’(s,a)\)
    • 定义了一种函数映射到函数的映射关系

on-policy算法一般会使用多个epoch吗?

  • 一般的on-policy算法上,比如普通的Policy Gradient(AC或REINFORCE),理论上有问题,迭代一次以后梯度就不同了
  • 对于PPO这种有重要性采样的形式的损失函数,理论上可以更新多个epoch,主要原因有以下两点:
    • 更新一次epoch后,只要距离不是特别大,理论上不违反新旧策略距离
    • 更新一次epoch后,旧策略没有变(样本没有变化,收集样本使用的行为策略还是旧策略);此时新策略变了,但重要性采样的可以保证梯度更新没有问题
  • 对于PPO,使用多次更新可以提升样本利用率,但为避免过拟合陷入局部最优,也不建议更新太多轮次;实践时,不同场景、不同学习率下的最优epoch_num超参可能不同,原始 PPO 论文中提到使用过3,10,15这样的数字

on-policy算法可用Adam优化器?

  • 问题补充:Adam会使用到之前的梯度,但是on-policy要求梯度更新是当前策略采样,且基于当前策略计算出来的,在on-policy更新时可以使用Adam吗?
  • 回顾Adam优化器:
    • \(\theta_{t} = \theta_{t-1} - \frac{\lambda}{\sqrt{\tilde{v}_t+\epsilon}} \tilde{m}_t\)
    • \(\tilde{v}_t=\frac{v_{t}}{1-\beta_{1}^{t}}\)
    • \(\tilde{m}_t=\frac{m_{t}}{1-\beta_{2}^{t}}\)
    • \(\lambda\) 是外层学习率,实际使用中,常常可以通过指数衰减、固定步长衰减、余弦退火衰减等学习率衰减策略更新
    • 梯度的指数衰减: \(m_{t} = \beta_{2}m_{t-1}+(1-\beta_{2})g_{t}\)
    • 梯度平方的指数衰减: \(v_{t} = \beta_{1}v_{t-1}+(1-\beta_{1})g_{t}^{2}\)
  • 答案是可以的,Adam 优化器会在优化过程里利用梯度的一阶矩估计(均值)和二阶矩估计(方差)来动态地调整每个参数的学习率和更新方向 ,但是不会改变梯度计算的原始公式,即每一步计算当前梯度时,原始公式不变
    • Adam 优化器利用之前的梯度信息调整学习率部分 \(\frac{\lambda}{\sqrt{\tilde{v}_t+\epsilon}}\),不会改变当前梯度方向,不用担心
    • Adam 一阶动量部分 \(\tilde{m}\) 会改变梯度的方向 ,但是跟常规的监督学习模型一样 ,这里仅仅是一种加快模型收敛的优化手段,本质上每一步的梯度还是根据原始公式计算出来的(这一点对on-policy的强化学习或监督学习都一样)

策略迭代和值迭代谁更优?

  • 策略迭代(Policy Iteration) :一次策略迭代包含策略评估(Policy Evaluation)和策略提升(Policy Improvement)两个步骤,多次策略迭代至算法收敛找到最优策略
    • 策略评估(Policy Evaluation) :基于贝尔曼期望方程进行迭代至收敛
    • 策略提升(Policy Improvement) :基于策略评估结果优化当前策略
  • 值迭代(Value Iteration) :基于贝尔曼最优方程进行迭代,找到最优值函数,从而找到最优策略
  • 两者最终都会收敛到最优策略,此时都满足贝尔曼最优方程
  • 值迭代更像是一个贪心策略,收敛快,实现成本低,但是容易陷入局部最优;策略迭代收敛慢,但是一步步优化,更扎实,最终效果往往由于值迭代
    • 从经验上看,值迭代可以更快收敛,但效果一般不如策略迭代,比如DQN收敛更快,而A2C前期收敛慢,但后期效果往往高于DQN

策略梯度法中,计算奖励时为什么要丢弃过去时间片的奖励?

  • 理论上过去时间片的奖励加不加都不影响最终效果
  • 过去时间片的奖励期望为0,但方差不为0,加入以后只会影响策略的学习

折扣因子是否在鼓励较短路径?

  • 补充问题:在只有最终步有固定奖励的场景,假设奖励固定(比如二值奖励 0 or 1)且只在最后一步有 Reward,但到达最终状态的路径长度不同,此时使用小于 0 的折扣因子是否是在鼓励较短路径?
  • 结论:
    • 当 \(\gamma < 1\) 时,模型倾向于鼓励大于 0 的短路径和鼓励小于 0 的长路经
      • 当奖励为正值时,策略更倾向于短路径
      • 当奖励为负值时,策略更倾向于长路径(注意部分使用 Reward 归一化的场景也会导致一些奖励从正值或 0 值变成负值)
    • 当 \(\gamma = 1\) 时,模型没有长度倾向
  • 补充说明:
    • 一般来说,为了防止智能体浪费步骤(比如不断绕圈),在 Reward 设计中,我们常常会将中间步骤的每一步作为惩罚加入,这也是在鼓励短路径,但这里我们暂时不考虑这种情况(假设中间步骤没有任何奖励)
  • 注:当前的 LLM 训练中,一般都设置 \(\gamma=1\),即本身不鼓励也不抑制序列长度

Vanilla Policy Gradient 中的一些数值分析

  • 在考虑折扣因子 \(\gamma\) 时,当前状态到结束的累计折扣奖励为:
    $$ G_t = \sum_{t’=t} \gamma^{t’-t}r_{t’} $$
  • 由于奖励只发生在最后一步,则有:
    $$ G_t = \gamma^{T-t}r_{T} $$
    • 其中 \(T\) 是轨迹长度,这里我们假定 \(r_{T}\) 是固定的
    • 显然,如果轨迹长度 \(T\) 是不固定的,则对同样的第 \(t\) 步,轨迹越长,对应的 \(\gamma^{T-t}\) 越小
    • 当 \(\gamma = 1\) 时,累计奖励不变,所以模型本身没有倾向
  • 当奖励为正时(\(r_{T} > 0\)):
    • 此时 \(T\) 越大,累计奖励 \(G_t\) 越小,模型更倾向于 \(T\) 更小的短路径
  • 当奖励为负时(\(r_{T} < 0\)):
    • 此时 \(T\) 越大,累计奖励 \(G_t\) 越大,模型更倾向于 \(T\) 更大的长路径
  • 举例:
    • 奖励为正时的一个简单的例子:
      • 对于相同的状态 \(s_t\),假设有两个动作 \(a_1,a_2\),他们最终都会取得成功(最后一步奖励为 1),但是动作 \(a_1\) 对应的路径比动作 \(a_2\) 对应的路径长 ,则动作 \(a_1\) 对应的累计奖励比动作 \(a_2\) 对应的累计奖励小 ,模型会倾向于选择动作 \(a_2\)
    • 奖励为负时的一个简单的例子:
      • 同上设定,当奖励为负时,对相同的奖励,模型倾向于长路经(此时的累计奖励更大)

PPO(with GAE)

  • 当考虑使用 Advantage 时,或考虑 GAE 下的 PPO 时,问题可能会变得更复杂
    $$ A_t = r_t + V(s_{t+1}) - V(s_t) $$
    • 由于在当前设定下 \(r_t = 0\),所以:
      $$ A_t = V(s_{t+1}) - V(s_t)$$
    • 考虑到
      $$ V(s_t) = \mathbb{E}[G_t] $$
      • 则可以进一步去掉期望进行简单推导得到(不严谨)
        $$ A_t = \gamma^{T-(t+1)}r_{T} - \gamma^{T-t}r_{T} = (\gamma^{T-(t+1)}-\gamma^{T-t}) r_{T}$$
    • 显然,此时 \(t\) 不变的情况下 \(T\) 越大,两者差异 \((\gamma^{T-(t+1)}-\gamma^{T-t})\) 越小
      $$ \gamma^{T-(t+1)}-\gamma^{T-t} = \color{red}{\gamma^{T-t-1}}(1-\gamma) $$
      • \((1-\gamma)\) 为固定值,\(\color{red}{\gamma^{T-t-1}}\) 随 \(T\) 的增大逐渐变小
  • 基本结论
    • 当 \(\gamma < 1\) 时,(与 Policy Gradient 相似)模型倾向于鼓励大于 0 的短路径和鼓励小于 0 的长路经
      • 当奖励为正时(\(r_{T} > 0\)):
        • 此时 \(T\) 越大,\(A_t = (\gamma^{T-(t+1)}-\gamma^{T-t}) r_{T}\) 越小
      • 当奖励为负时(\(r_{T} < 0\)):
        • 此时 \(T\) 越大,\(A_t = (\gamma^{T-(t+1)}-\gamma^{T-t}) r_{T}\) 越大
    • 当 \(\gamma = 1\) 时,模型没有长度偏好

PPO 的连续版本和离散版本策略熵分别如何实现?

PPO 连续版本

  • 连续版本的熵一般用蒙特卡洛近似

    1
    2
    def compute_entropy(log_probs):
    entropy = -log_prob.mean() # 蒙特卡洛估计熵
  • 此时无法枚举所有动作,不使用蒙特卡罗近似难以计算熵

  • 由于数据就是当前策略采样的(PPO一次数据多轮更新下可能存在微小误差),所以对数据上的 -log_prob直接求均值就是熵的近似估计

PPO 离散版本中使用熵的原始定义估计

  • 遵循熵的定义直接定义即可:

    1
    2
    def compute_entropy(probs):
    return -torch.sum(probs * torch.log(probs + 1e-10), dim=-1) # 加1e-10防止log(0)
  • 部分实现时模型可能输出的是 log_probs,此时为:

    1
    2
    def compute_entropy(log_probs):
    return -torch.sum(log_probs.exp() * log_probs, dim=-1) # 加1e-10防止log(0)
  • 注:x.log() == torch.log(x)(以自然数为底的对数);x.exp() == torch.exp(x)

  • 为什么离散版本不继续采样蒙特卡罗估计?

    • 因为对所有动作求和更准确,且离散场景下该动作是可行的

对照 SAC 中熵的计算方式

  • 回顾SAC离散策略的的更新目标 :
    $$
    J_\pi(\phi) = \mathbb{E}_{s_t \sim \mathcal{D}, a_t\sim\pi_\phi} [ \alpha \log \pi_\phi(a_t \vert s_t) - Q_\theta(s_t, a_t) ]
    $$
  • 回顾SAC连续动作的策略更新目标 ,首先需要采用重参数法建模连续动作 \(a_t = f_\phi(\epsilon_t; s_t)\),然后有:
    $$
    J_\pi(\phi) = \mathbb{E}_{s_t \sim \mathcal{D}, \epsilon_t \sim \mathcal{N}}[\alpha \log \pi_\phi(f_\phi(\epsilon_t;s_t)\vert s_t) - Q_\theta(s_t, f_\phi(\epsilon_t; s_t))]
    $$
  • SAC 实现时,与 PPO 中类似,连续版本用蒙特卡罗估计,离散版本积分求出
  • 问题:SAC 方法是 off-policy 的,连续版本中,使用蒙特卡罗估计得到的熵是有偏的?
    • 回答:不是有偏的,SAC 中的数据是从其他策略采样来的,但是计算熵时使用的动作是从当前策略分布采样出来的(重参数法)
    • PS:可以从更新公式中看到,更新 Policy 网络时,仅状态 \(s\) 是从历史数据集中采样的,更新 Critic 网络时,则使用的是从历史数据采样的 \((s,a)\)

RL 中 Reward 的线性变换如何影响梯度?

对于做 Reward 归一化的场景

  • 如果做了 Reward 归一化,理论上减去均值并除以方差,可以将 Reward 的 线性变化消去,此时不会造成任何影响,但要求线性变化的系数要大于 0
  • 补充说明:因为对于 \(Y = kX + b\),有 \(X, Y\) 分别做归一化得到的结果是不一定相等的
    • 当 \(k>0\) 时:归一化后的结果完全一样 (Advantage 完全一样)
    • 如果 \(k < 0\):归一化后的结果互为相反数(符号相反,绝对值相同)
    • 当 \(k=0\) 时:\(Y\) 为常数 0
  • 证明:
    • 假设对 \(X\) 进行归一化(Z-score standardization),结果记为 \(Z_X\):
      $$Z_X = \frac{X - \mu_X}{\sigma_X}$$
    • 对于 \(Y = kX + b\):
      $$
      \mu_Y = E[kX + b] = kE[X] + b = k\mu_X + b \\
      Var(Y) = Var(kX + b) = k^2 Var(X) = k^2 \sigma_X^2 \\
      \sigma_Y = \sqrt{k^2 \sigma_X^2} = |k|\sigma_X
      $$
    • 对 \(Y\) 进行归一化,结果记为 \(Z_Y\):
      $$Z_Y = \frac{Y - \mu_Y}{\sigma_Y}$$
    • 将 \(Y, \mu_Y, \sigma_Y\) 代入公式:
      $$Z_Y = \frac{(kX + b) - (k\mu_X + b)}{|k|\sigma_X}$$
    • 化简分子(\(b\) 被抵消):
      $$Z_Y = \frac{kX - k\mu_X}{|k|\sigma_X} = \frac{k(X - \mu_X)}{|k|\sigma_X}$$
    • 整理公式,提取出 \(Z_X\) 的部分:
      $$Z_Y = \frac{k}{|k|} \cdot \left( \frac{X - \mu_X}{\sigma_X} \right)$$
    • 即:
      $$Z_Y = \text{sign}(k) \cdot Z_X$$

对于未做 Reward 归一化的场景 - Policy Gradient

REINFORCE
  • 对于普通策略梯度法,因为累计收益是梯度系数,故而会放大或缩小梯度系数(Reward 变化时需要调整学习率)
    $$
    \begin{align}
    \nabla_\theta J(\theta) &\approx \frac{1}{N}\sum_{n=1}^N \sum_{t=1}^{T_n} R(\tau^n) \nabla_\theta \log \pi_\theta(a_t|s_t) \\
    &\approx \frac{1}{N}\sum_{n=1}^N \sum_{t=1}^{T_n} G_t^n \nabla_\theta \log \pi_\theta(a_t|s_t)
    \end{align}
    $$
PPO 等使用 Advantage 的策略梯度法
  • 对于 PPO,由于梯度更新系数与 Advantage 有关,而 Advantage 可能受到 Reward 影响,故而需要考虑这个 Reward 本身对 Advantage 的影响
  • 结论先行:Reward 线性变换会对 Advantage(优势函数)的数值产生直接影响,但对最终训练效果的影响还取决于是否使用了 Advantage Normalization(优势归一化)
  • GAE 估计下的 \(A_t\) 是 \(\delta_t\) 的加权和:
    $$ A_t = \sum_{l=0}^{\infty} (\gamma \lambda)^l \delta_{t+l} $$
    • \(\delta_t\) 为 TD Error
      $$ \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) $$
    • 其中 \(V(s)\) 是状态价值函数,它拟合的是期望回报(Expected Return)
    • 为了简单起见,下面拆成缩放和平移两种情况进行分析
  • 情况 A: 对 Reward 进行缩放 (乘以常数 \(c\))
    • 假设 \(r’_t = c \cdot r_t\)
    • 1)Value Function 变化 :因为 \(V\) 拟合的是累积 Reward,所以理想情况下,新的 Value Function \(V’\) 会收敛到 \(c \cdot V\)
    • 2)TD Error 变化 :
      $$ \delta’_t = c \cdot r_t + \gamma (c \cdot V(s_{t+1})) - (c \cdot V(s_t)) = c \cdot \delta_t $$
    • 3)Advantage 变化 :
      $$ A’_t = c \cdot A_t $$
      • 结论 :Advantage 的数值会严格按照比例 \(c\) 缩放
  • 情况 B: 对 Reward 进行平移 (加减常数 \(b\))
    • 假设 \(r’_t = r_t + b\)
    • 1)Value Function 变化 :
      $$ V’(s) = \mathbb{E}[\sum \gamma^k (r_{t+k} + b)] = V(s) + \sum \gamma^k b = V(s) + \frac{b}{1-\gamma} $$
      • 注:这是在无限视界下的理论值,如果是有限步长,则是一个随步数变化的偏移量
    • 2)TD Error 变化 :
      $$
      \begin{align}
      \delta’_t &= (r_t + b) + \gamma (V(s_{t+1}) + \frac{b}{1-\gamma}) - (V(s_t) + \frac{b}{1-\gamma}) \\
      &= r_t + \gamma V(s_{t+1}) - V(s_t) + \left( b + \frac{\gamma b}{1-\gamma} - \frac{b}{1-\gamma} \right) \\
      &= r_t + \gamma V(s_{t+1}) - V(s_t) + \left( b + \frac{b(\gamma - 1)}{1-\gamma} \right) \\
      &= r_t + \gamma V(s_{t+1}) - V(s_t) + \left( b - b \right) \\
      &= r_t + \gamma V(s_{t+1}) - V(s_t) = \delta_t \\
      \end{align}
      $$
    • 3)Advantage 变化 :
      $$ A’_t = A_t $$
      • 结论 :理论上,对 Reward 进行加减常数操作,Advantage 的值是不变的(因为常数项在差分计算中被抵消了)
附录:对最终训练的影响
  • Advantage Normalization:标准的 PPO 实现(如 OpenAI Baselines, Stable Baselines3, CleanRL 等)在将 Advantage 输入到 Policy Loss 之前,都会进行 归一化处理 :
    $$ A_{norm} = \frac{A - \text{mean}(A)}{\text{std}(A) + \epsilon} $$
    • 注:在标准的 PPO 实现中(如 OpenAI Baselines, Stable Baselines3, CleanRL),Advantage 的归一化通常 不是 使用滑动平均(Running Mean/Std) ,而是基于 当前采集的整个 Batch(Rollout Buffer) 进行统计计算的
  • 如果是缩小(乘以小于 1 大于 0 的值) :
    • 如果开启了归一化:\(\frac{c \cdot A}{c \cdot \text{std}} = \frac{A}{\text{std}}\)。完全没有影响
    • 如果未开启归一化:Advantage 变小,Policy Gradient 的梯度变小,相当于降低了 Policy 的学习率
  • 如果是平移(比如减去 5) :
    • 理论上 Advantage 不变,所以 Policy 的更新方向不变
    • 但是(重要隐患) :Value Network 的拟合难度会剧增
      • 假设 \(\gamma = 0.99\),Reward 减去 5,意味着 Value Function 的目标值整体偏移了 \(\frac{-5}{1-0.99} = -500\)
      • 神经网络通常初始化在 0 附近。如果 Target Value 突然变成了 -500,Value Loss 会变得巨大
      • 这会导致 Value Network 的梯度非常大,如果 Policy 和 Value 共享部分网络层(Shared Backbone),巨大的 Value Loss 梯度会破坏 Policy 的特征提取能力,导致训练崩溃
    • 此时变化很大的原因是每一步都有 Reward,所以整体被放大了,如果中间 Reward 都为 0,只有最终的一个 Reward,则平移 -5 带来的累计 Reward 变化也只有 -5
附录:PPO 最佳实践建议:
  • Reward Scaling(推荐) :将 Reward 乘以一个系数,使其方差维持在较小范围(例如 1 左右),这对神经网络训练非常有利
  • Reward Clipping(推荐) :直接将 Reward 截断在 \([-10, 10]\) 之间,防止异常值破坏训练
  • 避免 Reward Shifting :尽量不要对 Reward 做大的加减平移(除非是为了处理生存奖励等特定逻辑),因为这会通过 \(\frac{1}{1-\gamma}\) 放大 Value 的数值范围
  • 始终使用 Advantage Normalization :这能让 PPO 对 Reward 的缩放不敏感,保证训练的稳定性

对于未做 Reward 归一化的场景 - Q-Learning

  • Q-Learning 学习的是 Reward 的绝对值,最终收敛后使用的策略是 argmax,所以理论上平移不会影响 RL 的最终结论
  • 除了 Reward 太大可能导致初始化值到收敛到最优 Reward 需要一点时间,外,不会有别的影响
  • 注意:Policy Gradient 中的 Value 学习一样,随意调整 Reward 对价值函数是一个挑战

不同算法下 Critic 损失的定义方式

  • Actor Critic 方法的 Critic 损失函数:
    $$
    Loss_{\text{critic}} = \sum (r_t + \gamma Q^{\bar{w}}(s_{t+1}, a_{t+1}) - Q^{w}(s_{t}, a_t)) ^ 2
    $$
  • A2C(Advantage Actor Critic)算法引入了优势函数(Advantage),这也是 A2C 名字的由来,A2C 算法中,Critic 网络的损失函数:
    $$
    Loss_{\text{critic}} = \sum (r_t + \gamma V^{\bar{w}}(s_{t+1}) - V^{w}(s_{t})) ^ 2
    $$
  • PPO(with GAE)中,Critic 也是通过最小化 MSE 来逼近回报估计值 \(V_{target}\)(注:这个目标相对 A2C 中的 \(r_t + V(s_t)\) 方差更小)
    • Critic 的拟合目标 \(V_{target}\) 通常被设定为 \(V_{target} = V_{old}(s_t) + A^{GAE}_t\)(即估计的真实回报 Returns)
      $$
      Loss_{\text{critic}} = \sum (V_{old}(s_t) + A^{GAE}_t - V^{w}(s_{t})) ^ 2
      $$
    • PPO 中 Critic 的一般更新过程为:
      • step 1: Rollout 并最终得到奖励
      • step 2: 计算每个 token 步骤的 TD-Error(\(\delta_t\))
      • step 3: 利用 \(\delta_t\) 计算 GAE 优势
      • step 4: 最小化 \(Loss_{\text{critic}}\) 更新 Critic 参数

RL——模仿学习

  • 参考链接:模仿学习(Imitation Learning)概述

模仿学习介绍

  • 定义 :模仿学习(Imitation Learning)是一个广义的概念,模仿学习是一种通过观察专家的行为来学习如何执行任务的机器学习方法
  • 特点 :在模仿学习中,学习算法试图复制专家的行为,而不是直接优化一个明确指定的目标函数
  • 应用场景 :这种方法可以用于解决那些难以定义奖励函数的问题
  • 分类 :一般包含了行为克隆(Behavioral Cloning,BC)、逆向强化学习(Inverse Reinforcement Learning, IRL)和生成对抗模仿学习(Generative Adversarial Imitation Learning,GAIL)等

行为克隆

  • 定义 :从一组专家示范的数据集中,通过监督学习方法来训练策略模型(预测给定状态下的最佳动作)。类似于传统监督学习中的分类或回归问题,其中输入是环境的状态,输出是专家采取的动作
  • 优点 :简单易用,当专家策略特别好,且数据量充足时往往会有不错的效果
  • 缺点 :无法很好地处理训练数据中未见过状态

逆强化学习

  • 定义 :从观察到的专家策略行为中推断出奖励函数,然后根据该奖励函数训练标准强化学习模型
  • 理解 :逆强化学习的目标是从观察到的专家策略行为中推断出奖励函数,这样就可以理解专家是如何评估不同行为的好坏的。一旦获得了这个奖励函数,就可以使用标准的强化学习技术来找到最优策略。与模仿学习和行为克隆相比,IRL试图更深入地理解专家决策背后的逻辑,而不仅仅是复制其行为

生成对抗模仿学习

  • 定义 :基于生成对抗网络的模仿学习,包含两个网络,生成器(Generator)和判别器(Discriminator),生成器就是策略,负责预测特定状态下的动作,判别器用于判断该动作是否由专家策略生成
  • 训练过程(循环以下流程) :
    • 生成器与环境交互生成轨迹
    • 判别器训练:判别目标是判断状态动作对 \((s,a)\) 是否由专家策略产生,损失函数是专家数据分数最大化、生成器数据分数最小化
    • 将判断器作为Reward Model训练生成器
  • 训练到最后,生成器生成的轨迹应该和专家轨迹接近

RL——强化学习相关概念汇总


Episodic Task 和 Continuing Task

  • Episodic Task :(分幕任务)环境有明确的终止状态(如游戏通关、任务完成),每个独立的交互片段称为一个 episode
    • 例如:象棋游戏等
  • Continuing Task :(持续任务)环境无终止状态,智能体需无限持续交互。无步长限制(即没有明确终止状态或时间限制)的环境称为 Continuing Task(持续任务)
    • 例如:长期运行的自动化控制系统等
    • 在持续任务中,通常需要引入 折扣因子(γ) 来确保回报的无穷和有界
    • 术语 “Non-episodic“ 或 “Infinite-horizon“ 也偶尔用于描述这类场景,但 Continuing Task 是强化学习领域的标准术语(参考 Sutton & Barto 的《Reinforcement Learning: An Introduction》)

Episode、Rollout 和 Trajectory

  • Episode(回合) :一个完整的交互过程,从初始状态开始,到终止状态结束(适用于Episodic Task)
    • 明确的时间边界(如游戏的一局、机器人完成一次任务),Episodic Task任务才有明确的终止状态
    • 通常用于有终止条件的任务(例如:Atari游戏通关、机器人到达目标)
  • Rollout(展开、推演) :包含从当前状态(或某个特定状态)到终止状态的完整过程,和Episode的区别是可以不从初始状态开始
  • Trajectory(轨迹) :智能体在环境中产生的一系列状态、动作、奖励的序列,形式化为:
    $$
    \tau = (s_0, a_0, r_1, s_1, a_1, r_2, \dots)
    $$
    • 更通用的术语,可指完整或部分序列 ,更侧重强调行为路径和决策序列
    • 既可用于Episodic Task(一个 trajectory 对应一个 episode),也可用于持续任务(Continuing Task ,无终止状态)
    • 在强化学习理论和推导中常用(如策略梯度方法中的轨迹优化)

State 和 Observation

  • State(状态) :对环境的完整描述称为状态
  • Observation(观测) :对环境(或状态)的部分描述称为观测
  • 对比:状态包含了环境的全部信息,能够完全确定环境的当前情况以及未来的演化趋势(在给定策略和环境动态的情况下);观测则往往是不完整的、有噪声的,可能丢失了一些对决策重要的信息
  • 说明:大部分论文中不会严格说明是否获取到了环境的完整描述,会使用 State 来表述观测 Observation

Fully Observable 和 Partially Observable

  • Fully Observable :如果智能体能看到完整的环境描述,则称环境为完全可观测的
    • 在完全可观测的环境中,智能体能够直接获取到环境的完整状态信息,即智能体观测到的内容与环境的真实状态完全一致,不存在任何隐藏信息或不确定性
  • Partially Observable :如果智能体能看到部分的环境描述,则称环境为不完全可观测的
    • 在部分可观测的环境中,智能体只能获得环境状态的部分信息,无法直接观测到环境的真实状态。这种情况下,智能体需要根据有限的观测信息来推断环境的状态,进而做出决策

Reward 和 Return

  • Reward(奖励) :关注的是当前时刻的单一行动所获得的反馈,是一个即时的、局部的概念
  • Return(回报) :从当前状态开始的整个未来序列的累计奖励情况,是一个长期的、全局的概念

State Transition 和 Dynamics

  • State Transition(状态转移) :主要关注智能体的状态如何从一个时刻转变到下一个时刻,是 Dynamics(动力学)的一个部分,一般用状态转移概率矩阵或状态转移函数来表示
    • 离散空间状态转移通常可表示为 \(p\)
  • Dynamics(动力学) :通常指环境的动态变化规律,它描述了环境如何随着时间步的推进以及智能体的行动而演变。动力学不仅包括状态转移(State Transition)关系,还涵盖了环境中各种因素的变化规律,如奖励的生成机制、环境中其他实体的行为模式等
    • Dynamics(动力学)通常用一组复杂的方程或规则来描述,可能涉及多个变量和参数,并且可能需要考虑环境中的各种随机因素和外部干扰

MP、MRP、MDP、POMDP、Contextual MDP

  • 马尔可夫性(Markov property) :马尔可夫性是指在一个随机过程中,系统在未来时刻的状态只取决于当前时刻的状态,而与过去的历史状态无关
  • MP(Markov Process) :即马尔可夫过程 ,也称为马尔可夫链。形式上,一个马尔可夫过程可以用一个二元组 \((S, P)\) 表示
    • 其中 \(S\) 是状态空间, \(P\) 是状态转移概率矩阵, \(P_{ss’}=P(s_{t + 1}=s’|s_t = s)\) 表示在时刻 \(t\) 处于状态 \(s\) ,在下一时刻 \(t + 1\) 转移到状态 \(s’\) 的概率,它是一个无记忆的随机过程,具有马尔可夫性质,即未来的状态只取决于当前状态,与过去的历史无关
  • MRP(Markov Reward Process) :马尔可夫奖励过程 ,是在马尔可夫过程的基础上增加了奖励信号。它是一个四元组 \((S, P, R, \gamma)\)
    • 其中 \(S\) 是状态空间, \(P\) 是状态转移概率矩阵, \(R\) 是奖励函数, \(R(s)=E[r_{t + 1}|s_t = s]\) 表示在状态 \(s\) 下获得的即时奖励, \(\gamma\) 是折扣因子,用于衡量未来奖励的重要性,取值范围通常在 \([0, 1]\) 之间
  • MDP(Markov Decision Process) :马尔可夫决策过程 ,它在马尔可夫奖励过程的基础上增加了决策动作 ,是一个五元组 \((S, A, P, R, \gamma)\)
    • 其中 \(S\) 是状态空间, \(A\) 是动作空间, \(P\) 是状态转移概率矩阵, \(P_{ss’}^a = P(s_{t + 1}=s’|s_t = s, a_t = a)\) 表示在状态 \(s\) 下执行动作 \(a\) 后转移到状态 \(s’\) 的概率, \(R\) 是奖励函数, \(R(s, a)=E[r_{t + 1}|s_t = s, a_t = a]\) 表示在状态 \(s\) 下执行动作 \(a\) 获得的即时奖励, \(\gamma\) 是折扣因子。智能体通过选择不同的动作来最大化长期累积奖励
  • POMDP(Partially Observable Markov Decision Process) :部分可观测马尔可夫决策过程 ,在POMDP中,智能体不能直接观测到环境的真实状态,只能通过观测函数获得部分观测信息。它是一个七元组 \((S, A, O, P, R, \gamma, Z)\)
    • 其中 \(S\) 、 \(A\) 、 \(P\) 、 \(R\) 、 \(\gamma\) 与MDP中的含义相同, \(O\) 是观测空间, \(Z\) 是观测函数, \(Z(o|s, a)=P(o_{t + 1}=o|s_{t + 1}=s, a_t = a)\) 表示在状态 \(s\) 下执行动作 \(a\) 后,观测到 \(o\) 的概率。POMDP比MDP更具挑战性,因为智能体需要根据不完整的观测信息来做出决策
  • Contextual MDP(Contextual Markov Decision Process) :上下文马尔可夫决策过程 ,Contextual MDP可以表示为六元组 \((S, A, C, P, R, \gamma)\)
    • 其中 \(C\) 是上下文空间,状态转移概率 \(P\) 和奖励函数 \(R\) 都与上下文 \(c\) 有关,即 \(P_{ss’}^a(c)=P(s_{t + 1}=s’|s_t = s, a_t = a, c_t = c)\) , \(R(s, a, c)=E[r_{t + 1}|s_t = s, a_t = a, c_t = c]\)。它在MDP的基础上增加了上下文信息,即状态不仅取决于当前的环境状态,还与一些外部的上下文因素有关。例如,在推荐系统中,用户的偏好和行为可能受到当前的时间、地点、用户历史记录等上下文因素的影响。智能体需要根据当前的上下文信息来选择合适的动作,以最大化长期累积奖励

MC、TD、DP

  • 蒙特卡罗方法(Monte-Carlo,MC):通过完整轨迹的采样回报估计值函数,依赖实际经验,无需环境模型
  • 动态规划(Dynamic Programming,DP):基于模型的全宽度(Full-width)回溯更新,利用贝尔曼方程迭代求解值函数,需已知环境动力学
  • 时序差分(Temporal-Difference,TD):结合当前奖励和后续状态的价值估计来更新当前状态
  • 三者比较如下:

Bellman Operator

  • 贝尔曼算子(Bellman Operator),又称为贝尔曼Backup算子(Bellman Backup Operator)
  • 一句话理解:贝尔曼算子是一种函数到函数的映射,Bellman的核心原理是利用当前状态的奖励和后续状态的价值来更新当前状态的价值估计
  • 其本质可以表达为“当前状态的价值 = 即时奖励 + 未来价值的折扣期望”
  • 贝尔曼算子作用到Q函数上:
    $$ \mathcal{T^\color{red}{\pi}}Q^{k+1}(s,a) = \mathbb{E}_{s’\sim P(\cdot|s,a)}[r(s,a) + \gamma\mathbb{E}_{a’\sim \color{red}{\pi}(\cdot|s’)}[Q^k(s’,a’)]] $$
    • 以上迭代公式走下去,Q值会收敛到:\(Q^\pi(s,a)\)
  • 贝尔曼算子作用到V函数上:
    $$ \mathcal{T^\color{red}{\pi}}V^{k+1}(s) = \mathbb{E}_{a \sim \color{red}{\pi}(\cdot|s)}[r(s,a) + \gamma\mathbb{E}_{s’\sim P(\cdot|s,a)}[V^k(s’)]] $$
    • 以上迭代公式走下去,Q值会收敛到:\(V^\pi(s)\)

SMDP

  • 半马尔可夫决策过程(Semi-Markov Decision Process, SMDP)是马尔可夫决策过程(MDP)的扩展,专门用于建模动作持续时间可变的决策问题
  • 在标准 MDP 中,每个动作在一个时间步内完成,而 SMDP 允许动作持续多个时间步,这在现实世界应用中更为常见,引入 SMDP 的核心原因是:
    • 时间抽象:许多真实世界的决策涉及持续时间不同的动作
    • 层次决策:高层决策可能对应一系列底层动作的执行
    • 计算效率:在更高时间尺度上进行规划可减少决策频率
  • 一个 SMDP 可以表示为五元组:\( (S, A, P, R, F) \)
    • \( S \):状态空间(有限或可数)
    • \( A \):动作空间(有限或可数)
    • \( P \):状态转移函数,包含时间维度
    • \( R \):奖励函数,考虑累积折扣奖励
    • \( F \):动作持续时间分布
  • 在 SMDP 中,转移概率包含时间维度 :
    $$
    P(s’, \tau | s, a) = \mathbb{P}(S_{t+\tau} = s’, \tau | S_t = s, A_t = a)
    $$
    • 其中 \( \tau \in \mathbb{Z}^+ \) 是动作执行的时间步数
    • 作为对比:在传统的 MDP 中,转移概率为:
      $$
      P(s’ | s, a) = \mathbb{P}(S_{t+1} = s’ | S_t = s, A_t = a)
      $$
  • SMDP 的状态-动作转移概率:
    • 当在状态 \( s \) 执行动作 \( a \) 时,系统以概率 \( P(s’, \tau | s, a) \) 在 \( \tau \) 步后转移到状态 \( s’ \)
  • SMDP 的累积折扣奖励:不是每个时间步单独获得,而是在动作执行期间累积:
    $$
    r(s, a) = \mathbb{E} \left[ \sum_{k=0}^{\tau-1} \gamma^k R_{t+k} \mid S_t = s, A_t = a \right]
    $$
    • \( \tau \) 是动作 \( a \) 的持续时间
    • \( R_{t+k} \) 是在时间步 \( t+k \) 获得的即时奖励
    • \( \gamma \in [0, 1] \) 是折扣因子
  • SMDP 下的贝尔曼最优方程
    • 状态值函数:
      $$
      V^*(s) = \max_{a \in A} \left[ r(s, a) + \sum_{s’, \tau} \gamma^\tau P(s’, \tau | s, a) V^*(s’) \right]
      $$
    • 动作值函数:
      $$
      Q^*(s, a) = r(s, a) + \sum_{s’, \tau} \gamma^\tau P(s’, \tau | s, a) \max_{a’ \in A} Q^*(s’, a’)
      $$
    • 注意:折扣因子 \( \gamma \) 的指数是 \( \tau \)(动作持续时间),这与 MDP 中每个时间步都打折不同
  • SMDP的求解方法(Q-learning 的 SMDP 扩展),即SMDP Q-learning 更新规则:
    • 当在状态 \( s \) 执行动作 \( a \),经过 \( \tau \) 步后到达状态 \( s’ \) 并获得累积奖励 \( r \) 时:
      $$
      Q(s, a) \leftarrow Q(s, a) + \alpha \left[ r + \gamma^\tau \max_{a’} Q(s’, a’) - Q(s, a) \right]
      $$

Option 相关概念

Option 的基本定义

  • Option(选项)框架是实现层级强化学习(Hierarchical Reinforcement Learning, HRL) 的核心机制
    • Option 由 Sutton, Precup 和 Singh 在 1999 年正式提出,旨在解决传统 RL 在处理长路径、复杂任务时的“维度灾难”
  • Option 是一种形式化的时间抽象机制,允许智能体在更高的时间尺度上进行规划和决策
    • 一个 Option 可以看作是一个可重用的技能或子策略,它能够在一段时间内自主执行,直到满足特定的终止条件
  • 简单来说,Option 的本质就是一种 “宏动作”(Macro-action)
    • 比如“回家”这个任务,传统的动作是“左转”、“右转”、“迈步”,而 Option 则是“出办公室”、“下楼”、“开车”等一系列子策略
    • 下面的概念理解中,可以将 Option 当做一种 “抽象高阶动作” 来看
  • Option 允许在更长的时间尺度上进行规划;学习到的技能可以在不同任务中重复使用;可通过高层策略加速探索过程;
  • Option 通常对应有意义的子任务或技能
  • Option 常用于层次强化学习中:构建多层次的决策体系

Option 的数学定义

  • 一个 Option \( \omega \in \Omega \) 是一个三元组,由三个部分组成:
    $$
    \omega = \langle I_\omega, \pi_\omega, \beta_\omega \rangle
    $$
    • 起始状态集 \( I_\omega \subseteq S \): Option 可以被调用的状态集合
      • 表示在哪些状态下可以启动这个 Option,启动这个 Option 相当于是做了一个 Action
    • 内部策略 \( \pi_\omega : S \times A \rightarrow [0,1] \):Option 执行期间遵循的策略
      • 即该 Option 运行时,Agent 采取动作的概率分布
      • 注意:每个 Option \(\omega\) 对应的策略 \(\pi_\omega\) 不同,也就是说,对于相同的状态 \(s_t\),若其 Option \(\omega\) 不同,则选择的动作 \(a_\omega \sim \pi_\omega(\cdot|s_t)\) 也不同
    • 终止函数 \( \beta_\omega : S \rightarrow [0,1] \):表示在给定状态下 Option 终止的概率
      • 即在某个状态下,该 Option 终止的概率
      • 理解:在有些状态下,可以认为是这个 Option 的目标(如下楼 )已经达到了,所以可以退出这个 Option 了,有些状态下则还没达到,不能退出

Option 的执行过程

Option 的调用和执行
  • 当智能体在状态 \( s_t \) 且 \( s_t \in I_\omega \) 时,可以选择执行一个 Option \( \omega \)
  • 在选择确定的 Option \( \omega \) 后,后续的动作选择如下:
    • 1)智能体按照 Option \( \omega \) 对应的策略 \( \pi_\omega \) 选择动作 Action 并与环境多次交互,直到 Option 终止
    • 2)在每个时间步 \( t’ \) 时,Option 都以概率 \( \beta_\omega(s_{t’}) \) 终止
      • 注:不同状态的终止概率不同,极端情况下,有的状态表示已经达成 Option 的目标,则结束概率是 1,有的状态表示完全未完成目标,结束概率是 0;
      • 理解:这有点类似于状态转移,但不完全相似

Option 的马尔可夫性

  • 尽管单个 Option 的内部执行(按策略 \( \pi_\omega \) 选择动作 Action 并与环境多次交互)可能依赖于历史,但在半马尔可夫决策过程(Semi-Markov Decision Process, SMDP) 的框架下,Option 的选择可以被视为一个马尔可夫决策过程
  • 注:SMDP 是 MDP 的扩展,专门用于建模动作持续时间可变的决策问题
    • 在标准 MDP 中,每个动作在一个时间步内完成
    • SMDP 允许动作持续多个时间步,这在现实世界应用中更为常见

Option 框架下的值函数

Option 的价值函数
  • 对于一个 Option \( \omega \),其状态 \( s \) 的价值函数定义为从状态 \( s \) 开始执行该 Option 的期望回报:
    $$
    V^\omega(s) = \mathbb{E} \left[ r_{t+1} + \gamma r_{t+2} + \cdots + \gamma^{k-1} r_{t+k} + \gamma^k V(s_{t+k}) \mid \mathcal{E}(\omega, s, t) \right]
    $$
    • 其中 \( \mathcal{E}(\omega, s, t) \) 表示在时间 \( t \) 从状态 \( s \) 开始执行 Option \( \omega \),\( k \) 是 Option 执行的步数

SMDP 贝尔曼方程

  • 在 Option 框架下,SMDP 的贝尔曼方程为(理解:本质就是把 Option 的选择看做是一种动作的选择,从而构成贝尔曼方程):
    $$
    V(s) = \max_{\omega \in \Omega_s} \left[ R(s,\omega) + \sum_{s’} \gamma^\tau P(s’ \mid s,\omega) V(s’) \right]
    $$
    • \( \Omega_s \) 是在状态 \( s \) 下可用的 Option 集合
    • \( R(s,\omega) \) 是从状态 \( s \) 执行 Option \( \omega \) 的期望累积折扣回报
    • \( P(s’ \mid s,\omega) \) 是从状态 \( s \) 执行 Option \( \omega \) 后到达状态 \( s’ \) 的概率
    • \( \tau \) 是 Option 执行的期望步数

分层学习:Intra-Option 学习(Option 内部学习)

  • Intra-Option 学习关注于改进单个 Option 的内部策略 \( \pi_\omega \),而不改变 Option 的选择策略
  • 这可以通过各种策略梯度方法实现:
    $$
    \nabla_\theta J(\pi_\omega) = \mathbb{E}_{s \sim \rho^\omega, a \sim \pi_\omega} \left[ \nabla_\theta \log \pi_\omega(a|s) Q_U(s,\omega) \right]
    $$
    • 其中 \( Q_U(s,\omega) \) 是执行 Option \( \omega \) 的动作值函数
    • 整个优化过程会优化策略 \(\pi_\omega\) 本身,但不会优化选择 \(\omega\) 的概率分布(或高阶策略)

分层学习:Inter-Option 学习(Option 间学习)

  • Inter-Option 学习关注于学习如何在不同 Option 之间进行选择,即学习一个上层策略 \( \mu : S \times \Omega \rightarrow [0,1] \):
    $$
    Q_\Omega(s,\omega) = R(s,\omega) + \sum_{s’} P(s’ \mid s,\omega) \max_{\omega’ \in \Omega_{s’} } Q_\Omega(s’,\omega’)
    $$

Option 发现方法

基于子目标的方法
  • 通过识别状态空间中的关键状态(子目标)来构建 Option:
    $$
    g^* = \arg \max_g \Phi(g)
    $$
    • 其中 \( \Phi(g) \) 是子目标 \( g \) 的某种效用函数,如访问频率或状态覆盖度
基于技能的方法
  • 通过最大化多样性来发现有用的技能:
    $$
    \max_{\pi} \mathbb{E}_{s \sim p(s)} \left[ H(\pi(\cdot|s)) \right] \\
    \text{s.t. } \mathbb{E}_{z \sim p(z), s \sim \pi_z} \left[ | z - f(s) |^2 \right] \leq \epsilon
    $$
    • 其中 \( z \) 是技能隐变量,\( f(\cdot) \) 是状态编码器

SMDP 与 Option 框架的关系

  • Option 作为 SMDP 动作:在 Option 框架中,每个 Option \( \omega \) 可以看作 SMDP 中的一个”动作”:
    • 持续时间:Option 执行的时间步数 \( \tau \)
    • 累积奖励:Option 执行期间获得的折扣奖励之和
    • 转移概率:从起始状态到终止状态的概率
  • Option 的 SMDP 参数概念对齐:对于 Option \( \omega = \langle I_\omega, \pi_\omega, \beta_\omega \rangle \),对应的 SMDP 参数为:
    • 累积奖励函数:
      $$
      r(s, \omega) = \mathbb{E} \left[ \sum_{k=0}^{\tau-1} \gamma^k R_{t+k} \mid S_t = s, \text{执行} \omega \right]
      $$
    • 状态转移概率:
      $$
      P(s’, \tau | s, \omega) = \mathbb{P}(S_{t+\tau} = s’, \tau \mid S_t = s, \text{执行} \omega)
      $$
  • SMDP 中的 Option 策略:在 SMDP 层面,策略 \( \mu : S \rightarrow \Delta(\Omega) \) 选择 Option,满足 SMDP 贝尔曼方程:
    $$
    V_\mu(s) = \sum_{\omega \in \Omega} \mu(\omega | s) \left[ r(s, \omega) + \sum_{s’, \tau} \gamma^\tau P(s’, \tau | s, \omega) V_\mu(s’) \right]
    $$

CPI:保守策略迭代 (Conservative Policy Iteration)

  • CPI 是由 Kakade 和 Langford 在 2002 年提出的一种策略迭代改进方法,是 RL 领域具有里程碑意义的算法
  • CPI 的核心思想是“保守”地更新策略,即不直接将策略完全替换为当前的贪婪策略(Greedy Policy),而是采用一种混合策略(Mixture Strategy)的方式进行更新
  • TLDR:CPI 告诉我们在理论上“怎么走才安全”(步子要小,要混合)
  • CPI 的核心思路 & 贡献:
    • 混合策略更新:
      • 传统的策略迭代通常直接寻找使当前优势函数最大化的动作作为新策略
      • CPI 提出,新策略 \(\pi_{new}\) 应当是旧策略 \(\pi_{old}\) 和贪婪策略 \(\pi’\) 的加权混合:
        $$ \pi_{new} = (1-\alpha)\pi_{old} + \alpha \pi’ $$
        • 其中 \(\alpha\) 是一个很小的正数(步长)
    • 单调性证明:CPI 的最大贡献在于它从理论上证明了,只要 \(\alpha\) 足够小,这种混合更新策略可以保证值函数(Value Function)的单调不减(Monotonic Improvement)
    • 解决的问题: 在近似强化学习中,由于函数逼近误差的存在,直接的贪婪更新往往会导致策略性能突然大幅下降(Policy Degradation)
      • CPI 通过限制策略变化的幅度,给出了策略提升的下界(Lower Bound),从而保证了学习过程的稳定性
  • CPI 的 地位:CPI 是现代“信赖域”类算法(如 TRPO)的理论鼻祖,其关于“策略提升下界”的推导直接启发了后续算法中对 KL 散度约束的使用

NPG:自然策略梯度 (Natural Policy Gradient)

  • NPG 是由 Kakade 提出的一种优化方法,是 RL 领域具有里程碑意义的算法,它试图解决标准梯度下降在强化学习参数空间中效率低下的问题
  • NPG 是 TRPO 算法的前身,其核心在于引入了黎曼几何的概念来优化策略参数
  • TLDR:NPG 告诉我们在数学上“朝哪个方向走最快”(考虑分布的几何形状)
  • NPG 的核心思路 & 贡献
    • 黎曼几何与费雪信息矩阵(Fisher Information Matrix):
      • 标准的策略梯度(Policy Gradient, PG)假设参数空间是欧几里得空间,使用标准的梯度下降方向
        • 然而,策略通常以概率分布的形式存在,参数的一点点变化可能导致概率分布的剧烈变化
      • NPG 认为,更新方向不应基于参数的欧氏距离,而应基于分布之间的距离(通常用 KL 散度衡量)
    • 自然梯度更新:
      • NPG 在更新参数时,使用费雪信息矩阵 \(F\) 的逆矩阵对标准梯度 \(\nabla J(\theta)\) 进行预处理(Preconditioning):
        $$ \theta_{new} = \theta_{old} + \eta F^{-1} \nabla J(\theta) $$
        • 这种更新方向被称为“自然梯度”,它代表了在分布流形上最陡峭的下降方向
    • 协变性(Covariant): NPG 的更新具有参数化无关性,即无论你如何参数化你的策略网络,只要表示的分布相同,更新的轨迹就是一致的
  • 特别说明:
    • NPG 极大地提高了策略梯度的收敛效率,特别是在高维参数空间中
    • NPG 指出了标准 PG 算法在“步长”和“方向”上的缺陷,直接催生了后来的 TRPO(TRPO 可以看作是 NPG 加上了强制的 KL 散度约束步长)
  • CPI 和 NPG 的比较:
    • CPI 和 NPG 这两个方法通常被放在一起讨论,因为它们共同构成了现代深度强化学习中策略优化(Policy Optimization) 流派的基石:
    • 简单来说,CPI 告诉我们在理论上“怎么走才安全”(步子要小,要混合),而 NPG 告诉我们在数学上“朝哪个方向走最快”(考虑分布的几何形状) 这两者的结合最终导致了 TRPO 等算法的诞生,使得强化学习能够更稳定地应用于复杂的连续控制任务中

RL——强化学习与动态规划

  • 参考文献
    • 【强化学习】个人总结03——动态规划寻找最优策略
    • 手把手教你强化学习 (四)动态规划与策略迭代、值迭代
    • 第四章 动态规划(DP)、蒙特卡罗(MC)、时间差分(TD) - 臭皮匠的文章 - 知乎
    • David Silver强化学习之动态规划寻找最优策略理论与实战(三) - CristianoC的文章 - 知乎

动态规划方法

  • 动态规划(Dynamic Programming, DP) 是一种将复杂问题分解为若干子问题,并通过求解这些子问题来获得原问题解的方法。它适用于满足以下两个性质的问题:
    • 最优子结构(optimal substructure) :原问题可以分解为多个子问题,通过解决这些子问题并将其解组合起来,可以得到原问题的最优解
    • 重叠子问题(overlapping subproblems) :子问题在求解过程中会多次出现,且这些子问题的解可以被存储并重复利用
  • 马尔可夫决策过程 (MDP) 恰好满足动态规划的这两个条件:贝尔曼方程将问题分解为递归结构的子问题,而价值函数能够存储并复用其最优解。因此,我们可以使用动态规划来解决马尔可夫决策过程的核心问题:预测和控制
    • 预测(prediction) :给定一个马尔可夫决策过程 MDP 和一个策略 \(\pi\),或者给定一个马尔可夫奖励过程 MRP,求解基于该策略的价值函数 \(v_\pi\)。(评估给定策略的效果)
    • 控制(control) :给定一个马尔可夫决策过程 MDP,求解最优价值函数 \(v_\∗\) 和最优策略 \(\pi_\∗\)。(即寻找最佳策略)
  • 预测问题和控制问题的主要区别在于,预测问题是在策略已知的情况下求解其价值函数,而控制问题则是在没有给定策略的情况下寻找最优价值函数及其对应的策略。两者之间存在递进关系:在强化学习中,我们通常先解决预测问题,再进一步解决控制问题

同步动态规划(Synchronous Dynamic Programming)

  • 同步动态规划算法中,每一次迭代都更新所有状态的价值
  • 参考链接:策略迭代与值迭代的区别
策略迭代(policy iteration)
  • Step1:初始化所有状态的价值 (\(v(s)\)) 和策略 (\(\pi(s)\))
  • Step2:策略评估(policy evaluation):按照贝尔曼方程迭代,直到收敛(求解当前策略下的不动点)
  • Step3:策略迭代(policy improvement):寻找一个新的策略,在任意状态下都有 \(Q^\pi(s,\pi’(s)) \ge V^\pi(s)\),可以证明此时有 \(V^{\pi’}(s) \ge V^\pi(s)\)
  • 循环Step2和Step3直到收敛
值迭代(value iteration)
  • Step1:初始化所有状态的价值 (\(v(s)\))
  • Step2:迭代找到最优策略(常常隐含直接使用 argmax 作为最优策略决策),直到收敛
  • Step3:最优策略提取
  • 这种方法仅进行价值函数迭代,不考虑策略,最终迭代到最优价值函数后,即可反向求解得到最优的策略

异步动态规划(Asynchronous Dynamic Programming,非重点)

  • 异步动态规划算法中,在每一次迭代时,根据特定规则选择性地更新部分状态的价值(并不更新所有状态的价值)
    • 注:这种方法可以显著节省计算资源,并且只要所有状态能够持续被访问和更新,算法最终仍能收敛到最优解
    • 目标:解决经典DP需遍历全部状态的问题,通过异步更新提高效率
  • 以下是几种常见的异步动态规划方法:
    • 就地动态规划(In-place DP) :直接覆盖旧价值函数,而非保留新旧两份
    • 优先扫描(Prioritized Sweeping) :根据优先级对状态进行排序,优先更新优先级较高的状态的价值(优先更新贝尔曼误差较大的状态)
    • 实时动态规划(Real-time DP) :仅更新当前交互中访问的状态

动态规划的其他说明

  • 动态规划算法的缺点 :采用全宽度(full-width)的回溯机制来更新状态价值,算力需求大且容易导致维度灾难
    • 无论是同步还是异步动态规划,每次回溯更新某个状态的价值时,都需要追溯到该状态的所有可能后续状态,并结合已知的马尔可夫决策过程的状态转换矩阵和奖励来更新该状态的价值
    • 解决方案:可以使用采样回溯方法来近似估计价值,从而减少算力的消耗
  • 动态规划算法是model-based算法,因为需要回溯当前状态的所有后续状态(full-width回溯机制)

Model-free方法辨析

  • Model-free 不知道环境动力学(Dynamics),无法精确计算精确的目标值,所以通常使用 TD 和 MC 这两种方案来近似估计目标值
    • 时序差分(Temporal Difference, TD):Q-learning 和 SARSA 都使用这种方法估计目标值
    • 蒙特卡罗方法(Monte-Carlo,MC):REINFORCE方法就使用了MC方法估计目标值
    • n-step方法是 TD 和 MC 的结合
  • TD 其实同时是结合了 MC 和 DP 的思想,更详细的比较见附录
    • 需要模拟交互序列(和MC方法一样);但不需要积累很多交互数据求均值(不同于 MC)
    • 使用当前回报和下一时刻的价值预估来更新当前时刻的价值(和DP方法一样);不需要知道状态转移矩阵,也不需要full-width回溯(不同于DP方法)
  • Q-learning 和 SARSA 为什么不是DP方法?
    • Q-learning 并不基于模型计算期望(不需要依赖状态转移矩阵),是直接使用贪心策略
    • SARSA 依赖采样,并不是精确计算期望

附录:Planning、Decision、Prediction和Control的区别

  • 在自动化和智能系统中,Planning、Decision、Prediction 和 Control 是四个关键的概念:
    • Planning(规划) :制定未来行动步骤,生成一系列行动步骤或策略
    • Decision(决策) :在当前时刻选择最佳行动,即在已知状态下决策动作
    • Prediction(预测) :推断未来状态或事件,预估环境相关的变化等
    • Control(控制) :实时调节系统行为以实现目标,常常涉及到反馈机制,以应对环境变化
  • 实际例子举例:自动驾驶中,Prediction用于预测其他车辆行为,Decision用于选择最佳驾驶策略,Planning用于规划路径,Control用于执行驾驶操作

附录:MC、TD和DP 详细比较

  • 蒙特卡罗方法(MC) :通过完整回合的采样回报估计值函数,依赖实际经验,无需环境模型
  • 时序差分(TD) :结合MC和DP思想,结合当前奖励和后续状态的价值估计来更新当前状态
  • 动态规划(DP) :基于模型的全宽度回溯更新,利用贝尔曼方程迭代求解值函数,需已知环境动力学

动态规划(DP)

  • 核心思想 :利用贝尔曼方程全宽度回溯更新值函数,需已知状态转移 \( P \) 和奖励函数 \( R \)
  • 策略评估(Policy Evaluation) :
    $$
    V_{k+1}(s) = \sum_a \pi(a|s) \sum_{s’, r} P(s’, r|s, a) \left[ r + \gamma V_k(s’) \right]
    $$
    • 迭代至收敛得到精确值函数
  • 策略改进(Policy Improvement) :
    $$
    \pi’(s) = \arg\max_a \sum_{s’, r} P(s’, r|s, a) \left[ r + \gamma V^\pi(s’) \right]
    $$

蒙特卡罗方法(MC)

  • 核心思想 :用完整回合的回报均值估计值函数,仅适用于回合制任务
  • 值函数更新 :
    $$
    V(S_t) \leftarrow V(S_t) + \alpha \left[ G_t - V(S_t) \right]
    $$
    • 其中 \( G_t = \sum_{k=t}^T \gamma^{k-t} R_{k+1} \) 是实际回报,\( \alpha \) 为学习率,\( T \) 为回合终止时刻

时序差分(TD)

  • 核心思想 :用当前奖励与下一状态估计值(自举)更新值函数,可在线学习
  • TD(0) 更新 :
    $$
    V(S_t) \leftarrow V(S_t) + \alpha \left[ R_{t+1} + \gamma V(S_{t+1}) - V(S_t) \right]
    $$
    • 其中 \( R_{t+1} + \gamma V(S_{t+1}) \) 称为 TD目标 ,\( \delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t) \) 为 TD误差

三者方差偏差比较

  • MC :高方差、无偏估计,需完整回合
  • TD :低方差、有偏估计,可在线学习
  • DP :精确但计算量大,需模型支持。

RL——强化学习中的探索与利用


整体说明

  • 在强化学习中,探索(Exploration)与利用(Exploitation)的平衡是核心挑战之一
  • 探索(Exploration) :探索旨在发现环境中未知的信息
  • 利用(Exploitation) :根据已有知识选择最优动作以最大化收益

基于随机性的策略

\(\epsilon\)-贪心(\(\epsilon\)-Greedy)

  • 核心思路是以概率 \(\epsilon\) 随机选择动作(探索),否则选择当前最优动作(利用)
  • \(\epsilon\)-Greedy简单易实现,但探索效率低(可能重复探索次优动作)
  • 升级版 :\(\epsilon\) 可随时间衰减(如 \(\epsilon = \frac{1}{\sqrt{t}}\) )

Boltzmann探索(Softmax)

  • 核心思路 :根据动作的Q值按概率分布选择动作(玻尔兹曼分布),使用温度参数 \(\tau\) 控制探索强度:
    $$
    P(a) = \frac{e^{Q(a)/\tau} }{\sum_{b} e^{Q(b)/\tau} }
    $$
    • 玻尔兹曼分布 ,英文名是Boltzman Distribution ,又称吉布斯分布或Softmax分布
    • \(\tau\) 是温度系数,\(\tau\)高时偏向探索,\(\tau\) 低时偏向利用

基于不确定性的策略

Upper Confidence Bound (UCB)

  • 基本思路 :为每个动作的Q值添加置信区间上界,选择上界最大的动作:
    $$
    A_t = \arg\max_a \left[ Q(a) + c \sqrt{\frac{\ln t}{N_t(a)} } \right]
    $$
    • \(N_t(a)\) 是动作 \(a\) 被选择的次数,\(c\) 是探索系数
    • 特点 :适用于多臂赌博机(Multi-Armed Bandit, MAB) ,但需维护动作计数(注:Bandit 本意为 “匪徒/强盗”,之前的老虎机又称为 “单臂强盗”)
  • MCTS 中的UCT(Upper Confidence Bound applied to Trees)和PUCT(Polynomial Upper Confidence Trees)都是 UCB 的变体
  • UCT(Upper Confidence Bound applied to Trees)算法:
    $$
    \textit{UCT}(s, a) = Q(s, a) + c \sqrt{\frac{\ln N(s)}{N(s, a)} }
    $$
    • \(Q(s, a)\):动作\(a\)的平均回报
    • \(N(s)\):状态\(s\)的访问次数,\(N(s, a)\):动作\(a\)的访问次数(注:如果分母不会为0,此时所有节点都是完整探索过的,不存在为0的情况)
    • \(c\):探索系数(通常设为\(\sqrt{2}\)),当\(c=\sqrt{2}\)时,UCT的累计遗憾(Regret)有对数上界
    • 探索时选择动作为: \(a^* = \arg\max_a \textit{UCT}(s, a)\)
  • PUCT(Predictor UCT)算法:
    $$
    \textit{PUCT}(s, a) = Q(s, a) + c_\textit{puct}\cdot P(s,a)\cdot \frac{\sqrt{N(s)}}{1+N(s, a)}
    $$
    • \(P(s,a)\):即 \(\pi(a|s)\),是先验策略网络(Policy Network)给出的先验概率(表示动作 \(a\) 的初始偏好)
    • \(c_\text{puct}\):探索系数(控制先验权重,AlphaZero中常设为1~2)
    • \(1+N(s,a)\):(不用为了避免除0操作而增加1吧,毕竟此时所有节点都是完整探索过的,不存在为0的情况)
    • 探索时选择动作为:探索时选择 \(a^* = \arg\max_a \textit{PUCT}(s, a)\)
    • 基本思想:采样次数 \(N(s, a)\) 少时,按照策略 \(\pi(a|s)\) 为主探索;采样次数 \(N(s, a)\) 多时,按照价值 \(Q(s, a)\) 为主探索

Thompson Sampling

  • 汤普森采样(Thompson Sampling, TS)的核心思想是通过概率分布建模不确定性,并基于后验分布采样
  • 汤普森采样也称为后验采样(Posterior Sampling) ,是一种基于贝叶斯思想的探索与利用(Exploration-Exploitation)策略
  • 广泛应用于多臂赌博机、推荐系统、在线广告投放和强化学习等领域
  • 以点击率预估中伯努利奖励为例:
    • 初始化 :
      • 假设每个动作 \( a \) 的奖励服从伯努利分布(Bernoulli Distribution) \( \text{Bern}(\theta_a) \),即两点分布或0-1分布 ,\(\theta_a\) 是动作 \(a\) 真实的奖励期望
      • 设先验分布为 Beta 分布 :\( \theta_a \sim \text{Beta}(\alpha_a, \beta_a) \)(初始 \( \alpha_a = \beta_a = 1 \))
        • 注:Beta 分布的概率密度函数为\(f_\text{Beta}(x;\alpha, \beta) = \frac{1}{B(\alpha,\beta)}x^{\alpha-1}(1-x)^{\beta-1}\),其中 \(B(\alpha,\beta) = \frac{\Gamma(\alpha)\Gamma(\beta)}{\Gamma(\alpha+\beta)}\) 是 Beta 函数 ,(\(\Gamma(\alpha)\)为 伽马函数)
    • 迭代过程 :
      • 采样 :对每个动作 \( a \),从其后验分布 \( \text{Beta}(\alpha_a, \beta_a) \) 中采样动作 \(a\) 的奖励值 \( \hat{\theta}_a \)(当采样次数足够多以后有\( \hat{\theta}_a = \theta_a\))
        • 注意:每个动作都有一个自己的 Beta分布,都有自己的后验数据 \(\alpha_a, \beta_a\)
      • 选择动作 :执行 \( a^* = \arg\max_a \hat{\theta}_a \)
      • 观测奖励 :获得二元反馈 \( r \in {0, 1} \)
      • 更新后验 :
        • 若 \( r = 1 \),则 \( \alpha_{a^*} \leftarrow \alpha_{a^*} + 1 \)
        • 若 \( r = 0 \),则 \( \beta_{a^*} \leftarrow \beta_{a^*} + 1 \)

基于内在激励的策略

好奇心驱动(Intrinsic Curiosity)

  • 基本思想 :通过预测环境动态模型(如状态转移)的误差作为内在奖励,鼓励探索新状态
  • ICM(Intrinsic Curiosity Module)是一种好奇心驱动策略,通过预测下一状态和动作的误差生成奖励

计数基方法(Count-Based)

  • 基本思想 :对访问过的状态或动作计数,给予未频繁访问的状态更高探索奖励:
    $$
    R_{intrinsic} = \frac{1}{\sqrt{N(s)} }
    $$

神经网络中的探索策略

对输出动作加噪

  • 动作空间加噪 :直接在输出动作上添加噪声(如高斯噪声),思路和离散动作中的 \(\epsilon\)-Greedy 思想相似

对输入观测加噪

  • 观测空间加噪 :直接在观测上添加噪声(如高斯噪声)

对参数加噪(Parameter Noise)

  • 参数空间加噪 :直接在策略网络的参数上添加噪声(如高斯噪声)

熵正则化(Entropy Regularization)

  • 在策略梯度目标函数中增加策略熵项,鼓励动作多样性:
    $$
    \nabla J(\theta) = \mathbb{E} \left[ \nabla \log \pi(a|s) Q(s,a) + \beta H(\pi(\cdot|s)) \right]
    $$
    • \(H\) 是熵,\(\beta\) 是权重系数

一些实践技巧

  • 离散动作使用\(\epsilon\)-Greedy;连续动作使用参数噪声或动作噪声
  • Thompson采样成本太高,一般选择 \(\epsilon\)-Greedy这种简单的即可

RL——Gym安装问题记录


Gym 安装问题记录

  • 一般来说使用 pip install gym 安装即可
  • 但从经验来看往往会出现一些使用上的异常:由于经过多版迭代,导致很多接口不兼容,此时需要查看版本是否和目标项目是否一致
  • 经验1:如果安装后使用 env.render() 无法显示可视化图像,可考虑安装 0.21.0 版本(亲测可用)
    1
    pip install gym==0.21.0

PyTorch——FSDP的使用

  • 参考链接:
    • 官方文档:docs.pytorch.org/docs/stable/fsdp.html

整体说明

  • FSDP(Fully Sharded Data Parallel)是 PyTorch 推出的分布式训练技术,专为大规模模型训练设计
  • FSDP 通过分片模型参数、梯度和优化器状态到多个 GPU,显著降低单卡内存占用,支持训练远超单卡内存容量的大型模型(如千亿参数级模型)
  • 与传统的 DDP(Distributed Data Parallel)相比
    • DDP 会在每个 GPU 上保存完整的模型参数、梯度和优化器状态(数据并行)
    • FSDP 仅在每个 GPU 上保存部分分片,大幅提升内存效率(模型并行)
  • FSDP 的核心特性包括
    • 全分片机制 :模型参数、梯度、优化器状态均被分片存储在不同 GPU,仅在计算时临时聚合所需分片
    • 自动通信优化 :通过重叠计算与通信、按需聚合分片,减少分布式训练的通信开销
    • 混合精度支持 :原生支持 FP16/BF16 混合精度训练,进一步节省内存
    • 灵活的包装策略 :可通过 wrap_policy 指定需要分片的子模块,支持部分模块分片、部分模块复制(如小模型组件)
    • 与模型并行结合 :可与 Tensor Parallel 等模型并行技术结合,支持超大规模模型训练
  • FSDP 的工作流程
    • 1)初始化 :将模型划分为多个子模块,每个子模块的参数被分片到不同 GPU
    • 2)前向传播 :当需要某子模块参数时,FSDP 自动聚合所需分片到当前 GPU,计算完成后释放临时聚合的参数
    • 3)反向传播 :梯度按相同策略分片存储,避免全量梯度占用内存
    • 4)参数更新 :优化器仅更新本地保存的分片参数,通过通信同步确保全局一致性
  • 注意事项:
    • 模型保存/加载时需使用 FSDP 提供的 state_dict() 方法,避免直接保存原始模型参数(分片后参数分布在不同进程)
    • 可结合 activation checkpointing 进一步节省内存

FSDP 单机多卡使用示例

  • FSDP 单机多卡示例代码

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.utils.data import Dataset, DataLoader
    import torch.distributed as dist
    from torch.nn.parallel import DistributedDataParallel as DDP
    from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
    from torch.distributed.fsdp.fully_sharded_data_parallel import (
    CPUOffload,
    BackwardPrefetch,
    ) # 用括号的导入方式跟普通方式效果完全相同,区别在于括号的方式看起来更容易管理和注释某一行
    from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy
    import torch.multiprocessing as mp
    from torch.utils.data.distributed import DistributedSampler

    # 1. 定义数据集
    class DiyDataset(Dataset):
    def __len__(self):
    return 1000

    def __getitem__(self, idx):
    x = torch.randn(10) # 输入特征
    y = torch.randint(0, 2, (1,)).item() # 分类标签(二分类)
    return x, y

    # 2. 定义模型(包含多个子模块,便于后续使用 FSDP 分片)
    class SubModule(nn.Module):
    def __init__(self, input_dim, output_dim):
    super().__init__()
    self.fc = nn.Linear(input_dim, output_dim)
    self.relu = nn.ReLU()

    def forward(self, x):
    return self.relu(self.fc(x))

    class MainModel(nn.Module):
    def __init__(self):
    super().__init__()
    self.layer1 = SubModule(10, 20)
    self.layer2 = SubModule(20, 30)
    self.layer3 = SubModule(30, 2) # 输出维度为2(二分类)

    def forward(self, x):
    x = self.layer1(x)
    x = self.layer2(x)
    x = self.layer3(x)
    return x

    # 3. 分布式训练主函数
    def train(rank, world_size):
    # 初始化分布式环境 通过`dist.init_process_group`配置 NCCL 后端(GPU 推荐),并绑定进程到对应 GPU
    dist.init_process_group(
    backend='nccl', # GPU 推荐使用 nccl 后端,与 DataParallel 类似
    init_method='tcp://localhost:12355', # init_method 是必填参数(或通过环境变量指定),用于初始化分布式进程组,确保单机内的多个进程(每个进程对应一个 GPU)能够相互发现并建立通信,localhost表示只有本地一台机器
    rank=rank,
    world_size=world_size
    )
    torch.cuda.set_device(rank) # 绑定当前进程到对应的GPU

    # 4. 配置 FSDP 参数
    # 定义自动包装策略:当子模块参数数量超过 1e6 时进行分片
    auto_wrap_policy = size_based_auto_wrap_policy(min_num_params=1e6)

    # 初始化模型并应用 FSDP
    model = MainModel().cuda(rank)
    model = FSDP(
    model,
    auto_wrap_policy=auto_wrap_policy, # 自动递归包装子模块,避免手动指定 `wrap_module`,简化大模型配置 `size_based_auto_wrap_policy` 函数生成的对象,表示按参数大小进行分片,前面定义的是 >1e6 的子模块被分片
    cpu_offload=CPUOffload(offload_params=False), # False(默认值)表示仅将梯度卸载到 CPU;True 表示将梯度和参数都卸载到 CPU;前向传播时加载,反向传播后卸载
    backward_prefetch=BackwardPrefetch.BACKWARD_PRE, # 反向传播前预取参数,控制 反向传播预取
    sharding_strategy=FSDP.ShardingStrategy.FULL_SHARD, # 全分片策略(`FULL_SHARD`策略),即参数、梯度、优化器状态均分片(内存使用最小的策略);还可以使用 `SHARD_GRAD_OP`(平衡内存和性能)
    device_id=rank, # 当前设备 ID
    )

    # 5. 数据加载(分布式采样)
    dataset = DiyDataset()
    sampler = DistributedSampler(dataset, shuffle=True) # 确保各卡数据不重复,类似 DDP 的做法
    dataloader = DataLoader(
    dataset,
    batch_size=32,
    sampler=sampler,
    num_workers=2
    )

    # 6. 定义损失函数和优化器
    criterion = nn.CrossEntropyLoss().cuda(rank)
    optimizer = optim.Adam(model.parameters(), lr=1e-3)

    # 7. 训练循环
    model.train()
    for epoch in range(3):
    sampler.set_epoch(epoch) # 每个 epoch 打乱数据
    total_loss = 0.0
    for x, y in dataloader:
    x = x.cuda(rank)
    y = y.cuda(rank)

    optimizer.zero_grad() # 清零本地梯度分片
    outputs = model(x) # 前向传播(FSDP 自动聚合所需参数分片)
    loss = criterion(outputs, y) # 损失计算,loss 与 outputs 绑定,从而与模型绑定,故而可以使用 FSDP 的特性
    loss.backward() # 反向传播:触发梯度计算与分片梯度同步(核心行),类似操作可以参考 DataParallel 的实现方式
    optimizer.step() # 用同步后的梯度更新本地参数分片

    total_loss += loss.item()

    # 仅在主进程(rank=0)打印日志
    if rank == 0:
    avg_loss = total_loss / len(dataloader)
    print(f"Epoch {epoch+1}, Average Loss: {avg_loss:.4f}")

    # 清理分布式环境
    dist.destroy_process_group()

    # 8. 启动多进程训练
    def main():
    world_size = 2 # 使用 2 个 GPU,注意这里是单机模式,可以直接写死各种配置
    mp.spawn(train, args=(world_size,), nprocs=world_size, join=True)

    if __name__ == "__main__":
    main()
    • 反向传播预取说明:
      • FSDP 中,模型参数被分片存储在不同 GPU 上
      • 在反向传播时,计算某层梯度可能需要其他 GPU 上的参数分片(如计算梯度需要完整的参数信息)
      • backward_prefetch 决定了何时提前获取这些所需的参数分片,从而让数据传输(通信)与计算重叠进行,减少空闲时间
  • 运行上述单机多卡代码,正常使用 python 命令即可,不需要特殊命令启动:

    1
    python fsdp_example.py

FSDP 多机多卡使用示例

  • FSDP 多机多卡使用示例(其实也可以用多机多卡直接改成单机单卡形式)
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.utils.data import Dataset, DataLoader
    import torch.distributed as dist
    from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
    from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy
    from torch.utils.data.distributed import DistributedSampler
    import torch.multiprocessing as mp
    import argparse

    # 数据集和模型定义(正常定义)
    class DiyDataset(Dataset):
    def __len__(self):
    return 10000
    def __getitem__(self, idx):
    return torch.randn(128), torch.randint(0, 10, (1,)).item()

    class SubModule(nn.Module):
    def __init__(self, in_dim, out_dim):
    super().__init__()
    self.fc = nn.Linear(in_dim, out_dim)
    self.bn = nn.BatchNorm1d(out_dim)
    self.relu = nn.ReLU()
    def forward(self, x):
    return self.relu(self.bn(self.fc(x)))

    class MainModel(nn.Module):
    def __init__(self):
    super().__init__()
    self.layer1 = SubModule(128, 256)
    self.layer2 = SubModule(256, 512)
    self.layer3 = SubModule(512, 1024)
    self.final = nn.Linear(1024, 10)
    def forward(self, x):
    x = self.layer1(x)
    x = self.layer2(x)
    x = self.layer3(x)
    return self.final(x)

    # 子进程函数(每个进程对应一个GPU)
    def train_fn(local_rank, args, world_size):
    rank = args.start_rank + local_rank # 全局进程编号 = 起始rank + 本地进程编号
    print(rank) # 输出全局进程编号
    # 初始化分布式进程组
    dist.init_process_group(
    backend="gloo", # GPU 时使用 `nccl`
    init_method=f"tcp://{args.master_addr}:{args.master_port}", # 多机多卡需要指定服务器地址,不能写死成 local
    world_size=world_size,
    rank=rank, # 全局进程编号 = 起始rank + 本地进程编号
    )

    # 绑定本地GPU
    torch.cuda.set_device(local_rank)
    device = torch.device("cuda", local_rank)

    # 初始化 FSDP 模型
    auto_wrap_policy = size_based_auto_wrap_policy(min_num_params=2e6)
    model = MainModel().to(device)

    model = FSDP( # 整体配置同单进程模式,为了简化,这里不写 cpu_offload 和 backward_prefetch 的配置
    model,
    auto_wrap_policy=auto_wrap_policy, # 自动递归包装子模块,避免手动指定 `wrap_module`,简化大模型配置
    sharding_strategy=FSDP.ShardingStrategy.FULL_SHARD,
    device_id=local_rank,
    )

    # 数据加载和训练逻辑(与之前类似)
    dataset = DiyDataset()
    sampler = DistributedSampler(dataset, world_size=world_size, rank=args.start_rank + local_rank) # 为不同 GPU 分发不同机器,分成 world_size 个并取 第 args.start_rank + rank 份
    dataloader = DataLoader(dataset, batch_size=64, sampler=sampler, num_workers=4)
    criterion = nn.CrossEntropyLoss().to(device)
    optimizer = optim.Adam(model.parameters(), lr=1e-4)

    model.train()
    for epoch in range(5):
    sampler.set_epoch(epoch)
    total_loss = 0.0
    for x, y in dataloader:
    x, y = x.to(device), y.to(device)
    optimizer.zero_grad()
    outputs = model(x)
    loss = criterion(outputs, y)
    loss.backward()
    optimizer.step()
    total_loss += loss.item()

    if args.start_rank + local_rank == 0: # 主进程打印日志
    print(f"Epoch {epoch+1}, Avg Loss: {total_loss/len(dataloader):.4f}")

    dist.destroy_process_group()

    def main():
    # 对于多机多卡,为了复用同一套代码(方便管理),使用传入参数的方式启动代码,方便传参实现不同 node 机器的启动
    parser = argparse.ArgumentParser()
    parser.add_argument("--master_addr", type=str, default="localhost", help="主节点IP")
    parser.add_argument("--master_port", type=str, default="12355", help="主节点端口")
    parser.add_argument("--world_size", type=int, required=True, help="总进程数")
    parser.add_argument("--start_rank", type=int, required=True, help="当前机器进程的起始全局rank")
    parser.add_argument("--num_gpus", type=int, default=torch.cuda.device_count(), help="当前机器的GPU数量")
    args = parser.parse_args()
    print(args)
    mp.spawn(
    train_fn,
    args=(args, args.world_size), # 传递给train_fn的参数
    nprocs=args.num_gpus, # 进程数=当前机器的GPU数,启动当前机器的所有进程(num_gpus个),每台机器启动自己的进程数即可
    join=True # 等待所有子进程完成
    )

    if __name__ == "__main__":
    main()

关于启动命令项的说明

  • 也可以通过 torchrun 启动上述多进程(而不使用 mp.spawn),此时 torchrun 会自动生成环境变量(注意:无需手动定义环境变量即可启动)
  • 更多 PyTorch 分布式程序启动详情可参考:/Notes/PyTorch/PyTorch——分布式程序启动方式汇总

附录:集成了 FSDP 分布式框架 HuggingFace Accelerate

  • HuggingFace Accelerate 是集成 了 FSDP 技术的工具,属于 accelerate 库
  • 两者辨析:FSDP 是一种分布式训练技术,Accelerate 是集成该技术的工具
    • FSDP 是由 PyTorch 官方推出的分布式训练策略,核心是通过将模型参数、梯度和优化器状态进行分片存储,显著降低单卡内存占用,支持训练超大规模模型(如千亿参数模型)
      • 属于“分布式训练方法”的范畴
    • HuggingFace Accelerate 是一个简化分布式训练的工具库,其核心功能是屏蔽不同硬件环境(单卡、多卡、GPU/TPU)和分布式策略(如数据并行、模型并行、FSDP 等)的底层细节,让用户用少量代码即可实现分布式训练
      • Accelerate 内部集成了 FSDP ,将其作为一种可选的分布式策略供用户使用
  • Accelerate 对 FSDP 起到封装和简化作用 :使用原生 PyTorch 的 FSDP 时,需要手动编写较多分布式配置代码(如初始化进程组、包装模型、设置通信策略等)
    • Accelerate 通过抽象接口,将 FSDP 的使用简化:
    • 用户只需通过 Accelerator 类,并在配置中指定使用 FSDP,即可自动完成 FSDP 的初始化和模型包装
    • 例如,通过 accelerate launch 命令启动训练时,Accelerate 会根据配置自动选择包括 FSDP 在内的最佳分布式策略,无需用户手动调用 torch.distributed 或 torch.distributed.fsdp 的底层 API
  • Accelerate 则不局限于 FSDP,还支持数据并行、DeepSpeed 等其他策略,其价值在于统一接口 ,让用户在不同分布式策略之间无缝切换,无需大幅修改代码
  • HuggingFace Accelerate 的使用见:PyTorch——Accelerate使用总结

附录:FSDP 和 FSDP2 的区别

  • FSDP2 是 FSDP 的全新版本
  • 架构上:
    • FSDP1 采用 FlatParameter 设计,将一组张量展平、连接并分块,使得每个设备上的数据推理和重新分片变得复杂
    • FSDP2 使用 DTensor 基础架构,通过在 dim-0 上对每个参数进行分片,每个参数在数据并行工作器之间按 dim-0 进行分块,提供了更简单的分片表示,也使得对单个参数的操作更便捷,还实现了无需通信的分片状态字典
  • 内存管理上:
    • FSDP1 使用 recordStream 机制,导致 GPU 内存使用不够优化且非确定性,有时还需要 CPU 同步
      • recordStream 机制是一种用于通过 CUDA 流(Stream)记录和同步张量生命周期 的内存管理技术,主要用于优化 GPU 显存使用和计算效率,但也存在一定局限性
      • recordStream 跟踪张量在 CUDA 流中的使用时机,确保张量在计算完成后再被释放或重用于其他操作,避免因显存提前回收导致的计算错误
      • FSDP V1 在训练过程中需要频繁在不同设备(或分片)间迁移参数(如 Forward 时 AllGather 完整参数、Backward 后释放显存)
    • FSDP2 借助 DTensor,实现了更低且确定性的 GPU 内存使用,避免了 recordStream 机制,无需 CPU 同步,能更有效地管理内存
  • API设计:上有所增减
  • 在实际测试中,FSDP2 相比 FSDP1实现了更高的MFU(模型浮点利用率),峰值内存降低了 7%,且能保持相同的损失曲线(Llama-7B 模型在 8×H100)
  • FSDP2 不直接支持完整的状态字典,用户需要使用 DTensor 的 API 或更高层次的 API 将包含 DTensor 的分片状态字典重新分片为完整状态字典,而 FSDP1 支持完整的状态字典
    • FSDP1 使用 FlatParameter 将多个小参数张量展平并拼接成一个大张量,然后对这个大张量进行分片
      • 在这种情况下,FSDP1 可以通过 state_dict_config 配置来保存和加载完整的状态字典,其中完整的状态字典中的值是未分片的 PyTorch 张量
    • FSDP2 采用了基于 DTensor 的逐参数维度分片方式,每个参数张量直接按维度进行分片,其参数表示直接与分片状态字典匹配
      • 在 FSDP2 中,调用 model.state_dict() 返回的是一个分片状态字典,且不涉及任何计算或通信,FSDP2 也只支持加载分片状态字典
      • 如果需要完整的状态字典,用户需要使用 DTensor 的相关 API,如 dtensor.full_tensor(),或使用更高层次的 API,如 PyTorch 分布式检查点的分布式状态字典 API,在 FSDP2 之外进行转换
1…181920…61
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

608 posts
49 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4