赵杰_yiwenshengmei

钟谛特牙3.2 REINFORCE: 最早的策略梯度算法

在完成策略梯度定理的推导后,我们获得了梯度的理论形式:

然而,这个期望本身仍然无法直接计算。我们面临的根本问题是:轨迹空间是高维甚至连续无限的,无法枚举所有可能的

组合。策略优化的实践核心在于用有限采样近似期望:与环境交互收集

条轨迹

,然后用经验平均估计梯度:

这就是 REINFORCE 算法(Williams, 1992)的核心思想。其训练流程为:

用当前策略

采样

条完整轨迹

对每条轨迹计算累积回报

(从时刻

到终止)

可选地引入固定 baseline

(如所有轨迹的平均回报)

计算梯度并更新参数:

采样带来的根本挑战:方差问题

我们真正想要的是策略的平均性能,但只能通过有限采样来估计。这引入了两个核心要求:

无偏性(unbiased):采样梯度的期望应等于真实梯度

低方差(low variance):不同采样批次的梯度应相近

REINFORCE 满足无偏性,但存在高方差问题。考虑一个简单例子:

示例:训练语言模型回答医疗问题。

Prompt: "如何缓解头痛?"

Response 1(轨迹1): "多喝水,适当休息,必要时服用布洛芬。" → 奖励

Response 2(轨迹2): "头痛可能由多种原因引起..." (啰嗦但正确) → 奖励

Response 3(轨迹3): "建议立即手术治疗。" (错误) → 奖励

即使这三条回复来自同一个策略,它们的回报差异巨大(

)。用这些样本计算的梯度会剧烈波动,导致:

需要大量轨迹(如

)才能得到稳定估计

训练过程缓慢且不稳定

对于长对话(如

轮),方差会指数级增长

关键疑问:每次更新参数后策略就变了,那我是只用一条轨迹就更新吗?

回答:不是。REINFORCE 的标准做法是:

用当前策略

采样

条轨迹(如

)

用这

条轨迹的平均梯度更新参数一次

更新后策略变为

,之前的

条轨迹全部作废

重新用

采样新的

条轨迹,重复上述过程

这就是 On-Policy 的含义:数据必须来自当前策略,每次更新后旧数据失效,导致样本效率极低。

3.3 Actor-Critic

REINFORCE 的高方差源于用 Monte Carlo 回报

(需要完整轨迹)。如果能用一个学习出来的函数估计未来回报,就可以:

降低方差(函数估计比单次采样稳定)

支持单步更新(不需要等轨迹结束)

这就是 Actor-Critic 框架的核心思想:引入 Critic 网络

估计状态价值,用它构造低方差的优势函数。

双网络架构

系统维护两个神经网络:

Actor

:策略网络,负责生成动作

Critic

:价值网络,评估状态的好坏

训练目标:

Critic 的更新:学习预测真实回报

其中

是实际观察到的累积回报(监督信号)。

Actor 的更新:用 Critic 估计的优势调整策略

其中优势函数

衡量动作相对于平均水平的好坏。

关键实现细节:计算优势时必须阻断梯度:

advantage = reward - value.detach() # ? 阻断梯度回传

这确保 Actor 的更新不会干扰 Critic 的学习目标。

单步更新的进阶:TD 误差

在 Actor-Critic (AC) 框架中,我们可以使用 TD (Temporal Difference) 误差 来替代传统的 Monte Carlo 回报,从而实现单步更新。

TD 优势的定义如下:

与 Monte Carlo 方法对比:

Monte Carlo 优势 (

):

公式:

特点:需要运行完整个轨迹才能计算,是无偏估计,但通常具有很高的方差。

TD 优势 (

):

公式:

特点:只需要一步(single-step transition)即可计算,方差较低,但是一个有偏估计(其准确性依赖于价值函数

的估计精度)。

3.4 GAE (Generalized Advantage Estimation) 的推导

1. 真实的优势函数

我们首先定义一个理论上“真实”的优势函数,它使用实际的未来回报

