ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

自动驾驶决策算法深度解析:从分层设计到工程落地实践

自动驾驶决策算法深度解析:从分层设计到工程落地实践

自动驾驶的决策算法,是整个系统里最不像“技术问题”的技术问题。感知错了可以怪传感器,执行偏了可以怪控制,但决策一旦出了问题,责任往往直接落在“这车到底该不该这么做”上,涉及安全、伦理、体验和法规。我过去几年反复被问到一个问题:决策模块到底在解决什么?为什么感知都做到那么准了,车还是会在路口显得很“笨”?这篇文章想把这个问题拆开聊一聊,也算是对自动驾驶决策算法的一次系统性梳理。

无论你做的是L2的辅助驾驶,还是L4的Robotaxi,决策算法都是那个决定“车怎么开”的脑子。它不负责看路,不负责踩油门,但负责回答三个要命的问题:现在该往哪走、该以什么方式走、以及如果情况变了该怎么改。这个模块的难度在于,它没有一个标准答案,所有输出都是“在不确定中做选择”。

1. 决策系统到底在解决什么问题

1.1 从感知到执行,决策是那个“翻译官”

感知模块输出的是目标列表、车道线、交通标志、障碍物的速度与类型,甚至还有未来几秒的轨迹预测。控制模块需要的是一个具体的动作指令:方向盘转角、油门开度、刹车踏板深度。决策模块就夹在这两者中间,把“世界是什么样”翻译成“我接下来要做什么”。

但真正做过的人会告诉你,这个翻译过程远不止“规则映射”那么简单。看一条车道线,人类司机能瞬间判断出“这段路况是否允许我变道”,但算法要处理的是:目标车道是否有车、后车的相对速度是多少、它是否在加速、我是否有足够的时间完成变道、变道之后会不会影响其他交通参与者。任何一个环节的信息缺失,都会让决策变得“胆小”或者“鲁莽”。

更麻烦的是,感知输出往往带有不确定性。视觉检测可能漏检一个锥桶,毫米波雷达可能把一个静止的铁板误判成车辆。决策算法必须在一堆“可能对也可能不对”的信息里做出一个“当下最优”的选择。所以行业内有一句话:感知决定了下限,决策决定了上限。

1.2 决策不是单点模块,是一套分层体系

很多人以为决策算法就是一个状态机,配上若干条规则就完事了。实际上,成熟的自动驾驶决策系统是一个多层的结构:

  • 任务规划:从导航路径中提取宏观意图,比如“两公里后向右变道下高架”,这个层面基本不依赖实时感知,只负责决定“大方向”。
  • 行为决策:负责处理“附近几百米内的驾驶行为”,包括跟车、超车、让行、停车、绕障、通过路口等。这一层必须感知紧密耦合,通常有限状态机或行为树来实现。
  • 运动规划:在行为决策确定“我要变道”之后,生成一条具体可执行的轨迹,包含路径和速度曲线。这一层需要考虑车辆运动学约束、动力学约束、障碍物安全边界。
  • 轨迹优化与反馈:对生成轨迹做平滑、避障、符合乘坐舒适性的优化,并持续跟踪执行效果。

这套分层体系的本质,是把一个极其复杂的问题,拆成不同时间尺度、不同空间尺度、不同抽象级别的小问题,每个子问题都有成熟的方法论,整体才能工程化地推进。如果你试图用一个巨大的神经网络直接端到端输出控制指令,不是说绝对不行,而是调试、验证、安全保障的难度会指数级上升。

注意:这里的“分层”是实践中的主流做法,不是唯一答案。行业内也有“端到端”路线的支持者,后面会单独聊这部分。

2. 决策算法三大流派,谁更适合量产

2.1 基于规则的方法:可解释、可靠、但很“脆”

基于规则的决策,最常见的就是有限状态机(FSM)和基于行为的层级状态机。上车之后,系统处于“正常行驶”状态,检测到前车减速,切换到“跟车巡航”状态,前车刹停,切换到“停车等待”状态,前方障碍物横穿,切换到“紧急制动”状态。

这套方案的优点非常突出:可解释性强,一条规则为什么触发、输出了什么动作,逻辑清晰,工程师能直接复盘;可靠性高,规则是确定性的,同样的输入必然得到同样的输出;便于量产落地,车规级验证容易通过。

