
Course3 的 Week3 轮到强化学习我看第一遍视频的时候是带着这不就是换个名字的监督学习这种心态进去的结果第十分钟就被打脸了。它跟前面几周的推荐系统、异常检测完全是两条技术路线监督学习是拿着标准答案反推映射关系强化学习连这一步走得好不好都没人告诉你只有一个延迟到十几步之后才兑现的奖励信号。这一周的笔记我前后整理了三遍把火星车的例子、回报与折扣因子、状态价值函数、贝尔曼方程一直到 LunarLander 上的 DQN 训练循环全部重新推了一遍顺带把最近热词里出现的 MILP 与强化学习、基于模型强化学习、IQL 离线强化学习、机械臂实战、Q-learning 自适应 PID 这些方向也一起梳理了。如果你刚接触强化学习入门内容或者已经在用 python 写强化学习代码但总感觉跑得起来、学不明白这篇东西应该能帮你把中间的断层补上。1. 强化学习到底在解一道什么样的题1.1 没有标签这件事才是它最难的地方很多人第一次学强化学习会习惯性地把它往监督学习上套输入是状态输出是动作那不就是个分类问题吗这个类比在形式上没错但在本质上是错的。监督学习里每个样本都带着一个正确答案模型预测错了梯度直接告诉你往哪个方向修正。强化学习里没有这个东西你只在一个 episode 结束或者某个关键节点拿到一个标量奖励中间几十上百步的动作没有被单独评价过。这就带来一个非常现实的问题信用分配credit assignment。火星车那个例子里从状态 4 走到状态 6 拿到 100 分到底是因为最后一步选对了方向还是因为前面四步都恰好没走进死路没有标签你就没法直接回答。所有的强化学习算法从最朴素的 Q-learning 到现在的深度强化学习本质上都在用不同的方式回答这个问题。我自己的理解是强化学习不是学习一个映射而是学习一套评估体系。它先学会给每个状态、每个动作打分再根据分数去选动作。想清楚这一点后面的状态价值函数、动作价值函数、贝尔曼方程就都顺了。1.2 延迟奖励决定了整套算法的形状延迟奖励这个特性直接决定了强化学习算法的几个关键设计。第一你必须有回报这个概念把未来的奖励折算回来否则当前这一步根本无从评价。第二你必须能处理走一步看很多步的推演这就是贝尔曼方程存在的意义——它把无穷远的未来压缩成一个递归形式。我见过不少初学者在这里卡住写代码的时候直接把reward当成target去做回归那训练出来的策略必然是短视的。用生活里的例子说如果一个学生只看今天这道题做对没有来决定学习策略他永远不会为了三个月后的考试去啃那些短期看不到收益的基础章节。折扣因子就是他心里那杆未来值多少的秤。2. 回报、折扣因子与策略把长期收益翻译成数学2.1 折扣因子 γ 到底该怎么取回报Return的定义是把这一时刻之后所有奖励加起来。但如果不打折一个无限长的任务回报就是无穷大计算上根本没法收敛而且现实中远期收益本身确定性就差。所以引入折扣因子 γReturn R₁ γR₂ γ²R₃ γ³R₄ ...γ 越接近 1说明这个智能体越有耐心看重长远γ 越接近 0就越短视只看眼前那点奖励。课程里给的常见取值是 0.9 到 0.99但这个区间背后的取舍其实很具体。γ 取值有效视野大致步数适用场景侧写0.8约 5 步高频控制、快速响应任务反应快但容易陷入局部最优0.9约 10 步一般控制与游戏任务平衡点调参起点0.95约 20 步需要中等规划的任务训练稍慢策略更稳0.99约 100 步长程规划、稀疏奖励收敛慢方差大0.999约 1000 步极长程任务基本要靠离线算法兜底有效视野可以粗略用 1/(1-γ) 来估。我实测下来如果你的任务平均 20 步内能拿到一次有效反馈γ 从 0.95 起步是个不错的默认值如果奖励特别稀疏比如机械臂抓取里要十几秒才碰到一次成功那就得往 0.99 甚至更高调同时接受训练时间成倍增长。有一个坑我踩过把 γ 调高之后没动学习率结果 Q 值直接发散。原因是折扣因子变大目标值的量级跟着放大梯度尺度也变了。γ 和 learning rate 是联动的改一个就得看看另一个。2.2 策略 π 的两种表达方式策略就是在状态 s 下该选什么动作的规则。它有两种写法理解这两种写法的区别对后面看算法特别有帮助。第一种是确定性策略s → a一对一映射。第二种是随机策略s → 各动作的概率分布。课程里火星车那部分用的是确定性策略但在深度强化学习的实战中尤其是探索阶段随机策略几乎是标配。为什么要随机的因为你不知道哪个动作更好如果一直选当前评估最高的那个你永远发现不了可能更好的选项。这就是探索与利用exploration vs exploitation的矛盾后面讲 ε-greedy 的时候会展开。这里插一句最近热词里图强化学习与深度强化学习的结合点当状态本身是图结构比如交通路网、分子结构、调度依赖关系策略网络就不再是简单的 MLP而要用图神经网络做编码器这时候策略的输入维度是可变的传统的 Q 表彻底失效必须走参数化路线。2.3 一个可以手算的小例子为了确认自己真的理解了折扣因子我建议你手算一遍。假设火星车有三个状态s1、s2、s3奖励分别是 0、0、100动作只有左和右走到 s3 就终止。取 γ 0.5。从 s2 出发往右走一步到 s3回报 0 0.5 × 100 50。从 s1 出发往右走到 s2再往右到 s3回报 0 0.5 × 0 0.25 × 100 25。如果 γ 0.9则 s1 的回报变成 0.81 × 100 81比 γ 0.5 时的 25 高出三倍多。这个数字对比非常直观地说明了折扣因子的作用它把距离终点还有多远直接编码进了价值里。手算一遍比看十遍公式管用。3. 状态价值函数Week3 真正的核心枢纽3.1 Q(s,a) 和 V(s) 差在哪里课程里最先引入的是 Q(s,a)也就是状态-动作价值函数直观含义是从状态 s 出发先执行动作 a之后一直按最优策略走能拿到的期望回报。后来又出现了 V(s)状态价值函数含义是从状态 s 出发按最优策略走能拿到的期望回报。两者的关系其实很朴素V(s) max over a of Q(s,a)也就是说V(s) 是 Q(s,a) 在动作维度上取了最大值。这个关系看起来简单但在算法设计上影响巨大。强化学习中状态价值函数的作用是什么这是热词里被问得最多的一个问题。我总结下来有三点。第一它是策略评估的标尺用来判断一个状态本身好不好和具体动作无关这让它天然适合做基线baseline。第二它降低方差在策略梯度类算法里用 V(s) 作为基线算优势函数 A(s,a) Q(s,a) - V(s)能大幅减少梯度估计的方差训练稳定性提升非常明显。第三它是自举bootstrapping的载体贝尔曼方程右边的那个下一状态的价值用的就是 V 或者 Q正是靠它才能把远期回报压缩成单步递推。IQL 离线强化学习里那个被反复提到的 V 网络本质也是在扮演这个角色。对比项V(s)Q(s,a)输入维度状态维度状态维度 动作维度输出含义状态好坏动作好坏选动作时的用途需要配合模型预测下一状态直接取 argmax 即可是否需要环境模型需要否则不知道动作后去哪不需要典型算法Actor-Critic 的 Critic、IQLDQN、Q-learning、SARSA这张表最关键的一行是是否需要环境模型。这就是为什么无模型的深度强化学习里代码几乎只写 Q(s,a)——你不需要知道动作执行后世界会变成什么样Q 函数自己就把下一步的价值打包进去了。而基于模型强化学习MBRL反过来它显式学一个环境模型然后就能用 V(s) 做规划这是两条完全不同的技术路线。3.2 贝尔曼方程把无穷远折叠成一步贝尔曼方程是整个强化学习的地基写出来就一行Q(s,a) R(s) γ · max over a of Q(s, a)翻译成人话就是现在这个动作的价值等于这一步拿到的奖励加上打折之后下一状态最好动作的价值。无穷远的未来被递归压缩成了一次单步查询。我第一次看这个式子的时候觉得这也太取巧了但仔细想想它成立的唯一前提是马尔可夫性——下一状态只取决于当前状态和动作跟更早的历史无关。这个假设在课程里的火星车、LunarLander 这类环境里都成立但在很多真实系统里是不完全成立的。比如机械臂的力控任务接触瞬间的形变历史会影响后续状态这时候就需要把历史观测堆叠进状态或者用带记忆的网络结构。贝尔曼方程还有一个实操上的意义它是 DQN 里那个 target 的计算公式。你看到代码里y r gamma * q_target(s2).max()它就是贝尔曼方程的直接翻译。理解了这个式子代码就没有黑箱了。3.3 用表格法先跑一遍 Q-learning很多教程一上来就上神经网络我觉得这是劝退的主要原因。先用 Q 表在离散环境上写一遍 Q-learning二十行代码能跑通再看 DQN 会顺很多。import numpy as np n_states, n_actions 6, 4 Q np.zeros((n_states, n_actions)) alpha, gamma, eps 0.1, 0.95, 0.2 for episode in range(2000): s env.reset() done False while not done: if np.random.rand() eps: a np.random.randint(n_actions) else: a int(np.argmax(Q[s])) s2, r, done, _ env.step(a) best_next 0.0 if done else np.max(Q[s2]) # Q-learning 的更新式注意用的是 max与行为策略无关 Q[s, a] alpha * (r gamma * best_next - Q[s, a]) s s2这里有一个特别容易被忽略的点Q-learning 是 off-policy 的。注意上面那行更新用的是max(Q[s2])也就是下一步我会选最好的那个动作而不是下一步我实际按 ε-greedy 选的那个动作。后者是 SARSA属于 on-policy。这个差别在安全敏感的场景里非常关键SARSA 会把探索带来的风险算进价值里因此在靠近危险边界时更保守Q-learning 不管你怎么探索它评估的始终是最优行为学出来的策略更激进。我在做约束比较紧的任务时会优先考虑 SARSA 或者带约束的变体。4. 从 Q 表到 DQN训练循环里的每一步都在解决什么问题4.1 连续状态逼着你把 Q 表换成网络LunarLander 的状态是 8 维连续量水平位置、垂直位置、水平速度、垂直速度、倾角、角速度再加左右两条着陆腿是否触地。这种状态空间根本没法枚举Q 表直接报废。所以必须换成函数逼近输入 8 维状态输出 4 个动作各自的 Q 值。课程里给的网络结构很朴素两层 64 单元的隐层ReLU 激活。我照着复现过也试过更宽更深的版本结论是这个任务规模下结构不是瓶颈训练循环的细节才是。加宽到 256 反而更容易过拟合回放缓冲区里的近期样本。有一点值得单独提输出的 4 个 Q 值共用同一个网络主干这叫共享主干的多头输出比训练四个独立网络要高效得多而且能让不同动作的价值估计保持一致。这个设计后来在几乎所有离散动作的深度强化学习算法里都沿用了。4.2 经验回放和软更新是稳定性的两根支柱DQN 相比朴素 Q-learning 的两个核心改动都是为了解决用神经网络逼近 Q 函数带来的不稳定问题。经验回放experience replay把每一步的 (s, a, r, s, done) 存进一个缓冲区训练时随机采样一个小批量。为什么要随机采样而不是按顺序学因为连续的时间步之间高度相关按顺序训练相当于每次都喂给网络一批极其相似的样本梯度方向单一网络会在这批样本上反复过拟合然后忘掉之前学到的东西——这就是灾难性遗忘。随机采样打断了时间相关性让每批数据更接近独立同分布。目标网络target network与软更新计算 target 时如果用同一个正在更新的网络就变成了自己追自己目标值每步都在动训练极易发散。解决办法是维护一个参数更新慢得多的目标网络。课程里讲的是软更新θ_target τ · θ (1 - τ) · θ_target其中 τ 通常取 0.01这个式子读起来就是目标网络每步只朝着主网络挪动 1%。看起来微不足道但正是这种缓慢跟随给了训练过程一个相对稳定的锚点。相比每隔 N 步硬拷贝一次参数的硬更新软更新过渡更平滑实测在 LunarLander 上收敛曲线明显更干净。4.3 ε-greedy 的衰减节奏比初始值更重要探索策略的选择直接决定你能不能找到好的策略。ε-greedy 的做法是以 ε 的概率随机选动作以 1-ε 的概率选当前 Q 值最大的动作。关键不在 ε 从多少开始而在于它怎么衰减。我见过最常见的错误是把 ε 固定成 0.1 从头跑到尾结果策略永远在原地抖动因为智能体一直有 10% 的概率在关键时刻乱来。另一个极端是衰减太快前几百步就掉到 0.01智能体还没来得及探索完状态空间就锁死在了局部最优上。我通常在 LunarLander 上用这样一个节奏训练阶段ε 取值目的前 5% 步数1.0 → 0.5充分随机快速填充缓冲区5% ~ 50% 步数0.5 → 0.1逐步转向利用策略开始成型50% 之后0.1 → 0.01精细打磨减少无效动作评估/部署0纯贪心只在测试时关掉探索还有一个小技巧ε 的衰减最好按已执行步数而不是episode 数来算。因为不同 episode 长度差异巨大按 episode 衰减会导致前期探索严重不足。4.4 一个能直接跑起来的 DQN 骨架下面这段是我自己整理过的版本结构上跟课程思路一致但把容易出错的几个地方都做了标注。import random from collections import deque import numpy as np import torch import torch.nn as nn import torch.optim as optim class QNet(nn.Module): def __init__(self, state_dim8, action_dim4, hidden64): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim) # 每个动作一个 Q 值 ) def forward(self, x): return self.net(x) GAMMA, TAU, BATCH, LR 0.99, 0.01, 64, 1e-3 BUFFER_SIZE 100_000 q_net QNet() q_target QNet() q_target.load_state_dict(q_net.state_dict()) # 初始参数必须一致 opt optim.Adam(q_net.parameters(), lrLR) buf deque(maxlenBUFFER_SIZE) def select_action(state, eps): if np.random.rand() eps: return np.random.randint(4) with torch.no_grad(): q q_net(torch.as_tensor(state, dtypetorch.float32).unsqueeze(0)) return int(torch.argmax(q, dim1).item()) def train_step(): if len(buf) BATCH * 5: # 缓冲区太小时先不训避免早期过拟合 return batch random.sample(buf, BATCH) s, a, r, s2, d map(np.asarray, zip(*batch)) s torch.as_tensor(s, dtypetorch.float32) s2 torch.as_tensor(s2, dtypetorch.float32) a torch.as_tensor(a, dtypetorch.long).unsqueeze(1) r torch.as_tensor(r, dtypetorch.float32).unsqueeze(1) d torch.as_tensor(d, dtypetorch.float32).unsqueeze(1) q_sa q_net(s).gather(1, a) # 只取实际执行动作的 Q with torch.no_grad(): q_next q_target(s2).max(dim1, keepdimTrue)[0] y r GAMMA * q_next * (1 - d) # 终止状态不 bootstrap loss nn.functional.mse_loss(q_sa, y) opt.zero_grad() loss.backward() nn.utils.clip_grad_norm_(q_net.parameters(), 10.0) # 梯度裁剪防爆 opt.step() with torch.no_grad(): # 软更新目标网络 for p, pt in zip(q_net.parameters(), q_target.parameters()): pt.mul_(1 - TAU).add_(TAU * p)这段代码里有三个地方最容易写错我按踩坑频率排一下。第一是(1 - d)这一项终止状态必须把未来的 Q 砍掉否则智能体会幻想落地之后还能继续得分学出来的策略会拖到最后不肯结束。第二是gather(1, a)只能取实际执行过的那个动作的 Q 值不能拿全部输出去算 loss。第三是梯度裁剪LunarLander 前期奖励波动大不加裁剪偶尔会出现 loss 突然炸到 NaN。5. 跳出课程热词背后的真实工程场景5.1 基于模型强化学习在什么情况下才值得上无模型方法Q-learning、DQN、PPO 这些最大的问题是样本效率低。LunarLander 要跑几千个 episode换成真实机械臂几千次试错意味着设备磨损、时间成本和安全隐患根本承受不起。基于模型强化学习的思路是先学一个环境模型——给定 (s, a)预测 s 和 r——然后用这个模型生成虚拟经验来训练策略比如 Dyna 框架、PETS、Dreamer 系列。它的样本效率通常能高出无模型方法一个数量级代价是模型误差会被策略利用。智能体很快就会发现模型在某片区域预测不准我在那儿刷分最划算这就是所谓的 model exploitation。我的判断标准很实际如果单次交互成本高真实机器人、化工过程、医疗决策就必须考虑基于模型如果是仿真环境里跑游戏无模型方法足够别给自己加复杂度。另外一个折中做法是先用模型预训练再切到真实环境微调兼顾效率和最终性能。5.2 IQL 离线强化学习解决的问题不许试错怎么办离线强化学习offline RL的设定是你手上只有一份别人采集的历史数据训练期间不能再跟环境交互。这个设定非常贴合现实——医疗、金融、工业控制你都不可能让一个随机初始化的策略去真实系统里乱试。难点在于分布偏移。标准的 Q-learning 在算 target 时会取max over a如果这个最优动作在数据集里从没出现过Q 网络对它的估计就是凭空外推的往往严重高估。这个高估值一旦被当成 target 回传整个价值函数就被污染了这叫外推误差extrapolation error。IQLImplicit Q-Learning的处理方式挺巧妙它不去查询数据集外的动作而是用**期望回归expectile regression**在数据集内估计 V(s)再反推出 Q。简单说它问的不是最好的动作有多好而是数据集里那些相对好的动作有多好。这个相对好由分位数参数 τ 控制τ 取 0.7 到 0.9 之间比较常见。这样既避免了外推又能让学到的策略在原有数据基础上有所提升。如果你手上有机器人历史轨迹数据想做策略优化IQL 是个值得优先试的选项。5.3 机械臂与 AUV把 Q-learning 用到真实控制回路上热词里机械臂强化学习实战和基于 Q-learning 自适应强化学习 PID 控制器在 AUV 中的应用研究这两个方向其实代表了两种截然不同的落地方式。机械臂强化学习实战的典型难点是稀疏奖励和仿真到现实的鸿沟。抓取任务里只有真正抓住的那一刻才有 1中间的接近过程没有任何反馈随机探索几乎不可能撞到这个奖励。常见的解法是奖励塑形加距离势能项、HERHindsight Experience Replay把没成功的轨迹重新标注成达成了某个中间目标以及课程学习先学简单的正对抓取再学带角度和遮挡的。落地到真机时动作空间通常要限制在关节速度级而不是力矩级并且要留安全限位防止策略发散时把设备撞坏。AUV 那条路线则是另一种思路不直接用强化学习输出控制量而是用它来调 PID 参数。传统的 PID 参数是固定值面对洋流扰动、负载变化时性能会下滑。基于 Q-learning 的自适应方案把误差和误差变化率作为状态把Kp、Ki、Kd 的调整量作为动作奖励设计成误差绝对值的负值加上控制量变化惩罚。这么做的最大好处是保留了 PID 的稳定性和可解释性只在参数层面引入学习能力工程上接受度高得多也更容易通过安全审查。我做类似改造时的经验是动作要离散成小幅增量比如每次调 ±2%奖励里一定要加控制量的平滑项否则学出来的参数会来回跳执行机构寿命直接受影响。5.4 MILP、图强化学习、联邦深度强化学习各自的边界MILP 与强化学习的交叉主要有两个方向。一个是用强化学习去加速求解器比如让智能体学习分支变量的选择策略或割平面的生成顺序替代人工设计的启发式规则这类任务的状态是求解器的内部状态动作是选哪个变量分支奖励是节点数的减少量。另一个是用 MILP 来约束强化学习把安全约束编码成整数规划在策略输出动作后做一次可行性投影。前者需要跟求解器内部打交道门槛较高后者在安全关键场景里反而更实用。图强化学习与深度强化学习的结合点在编码器。当状态是拓扑结构、动作是从图中选节点或边使用图神经网络GNN做特征提取能让策略对图规模变化有一定泛化能力典型场景是组合优化、路网信号控制和芯片布局。需要注意的是GNN 的泛化能力经常被高估图规模变化超过训练分布两倍以上时性能衰减会很明显。联邦深度强化学习的动机是数据不能集中多个设备各自采集交互数据只上传梯度或参数更新服务器做聚合。它解决的是隐私和带宽问题但引入了新的麻烦——各客户端的环境动态不一样聚合出来的策略可能在每个客户端上都不够好也就是客户端漂移。常见对策是加个性化层或者按客户端做加权聚合。如果你的场景里数据本来就能集中别为了架构好看上联邦那是给自己找麻烦。6. 训练不收敛时的排查链路和奖励设计的坑6.1 奖励设计出错的三种典型症状奖励函数是强化学习里最像手艺活的部分我总结出三种症状基本能反推问题所在。症状一智能体学会原地打转或者撞墙刷分。这说明你的奖励里存在某个不需要完成任务就能反复领取的项。LunarLander 里如果把存活奖励设得过高智能体会悬停在天上不下来。修法是给每一步加一个微小的负奖励让拖延本身有成本。症状二前期学得飞快中后期突然崩盘。这通常是价值高估导致的。Q 网络对没见过的情况给了过高的估计策略被吸引过去实际执行后发现很差价值函数整体被拉垮。对策是检查 target 计算有没有 bug、加大目标网络的更新延迟、或者换成 Double DQN 那种解耦动作选择和动作评估的做法。症状三学习曲线方差巨大同一个配置跑两次结果完全不同。这往往是探索策略或者采样批次的问题。先把 ε 的衰减放慢缓冲区开大一点随机种子固定住做对照实验。如果三个种子跑出来的曲线差异超过一倍那说明当前配置对超参太敏感不该拿去对比算法。6.2 我实际用的排查顺序遇到不收敛我不会一上来就改网络结构。下面这个顺序帮我省了大量时间。先确认环境接口没问题。手动跑一条随机策略看看 reward 的量级和分布是否合理done 标志是否正确触发。我曾经因为环境返回的 done 写反白白调了两天超参。把 γ 和奖励尺度对齐。折扣因子乘上奖励尺度决定了 Q 值的量级。如果 Q 值动辄上千学习率就得相应调小。检查 target 计算。单独打印一批y和q_sa看数值范围是否在一个量级。如果 y 的绝对值大得离谱基本可以确定是 bootstrap 出了问题。观察 TD 误差的走势。正常训练下它应该是先快速下降、再缓慢波动。如果它一路上升通常是学习率太大或者目标网络更新太快。最后才动网络结构。绝大多数不收敛跟网络宽度深度没关系。6.3 从仿真到真实系统的落差这是我最想强调的一点LunarLander 上跑出 200 分以上不代表你能把它搬到真实系统上。仿真里有几个东西是缺失的观测延迟、执行器饱和、传感器噪声、以及最重要的——状态不完全可观测。我在做机械臂相关工作时习惯在仿真里就把这些干扰加进去观测加高斯噪声、动作加一阶滞后、每隔几十步随机丢一帧。这样训出来的策略鲁棒性明显更好迁移到真机上需要的微调量小很多。另外真机上一定要有一个独立于策略之外的安全层比如关节速度硬限幅、力矩上限、紧急停止逻辑。策略再聪明也不能把安全托付给它。7. 三个我觉得最值得反复琢磨的细节第一个是贝尔曼方程里的 max 是把双刃剑。它让 Q-learning 能够直接学习最优策略不需要行为策略足够好但也是高估的源头尤其在函数逼近和离线数据下。理解这一点你就能明白为什么后来会有 Double DQN、CQL、IQL 这么多变体——它们都在用不同方式给这个 max 降火。第二个是状态价值函数看起来只是 Q 的一个简化版实际上是很多算法的稳定器。策略梯度类算法如果没有 V(s) 做基线梯度方差会大到没法训练离线算法如果没有 V 网络做中介就绕不开外推误差。所以热词里反复问状态价值函数的作用是什么答案真的不只是一句衡量状态好坏。第三个是样本效率永远是强化学习最贵的资源。无论是在机械臂上还是在 AUV 参数整定上真正卡住项目的从来不是算法不够先进而是你能承受多少次试错。想清楚这一点选型时就会自然而然地在无模型和基于模型之间、在线和离线之间做出更务实的判断而不是追着最新的算法跑。我个人在实际操作中的体会是强化学习入门最难的不是数学而是建立价值这个概念的直觉。建议你先把 Q 表版本在一个十来个状态的离散环境里跑通亲眼看着 Q 值一步步收敛成有意义的样子再去看 DQN 的代码很多原本看不懂的设计——经验回放、目标网络、软更新——会突然变得理所当然。这个顺序花的时间不多但能省掉后面大量的困惑。