我们的目标是使用一系列的 TD 误差

来构造一个对这个“真优势”的良好估计。

2. 基于 Bellman 方程的展开

根据 Bellman 递推公式,任意时刻的回报

可以展开为:

将其代入真实优势的定义中:

为了引入 TD 误差

,我们在上式中同时加上和减去

观察上式,我们可以发现:

第一个方括号内的部分正好是 TD 误差

第二个方括号内的部分是下一时刻的真实优势

于是,我们得到了一个关于真实优势的递归关系:

3. 递归展开与关键结论

将上述递归关系不断展开,可以得到:

关键结论:真实的优势函数,等于所有未来 TD 误差的折扣加权和。

这个结论非常直观:

代表当前这一步决策带来的“惊喜”或“估计误差”。

代表未来每一步的误差。

折扣因子

确保了越遥远的未来,其误差对当前优势的影响越小。

GAE 的核心思想:偏差-方差的权衡

问题与动机

虽然上述展开式在理论上很完美,但在实践中存在两个问题:

依赖完整轨迹:它依然需要未来所有的

值,这意味着必须等到整个回合(episode)结束后才能计算,这本质上是 Monte Carlo 风格的估计,方差很大。

误差累积:我们不希望使用过长的序列,因为未来的不确定性高,价值函数的估计误差会不断累积。

我们需要在“充分利用未来信息”和“抑制噪声(降低方差)”之间找到一个平衡点。

引入

:偏差-方差的平衡因子

GAE 的核心思想是引入一个衰减系数

(通常取值在 0.9 到 0.99 之间),用它来控制未来 TD 误差的权重。

GAE 的定义:

:环境的奖励折扣因子,反映了任务本身对未来的重视程度。

:优势函数的折扣因子,是我们用来控制偏差-方差权衡的人为超参数。

:每一步的 TD 误差。

理解

的作用

时:

这等价于传统的 TD(0) 误差,只考虑一步信息。这种方法偏差最大,但方差最小。

时:

这恢复了原始的展开式,等价于 Monte Carlo 方法。这种方法无偏,但方差最大。

时:

GAE 在 TD 和 Monte Carlo 之间进行插值。未来的

权重会以

的速率衰减,实现了在“看得多远”与“抑制噪声”之间的平滑过渡。

GAE 的计算与实现

上述求和公式可以转化为一个高效的反向递推形式,非常适合在代码中实现。

GAE 递推公式:

这个计算过程类似于循环神经网络(RNN)中的反向传播,我们从轨迹的末端开始,反向遍历计算每一时刻的优势值。

伪代码示例:

advantages = torch.zeros_like(rewards)

gae = 0

# 从后往前遍历时间步

for t in reversed(range(T)):

# 1. 计算当前步的 TD 误差 delta

delta = rewards[t] + gamma * values[t+1] - values[t]

# 2. 使用递推公式计算 gae

gae = delta + gamma * lam * gae

# 3. 存储当前步的优势值

advantages[t] = gae

注意:

计算必须反向遍历时间,因为

依赖于未来的

values[t+1] 是 Critic 网络对下一状态的价值预测。

这个高效的计算方法是 PPO、A2C、A3C 等现代强化学习算法的标准组成部分。

GAE 与 n-step TD 的关系

GAE 还可以被看作是所有 n-step TD 优势估计 的指数加权平均:

其中,n-step 优势

的定义为:

总结来说:

决定了我们将多少不同长度(n-step)的 TD 估计综合在一起。

较小的

更侧重于短期的、偏差较大的估计。

较大的

更侧重于长期的、方差较大的估计。

在实践中,

通常是一个很好的经验默认值。

3.5 On-Policy 的困境与重要性采样

样本效率的致命弱点

前述所有算法(REINFORCE, AC, A2C/A3C)都是 On-Policy:梯度计算要求数据来自当前策略

。这导致:

每次更新后,

改变,旧数据立即失效

对于 LLM,生成一次回复需要数秒,但只能用一次就丢弃

训练 100 万步需要采样 100 万条新数据