但它的缺点也很明显:规则是人写的,人不可能枚举所有场景。举个例子,城市道路上的“加塞”场景,前车突然并入你的车道,距离只有10米,你是让还是不让?让,可能会被后车追尾;不让,可能发生碰撞。不同城市的驾驶风格还不同。这种场景靠写规则是写不完的,而且规则之间很容易互相冲突,需要大量的优先级判断和例外处理,维护成本极高。

我见过一些初创团队把状态机写到几千个状态,结果每加一个场景就要重构整个状态图,最终走向失控。基于规则的方法适合做安全底座和简单场景,但不适合作为唯一的决策大脑。

2.2 监督学习与模仿学习:学人类怎么开

模仿学习的思路很直接:既然规则写不完,那就让算法模仿人类司机的驾驶行为。收集大量人类驾驶数据,用行为克隆(Behavior Cloning)的方式训练一个神经网络,输入是感知结果,输出是驾驶动作。

这个路子有两个致命的坑。第一个是分布偏移:神经网络在训练时看到的都是“正常状态”,但一旦遇到训练分布之外的状态,它就会输出一些很奇怪的动作,而且会越走越偏,最终陷入从未见过的状态里,完全没有恢复能力。第二个是误差累积:学习模型偶尔输出一个次优动作(比如方向盘打多了),由于车辆状态发生了偏移,下一个时刻它面对的是一个新的状态,而这个状态它同样没学过,于是继续输出偏差动作,偏差叠加,最终失控。

后来大家做了很多改进,比如DAgger算法(人类专家在线纠正)、ChauffeurNet之类的架构,但本质问题还是:模仿学习只能模仿“见过”的行为,不能保证“没见过”的情况下的安全性。目前这种方案更多用于辅助决策模块的训练,而不是直接作为量产决策主体。

2.3 强化学习与搜索优化:上限高,但工程化难度大

强化学习的思路完全不同:不模仿人类,而是通过与环境交互,不断试错,最大化长期收益。理论上,强化学习可以让决策算法学会很多人类都想不到的巧妙策略,比如在某些拥堵场景下通过“小幅度的博弈式前探”来换取汇入主路的机会。

但强化学习在自动驾驶里落地的挑战极大。一方面,自动驾驶环境是连续状态空间、连续动作空间、多智能体交互的复杂场景,直接训练收敛极难;另一方面,真实环境试错的风险太高——你不可能让一辆车在真实道路上用撞车来学习。所以目前的方案大多是“仿真训练+真实场景微调”,或者用强化学习做特定的决策子模块(比如换道时机、无保护左转博弈)。

搜索优化类方法,比如蒙特卡洛树搜索(MCTS)在决策中的应用,思路是在每个决策时刻,做一定深度的前瞻搜索,评估各种备选动作的长期收益。这种方案的优点是能应对复杂的序列决策问题,缺点是计算开销大,实时性难以保证。

2.4 我的建议:混合方案才是工程现实

真正量产落地的系统,几乎都是“规则的骨架 + 学习的血肉”:用有限状态机或行为树保证系统行为整体可解释、可控制、可兜底;用机器学习模型去解决那些规则很难覆盖的、需要“感觉”的感知融合和场景判断;用强化学习或搜索方法做局部优化,比如换道决策、路口博弈。

我个人的经验是,不要试图用某一种单一方法解决所有问题。纯规则方案撑不住复杂交互,纯学习方案扛不住安全验证。混合方案虽然看起来“不够性感”,但它是工程上最稳妥的路径。

3. 实操层面的几个关键设计

3.1 行为树与状态机的选择标准

决策模块的老祖宗是状态机,但现在的项目里,行为树越来越流行。状态机的核心是“状态+转移条件”,适合强约束的顺序流程;行为树的核心理念是“任务+子节点”,通过选择节点、序列节点、条件节点组成一棵可复用的逻辑树。

我有一个很实用的判断标准:如果你的驾驶场景是高度顺序化的(比如封闭园区的固定路线接驳),状态机就够了,简单直接;如果场景里有很多“随时可能被打断”的行为(比如开放道路遇到突然切入的自行车),行为树更合适,因为它能方便地给低优先级行为挂载“中断条件”。

