深度学习里的强化学习之所以另类,是因为它压根不是"教"出来的
我接触深度学习的顺序和大多数人差不多:先图像分类,再目标检测,后来折腾几天NLP的序列模型。等到翻开强化学习这一章时,突然发现之前那套"喂数据、算损失、反向传播"的肌肉记忆全部失效了,训练一个智能体更像是在"放养一匹野马",不能直接告诉它对错,只能通过零散的奖励信号让它自己摸索。这种体验上的断裂感,单靠看开源代码是补不回来的。
这一章的核心内容会围绕三条线展开:解决离散动作控制问题的DQN,解决连续动作策略更新问题的PPO,以及把强化学习推到大众视野的ChatGPT对齐技术RLHF。它们表面上都是"强化学习+深度学习"的组合,但各自的原理切入点、训练范式、工程难点完全不同。适合已经掌握基础神经网络和反向传播的读者,也适合想从"跑通代码"进阶到"理解算法为什么这么设计"的人。
我在这篇文章里会把自己调参、复现、踩坑的经验一并放进去,尤其是那些论文里不写、但实际跑起来一定会撞上的问题。
1. 为什么强化学习是深度学习里最"别扭"的一门课
1.1 监督学习的"标准姿势":一张图换一个标签
先回顾一下我们熟悉的监督学习公式。分类任务中,输入是一张图片,输出是一个标签;回归任务中,输入是一组特征,输出是一个连续数值。无论在哪个框架下写代码,底层逻辑都是同一个:模型给出预测,预测和真实值之间算一个损失函数,然后反向传播更新权重。这个流程干净、确定、可复现,数据集质量决定模型上限,标注的可靠性直接决定你训练过程顺不顺畅。
强化学习彻底打破了这套流程。它没有固定的"标准答案",只有一串环境返回的奖励信号,而且这个奖励信号往往延迟、稀疏、不完整。拿 Atari 游戏举例,你按了左键,但得分的变化可能发生在几十步之后。这时候你根本没法判断是"这个动作导致得分涨了"还是"之前某个动作的功劳"。这就是强化学习里著名的时间信用分配问题。
深度学习在这个框架下扮演的角色也变了。神经网络不再是一个简单的"图像到标签"映射器,而是变成了一个函数逼近器,用来逼近价值函数或策略函数。换句话说,深度学习负责"拟合",强化学习负责"探索和决策",两者是通过梯度传播缝合在一起的。很多人觉得强化学习难入门,本质上是还没完成这种思维切换:价值函数、策略函数、奖励信号、折扣因子这些概念,不是在神经网络之前的"前置知识",而是和网络架构并列的核心组件。
1.2 强化学习真正棘手的地方:奖励不是标签
监督学习中,标签是绝对可以信任的上帝视角。标注错了是数据问题,模型训练过程本身不会产生新标签。但强化学习里的奖励信号是一个动态交互的产物,会随着策略改变而改变。
举个例子,我用 DQN 训练 CartPole 平衡木。一开始策略很烂,很快就倒下,获得的奖励低,模型会倾向于不再尝试激进的移动;但随着策略变好,它坚持的时间更长,探索到的状态分布就变了,奖励的分布也随之变化。这就是非平稳问题——数据分布不是静态的,策略一变,整个世界都变了。这是强化学习难复现、训练不稳定的最深层原因。
还有一个理解上的关键点:强化学习优化的不是某一条轨迹上的表现,而是期望回报。在离散动作空间里,我们关心的是每个状态下选哪个动作能让长期累积奖励最大化;在连续动作空间里,我们关心的则是策略分布的形状。这两个视角分别对应了后面要讲的 DQN 和 PPO 的数学形态,尽早建立这个认识特别重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. DQN:把 Q-Learning 搬进神经网络的两次关键创新
2.1 从表格到函数:Q-Learning 的思路和它的天花板
Q-Learning 的思路很直观:定义一个函数 Q(s, a),表示在状态 s 下执行动作 a,之后能拿到的期望折扣回报。选动作时,贪心地选 Q 值最大的那个就行。关键问题是 Q 值怎么得到?答案是用贝尔曼方程迭代:
[
Q_{new}(s,a) = Q_{old}(s,a) + \alpha \left( r + \gamma \max_{a'} Q(s',a') - Q_{old}(s,a) \right)
]
这个式子想表达的意思是:当前状态的 Q 值 = 即时奖励 + 折扣后的下一状态最优 Q 值。多迭代几轮,这个估计会逐步收敛到真实值。表格型 Q-Learning 就是这么做的,用一个二维表查状态和动作。
但真实任务中状态空间是巨大的。Atari 游戏的一帧画面有 210×160×3 个像素点,组合起来根本不可能用表格存。DQN 的核心贡献就是用一个神经网络 Q(s,a; θ) 来代替这张表格,输入状态,输出该状态下所有动作的 Q 值。这就是"从表格到函数"的跨越,本质上是把查表变成了函数拟合。
2.2 经验回放与目标网络:稳定性的两大支柱
如果把 Q-Learning 和神经网络生硬地拼在一起,训练会立刻崩溃。原因有两个。
第一,样本相关性。相邻两个时间步的状态是高度相关的,你用一串连续的 (s, a, r, s') 去梯度更新,等价于用一叠几乎相同的样本反复训练网络,神经网络会快速过拟合到近期经历上,前面的经验全部被冲刷掉。DQN 的做法是加一个经验回放缓冲区,把交互产生的四元组 (s, a, r, s') 存起来,训练时从中随机采样一个小批次。随机采样打破了时间相关性,同时让一条经验可以被反复利用,数据效率也更高。这个设计现在看来很朴素,但在当时是效果提升最明显的一刀。
第二个问题是自举训练的不稳定。看贝尔曼更新公式,新 Q 值要通过 max_{a'} Q(s',a') 来计算,这个 max 项用的是同一个正在被更新的网络。相当于模型拿自己刚出炉的预测当老师,预测波动会被放大,形成正反馈震荡。解决方案是引入一个目标网络,它的参数不实时更新,而是每隔 C 步,再把线上网络的权重复制过去。训练损失变成:
[
L = \mathbb{E} \left[ \left( r + \gamma \max_{a'} Q_{target}(s', a') - Q_{online}(s, a) \right)^2 \right]
]
因为目标网络在一段时间内是固定的,梯度更新期间"老师"的答案保持不变,训练目标就稳定下来了。
2.3 DQN 的训练流程与我的调试心得
DQN 的整体训练流程可以拆成几步:
- 初始化线上 Q 网络和目标 Q 网络,参数保持一致;
- 用 epsilon-greedy 策略与环境交互,epsilon 从 1.0 逐步线性衰减到 0.01;
- 把每一步的 (s, a, r, s') 存入经验回放缓冲区;
- 当缓冲区内样本足够时,随机采样一个小批次计算上述损失;
- 每 C 步把线上网络参数复制给目标网络。
其中 epsilion-greedy 的策略是:大多数时候按 argmax 选动作,但以 epsilon 概率随机选一个动作,目的是保证探索。衰减速度要根据状态空间大小来设:游戏状态比较复杂的场景,衰减要慢一些,否则模型还没见过足够的状态就开始贪心,容易陷入局部最优。
我实际调试时的几点经验:
- 奖励不要太大。DQN 对奖励尺度很敏感,经验上把奖励控制在 [-1, 1] 范围内能让训练稳定很多。如果环境的 reward 天然很大,考虑做归一化或裁剪。
- 目标网络更新频率 C 一般取 1000 到 10000 之间。更新得太快,目标网络失去了稳定作用;更新得太慢,目标网络和真实值偏差太久,训练也会拖缓。
- 经验缓冲区大小在 100 万到 100 万量级之间是常见设置,至少也要能覆盖几万步的交互。太小的话,采样里重复样本太多,等于没打破相关性。
DQN 的经典代码框架目前已经很成熟,但在自己任务上复现时,最值得优先排查的是稳定问题而不是精度问题:loss 曲线如果出现断崖式上升,先检查是不是 reward scale 太大,其次检查目标网络更新频率,最后检查 epsilon 衰减曲线是否合理。
3. PPO:为什么策略梯度家族里最后是它成了默认选择
3.1 策略梯度、Actor-Critic 与 TRPO 的演化逻辑
DQN 属于基于价值的方法,先学 Q 函数,再从 Q 函数导出策略。它在离散动作空间上表现出色,但面对连续动作空间就很麻烦:连续动作的 max 操作没法直接做,只能把动作空间离散化,但维度一高就爆炸。
解决这个问题的思路是直接优化策略本身,这一类方法统称策略梯度方法。基本思路是把策略参数化为一个分布 π_θ(a|s),让策略在那个状态下输出动作的概率分布,然后用带 log 概率乘上累积回报的梯度去更新。目标的梯度形式为:
[
\nabla J(\theta) = \mathbb{E} \left[ \nabla_\theta \log \pi_\theta(a|s) \cdot A(s,a) \right]
]
这里的 A(s,a) 是优势函数,表示当前状态动作相对平均水平好多少,不是直接用累积回报本身,这样可以降低方差。引入一个 Critic 网络来估计优势函数,就是 Actor-Critic 架构:Actor 负责输出策略,Critic 负责对状态做价值估计。
但策略梯度方法有个老大难问题:每次更新后的策略分布如果偏离旧策略太远,根据重要性采样理论,估计就会失真,更新步长一大直接崩溃。TRPO 的解决办法是给参数更新加一个硬约束,要求新旧策略之间的 KL 散度不能超过某个阈值。这个设计在理论上很漂亮,但实现时要用共轭梯度法解带约束的优化问题,繁琐且难以调试。
3.2 Clipped Surrogate Objective:一句话替代复杂约束
PPO 最大的贡献是把 TRPO 那个复杂的约束条件改成了一个简单的裁剪目标函数,效果却几乎一样好。让我拆开讲。
定义新旧策略的概率比值:
[
r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{old}(a_t|s_t)}
]
如果 r_t(θ) 大于 1,说明新策略比旧策略更倾向采纳这个动作;小于 1 则相反。PPO 的损失函数是:
[
L^{CLIP}(\theta) = \mathbb{E} \left[ \min \left( r_t(\theta) A_t, ; \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right]
]
这个公式的巧妙之处在于:当优势函数 A_t > 0 时,这个动作比平均好,我们希望把它做概率加大,但一旦 r_t(θ) 超过 1+ε,就会被裁剪掉,梯度变成 0,相当于阻止策略更新得太多;当 A_t < 0 时,我们希望压低这个动作的概率,但 r_t(θ) 一旦小于 1-ε,也会被裁剪,避免过度打压。这样就用一个轻量级的 min+clip 操作,实现了和 TRPO 的约束相近的效果。
和 DQN 不同,PPO 天然支持连续动作,只要把 Actor 的输出改成高斯分布的均值和方差即可。这也是它后来成为各类控制任务的默认算法的原因:实现简单,稳定性高,而且不需要像 DQN 那样精心调目标网络更新频率。
3.3 GAE 与训练配置:PPO 真正难调的其实是这些
PPO 的公式本身很简单,真正影响效果的往往是配套的组件,尤其是GAE(广义优势估计)。GAE 用带权重的多个 TD 误差组合来估计优势函数:
[
A_t = \sum_{l=0}^{\infty} (\gamma \lambda)^l \delta_{t+l}
]
其中 δ_t = r_t + γ V(s_{t+1}) - V(s_t)。λ 起到一个平衡偏差和方差的作用:λ 接近 0,估计偏向短期偏差;λ 接近 1,偏向蒙特卡洛式的长期回报。
我在实际项目中常用的配置如下:
| 参数 | 推荐值 | 备注 |
|---|---|---|
| clip_range (ε) | 0.2(连续动作可试 0.1) | 太大更新激进,太小学习慢 |
| GAE λ | 0.95(稀疏奖励可试 0.99) | 控制优势估计的偏差方差平衡 |
| 折扣因子 γ | 0.99 | 决定智能体多远视 |
| 学习率 | 3e-4 到 1e-4 | 用 Adam 优化器,过高必炸 |
| 每个 epoch 的 minibatch 大小 | 64 到 1024 | 根据任务复杂度调整 |
| 每次更新前的交互步数 | 2048 或更长 | 保证样本充分反映当前策略 |
另外还有一个工程细节非常重要:优势函数归一化。计算完 GAE 后,对整个 batch 的 A_t 做一次标准化(减去均值除以标准差),可以让训练稳定非常多。这个操作在原论文里没有着重强调,但实战中经常决定模型能否收敛。
我在训练机械臂连续控制任务时,发现 PPO 训练后期经常出现"奖励震荡但策略不提升"的现象。排查下来原因是 Critic 网络的估计方差过大,GAE 的优势估计噪音太高。解决方式是调大 λ 到 0.98,同时把 Critic 学习率调低到 Actor 的五分之一,让价值估计更平滑。这种"调 Critic 而非调 Actor"的思路,是 PPO 调参时容易被忽略的一环。
4. RLHF:从"会说话"到"懂你"的三段式对齐
4.1 第一段:SFT——先把反应堆燃料补足
聊完了 DQN 和 PPO,现在把目光转到 2023 年以来最引人注目的一项应用——让对话大模型的行为与人类偏好对齐的 RLHF 技术。很多人误以为 RLHF 就是强化学习模型套大语言模型,其实它是一套三阶段的完整流程,每一步都不可或缺。
第一阶段是监督微调(SFT)。预训练语言模型的目标只是"根据上文预测下一个词",它学会了语法和世界知识,但不知道该怎么好好回答问题。这一阶段用一批人工标注的高质量对话样本来微调模型,样本一般是"用户提问 + 优质回答"这样的 (prompt, response) 对。微调目标就是普通的下一个 token 预测。
这一步看起来平淡无奇,但实际非常重要。SFT 模型决定了后续强化学习阶段的起点分布,如果基础模型连基本的回答格式都不会,后面 RL 阶段再怎么调也是白费。我在复现简化版 RLHF 时,一开始跳过 SFT 直接用基础模型做 RL,结果模型很快开始生成大量无意义符号来刷奖励,这就是典型的"基础太差导致偏移"。
4.2 第二段:奖励模型——用人类偏好训练一个"裁判"
第二阶段是整个 RLHF 中工程量最大的环节:训练一个奖励模型(Reward Model)。
做法是:对一个 prompt,让模型生成多个候选回答,然后让人类标注员给这些回答排序。排序数据不需要精确打分,只要相对好坏。基于这些排序数据,训练一个奖励模型 r(x, y),输入是 prompt 和回答,输出一个标量分数。常用的训练损失是 Bradley-Terry 排序模型的变体:
[
\text{loss} = -\mathbb{E} \left[ \log \sigma \left( r(x, y_w) - r(x, y_l) \right) \right]
]
其中 y_w 是更偏好回答,y_l 是较差回答。这个损失函数的作用是把人类偏好压缩成一个"裁判函数",以后任何新回答都能打出分数。奖励模型的训练数据质量和覆盖度决定了裁判的可靠性。
实际操作中,我见过不少人在这一步栽跟头。同一 prompt 下生成的多个回答如果差异不大,排序标注就会很困难;如果只给两个回答做比较,模型学到的信号又过于粗糙。经验值是每个 prompt 下生成 4 到 9 个回答,两两组合做 pairwise 比较,既能控制标注成本,又能产生足够多的对别信号。
还有一个常见误区:奖励模型过拟合非常快。它本质上是个二分类式排序任务,训练几个 epoch 之后在验证集上的准确率就不再提升,继续训练只会记忆标注噪声。我自己的做法是训练过程中每 100 步在留出的验证集上评估一次排序准确率,一旦指标停滞就读档使用中间 checkpoint。
4.3 第三段:PPO 微调——在奖励模型和 KL 散度之间走钢丝
有了奖励模型作为"裁判",第三阶段才真正进入强化学习:用 PPO 微调语言模型。
在这个场景里,四要素是这样映射的:
| 强化学习要素 | RLHF 中的定义 |
|---|---|
| 状态 | 当前已经生成的文本序列(上文) |
| 动作 | 生成的下一个 token |
| 奖励 | 奖励模型对完整回答的打分 |
| 策略 | 语言模型本身的 token 生成分布 |
目标函数除了要最大化奖励分数,还要加入一项关键的 KL 散度惩罚:
[
\text{maximize} ; \mathbb{E} \left[ r(x, y) \right] - \beta \cdot KL(\pi_\theta(y|x) | \pi_{ref}(y|x))
]
这里的 π_ref 是第二阶段训练好的 SFT 模型,KL 惩罚的作用是限制当前策略不要偏离 SFT 模型太远。为什么需要这一项?因为奖励模型并不是完美的,它只是人类偏好的一个近似模拟。如果完全不做约束,模型会找到奖励模型的漏洞,生成人类并不喜欢但奖励分数高的"胡言乱语",这就是强化学习中著名的 reward hacking 问题。
β 这个系数非常关键。β 太大,KL 惩罚压过奖励,模型什么都学不到;β 太小,模型放飞自我,语言质量崩坏。实际操作中,β 通常要结合目标任务做几轮小范围搜索,没有跨任务的万能值。同时训练过程中使用的损失,除了 PPO 的 clipped objective 之外,还要包括 Critic 的价值函数回归损失,以及鼓励策略探索的 entropy bonus。
4.4 我在本地复现迷你 RLHF 时踩过的坑
最后分享一些我在最小化复现 RLHF 流程时积累的教训,这些坑在论文里不会写,但任何一个想动手实操的人都会撞上。
第一个坑是 KL 惩罚的数值稳定性。计算 KL 散度时,如果两个模型对同一 token 的分布差异悬殊,log 比值可能非常大,导致梯度爆炸。我的解决方式是给 KL 计算加一个 clip 上限,同时用混合精度训练时格外小心数值溢出。
第二个坑是 PPO 的 rollout 阶段和训练阶段相互干扰。语言模型生成是自回归的,生成一份 batch 的回答可能要几秒钟,比传统 RL 环境的一步交互慢得多。因此生成 batch 时要控制好 batch size 和 max_len,建议先把 max_len 设小(比如 128 token),验证完整流程跑通后再加到实际需要的长度。我最初直接在 512 token 长度上跑,一个 epoch 花了几小时,排查问题极其痛苦。
第三个坑是 奖励分数没有归一化。奖励模型打分范围可能很大,直接进入 PPO 目标会导致 clipping 机制失去意义——优势函数分布严重偏斜时,clip 根本不触发,PPO 退化为一个带方差的策略梯度。我建议把所有 reward 减去均值、除以标准差以后再进入 GAE 计算,这个操作在 RL 环境里很常见,在 RLHF 里同样适用。
第三个坑是 多次 rollout 之间的回答长度偏差。模型生成回答长度差异大,有的回答只有 20 个 token,有的 200 个,这样计算 rewards 时天然偏向长回答。在代码里要对每个样本的 reward 按长度做归一化处理,或者用 mask 保证 padding 部分不参与梯度计算。
我在实际调试中发现,无论是 DQN 还是 PPO,最消耗时间的永远不是理解算法公式,而是处理训练稳定性问题。DQN 崩溃通常写在 loss 曲线里,PPO 的退化则藏在优势分布里,RLHF 的翻车则往往要结合生成文本一个个翻出来看才知道。如果你正在复现这个系列的实验,我的建议是先跑通最小的端到端案例,把每个阶段的数据格式、损失函数、指标记录都理清楚,再逐步扩大到完整参数规模,这样排查问题的路径会短得多。
另外,关于 RLHF 的延伸方向,这里多说一句:后来主流的对齐流程在奖励模型之外也演化出许多变体,比如直接用偏好数据做 DPO 之类的方法,能在跳过显式奖励模型的情况下完成对齐。但万变不离其宗,理解 DQN 的价值函数迭代逻辑、PPO 的截断策略更新哲学,以及 RLHF 的三段式奖励建模思想,再看任何新出的对齐算法都不会觉得陌生。这部分在后续章节里如果大家感兴趣,我会单独拎出来再拆一篇。
