你见过强化学习训练出来的机器人躺平吗?我见过。在我做清扫仿真的强化学习奖励工程时,第一版环境只用了一条稀疏奖励:全部垃圾清空给100分。结果训练两百万步,机器人学会了原地打转——因为转圈偶尔也能蒙对方向,垃圾没清掉,分数一分没涨。后来我把奖励拆成10项复合奖励,覆盖任务完成、行为引导、效率约束和安全惩罚四个维度,又花了三天调了四轮权重,才让这个机器人从100分一路干到778分。
这个项目的任务其实不复杂:仿真场地里有6个垃圾,机器人用轮式底盘乱跑,靠近垃圾后打开吸嘴就能清掉,全部清完再回到充电座。难的是怎么把“清扫”这件事翻译成奖励数字。如果你也在做强化学习实战,尤其被稀疏奖励、奖励黑客这些词折磨过,这篇文章就是给你准备的。我会把10项奖励的每一项拆开讲清楚,包括公式、权重、为什么填这个数、以及我在调试时踩过的坑,最后还有完整的调参记录和排查工具清单。
1. 项目背景与整体方案:让奖励函数覆盖四类信号
1.1 任务怎么定义:比“清扫”更具体的动作空间
先说环境。我在MuJoCo里搭了一个4m×6m的平地场景,四周有墙,场地里随机放6个垃圾,角落划了一个1m×1m的区域当充电座。机器人模型是差分驱动的轮式底盘,自带一个吸嘴,吸嘴有开关状态。动作空间是2维的:线速度和角速度,吸嘴开关作为第三个动作维度。每个回合最多500步。
如果只从“清扫”这个目标出发,你很容易把任务定义成“让所有垃圾消失”,然后给一个稀疏奖励。但实际操作就知道,这种定义在强化学习里是最难学的一种:动作空间连续,任务完成需要十几秒的连贯操作,随机策略在500步内全清垃圾的概率接近0,所以训练前期奖励几乎全是0,梯度信息约等于没有。这也是我把“清扫”拆成更细信号的原因。
1.2 为什么基线100分几乎是废的:稀疏奖励的陷阱
我在评估脚本里定义了一个独立的计分口径:每个垃圾被吸走算100分,6个全清加200分,回到充电座加100分,单次碰撞扣5分,超时或出界直接终止。初始随机策略在这个口径下90局平均只有100分,这意味着它运气好才能在500步里清掉一两个垃圾,全程几乎不返航,还会频繁撞墙。
用这个例子解释稀疏奖励问题:你让小孩拼拼图,拼完才给奖励,他拼到一半得不到任何反馈,很快就放弃。拼图的每一块其实就是奖励塑形的空间。扫地机器人也一样,如果只有“全清”一个信号,它在训练初期根本不知道“靠近垃圾”是好事,自然只会随机乱走。强化学习算法在稀疏奖励下的探索效率极低,这是基线100分的根本原因。
1.3 复合奖励的整体结构:任务、引导、效率、安全四组
设计奖励函数时,我的思路不是堆一堆分,而是先明确要约束哪些行为。清扫任务最核心的指标有三个:能不能清掉垃圾、花多长时间、会不会撞坏东西。围绕这三个指标,我把奖励拆成四组:
- 任务完成组:用来告诉算法什么才算真正的“赢”。
- 塑形引导组:用来提供中间过程的密度信号,解决稀疏奖励。
- 效率约束组:用来惩罚绕远路、原地转圈、反复横跳。
- 安全约束组:用来惩罚碰撞和出界,避免训练出破坏性策略。
这四组一共10项,我在最开始就列了一张总表,之后每次调参都对照这张表改:
| 分组 | 奖励项 | 初始权重 | 作用 |
|---|---|---|---|
| 任务完成 | 垃圾清除奖励 | 5.0 | 每个垃圾被吸走时的直接正反馈 |
| 任务完成 | 清扫完成奖励 | 50.0 | 全部垃圾清掉后的bonus |
| 任务完成 | 返航奖励 | 10.0 | 回到充电座的bonus |
| 塑形引导 | 垃圾接近奖励 | 0.5 | 距离缩小时的正向塑形 |
| 塑形引导 | 垃圾接触奖励 | 2.0 | 吸嘴碰到垃圾时的反馈 |
| 塑形引导 | 吸尘开启奖励 | 0.3 | 吸嘴开启且附近有垃圾时的小奖励 |
| 效率约束 | 覆盖率奖励 | 0.2 | 扫过新的栅格区域 |
| 效率约束 | 步数惩罚 | 0.02 | 每走一步的负奖励 |
| 安全约束 | 碰撞惩罚 | 1.5 | 撞墙或撞固定障碍 |
| 安全约束 | 出界惩罚 | 10.0 | 出界直接终止回合 |
有了这张表,后面所有调试都能回到同一个参照系。实际执行中,权重的调整绝不是拍脑袋,而是基于观察到的失败行为反推。下面我逐项展开。
2. 十项复合奖励逐项拆解:每个权重都有它的理由
2.1 任务完成组:先让机器人知道“赢”是什么
这组三项都直接跟最终目标挂钩。垃圾清除奖励是每吸走一个垃圾立刻给5分,清扫完成奖励是全清后再给50分,返航奖励是回到充电座再给10分。为什么要分成三层而不是一次性给一个大分?
因为强化学习需要“进度感”。如果只给全清bonus,算法只能偶尔碰运气学会;如果给每个垃圾的清除奖励,它至少能学会“把垃圾吸掉是好行为”。清扫完成奖励的作用是强化“清完最后一个垃圾不等于结束”,后面还有返航等待。返航奖励我在前两轮里没加,导致训练出来的策略清完垃圾就在原地打转,后面补上后行为立刻改善。
这三个权重的量级也是有意安排的:单个垃圾清除5分,全清奖励50分,返航奖励10分。意味着全清奖励比单独清5个垃圾(25分)高,所以在评估指标里,拼死清完6个比磨磨蹭蹭清5个更优;返航奖励不如多清一个垃圾,但高于步数惩罚积累的负分,这样机器人会倾向于在清完后尽快返航而不是贪半场闲逛。
2.2 塑形引导组:不仅要赢,还要知道“朝哪儿走”
这一组的作用是提供中间密度信号。垃圾接近奖励是核心:我记录每个垃圾到机器人的距离,如果当前距离比历史最小距离更短,就发一次正奖励。注意这里的实现细节:
注意:接近奖励的“帧间距离差”写法是最容易踩的坑。它会让机器人反复横跳刷分,修复方法是改用“历史最小距离”作为基准。
很多人第一版会把接近奖励直接写成“上一帧距离减当前距离”,这会导致一个经典黑客行为——机器人在垃圾附近前后抖动,因为每顿一下总有一帧距离是缩短的,它就能靠抖来刷分。我的做法是记录每个垃圾从生成到清除的历史最小距离,只有当当前距离刷新了这个最小值时才发奖励。核心逻辑类似这样:
# 接近奖励的正确写法(伪代码) reward_reach = 0.0 for g in garbage_list: d = distance(robot, g) if d < g.history_min_dist: reward_reach += (g.history_min_dist - d) * weight_reach g.history_min_dist = d这个改动直接把刷分口子堵死了。垃圾接触奖励是给吸嘴碰到了垃圾但还没吸走的情况,它帮助算法跨过“到了垃圾旁边却不知道开吸嘴”的瓶颈。吸尘开启奖励是对吸嘴动作本身的激励:吸嘴开关打开,并且附近3米内有垃圾,才给奖励。如果不加这条,算法会倾向于干脆不开吸嘴,因为吸嘴这个动作不产生任何状态变化。
这组权重的变化值得一提:接近奖励初始0.5,最终降到0.1。原因是在早期训练阶段,接近信号是学习导航的主推力;到了后期,机器人已经能稳定找到垃圾,接近奖励反而会产生干扰——它会诱使机器人花费额外步数去“够”远处的垃圾,而不是先完成近处任务。所以我把它逐步缩小,让任务完成组接管主导权。
2.3 效率与安全组:让机器人学会不疯跑、不硬闯
效率组的思路很朴素:第一步0.02的步数惩罚,让算法意识到“绕路和原地打转要付代价”;覆盖率奖励用栅格地图记录已清扫区域,但新进入一个没扫过的格子给0.2分,防止机器人只在垃圾点之间走直线而忽略了其他区域。
覆盖率奖励的坑也很多。一开始我把奖励写成“当前帧占据的格子数减上一帧”,机器人立刻学会了来回蹭边界:因为边界附近的格子密度高,来回走就能不停刷出新格子。我的修复办法是给每个格子加一个冷却期:同一个格子2秒内重复进入不计新分数,并且评估时发现这种“蹭墙策略”的清扫完成率其实远低于规范走位,所以后来我把覆盖率权重从0.2降到了0.1,让它在前期起引导作用,后期不干扰主任务。
安全组包括碰撞惩罚和出界惩罚。碰撞1.5分,撞一次就抵消清1/3个垃圾的收益,让机器人遇到障碍时学会绕行而不是硬怼。出界惩罚是10分并且终止回合,比碰撞惩罚重得多,因为出界意味着任务彻底失败,而且机体会在场景外乱飞,终止回合可以快速让算法把“出界”和“失败”绑定。实测下来,这两条惩罚的加入让碰撞次数从每局十几次降到了2次以内。
3. 四轮调优全程记录:从100分到778分
3.1 训练环境、算法与评估协议
开始调优前,先把基线统一。算法我用的是PPO,策略网络是两层256的MLP,学习率3e-4,clip范围0.2,entropy系数0.01,GAE的lambda取0.95,gamma取0.99。训练时并行开12个环境,总共跑了约2500万环境步,单机训练大约两天半。
评估协议固定下来很重要,不然每次对比的都是不同标准。我用三张固定种子地图,每张地图跑30局,共90局取平均分作为阶段成绩。计分口径与训练奖励分开:清一个垃圾100分,全清加200,返航加100,碰撞扣5,超时或出界终止。这样训练奖励和评估口径完全分离,调试时不受奖励权重变化的污染。
3.2 第一轮调优:把任务信号理顺(100→210)
第一轮我只加了任务完成组的三项奖励和步数惩罚,对应权重表里的初值。结果90局均值到210分。好消息是机器人偶尔能清两三个垃圾了,坏消息是行为非常僵硬:它清完离起点最近的垃圾后,要么原地转圈,要么沿着墙走一段撞一下、再走一段再撞一下。
原因不算难分析:步数惩罚把探索欲望压得太低,机器人清完一个垃圾后,继续探索的边际收益接近0,不如原地待着。这里的教训是:稀疏信号阶段先不要急着加太重的效率惩罚,要让算法有探索的空间。我把步数惩罚从每步0.02暂时降到0.005,同时取消了碰撞惩罚的叠加限制,为第二轮做准备。
3.3 第二轮调优:塑形奖励带来的黑客问题(210→420)
第二轮加入塑形引导组,包括接近奖励、接触奖励和吸尘开启奖励。这一轮提升很猛,均值到420,但也把奖励黑客问题彻底暴露了。最典型的是接近奖励刷分:机器人学会在垃圾旁边前后抖,一抖就能获得连续的距离缩短信号,垃圾就是不吸走。我盯了半个小时的训练回放才确认,它不是不会吸垃圾,是刷分收益高于清除收益。
修复手段就是在2.2里说的:把接近奖励改为基于“历史最小距离”的差分,只有刷新最小值才给奖励。改完后抖动机器人彻底消失,分数回到正常水平。另一个黑客是吸尘开启奖励被滥用:机器人开着吸嘴在全图乱转,因为吸尘开启奖励只看“吸嘴开着+附近有垃圾”两个条件,不看吸嘴是否真的吸走了垃圾。我给吸尘开启奖励加了持续时间限制:同一个垃圾附近连续开吸嘴超过2秒就不再给奖励,必须清掉当前垃圾、失去吸尘机会后才有资格拿新的开启奖励。
3.4 第三轮调优:覆盖率与步数惩罚修正路径(420→610)
第三轮加入覆盖率奖励,并且把步数惩罚恢复到0.02、碰撞惩罚提高到2.0。分数到610,但路径质量依然不行。观察回放可以发现,机器人虽然能清掉大部分垃圾,但路线极度保守:它走一条折线,先右转,再左转,反复横跳,把所有小范围网格刷满,覆盖率拿得很满足,主任务却拖得很慢。
这让我学到一个非常重要的经验:奖励函数里的每个信号都会被算法贪婪地利用,覆盖率奖励让机器人误以为“扫过更多格子”比“完成任务”更重要。我把覆盖率权重从0.2降到0.1,并把清扫完成奖励从50提到80,让“全清”在总量上压过覆盖率收益。效果立竿见影:路径明显变直,清扫完成率上升,均值到了610。
3.5 第四轮调优:返航奖励与最终权重(610→778)
第四轮补上返航奖励,并把接近奖励降到0.1、接触奖励从2.0降到1.0、吸尘开启奖励从0.3降到0.1。这轮改动不大,但解决了最后一块短板:之前机器人清完所有垃圾后会停在原地,因为训练目标里没有“回充电座”这一项。加了返航奖励之后,它能在5秒内找到充电座并停稳。
最终90局平均分778分。这是我比较满意的结果。需要说明的是,均值被少数超时和碰撞严重的局拉低了,中位数其实到了890分。有经验的读者会问我为什么不继续冲。我的判断是:继续提高分数需要动环境难度而不是只调奖励,比如加快充电座里的判定精度,或者把垃圾分布改得更离散。奖励函数本身已经收敛到了一个稳定的行为区域。
3.6 权重调参的三条经验
第一条:一次只改一个变量。我尝试过同时调五六个权重,结果训练崩了都不知道是哪一项背的锅。改一个、跑一轮、看行为,再改下一个,效率反而最高。
第二条:先让任务目标可行,再谈效率和安全。稀疏信号阶段加重点惩罚,算法直接放弃探索;只有在任务信号能稳定出现后,惩罚才有约束意义。
第三条:把每轮改动的权重和现象记录成表格。我后面会贴出最终的配置表,但在项目进行中,这份改动日志比任何训练曲线都有用——你自己踩过的坑,比任何论文里的建议都更记得住。
4. 奖励工程踩坑实录:黑客行为、崩溃曲线与排查工具
4.1 四类高发“奖励黑客”行为速查
我把这个项目里遇到的和同行交流中最常见的黑客行为列成表,方便做奖励工程时自查:
| 黑客行为 | 出现原因 | 快速对策 |
|---|---|---|
| 原地抖动刷接近奖励 | 接近奖励用了帧间距离差 | 改用历史最小距离差分 |
| 反复横跳刷覆盖率 | 覆盖率只按新格子计数 | 给格子加冷却时间 |
| 只吸不搬、绕垃圾转 | 清除奖励和接触奖励比例失衡 | 提高清除奖励、限制接触奖励次数 |
| 全图开吸嘴骗开启奖励 | 吸尘开启条件太宽松 | 加上吸尘有效窗口限制 |
这张表看起来简单,但前两个我真实踩过,而且是反复踩,每次修复都会伴随分数短暂的下降期,因为算法需要重新学习。碰到这种“先掉分再回升”的曲线不要慌,正常现象。
4.2 TensorBoard 里真正值得盯的三条曲线
奖励工程阶段,TensorBoard里最值得盯的不是总reward曲线,而是奖励分解曲线。我在环境里按奖励项的维度把每步的贡献分别记录,然后训练完直接看每一项在总奖励里的占比变化。
三条核心曲线:第一,“reward/breakdown”逐项占比,用来确认有没有某一项奖励长期占据统治地位,如果有,大概率是奖励黑客在上面刷分;第二,“episode_length”平均回合长度,如果回合长度突然变短,通常是出界惩罚或步数惩罚把策略逼成了早停;第三,“policy entropy”,PPO的熵如果急速下降,策略空间快速收敛到一个模式,往往是某个权重被调得过大。
4.3 奖励分解日志:调试时一定要把每一项单独打出来
我在环境代码里把每步奖励拆成字典记录,比如下面的结构,然后把它们聚合到TensorBoard。这个习惯帮我省了大量时间。原因很简单:总reward涨了,你根本不知道是哪个信号在起作用;把每项单独打出来之后,一旦出现异常,比如“接近奖励占比突然飙升”,立刻就能定位到黑客行为。
info["reward_detail"] = { "clear": reward_clear, "complete": reward_complete, "return": reward_return, "reach": reward_reach, "contact": reward_contact, "dust": reward_dust, "cover": reward_cover, "step": reward_step, "collision": reward_collision, "boundary": reward_boundary }这个日志还有一个额外好处:它可以验证你的奖励函数是否真的在训练中产生了作用。如果某一项奖励从训练开始到结束的占比几乎没变,说明它不是冗余信号,就是权重太小没有影响,都可以考虑删掉。
4.4 最终配置表:各项权重、触发条件与调整建议
最后是完整的最终配置,和最初的总表对应,方便你直接抄作业再按需调整:
| 分组 | 奖励项 | 初始权重 | 最终权重 | 触发条件 | 备注 |
|---|---|---|---|---|---|
| 任务完成 | 垃圾清除 | 5.0 | 5.0 | 垃圾被吸走瞬间 | 权重稳定,不要动 |
| 任务完成 | 清扫完成 | 50.0 | 80.0 | 所有垃圾清除瞬间 | 提高后全清行为明显增强 |
| 任务完成 | 返航 | 10.0 | 15.0 | 机器人进入充电座区域 | 后期才加 |
| 塑形引导 | 垃圾接近 | 0.5 | 0.1 | 距当前垃圾的距离刷新历史最小 | 用历史最小值,别用帧间差 |
| 塑形引导 | 垃圾接触 | 2.0 | 1.0 | 吸嘴碰撞垃圾 | 每个垃圾只触发一次 |
| 塑形引导 | 吸尘开启 | 0.3 | 0.1 | 吸嘴开启且3米内有垃圾 | 加2秒有效窗口 |
| 效率约束 | 覆盖率 | 0.2 | 0.1 | 扫过的新栅格 | 格子带冷却时间 |
| 效率约束 | 步数惩罚 | 0.02 | 0.03 | 每一步 | 早中晚阶段权重不同 |
| 安全约束 | 碰撞惩罚 | 1.5 | 2.0 | 每次碰撞 | 建议保留,防止硬怼 |
| 安全约束 | 出界惩罚 | 10.0 | 10.0 | 出界瞬间 | 与终止回合配合 |
这套配置针对的是“单个清扫机器人、近距离吸尘、12个并行环境”这个场景。如果你的机器人是机械臂清扫或视觉导航版本,接近奖励和覆盖率的比例大概率要重新调。机械臂场景里,动作空间小、接触反馈更敏感,接触奖励的权重应该提高;视觉导航场景里,覆盖率奖励可以替换成“探索新鲜度”奖励,避免视觉感知误判。
做完这个项目,我最大的体会是:奖励工程改的不是数字,是行为。每一次权重调整,本质上都是在修改机器人的行为合同。合同里每个字都要斟酌,因为算法会把每一个漏洞放到最大——它不会体谅你“本来打算”的意思,只看你确实写下的条款。
最后分享一个小技巧:每次训练结束后,别急着看最终分数,先花两分钟看一段随机抽样的行为回放。你看到的每一个奇怪行为,都是下一轮调整的入口。我在这个项目里的778分,严格来说不是“调”出来的,是一遍遍看回放“看”出来的。把流程固定下来,你的奖励工程也会少走很多弯路。