量化对比(以 Qwen-7B 为例):

方法 单次采样耗时 数据复用 训练 1000 步总耗时

On-Policy 3 秒 1 次 3000 秒

Off-Policy(PPO) 3 秒 4 次 750 秒

重要性采样:Off-Policy 的数学工具

核心问题:能否用旧策略

的数据训练新策略

?

数学原理(重要性采样定理):对于任意函数

,

证明(简单积分变换):

应用到策略梯度:

原目标是

,但数据来自

,引入比率修正:

进一步简化(利用

),可将目标函数写为:

医疗问答示例:

旧策略生成:"多喝水,休息"(概率

)

新策略评估该回复:

(更倾向此回答)

优势

(好回答)

修正后的梯度贡献:

关键挑战:如果比率

过大(如 10),说明新旧策略差异巨大,重要性采样失效,梯度估计方差爆炸。需要约束策略更新幅度。

3.6 TRPO: 信赖域约束下的策略优化

优化目标的理论保证

TRPO(Schulman et al., 2015)的核心思想:在限制策略变化的前提下最大化性能提升。

优化问题:

KL 散度约束衡量两个分布的差异:

直观理解:

目标函数:最大化性能(用旧数据评估新策略)

约束条件:KL 散度

(如 0.01),确保新策略不偏离太远

医疗问答示例:

旧策略分布:P("多喝水")=0.3, P("休息")=0.4, P("吃药")=0.3

新策略分布:P("多喝水")=0.5, P("休息")=0.35, P("吃药")=0.15

计算 KL 散度:

如果

,则该更新违反约束,需要缩小更新步长。

实现方法:二阶优化

TRPO 用共轭梯度法求解带约束的优化问题,需要计算 Hessian 矩阵(目标函数的二阶导数)。虽然理论保证强(单调改进),但计算复杂度高,实现困难,调参敏感。

3.7 PPO

PPO(Schulman et al., 2017)用一阶优化 + 巧妙的目标函数设计达到 TRPO 的效果,成为深度 RL 和 RLHF 的标准算法。

3.7.1 PPO-Clip: 用裁剪替代 KL 约束

核心思想:不显式约束 KL 散度,而是直接限制比率

的变化范围。

目标函数:

其中

限制在

(通常

)。

逐项分析:

情况 1: 优势

(好动作,希望增加概率)

如果

:正常梯度,继续增加

如果

:被裁剪为

,停止增加(防止过度优化)

情况 2: 优势

(坏动作,希望减少概率)

如果

:正常梯度,继续减少

如果

:被裁剪为

,停止减少(防止过度惩罚)

医疗问答示例(具体计算):

Prompt: "如何缓解头痛?"

Response: "多喝水,适当休息"

旧策略:

(log prob = -4.6)

新策略:

(log prob = -3.5)

优势:

(好回答)

比率:

PPO 处理(设

):

原始项: r * A = 3.0 * 0.8 = 2.4

裁剪项: clip(3.0, 0.8, 1.2) * A = 1.2 * 0.8 = 0.96

最终: min(2.4, 0.96) = 0.96 ← 被裁剪!

解读:虽然新策略概率增加了 3 倍,但 PPO 只允许增加到 1.2 倍的幅度,防止策略突变。

3.7.2 PPO-KL: 自适应惩罚

另一种变体直接在目标中加入 KL 惩罚:

自适应

:

如果

:增大

(加强惩罚)

如果

:减小

(放松约束)

实践中 PPO-Clip 更常用,因为无需调节

3.7.3 PPO-Clip 完整训练流程