我用行为树时最深的体会是:不要把“条件判断”和“动作执行”混在一个节点里。树的每个叶子节点只做一件事,要么是条件检查,要么是动作执行,否则调试的时候你会疯掉。树的结构要尽量扁平,深度不超过四层,过深的树会让运行时的调试变得极其痛苦。

3.2 预测模块和决策模块之间的交互设计

很多团队把预测和决策分开开发,但两者是强耦合的。决策动作会影响预测结果,预测结果又决定决策动作。行业里管这叫“交互耦合”。

实操中要注意的一个细节是:别把预测结果当作“确定事实”传给决策模块。如果预测模块说“前车80%变道”,决策模块直接基于“一定会变道”来做规划,那就会反应过度;但如果决策模块完全忽略预测,又会在前车真正变道时毫无准备。

比较务实的做法是“势态感知”式的交互设计:决策模块维护一个危险势态场,把预测模块输出的概率信息转化成不同风险等级的约束条件。概率高的预测转化为“硬约束”,必须遵守;概率低的转化为“软约束”,只在没有冲突时遵守。这样既保证了安全,又不会让车辆变得过度犹豫。

3.3 决策结果必须能被“人”审查

这是我最想强调的一点。无论决策算法多先进,最终都需要人能审查它的行为是否合理。这就意味着决策模块的输出不能只是一条轨迹,还必须包括:

  • 决策意图:当前是在跟车、让行、还是换道?
  • 触发条件:为什么做出这个决策?是哪个目标触发的?
  • 候选集合:犹豫过的其他选项是什么?为什么没选?
  • 置信度:当前决策的把握有多大?

这套东西在实车测试里价值极高。你拿到一个测试日志,如果只有轨迹,根本没法定位问题。但如果你能看到决策时的完整“思维链”,就能很快判断问题出在感知、预测还是决策本身。我的习惯是让决策模块在跑环视时把所有这些信息记录到日志,并且支持离线回放。

4. 决策算法的定量评估与仿真验证

4.1 评估决策不好,必须看哪些指标

决策算法的评估是个很容易被带偏的事。很多人只看“有没有违章”“有没有碰撞”,这两条只是底线。真正衡量决策质量,至少要拆成几个维度:

  • 安全性指标:最小碰撞时间TTC(Time To Collision)、车头时距THW、紧急制动的频率。
  • 效率指标:平均速度、通过时间、通行效率(车流量一定时的平均流量)。
  • 舒适性指标:纵向加速度变化率(冲击度Jerk)、横向加速度、方向盘转角变化率。
  • 合规性指标:压线次数、超速时间占比、实线变道次数。
  • 交互博弈质量:对目标车辆的“让行”是否及时、对汇入车辆的“接纳”是否顺畅、是否出现明显的“犹豫”(比如反复微调、前进后退)。

这些指标各有各的测量难度,尤其是“交互博弈质量”这种主观性很强的维度,业界现在常用“驾驶员接管率”和“接管时的车辆状态”作为间接衡量。如果决策算法频繁触发自动驾驶退出或强制接管,行车质量一定不合格。

4.2 仿真闭环里的那些坑

仿真测试是决策算法开发的重头戏,但很多人对仿真的期望值不对,觉得“仿真过了就一定能跑实车”。这个认知很危险。决策算法的仿真测试,最大的价值是“批量复现和回归”,而不是“证明算法没毛病”。

几个仿真里常见的坑:

第一个是传感器噪声的建模失真。很多仿真平台的感知输出是“完美的”,没有漏检、误检、位置噪声。但实际传感器的输出又脏又乱,决策算法在真实感知输入下表现会大跌眼镜。解决思路是在仿真层加入感知噪声模型,把感知层面的不确定性提前暴露出来。

第二个是交通流参与者过于“礼貌”。仿真里的NPC车辆往往是规则的、没有攻击性的,永远不会加塞、不会在路口犹豫。这导致决策算法在仿真里表现得极好,但一上真实道路就被“社会毒打”。解决思路是用真实路采数据构建交通流场景,或者用“对抗性NPC”来故意测试决策算法的鲁棒性。

