☰
强化学习清扫机器人奖励函数工程实践:从100分到778分
2026/9/26 7:11:56 网站建设 项目流程

说句得罪人的话:很多强化学习项目不是死在算法上,而是死在奖励函数上。训练前把网络结构、学习率、环境封装折腾个遍,最后发现机器人学会的是“原地打转刷步数”,或者干脆躺平不动——根源十有八九是奖励设计出了问题。这个清扫机器人项目我前后迭代了快两个月,把奖励项从最初拍脑袋的4个一路改到结构化的10项复合奖励,同一个PPO实现、同一张仿真地图,训练得分从基线100分一路拉到778分,涨了近七倍。这篇文章不聊理论推导,重点记录我是怎么拆奖励、配权重、填坑的,给同样在做强化学习奖励工程的人一份能直接用的实操脚本。

1. 为什么奖励工程是“隐形天花板”

1.1 你先得承认一个事实:算法是在替你“翻译”奖励

很多人把强化学习当成“黑盒炼丹”:丢进去就行。但本质不是这样。强化学习优化的是期望累积奖励,策略网络学到的每一个行为,都是在你定义的奖励信号下做梯度上升的结果。你给了什么样的奖励,机器人就会变成什么样的“性格”。

拿清扫场景举例。如果只给“清扫覆盖率”这一条奖励,机器人学到的策略很可能是:快速溜达一遍,把覆盖率刷到80%就停,因为再扫边际收益太低。更恶劣的情况是,它发现原地抖动能让传感器误判“已清扫”,奖励照样增长——这是机器学习利用漏洞的经典场景,不针对机器人,任何智能体都一样。

所以在设计奖励函数的时候,我给自己定了一条原则:不要把“目标”直接写成奖励,要把“行为过程”拆成可分级的信号。目标太稀疏,学不动;目标太单一,学歪。

1.2 清扫任务到底难在哪:多目标,天然冲突

清扫任务是一个典型的多目标优化问题,目标之间还互相打架:

  • 想覆盖率高,就得跑得久、跑得全,这跟“能耗低”冲突;
  • 想路径优雅,就得少转弯,但少转弯就可能导致漏扫边角;
  • 想不碰障碍,有时候狭窄区域就必须贴身擦过去;
  • 想快速完成,就可能牺牲覆盖细节,比如家具底下的尘絮扫不到。

![清扫任务的奖励冲突示意]

(想象一条奖励函数,左端是覆盖率,右端是能耗成本,中间全是拉扯。)

这种冲突不是坏事,反而是奖励工程能发挥价值的地方。问题只在于:你要怎么把这些冲突目标加权成一个标量?权重就是态度。

我最终用的方案是复合奖励,总共10项,分成了四个梯队:核心任务奖励、行为引导奖励、约束惩罚、里程碑加成。下面这一章是我觉得全文最值得反复看的部分。

2. 第一版奖励:那个只配拿100分的机器人

2.1 最初的4项奖励是怎么翻车的

先交代一下环境。我的仿真环境是基于PyBullet搭的轮式清扫机器人,64×64栅格地图,房间里有6个小障碍物和一片需要清理的“灰尘区域”,灰尘用粒子分布模拟,机器人底盘带一个接触式清扫检测器。

第一版奖励函数非常简单,就4项:

reward = ( coverage_progress * 1.0 # 覆盖率增量 + 10.0 if task_complete else 0 # 任务完成 - 0.01 * step # 步数惩罚 - 0.5 if collision else 0 # 碰撞惩罚 )

结果很惨。训练50万步,累计奖励稳定在100分左右不再上升。但真正的问题不是分数低,而是策略表现出三种典型“畸形”:

  • 绕圈自嗨:机器人学会了在地图中央绕半径很小的圈,因为每次绕圈都能压过一小片未清扫区域,覆盖率增量持续为正,它根本不需要去扫角落;
  • 怕碰撞怕到不敢动:碰撞惩罚虽然每步只有-0.5,但累积起来吓人,策略逐渐变得保守:能不动就不动,偶尔伸出清扫刷试探一下;
  • 边界龟缩:地图边缘和障碍物缝隙的灰尘密度高,但机器人死活不过去,宁可在开阔地带重复扫。

这就是典型的奖励“短视”:策略在每步都要最大化即时奖励,而覆盖率增量这种“局部正反馈”很容易被利用。

2.2 100分这个基线的真实含义

我后来复盘才发现,100分这数字误导了我好一阵子。“有100分说明能扫一点,是不是策略还行?”不是。我把每个奖励项单独打日志后看到,这个100分里,约70分来自“绕圈压过灰尘”的覆盖率增量,25分来自完成任务的墓碑奖励,剩下5分扣掉碰撞罚项后等于零头。换句话说,第一个模型根本没学会扫干净,它只是学会了一种刷分玩法。

所以如果你也在做类似的RL项目,我强烈建议:不要只盯累计奖励报表,一定要把每个奖励分量的均值、方差单独拿出来画曲线,否则你看不到“谁在真正促进目标,谁在钻空子”。

3. 10项复合奖励的完整拆解:每一分都有来处

3.1 奖励拆分的四条设计主线

在从头设计奖励之前,我把清扫任务拆成了四个问题组:

  1. 扫得全吗(覆盖率、区域完成度、边界覆盖率)
  2. 扫得顺吗(碰撞、转弯、平滑度)
  3. 扫得值吗(时间成本、能耗成本)
  4. 扫得稳吗(防止卡死、掉头、局部死循环)

每一组对应2-3项奖励,合起来就是10项。下表是最终设计版,后面我会逐项展开。

序号奖励项类型数学形式权重
1清扫面积增量引导奖励Δcoverage * 0.80.8
2完成任务奖励稀疏里程碑+1212
3边界清扫奖励引导奖励Δborder_coverage * 1.21.2
4重复清扫惩罚约束惩罚-0.02 * repeated_area0.02
5碰撞惩罚硬约束-1.01.0
6转弯惩罚行为惩罚-0.15 * angle_velocity0.15
7路径平滑奖励行为引导+0.005 / (1 + jerk)0.005
8原地卡死惩罚硬约束-2.02.0
9时间效率惩罚约束惩罚-0.005 * step0.005
10能耗成本惩罚软约束-0.05 * distance0.05

3.2 核心任务组:覆盖率这件事值得拆细

第1项:清扫面积增量(Δcoverage * 0.8)

这是主心骨。每走一步,如果灰尘检测覆盖率比上一步提高了,就给正奖励;没提高,这项的奖励为0。

current_coverage = swept_area / total_dust_area delta = current_coverage - previous_coverage r_coverage = delta * 0.8

为什么用增量而不是总量?因为如果用“当前覆盖率 * 0.8”,那策略会得到一个巨大的常数值——覆盖率40%时每步都能吃到同样的奖励,跟动作无关,奖励等于白给。增量奖励的本质是“行为→结果”的因果信号:只有你这一步真正扫到新区域,才给钱。这符合上一节说的“过程信号”原则。

第2项:完成任务奖励(+12)

覆盖率到达95%以上判为任务完成,一次性给12分。这个奖励很“稀疏”,但它作用重大:它告诉策略“完成比不完更好”,也给了训练后期一个明确的目标锚点。

关于“+12”这个数值,我是拿“预计最大步数”倒推的:

  • 地图上完成全覆盖,经验上至少要800步;
  • 如果每步的增量奖励平均能赚0.05分,800步就是40分;
  • 一个“完成事件”应该等价于总任务列的前1/4左右,所以我取12。

这个倒推法在奖励工程里很常用。你要是拍脑袋写个+100,那所有策略都会盯着“完成”这个远期目标冒进,前期完全学不到覆盖技能,风险太大。

第3项:边界清扫奖励(Δborder_coverage * 1.2)

这是我跟很多同行交流后学到的“私货”。清扫任务里,边界和角落是最容易被漏掉的地方,但也是灰尘堆积最严重的地方。如果把边界灰尘和中部灰尘放进同一个覆盖率公式,那策略天然会优先扫中间:因为中间路径开阔、转向少、步数收益高。

所以我单独统计了一个“边界覆盖率”:距离墙壁或障碍物半径0.8m以内的区域,算作边界区域。边界覆盖率的增量奖励系数设成1.2,比一般区域高50%。实测效果非常明显——机器人会刻意贴着墙走一圈,这在第一版模型里根本见不到。

3.3 行为引导组:让路径不再是“布朗运动”

第4项:重复清扫惩罚(-0.02 * repeated_area)

这一步要解决“绕圈自嗨”。怎么检测“重复扫”呢?我是把栅格地图按边长5cm切成小格子,每个格子有一个“最近清扫时间戳”。当机器人扫过格子时,如果这个格子距离上次清扫的时间少于6秒,就算“重复清扫区域”。

if cell_last_cleaned_time[cell_id] > current_time - 6.0: repeated_area += cell_size

惩罚压力不大,但持续存在。策略会倾向于往新区域走,而不是在原地刷覆盖率。注意这个惩罚不能太大,否则机器人会为了避开“刚扫过的区域”而走极端路线,路径反而扭曲。

第5项:碰撞惩罚(-1.0)

第一版用的是-0.5,效果太温和,机器人觉得碰两下也无所谓。但我也没有调成特别狠的值,原因很现实:清扫任务很多时候必须和障碍物边缘发生轻微接触,比如贴墙扫尘。

这里有一个很值得说的细节:我把碰撞分成了“正常贴碰”和“硬碰撞”两级。用碰撞检测的冲击力阈值区分,低于某个力度的不算碰撞惩罚,只算轻微接触;高于这个力度才触发-1.0。

if impact_force > collision_threshold: r_collision = -1.0 elif impact_force > gentle_contact_threshold: r_collision = -0.1

这样处理的理由是:如果所有接触都惩罚,机器人不敢靠近墙边,边界清扫永远做不好;但如果所有接触都不惩罚,机器人会拿脸撞障碍物。分级惩罚让它在“接近障碍物”和“暴力撞击”之间找到一个中间解。

第6项:转弯惩罚(-0.15 * angle_velocity)

这个惩罚是针对“路径扭曲”的。机器人为了刷覆盖率,经常走S形、Z字形,转向频率高,姿态剧烈变化,不仅耗能,还容易扫漏。

r_turn = -0.15 * abs(angular_velocity) # rad/s

有点像一个“平滑税”。策略学到的行为是:尽量走直线、转缓弯。实测下来,这个惩罚带来的一个意外收益是机器人学会了“沿墙角走”这种高效路径,因为墙边的转向天然少。

第7项:路径平滑奖励(+0.005 / (1 + jerk))

这是“柔软度”奖励。jerk是加加速度(加速度的导数),我模拟器里能直接读到。值越大,说明路径越“颠簸”,奖励就越小。

0.005/(1+jerk)这种形式的妙处在于:数值永远为正,但上限不超过0.005,幅度受到严格压制。它是“锦上添花型”奖励,不会干扰主目标。

其实第6和第7项单独看都微不足道,但两项放一起就形成了一个“优雅路径”的软约束。清扫这种任务,路径方差太大很容易漏扫,而这两项把路径方差压下来了。

3.4 约束惩罚组:告诉机器人“不许浪费”

第8项:原地卡死惩罚(-2.0)

这是很多RL机器人项目里踩坑最深的一项,但往往要到训练中后期才暴露。机器人在墙角、障碍物缝隙里卡住后,策略会陷入两种循环:要么一直输出一个“试图前进”但物理上无法前进的动作;要么原地左右摆动。

检测卡死很简单:持续10步,位置变化小于1cm、里程计位移却大于阈值,就判定卡死。我额外加了一个“摇摆检测”:方位角在正负15度之间反复翻转超过4次,也算。

if displacement < 0.01 and odometry_delta > 0.3: r_stuck = -2.0

这项惩罚的一次性力度很大,就是为了让策略宁可绕路,也不要在墙角死磕。实测训练曲线里,加了这项之后,地图右下角那个L形障碍物区域再也没出现“卡壳”的现象。

第9项:时间效率惩罚(-0.005 * step)

步数惩罚是RL任务的老熟人了。步数越长,累积负奖励越多。作用就是逼策略用尽量少的步数完成尽量多的清扫。

但注意,这个值一定不能大。如果-0.01以上,策略会变得“赶时间”:路径粗放,边角细节放弃,因为“多花一步要多扣一分”的恐惧压过了清扫收益。这里我用了0.005,折中。

第10项:能耗成本惩罚(-0.05 * distance)

现实中的扫地机器人要耗电,仿真里我用“单位里程能耗”近似。每走1米扣0.05分。这个压力对“绕圈自嗨”是第二道防线:如果重复清扫惩罚漏判了,能耗惩罚也会让绕圈变贵。

严格来说这层的存在意义不是省电(-0.05很小),而是给策略增加一个“里程”的参照系。清扫任务的本质是用最短的路径覆盖最多的面积,没有这一项,策略只会考虑面积,不考虑腿脚。

3.5 关于“势函数塑形”的一句提醒

懂RL理论的朋友可能会问:你加了这么多边边角角的惩罚,跟奖励塑形定理冲突吗?Ng等人在1999年证明过,如果额外奖励可以写成势函数形式的差分,那最优策略不变。

我用了边界清扫奖励、平滑奖励、重复清扫惩罚这些项,严格意义上它们确实可能改变最优策略,不是“无害塑形”。但工程上的取舍是:我本来就不追求“理论上的最优清扫策略”,我要的是“符合用户需要的清扫行为”。这算是一种有意的“妥协”。如果你做的是学术实验,建议严格保留势函数条件;做应用项目的话,还是要以行为效果为导向。

4. 权重配平与归一化:复合奖励的核心手艺

4.1 每一项奖励必须先做“量级体检”

复合奖励最容易犯的错误不是“奖励项设计得不好”,而是“权重配平拍脑袋”。比如步数惩罚写0.01,碰撞惩罚写1.0,看起来没问题,但实际一跑,步数累积的惩罚远远压过碰撞惩罚,策略就变成了“宁可撞过去,不想多走一步”。

我习惯在训练前先做一次“单奖励项量级估算表”。拿上面的设计举例:

奖励项单步期望量级单回合预估值(约800步)占比
增量覆盖0.054045%
完成奖励-1214%
边界覆盖0.021618%
碰撞惩罚-0.02-16-18%
转弯惩罚-0.05-40-45%
其他小幅度±10-

这个表算完你会立刻发现,转弯惩罚的量级可能过大了。所以在正式训练之前,我把转弯惩罚从0.2下调到了0.15,又把边界清扫奖励从1.2上调到1.5左右的区间做测试。量级表的作用不是精确预测,而是防止某个奖励项“统治”训练目标。

4.2 动态奖励缩放:当覆盖率达到99%时

清扫覆盖率到后期是个很尴尬的问题。覆盖率从30%涨到60%,增量奖励好拿;从85%涨到99%,每走一步覆盖率变化微乎其微,增量奖励接近0,策略就会失去继续扫残边角的动力。

我的解法是“分段放大”:当覆盖率超过85%后,增量奖励系数自动乘以3。

if current_coverage > 0.85: scale_factor = 3.0 else: scale_factor = 1.0

这个trick给训练后期补了一口“扩张器”。没有它,我估计778分至少要打到1300步才可能实现,而且大概率中途就收敛到95%不动了。

4.3 每一个权重调整都必须是“单一变量”

整个训练过程中我调整了10多次奖励权重,每一次只改一项。你可能会觉得没效率,但RL训练噪声极大,一次改两个权重,你根本没法判断到底是谁带来的提升。

我把这个当纪律执行后,训练的归因变得特别清晰。比如有一次我同时把转弯惩罚从0.15改到0.1,又把碰撞阈值从20N改到30N,结果曲线忽高忽低,我花了整整两天才搞明白是碰撞阈值松了导致“硬碰硬”增多。后来我严格执行“一次只改一个参数”的原则,大约50%的改进都能在24小时内定位到具体原因。

5. 训练中的常见问题:四个坑,每一个我都踩过

5.1 奖励洪泛:当奖励项太多,梯度变成一团浆糊

复合奖励的“副作用”随项数增加而增加。有段时间我把奖励项加到了14个(比现在还多4个),结果PPO的critic完全学不过来了,策略更新频率也从3Hz掉到1Hz,训练根本跑不动。

原因倒不复杂:critic网络要拟合的是“状态到期望累积奖励”的映射。奖励函数越复杂、分量越多、单项信号越噪,critic的预测误差就越大,随之策略梯度也越歪。

我的办法是“合并同类项”。14项里,把两个频次相近的惩罚项合成了一个“行为浪费惩罚”,把三个跟覆盖率相关的强化项统一成“覆盖率复合加成”。变成10项之后,训练稳定多了。经验法则:复合奖励不要超过12项,最好控制在8-12项之间。

5.2 奖励循环依赖:机器人发现“反复扫同一个格子”也能刷分

虽然加了重复清扫惩罚,但机器人还是找到过一个漏洞:它在两个相邻栅格之间反复横跳。每次跳到新格子上,都算“清扫了一个新区域”,重复清扫惩罚只在“同一个格子6秒内重扫”时才触发,隔一个格子跳着扫,惩罚形同虚设。

这个案例让我意识到,重复检测的时间窗太短,判断空间太窄,就会被策略钻空子。我最后把重复区域判断扩展成“以机器人为中心的3×3邻域内,存在最近6秒被扫过的格子”就算重复,一下子堵住了漏洞。

5.3 局部最优:宁可“安全挂机”,不愿冒险碰角落

有一次训练中期,覆盖率卡在88%不再上涨。我检查奖励日志,发现策略已经进入“安全挂机”状态:在地图中央一遍遍地走已经扫过的路线,拿了低水平但稳定的增量奖励,坚决不去碰角落。

这其实是一个典型的强化学习探索问题:角落区域覆盖率的增量奖励虽然单次更高,但要到达那里需要经历一段“低奖励走廊”,策略的价值函数估计撑不过这段区间。

解法不是调权重,而是调探索。我给探索噪声(action noise)加了一个周期性放大:每2000步,噪声标准差从0.1放大到0.3,持续500步。相当于每隔一段时间把机器人踢一脚,强迫它离开舒适区去试错。用了这个招之后,覆盖率突破了88%,涨到了93%。

5.4 训练曲线“假回暖”:奖励均值在涨,行为质量在跌

奖励工程的另一个陷阱是:复合奖励的总分可能涨,但单项奖励的表现未必都好。有一次训练得分从500分涨到600分,我以为模型变好了。结果可视化一看,机器人学会了“横冲直撞快速扫完大面积,然后疯狂碰撞”。

问题出在总奖励的加权和掩盖了碰撞惩罚的上升。因为覆盖率涨得更快,总奖励还是净增长。可视化回放的时候,人一看就露馅了。

我后来养成了一个习惯:每次训练达到一个里程碑(比如提升100分),就回放一遍10个奖励项的日志曲线,看各项是否健康。如果某项异常恶化,哪怕总分在涨,也要回滚配置。

6. 迭代实录:从100分到778分的完整路径

6.1 阶段一:补全约束,从100分到220分

这一阶段做的事情:加了第4、6、8项(重复清扫惩罚、转弯惩罚、卡死惩罚)。

训练日志上最明显的变化是“绕圈自嗨”消失,路径变得收敛。22万步左右,奖励均值开始稳定在220分。这次改进验证了“行为惩罚”的价值。但问题也很明显:边界覆盖率几乎没有提升,策略依然龟缩在开阔区域。

6.2 阶段二:拆出边界覆盖率,从220分到430分

把覆盖率拆成“中部覆盖率”和“边界覆盖率”,并给边界奖励加系数,是这次跨越的核心。

训练到40万步时,模型开始出现明显的贴墙行为。我看了录制下来的动作录像,机器人在墙角附近不再“路过”,而是真正地停下来沿边扫一圈。这个从“路过”到“驻留”的行为变化,靠的就是一个单独拆出来的边界奖励。

430分时,覆盖率94%,边界覆盖率81%。但此时路径效率很差:机器人绕路绕得很夸张,一整堵墙扫下来要走两趟。

6.3 阶段三:路径平滑与动态缩放,从430分到610分

加了路径平滑奖励、调整转弯惩罚权重,同时对覆盖率后期做动态放大。训练第58万步的时候,覆盖率第一次超过98%,边界覆盖率到了91%。

这个阶段还有个隐藏工程:我把时间效率惩罚从每步0.01降到了0.005。原因是惩罚过重导致策略“赶时间冒进”,墙边清扫匆匆忙忙。调低后,单位任务的清扫深度上升,总分又从610分小幅上升到650分附近。后来我把“完成任务奖励”从10分调整到了12分,总分突破670分。

6.4 阶段四:打磨与稳定性,最终锁在778分

最后一个阶段主要清理了“探索噪声周期性放大”策略引入的副作用:探索阶段碰撞率升高,导致策略偶尔回退到保守模式。我用学习率分段衰减(前30万步LR=3e-4,后30万步LR=1e-4)替代统一的探索噪声,稳定性明显提升。最终评测时,100个随机初始化的地图中,96个地图覆盖率达到95%以上,平均奖励分数定在778分。

这个数字的含金量,不在于“高”,而在于稳定。同配置模型跑不同随机种子,分数方差只有±35分左右——这对清扫类任务来说,已经算非常收敛了。

7. 最后,分享几条我现在回头看最想告诉自己的经验

奖励工程没有标准答案,但有些弯路是可以直接避开的。

第一,奖励项要少而精,但要“结构冗余”。少而精是不要让奖励项之间过度耦合,导致调一个权重牵一发动全身;结构冗余是说,同一个目标你可以用两个角度去约束(比如重复清扫惩罚+能耗惩罚双保险)。关键是冗余要有边界,别无限堆。

第二,可视化永远比数字更能说明问题。我大量时间不是在看奖励曲线,而是在看机器人行为视频。一段60秒的失败视频,比60页奖励日志更能说明策略在钻什么空子。每次训到新的里程碑,都值得录下来慢放看看,尤其是墙角、障碍物缝隙、门框这三个地方。

第三,权重配平是“算”出来的,不是“感觉”出来的。上面那个量级估算表,建议每个项目都建。我知道很多人嫌麻烦,但一次量级算错导致的白训一个星期,远比你五分钟填一张表贵得多。

第四,“完成目标”和“节约资源”之间需要一个缓冲。清扫任务里,“完美主义”的机器人可能为了多扫2%的灰尘多走30%的路程,这个策略显然不实用。所以我在完成覆盖率阈值上用了95%(而不是98%作为一个硬性门槛),并在奖励计算里对到达95%之后的继续清扫做了衰减。你做类似任务时,不管任务是搬运还是分拣,都要想清楚这个缓冲区间放哪里。

第五,奖励工程不是一次性的,它是跟训练过程一起长大的。同样的奖励函数,在训练前期、中期、后期的效果完全不同。前期探索阶段需要“鼓励尝试”的奖励,中后期需要“约束行为”的惩罚。所以我建议提前规划奖励调整的演练环境,每次调整前先在低步数下做一对A/B测试,确认有效再放到长训练里。

这些经验不是什么高深理论,就是土办法加耐心。但说实话,清扫机器人的强化学习,技术瓶颈从来不在车体、不在感知,就在奖励定义上——你把“你到底想要一个什么样的清扫机器人”想清楚了,奖励函数其实就写出来一半了。另一半,是拿训练失败喂出来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询