你可以使用 Python 的 `python-docx` 库来读取和修改 `.docx` 文件中的文本内容。下面是一个完整的脚本,它会遍历指定文件夹中所有的 `.docx` 文件,并将其中所有的“李铎东”替换为“赵杰”,然后保存文件。 ### ✅ 解决方案代码: ```python import os from docx import Document def replace_text_in_docx_file(file_path, old_text, new_text): """在单个 .docx 文件中替换指定文本""" doc = Document(file_path) replaced = False # 标记是否发生了替换 # 替换正文段落中的文本 for paragraph in doc.paragraphs: if old_text in paragraph.text: for run in paragraph.runs: if old_text in run.text: run.text = run.text.replace(old_text, new_text) replaced = True # 替换表格中的文本 for table in doc.tables: for row in table.rows: for cell in row.cells: if old_text in cell.text: cell.text = cell.text.replace(old_text, new_text) replaced = True # 如果有替换,则保存文件 if replaced: doc.save(file_path) print(f"已更新: {file_path}") def batch_replace_in_docx_folder(folder_path, old_text="李铎东", new_text="赵杰"): """遍历文件夹中所有 .docx 文件并执行替换""" if not os.path.exists(folder_path): print(f"错误:路径 '{folder_path}' 不存在。") return # 遍历文件夹 for root, dirs, files in os.walk(folder_path): for file in files: if file.endswith(".docx") and not file.startswith("~$"): # 忽略临时文件 file_path = os.path.join(root, file) try: replace_text_in_docx_file(file_path, old_text, new_text) except Exception as e: print(f"处理文件时出错 {file_path}: {e}") # 使用示例 if __name__ == "__main__": folder = r"C:\your\folder\path" # <-- 修改为你的文件夹路径 batch_replace_in_docx_folder(folder) ``` --- ### 🔍 代码解释: 1. **`Document(file_path)`**: - 使用 `python-docx` 加载 `.docx` 文件。 2. **遍历段落(`doc.paragraphs`)**: - 每个段落包含多个 `run`(格式化片段),我们逐个检查并替换文本。 3. **遍历表格(`doc.tables`)**: - `.docx` 文件中的表格内容不会出现在段落中,必须单独处理。 4. **`os.walk()`**: - 递归遍历整个文件夹及其子文件夹。 5. **`not file.startswith("~$")`**: - 忽略 Word 自动生成的临时文件(如 ~$xxx.docx)。 6. **异常处理**: - 防止某个损坏的文件导致程序中断。 --- ### ⚠️ 注意事项: - 安装依赖库: ```bash pip install python-docx ``` - 脚本会**直接修改原始文件**,建议先备份文件夹。 - 不支持页眉、页脚、文本框等内容的替换(`python-docx` 对这些支持有限)。如果需要处理页眉页脚,请看下方扩展说明。 --- ### ✅ 扩展:包含页眉和页脚的替换(进阶) ```python def replace_in_paragraph(paragraph, old_text, new_text): """辅助函数:替换段落中所有 run 的文本""" if old_text in paragraph.text: for run in paragraph.runs: run.text = run.text.replace(old_text, new_text) return True return False def replace_text_in_docx_file_advanced(file_path, old_text, new_text): doc = Document(file_path) replaced = False # 正文段落 for paragraph in doc.paragraphs: if replace_in_paragraph(paragraph, old_text, new_text): replaced = True # 表格 for table in doc.tables: for row in table.rows: for cell in row.cells: for paragraph in cell.paragraphs: if replace_in_paragraph(paragraph, old_text, new_text): replaced = True # 页眉 for section in doc.sections: header = section.header for paragraph in header.paragraphs: if replace_in_paragraph(paragraph, old_text, new_text): replaced = True # 处理页眉中的表格(如有) for table in header.tables: for row in table.rows: for cell in row.cells: for paragraph in cell.paragraphs: if replace_in_paragraph(paragraph, old_text, new_text): replaced = True # 页脚 footer = section.footer for paragraph in footer.paragraphs: if replace_in_paragraph(paragraph, old_text, new_text): replaced = True for table in footer.tables: for row in table.rows: for cell in row.cells: for paragraph in cell.paragraphs: if replace_in_paragraph(paragraph, old_text, new_text): replaced = True if replaced: doc.save(file_path) print(f"已更新: {file_path}") ``` > 提示:页眉页脚的支持在某些复杂文档中仍可能不完整,因为 `.docx` 是基于 XML 的复杂结构。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值