第三个是时间同步问题。仿真里决策模块每帧处理时间不固定,可能导致实车中决策频率波动,影响时序关系。在实车上,决策算法相机延迟20ms,结果动作可能滞后一辆车身的距离。我的经验是在仿真中强制加入固定时间延迟和CPU负载模拟,尽早暴露时序问题。

4.3 一个简单的决策评估脚本示例

这里给一个我常用来做决策模块回归测试的Python示例,功能很简单:计算候选决策的碰撞风险与效率得分。

import numpy as np class DecisionEvaluator: def __init__(self, safety_weight=0.7, efficiency_weight=0.3): self.safety_weight = safety_weight self.efficiency_weight = efficiency_weight def ttc_score(self, relative_distance, relative_speed): """ 计算TTC得分:TTC越大,得分越接近1 相对速度大于0表示在靠近 """ if relative_speed <= 0: return 1.0 if relative_distance <= 0: return 0.0 ttc = relative_distance / relative_speed # 使用sigmoid形式做非线性映射 raw_score = 1.0 - 1.0 / (1.0 + np.exp(-(ttc - 3.0) / 1.5)) return float(max(0.0, min(1.0, raw_score))) def comfort_score(self, accel_history): """ 根据冲击度jerk均值计算舒适性得分 accel_history: 加速度列表 """ if len(accel_history) < 3: return 1.0 jerk = np.diff(accel_history) / 0.1 # 假设控制周期100ms mean_abs_jerk = np.mean(np.abs(jerk)) # 冲击度小于2 m/s^3基本无感,大于8会明显顿挫 score = max(0.0, min(1.0, 1.0 - (mean_abs_jerk - 2.0) / 6.0)) return score def evaluate(self, relative_distance, relative_speed, accel_history): safety = self.ttc_score(relative_distance, relative_speed) efficiency = self.comfort_score(accel_history) combined = self.safety_weight * safety + self.efficiency_weight * efficiency return { "safety_score": round(safety, 3), "efficiency_score": round(efficiency, 3), "combined_score": round(combined, 3), } if __name__ == "__main__": ev = DecisionEvaluator() # 例1:距离30米,相对速度8m/s(正在靠近),且加速度变化较平缓 accel_1 = [0.2, 0.1, -0.1, -0.2, -0.1] print(ev.evaluate(30.0, 8.0, accel_1)) # 例2:距离10米,相对速度5m/s(正在靠近),且加速度变化剧烈 accel_2 = [0.5, -0.4, 0.6, -0.5, 0.3] print(ev.evaluate(10.0, 5.0, accel_2))

这个脚本的思路不是要做一个完整决策系统,而是强调“决策评价本身需要指标化”。你可以在仿真环境中不断回放决策模块的每个输出,把这些指标计算出来,画成曲线,然后对比不同版本的决策算法参数,看哪个版本的行为更稳健。很多团队把决策算法调好之后,就把这套指标写进自动化回归流水线,这是我觉得性价比极高的一件事。

5. 常见问题与排查技巧实录

5.1 动态障碍物“反复横跳”,决策被带偏

实车测试中很常见的一个问题是:旁边车道的车辆因为预测模块的轨迹跳跃,导致决策模块频繁产生“变道-取消变道”的震荡。车辆的表现就是反复打方向灯,却不完成变道,或者方向盘左右小幅摆动。

排查思路是这样的:先看预测模块输出的目标轨迹是不是稳定。如果预测轨迹本身每帧跳变,那问题根源是“上游”,决策模块只是在如实反映输入的抖动。但如果预测轨迹稳定而决策还在反复切换,那就是决策模块的“迟滞”设计不足。

处理方法通常是在决策逻辑中加入迟滞区间(Hysteresis):变道的触发条件阈值,要比“取消变道”的阈值更严格。比如要判断“前方车太慢,我要换道”,需要慢车持续5秒以上,而不是一瞬间的速度差就触发;一旦决定换道,除非风险等级超过某个更高阈值,否则不取消。这个迟滞设计能有效过滤掉决策震荡。

5.2 交互场景的博弈不闭环

无保护左转是决策算法最容易出问题的场景。算法希望找到对面直行车流的“间隙”通过,但对面车辆也在动态变化,有的车在减速让行,有的车在加速抢行。很多决策算法只会简单计算“间隙是否足够”,却没有考虑“对方是否愿意让我”。

