一个做机器人操作的朋友前阵子跟我抱怨,说他在真实机械臂上跑了半年的行为克隆策略,精度始终上不去。数据攒了几万条,网络结构换了好几版,损失函数降到小数点后三位,可策略一旦碰到训练集里没见过的物体摆放角度,动作就开始抖。他问我:除了继续堆数据,还有没有别的路子?我说你可以试试不再模仿,而是让策略在数据里“反思”自己的动作,这就是离线Q-规划的路子。
这篇文章想聊的就是这件事:如何让机器人策略在不动真实硬件、不额外采集一条新数据的前提下,通过离线策略Q-规划实现自我改进。核心一句话就是——从模仿“别人怎么做”,转向评估“如果这样做,后果值多少”,然后沿着高价值方向持续迭代自己的行为分布。这个方向适合谁?适合已经在做模仿学习但觉得上限明显、适合手里有一批质量参差不齐的演示数据但不想全部丢弃、适合对强化学习有点基础但还没在机器人策略上真正跑通离线价值的从业者。
1. 先弄明白问题出在哪
1.1 模仿学习的天花板不是网络,是数据分布
行为克隆原理上很简单:给你一堆(state, action)对,用监督学习拟合一个映射 f: s → a。问题是,这个映射只负责“复刻”训练集里的动作,它根本不理解这个动作在环境里会产生什么后果。
真实部署时有个特别常见的情况:机械臂末端稍微偏离了训练数据的轨迹一点点,策略就开始“自我怀疑”。因为它是纯前向的,没有反馈回路,偏离一旦发生,下一帧的观测根本不在训练分布内,输出的动作就是外推的、没有依据的。这叫什么?叫分布漂移(distribution shift)。你数据再多,这个结构性缺陷都在。说到底,BC是开环式的模仿,策略学的是“别人在那个瞬间做了什么”,而不是“我该做什么才能把任务推进下去”。
再深一层,数据本身往往也是脏的。真人示教的数据里,有的轨迹中间卡顿了一下,有的轨迹末端修正了好几次,有的是新手录的,有的是老手录的。这些“低质量”轨迹在BC训练里跟高质量轨迹一视同仁地参与梯度下降,相当于你请了个经验参差不齐的教练团,策略就会被平均效果拖着走。
1.2 自我改进的真正含义:从评价到更新的闭环
那什么叫“策略的自我改进”呢?我的理解是三个层次的跃迁:
第一层是从“状态到动作”的映射,升级为“状态到价值”的映射。策略不仅要会做,还要知道“在某个状态下,哪些动作的长期回报更高”。这就是Q函数的角色。Q(s, a)回答的是:如果我在状态s执行动作a,接下来按照某个行为策略继续走,我期望获得多少累积回报。
第二层是策略更新不再依赖真实环境反馈,而是依赖一个学出来的“评论员”。行为克隆用的是真实人类动作作为监督信号,是外部给的;离线Q-规划用的是价值模型给出的偏好信号,是内部生成的。这样,策略就拥有了“自己评价自己”的能力,哪怕这个策略当前还很弱,只要Q函数相对准确,就能引导它往价值更高的区域移动。
第三层是把数据里隐含的成功信息与失败信息解耦开。原始数据不会告诉你哪一步是关键的,哪一步是多余的。但通过时序差分学习,价值函数会逐步把“成功结局的功劳”回溯分配给之前的一系列动作,也能够识别出“如果走这条路会掉进低分区域”。这种信用分配是监督学习难以直接提供的。
1.3 为什么要强调“离线”
有人会问,既然有Q-learning,为什么不在真实机器人上在线跑强化学习?这个问题的答案,做过真实机器人实验的人都懂:在线RL在仿真里跑得飞起,拿到真机上就是一场灾难。机械臂一个探索性的动作可能撞坏夹具,轮式机器人一个随机策略可能直接翻下台阶,更不用说每一次试错都需要真实的物理时间。几万步探索,对仿真来说是几十分钟的事,对真机来说是几个通宵。
离线强化的核心思想听上去甚至有点反直觉:只用过去收集到的数据,即使这些数据来自一个比较差的策略,也能学习出比这个策略更好的新策略。这在直觉上就跟“没吃过猪肉但见过猪跑”差不多。只要数据覆盖了足够多的“猪肉味道”的状态-动作组合,价值函数就有机会推断出哪些动作组合在长期回报上更优,然后让策略去偏向这些组合。
我要强调一下,离线Q-规划不是简单的“拿数据直接跑一个Q-learning”。它最大的工程挑战在于:你训练Q函数时,会遇到Q值的分布外高估(out-of-distribution overestimation)问题。简单说,如果一个(s,a)组合在数据集里没见过或很少见,Q网络会倾向于给出一个虚高或者虚低的预测,策略如果按这个错误的Q值去挑动作,就会越走越偏。后面第4章会专门讲这个问题的排查和修法。
2. 离线策略Q-规划的整体设计与核心思路
2.1 三件套:数据、价值网络、策略网络
一个完整的离线Q-规划项目,通常由三个核心组成部分构成。把这三个东西想清楚,项目就成功了一半。
第一部分是静态数据集D。数据形式一般是多条轨迹,每条轨迹是(s_0, a_0, r_0, s_1, a_1, r_1, ..., s_T)的序列。这里的关键不是“有没有数据”,而是“覆盖度如何”。我见过不少人上来就用自己之前攒的BC训练集,结果那个数据集里清一色是成功轨迹,几乎没有失败轨迹,也没有偏离轨迹。这种数据训练Q函数会有一个问题:Q函数只见过“从成功走向成功”的路径,它学不到“某一步做错之后如何挽回”的信息,更无法为低质量动作分配低价值。所以我的建议是:在收集数据阶段,有意识地混合一部分失败轨迹、一部分次优轨迹、一部分带有环境扰动或人为噪声的轨迹。目的不是让策略模仿这些轨迹,而是让价值函数知道“哪些地方是坑”。
第二部分是价值网络,也就是Q(s,a)。它吃进状态和动作,输出一个标量回报估计。这个网络需要经过足够多次的时序差分更新,最终学会给好动作打高分、给坏动作打低分。它在训练阶段只依赖数据集内部的转移关系,不直接跟环境交互。
第三部分是策略网络π(s)。它吃进状态,输出动作分布(连续控制通常是高斯分布,输出均值和方差)。它与Q网络交替更新:Q网络负责给当前的策略候选动作打分,策略网络则朝着打分更高的方向调整自己的输出分布。整个体系如果打个比方,很像一个学生(策略)在一位裁判(Q函数)的反复点评下不断修改自己的答卷。但裁判不能离开题库(数据集)凭空出题,学生的所有修改也都要参考题库里已有的答案结构。
2.2 为什么Q-规划比纯RL更适合机器人策略改进
在线RL和离线Q-规划最大的区别在于学习过程中是否触碰环境。前面说过在线RL的现实成本太高,这里再补充一个更微妙的点:即使环境成本不是问题,纯RL在机器人这类高维连续控制任务上的样本效率仍然很低。一个挥臂动作有6到7个自由度,每个自由度连续取值,探索空间几乎无穷大,随机初始化策略的探索行为大多是无用甚至有害的。
离线Q-规划则巧妙地绕开了这个困境。它把探索的压力转移到了数据收集阶段而非学习阶段。你不需要在学习过程中故意乱试动作,只需要在数据采集时做一定程度的动作扰动,确保数据覆盖一些“偏离但可挽回”的状态区域。一旦数据到位,学习过程就变成一个纯计算问题:一批GPU,几小时,策略就能完成一轮自我改进迭代。这在机器人项目里意义非常大,意味着策略更新和硬件运行可以完全解耦,不需要在真机旁边排期等待。
2.3 从行为克隆到Q-规划:两种范式的本质对比
我一直觉得,要理解离线Q-规划的价值,最好把它和BC放到一张对比表格里来看。
| 对比维度 | 行为克隆 (BC) | 离线Q-规划 |
|---|---|---|
| 监督信号 | 人类示教动作本身 | 学出来的Q值(长期回报估计) |
| 对低质量轨迹的态度 | 一视同仁地学习,容易被平均 | 通过价值评估自动降低其影响 |
| 是否理解后果 | 不理解,纯开环映射 | 理解,通过TD更新做因果分配 |
| 部署时的分布漂移 | 敏感,偏离训练分布后表现骤降 | 相对鲁棒,Q值可识别低置信区域 |
| 能不能超越数据集中的最优轨迹 | 不能,上限是模仿对象 | 能,理论上可通过价值引导组合出更优行为 |
“能不能超越数据集中的最优轨迹”这一条,是最吸引人的点也是很多人最怀疑的点。我解释一下直觉:行为克隆学的是数据集中的条件概率分布,如果一个动作在数据里从来没出现过,输出概率就是零。而Q-learning不同,它学的是贝尔曼最优方程,允许通过对已有经验片段的拼接重组,推断出数据中不存在但价值更高的动作序列。当然,理论上允许不代表一定能做到,实际效果严重依赖于Q值的准确性和约束手段,这个后面展开。
3. 核心实现:训练循环与关键技术细节
3.1 数据标准化的隐藏作用
开始搭训练代码前,先提醒一个容易被忽视但对训练稳定性影响极大的点:数据标准化。机器人状态和动作的量纲差异很大,关节角度通常在[-3, 3]之间,末端坐标可能是几十厘米,力传感器读数可能到几十牛。如果不做标准化直接喂给网络,Q函数的误差梯度会被大数值特征主导,小数值但高语义的特征(比如关节角度的微小变化)就学不到了。
具体做法是,在数据集上按维度计算均值和方差,把状态和动作各自归一化到零均值单位方差。这个标准化参数要在训练和部署时保持一致——部署策略时,输入观测要先减去训练集的均值再除以标准差,策略输出的动作也要反标准化回物理单位再去控制机器人。我见过好几次有人只在训练时做了归一化,部署时忘了给观测做同样处理,结果策略输出完全错乱,还以为是网络没收敛。
3.2 价值网络训练的关键循环
下面给出一段简化的PyTorch伪代码,展示离线Q-规划中价值网络和策略网络的交替训练结构。这段代码去掉了很多工程细节,只保留了核心骨架,你可以基于它做增量扩展。
import torch import torch.nn as nn import torch.optim as optim # 从数据集中采样一个batch def sample_batch(dataset, batch_size): batch = dataset.sample(batch_size) s = torch.FloatTensor(batch["observations"]) a = torch.FloatTensor(batch["actions"]) r = torch.FloatTensor(batch["rewards"]) s_next = torch.FloatTensor(batch["next_observations"]) done = torch.FloatTensor(batch["terminals"]) return s, a, r, s_next, done # 价值网络与目标网络 q_net = QNetwork(state_dim, action_dim, hidden_dim=256) target_q_net = QNetwork(state_dim, action_dim, hidden_dim=256) target_q_net.load_state_dict(q_net.state_dict()) policy_net = PolicyNetwork(state_dim, action_dim, hidden_dim=256) policy_optimizer = optim.Adam(policy_net.parameters(), lr=3e-4) q_optimizer = optim.Adam(q_net.parameters(), lr=3e-4) tau = 0.005 # 目标网络软更新系数 gamma = 0.99 # 折扣因子 batch_size = 256 for step in range(total_train_steps): s, a, r, s_next, done = sample_batch(dataset, batch_size) # ---- 更新Q网络 ---- with torch.no_grad(): # 策略在下一状态输出的动作(带噪声,用于平滑价值估计) a_next = policy_net(s_next).rsample() # 计算TD目标 q_target = target_q_net(s_next, a_next) y = r + gamma * (1 - done) * q_target q_pred = q_net(s, a) q_loss = nn.functional.mse_loss(q_pred, y) q_optimizer.zero_grad() q_loss.backward() q_optimizer.step() # ---- 更新策略网络 ---- # 策略输出动作,目标是让Q值最大化 a_new = policy_net(s).rsample() q_for_policy = q_net(s, a_new) policy_loss = -q_for_policy.mean() # 通常还会加入一个行为克隆正则项,防止策略离数据太远 # bc_loss = nn.functional.mse_loss(a_new, a_demo) * bc_weight # policy_loss = policy_loss + bc_loss policy_optimizer.zero_grad() policy_loss.backward() policy_optimizer.step() # ---- 软更新目标网络 ---- for target_param, param in zip(target_q_net.parameters(), q_net.parameters()): target_param.data.copy_(tau * param.data + (1.0 - tau) * target_param.data)上面代码有几个地方值得特别注意。
第一,策略网络输出动作时用的是rsample()而不是sample()。rsample()是重参数化采样,它保留了采样过程中的梯度路径,使得梯度能够从Q网络反向传播到策略网络的均值和方差参数上。如果你用普通采样,梯度断掉,策略网络就无法通过Q值信号更新。这是很多初学者踩的第一个坑。
第二,目标网络的存在是为了让TD目标值y保持相对稳定,避免Q值在更新过程中出现“自己追自己”的震荡。软更新的系数tau一般取0.005或0.001,太小则Q目标更新太慢,太大则训练不稳定。我习惯在训练曲线里同时盯Q值和TD损失,如果Q值持续增长但TD损失也一直在涨,通常说明目标网络更新太快或存在高估问题。
第三,策略更新的目标是最小化负的Q值。这背后有个细节:如果策略只让Q值最大化,它会很快找到一个Q网络打分虚高的区域钻进去。为了防止这个问题,需要在策略损失里加约束项,让策略不要偏离数据集中的行为分布太远。这也是离线强化学习的核心矛盾之一——你既希望策略往高价值方向走,又怕它走到Q函数不靠谱的区域。后面会讲几种常用的约束方式。
3.3 策略约束的选择:从CQL到IQL
约束策略偏离的方式有很多,选型直接决定了项目的复杂度和最终效果。我介绍两种在主流的框架里用得最广、也最值得动手实现的方案。
第一种叫CQL,保守Q学习。它的核心思想是在Q函数训练时加上一个正则项,让Q函数对“数据集中出现过的动作”给高估、对“没出现过的动作”给低估。也就是说,它不仅学习数据里的真实回报,还主动压制分布外动作的虚高估计。CQL的实现不算复杂,但对超参比较敏感,尤其是那个正则系数α,调小了压不住高估,调大了Q值整体偏低,策略会变得过于保守。我一般在调CQL时会先固定α跑500步,观察Q值的分布,再决定调大还是调小。
第二种叫IQL,隐式Q学习。它不直接学习完整的Q值,而是利用分位数回归学习期望回报的一个上界,然后只基于这个上界更新策略。IQL在实际效果上通常更稳定,因为它不需要对分布外动作做显式的惩罚,只需对状态价值做分位数截断,天然规避了高估问题。IQL的问题是分位数参数τ需要根据数据集质量来选:数据集质量越高,τ可以取得越大(比如0.9),如果数据集很杂,τ取0.7会更保守一些。
从我的经验来看,如果你的数据集中成功轨迹与失败轨迹混得比较厉害,CQL的保守性会更有用;如果你的数据质量整体较高且想要更好的最终性能,IQL往往表现更好。两个方案都值得在你的项目里做一个快速对比实验,花不了太多时间,结论却很有参考价值。
3.4 规划与自我改进的关系
再回到“规划”这个词上。很多人以为Q-规划里的“规划”是指类似MPC那种在线优化控制序列,其实在离线Q-规划的语境里,它更接近一种动态规划的思路:利用贝尔曼方程,把长期的回报逐步回传,让价值函数“看”得更远。策略的单步更新看似只用了当前状态下的Q值,但Q值本身已经蕴含了未来多步的信息,所以在效果上等价于策略在做前瞻规划。
在机器人任务里,这种“前瞻”特性非常关键。举个例子,机械臂抓取任务中,一个看起来与目标无关的动作(比如先把手臂抬高一些再下降)可能在单步回报上是负的,但如果Q函数学会了后续动作能顺利完成抓取,这个负回报会被未来的正回报抵消,整体价值仍然偏高。行为克隆是学不了这种“牺牲短期换长期”的模式的,因为它只盯着动作与示范的相似度。
4. 实操过程:把离线Q-规划落在真实机器人任务上
4.1 仿真环境验证的完整流程
我强烈建议所有从事真实机器人项目的朋友,在动真实硬件之前先在仿真里把离线数据训练和评估的流程完整跑通一遍。这不只是安全考虑,更是因为你在仿真里能拿到完美地面真值奖励函数,能对策略做大规模并行评估,能精确复现实验条件——这三样在真机上都是奢侈品。
以机械臂操作任务为例,完整流程可以这么走:
第一步,设计你的数据采集策略。不要只采集专家演示,建议用三类混合策略:一是专家策略采集30%的高质量演示;二是基于专家策略施加一定程度动作噪声采集40%的“次优但可学习”轨迹;三是用随机策略或刻意扰动策略采集30%的探索轨迹。最终数据集里应该包含成功、接近成功、中途失败等多种结局。
第二步,设定奖励函数。仿真里可以写得比较简单:任务成功给+1,碰撞给-0.1或直接终止,每步给一个小的负惩罚用于鼓励策略少走弯路。注意奖励的尺度和稀疏度会显著影响Q函数的学习难度,如果奖励太稀疏,刚开始Q函数基本都是0,策略没有梯度信号,训练会非常慢。
第三步,训练。用前面讲的三件套结构训练Q函数和策略,每隔固定步数保存一次checkpoint,并在仿真环境里做一次完整评估,记录任务成功率。
第四步,观察训练曲线。我特别关注三个曲线:Q值的平均值、TD损失的均值、评估成功率。三者配合着看比单看任何一个都更有诊断价值。比如Q值涨得很快但评估成功率上不去,很可能是高估问题;TD损失降但评估曲线震荡,可能是策略约束不够。
4.2 从仿真到真机的迁移注意事项
仿真里跑通的策略直接搬到真机上,几乎一定会遇到sim-to-real gap。常见问题包括:仿真里摩擦参数设置得太理想,真机摩擦力远大于预期,导致机械臂末端到位误差变大;模仿的环境光照、物体纹理与仿真差异导致视觉观测分布漂移;还有就是执行延迟,仿真里控制周期是精确的,真机上网络推理加通信延迟会引入误差。
我的经验是三步走:第一,在仿真的物理参数上做随机化,摩擦、质量、重心、延迟等参数全部加正态分布扰动。这个习惯能让策略学到更鲁棒的行为。第二,在部署真机时降低期望,第一轮实验只验证“策略能不能到达目标附近”,不要一上来就追求高成功率。第三,真机评估阶段保留一套安全保护逻辑,比如力矩超过阈值就自动中断。这不是不信任策略,是职业素养问题。
4.3 一个最小可跑的实验配置参考
整理一份我常用的最小配置表,供首次尝试这个方向的朋友参考。这里的数字不是金科玉律,但作为起点可以减少很多弯路。
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| 数据集规模 | 1000-5000条轨迹 | 太少价值函数学不准,太多训练成本上升 |
| 轨迹长度 | 50-200步不等 | 按任务时长定,包含完整结局 |
| 状态维度 | 原始关节+末端信息 | 按具体机器人定,做好标准化 |
| 动作维度 | 6-7自由度 | 连续控制,输出均值+方差 |
| Q网络结构 | 3层MLP,256宽度 | 使用LayerNorm,防止训练震荡 |
| 策略网络结构 | 3层MLP,256宽度 | 与Q网络独立参数 |
| 学习率 | 3e-4 | 两个网络分开设,Q网络可以稍低 |
| 优化器 | Adam | 默认beta参数即可 |
| Batch size | 256 | 小batch容易震荡,太大显存吃紧 |
| 策略约束 | CQL或IQL | 按数据质量选型 |
| 训练步数 | 10万-50万 | 观察三条曲线决定 |
这个配置在我的几个任务上都稳定收敛过。但要注意,这里面没有一劳永逸的方案——不同任务、不同状态空间对超参的敏感度差异很大,建议拿到新数据集时先跑一个非常小的实验(比如5000步训练),快速定位问题,再做大规模训练。
5. 常见问题与排查技巧实录
5.1 Q值高估:离线强化学习的第一公敌
写一章专门说这个问题,因为它在离线Q-规划里几乎一定会出现,而且症状经常被误判。
典型表现是:训练过程中Q值均值一路飙升,从5涨到500,但策略评估时成功率反而下降了。原因在于Q函数在训练时遇到了大量数据集外的(s,a)组合,这些组合没有被真实回报约束过,网络就随意给了高分。策略顺着高分区域找动作,结果走到了一片“虚拟绿洲”。
排查和修正手段包括几个。一是观察TD误差是否持续增长,如果是,说明目标值本身就不可信,需要调小tau或降低学习率。二是加入CQL惩罚项,让Q函数对分布外动作压低打分。三是检查数据集覆盖度,如果某些状态区域几乎只有一条轨迹经过,Q值在那个区域基本是乱猜的,可以考虑补充数据或做裁剪。
我自己的调参经验是:先不加任何正则项训练一轮,记录Q值分布范围;然后引入CQL惩罚,观察Q值分布的压降幅度是否合理。如果Q值被压到几乎全部为负,说明α太大;如果Q值跟没加正则差不多,说明α太小。这个调参过程不用追求一步到位,多跑几组对比就熟练了。
5.2 策略在训练后期陷入局部最优
另一种常见问题是:策略训练到后期,成功率停留在某个水平上不去,Q值也没有明显增长。这种情况通常是策略已经收敛到了一个局部最优区域,而这个区域不是全局最优。
处理思路有三条:
- 适当增加策略网络的熵正则或动作噪声系数,让策略保持一定的探索能力。
- 在策略损失中降低BC正则项的权重,给策略更多自由去偏离数据集中的行为模式。
- 检查奖励函数设计是否存在“捷径”,比如策略发现碰撞不扣分时,它可能学会大力把物体推到位而不是抓取。这种情况需要调整奖励函数本身。
5.3 数据集质量不高时的补救措施
手里数据集质量不高是常态,很多项目组没有条件重新大规模采集数据。这时我有几个补救手段可以分享:
- 做数据清洗,把明显失败的轨迹过滤掉,或者给轨迹打上质量标签,在训练时按质量加权采样。
- 用数据增强,对机器人状态做小幅扰动,比如在关节角度上加上高斯噪声,配合原始数据一起训练。这相当于扩大了状态覆盖度,能让Q函数的估计更平滑。
- 提高奖励的密度,在真机或仿真上对离线数据集做一次“离线回报标注”。如果你有一个足够准确的仿真环境,可以把每条轨迹在仿真里重新播放,补上稀疏奖励之外的密集过程奖励。当然,这需要仿真环境足够接近真实。
5.4 常见问题速查表
| 现象 | 可能原因 | 优先排查动作 |
|---|---|---|
| Q值持续快速增长,评估成功率不升反降 | 分布外高估 | 加入CQL惩罚,降低学习率,调小tau |
| 策略训练后期卡住不动 | 局部最优或BC正则过强 | 增大探索噪声,降低BC权重 |
| TD损失降不下来 | 目标网络更新过快或奖励稀疏 | 调小tau,增加密集奖励 |
| 真机部署后动作抖动剧烈 | 标准化不一致或执行延迟 | 检查推理时观测归一化,加入动作平滑滤波器 |
| 训练前期策略就偏向单一动作 | 探索不足或数据严重不平衡 | 检查数据集中动作分布,做类别重采样 |
| 评估时策略“犹豫不决” | 策略网络方差过大 | 降低高斯策略的初始方差,或对方差做进一步约束 |
5.5 关于实验记录的一个小建议
最后分享一个我自己的习惯:每次跑实验,我会把数据集信息(轨迹数、成功比例、状态维度)、超参数配置、训练曲线截图、策略评估成功率、使用的随机种子这五样东西存成一个实验文件夹。半个月后回来看,你会发现这个习惯的价值比任何调参技巧都大。离线强化学习的实验可变因素实在太多,不做系统记录就是在浪费自己之前的调试时间。
写在最后
回到我那位朋友的问题。我建议他把BC的策略输出作为初始策略,用他手里那几万条数据去训练一个Q函数,再让初始策略往Q值高的方向微调。两周后他告诉我,同一批数据,策略在未见过的物体摆放角度上的成功率从不到30%提到了接近六成。他不确定天花板在哪里,但至少确认了一件事:策略是可以自己“反思”动作的,不需要每次都从外部获取新的示范。
离线策略Q-规划这条路,早期确实被很多工程团队视为“理论上很美、工程上很难”。但近两年随着工具链成熟、开源实现变多,它已经成为机器人策略迭代的一个实际可选方案。它不会替代模仿学习,但它能让你的策略在已有数据的基础上多走一步,这一步,很多时候就是项目上限的突破。建议你先在一个仿真任务上跑通整个流程,感受一下Q函数引导策略的那种微妙变化,再把这套思路迁移到你的真实任务上。