这里我常用的排查技巧是,在回放工具里把目标车辆的轨迹预测不确定性可视化。很多问题的根源是预测模块只输出了一条期望轨迹,没有输出轨迹的“悲观估计”。当你看到决策算法在犹豫时,通常是因为预测模块给出的间隙窗口一直在跳。解决方法是在决策模块里引入“博弈等待时间”:如果算法判断需要博弈但间隙条件不满足,就维持等待状态,并持续监控间隙变化趋势。如果趋势是“逐渐变大”,就准备起步;如果趋势是“逐渐变小”,就完全放弃本轮的穿越窗口。

5.3 规则冲突导致决策死锁

行为树和状态机在规则增多后,会出现一个经典问题:两条规则同时满足触发条件,但动作完全相反。比如“前方公交车进站,需要减速避让”和“本车即将错过出口,需要尽快变道”,两个动作冲突,系统可能陷入决策死锁,表现为车辆停在路中间不动,直到触发最小风险状态。

这个问题最适合用“规则优先级”和“规则抑制”来解决。给每条规则显式地定义优先级,并且规定高优先级规则执行时,可以抑制哪些低优先级规则。注意优先级不能写死,要根据场景动态调整。我的做法是维护一个“风险等级-优先级”的映射表:当事件风险等级高(如即将碰撞)时,碰撞规避规则优先级最高;当没有高风险事件时,效率类规则优先级提升。

5.4 问题速查表

现象可能原因排查方法解决方向
决策频繁震荡预测轨迹跳变回放预测轨迹稳定性增加预测平滑;决策增加迟滞
无保护左转不敢走预测不确定度过大可视化预测轨迹集引入间隙趋势分析,增加博弈等待
规则冲突死锁多条规则同时触发打印规则触发记录建立动态优先级表和规则抑制关系
仿真完美实车拉胯感知噪声建模不足对比仿真输入与真实感知输出加入感知噪声层和对抗性NPC
行驶效率过低安全类规则权重过重统计各规则的触发频率调整软硬约束划分,动态调权
变道总是变不过去目标车道后车预测加速查看后车轨迹预测集优化预测模型或接受“延迟变道”

这套速查表是我在项目迭代中一点点攒出来的,每次遇到新问题就往上加一行,现在已经有几十行了。我觉得做决策算法最有价值的事情之一,就是建立自己团队的问题库,很多东西你反复踩坑,不如记录下来让后来者少走弯路。

6. 决策算法的落地现实与个人体感

做决策算法这些年,一个很深的感受是:这个领域最难的从来不是某个算法的数学推导,而是如何在不确定性中持续做出“可以被解释”的选择。感知和预测的不确定性是永远存在的,决策算法必须接受这一点,并且在设计逻辑时就把这种不确定性考虑进去,而不是假设输入全部正确。

我见过不少团队花了很多精力去追求一个完美的单一算法模型,却忽略了对决策链路的可观测性和可调试性设计。最后的教训是:一个能被快速定位问题的“朴素决策系统”,远好过一个无法调参的“花哨黑盒”。还有一个经常被忽视的点:决策算法工程师必须花大量时间看真实的道路数据,去理解人类驾驶行为里的“潜规则”——什么时候该果断,什么时候该犹豫,什么时候一个小小的加速反而能让对方让行。这些感觉是坐在办公室里写规则永远得不到的。

如果你正准备进入这个领域,我的建议是先找开源决策框架跑起来,把状态机和行为树的代码读透,然后用真实路采数据做回放迭代,逐步理解“决策”和“预测”“规划”之间那些微妙的关系。等你真正经历过一次从决策错误到交通事故排查的过程,你会明白决策算法这份工作的分量。

最后分享一个小技巧:每次做仿真测试时,给决策算法故意注入一些感知错误(例如某帧漏检一个前方静止车辆),看系统能不能识别出“感知状态不可靠”并进入保守模式。这个测试绝大部分团队不做,但一旦在实车上遇到感知漏检,决策算法有保守兜底,和没有保守兜底,结果可能是天壤之别。这也是我至今保留的习惯。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进