自动驾驶决策算法深度解析:从分层设计到工程落地实践
自动驾驶的决策算法是整个系统里最不像“技术问题”的技术问题。感知错了可以怪传感器执行偏了可以怪控制但决策一旦出了问题责任往往直接落在“这车到底该不该这么做”上涉及安全、伦理、体验和法规。我过去几年反复被问到一个问题决策模块到底在解决什么为什么感知都做到那么准了车还是会在路口显得很“笨”这篇文章想把这个问题拆开聊一聊也算是对自动驾驶决策算法的一次系统性梳理。无论你做的是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 评估决策不好必须看哪些指标决策算法的评估是个很容易被带偏的事。很多人只看“有没有违章”“有没有碰撞”这两条只是底线。真正衡量决策质量至少要拆成几个维度安全性指标最小碰撞时间TTCTime To Collision、车头时距THW、紧急制动的频率。效率指标平均速度、通过时间、通行效率车流量一定时的平均流量。舒适性指标纵向加速度变化率冲击度Jerk、横向加速度、方向盘转角变化率。合规性指标压线次数、超速时间占比、实线变道次数。交互博弈质量对目标车辆的“让行”是否及时、对汇入车辆的“接纳”是否顺畅、是否出现明显的“犹豫”比如反复微调、前进后退。这些指标各有各的测量难度尤其是“交互博弈质量”这种主观性很强的维度业界现在常用“驾驶员接管率”和“接管时的车辆状态”作为间接衡量。如果决策算法频繁触发自动驾驶退出或强制接管行车质量一定不合格。4.2 仿真闭环里的那些坑仿真测试是决策算法开发的重头戏但很多人对仿真的期望值不对觉得“仿真过了就一定能跑实车”。这个认知很危险。决策算法的仿真测试最大的价值是“批量复现和回归”而不是“证明算法没毛病”。几个仿真里常见的坑第一个是传感器噪声的建模失真。很多仿真平台的感知输出是“完美的”没有漏检、误检、位置噪声。但实际传感器的输出又脏又乱决策算法在真实感知输入下表现会大跌眼镜。解决思路是在仿真层加入感知噪声模型把感知层面的不确定性提前暴露出来。第二个是交通流参与者过于“礼貌”。仿真里的NPC车辆往往是规则的、没有攻击性的永远不会加塞、不会在路口犹豫。这导致决策算法在仿真里表现得极好但一上真实道路就被“社会毒打”。解决思路是用真实路采数据构建交通流场景或者用“对抗性NPC”来故意测试决策算法的鲁棒性。第三个是时间同步问题。仿真里决策模块每帧处理时间不固定可能导致实车中决策频率波动影响时序关系。在实车上决策算法相机延迟20ms结果动作可能滞后一辆车身的距离。我的经验是在仿真中强制加入固定时间延迟和CPU负载模拟尽早暴露时序问题。4.3 一个简单的决策评估脚本示例这里给一个我常用来做决策模块回归测试的Python示例功能很简单计算候选决策的碰撞风险与效率得分。import numpy as np class DecisionEvaluator: def __init__(self, safety_weight0.7, efficiency_weight0.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. 决策算法的落地现实与个人体感做决策算法这些年一个很深的感受是这个领域最难的从来不是某个算法的数学推导而是如何在不确定性中持续做出“可以被解释”的选择。感知和预测的不确定性是永远存在的决策算法必须接受这一点并且在设计逻辑时就把这种不确定性考虑进去而不是假设输入全部正确。我见过不少团队花了很多精力去追求一个完美的单一算法模型却忽略了对决策链路的可观测性和可调试性设计。最后的教训是一个能被快速定位问题的“朴素决策系统”远好过一个无法调参的“花哨黑盒”。还有一个经常被忽视的点决策算法工程师必须花大量时间看真实的道路数据去理解人类驾驶行为里的“潜规则”——什么时候该果断什么时候该犹豫什么时候一个小小的加速反而能让对方让行。这些感觉是坐在办公室里写规则永远得不到的。如果你正准备进入这个领域我的建议是先找开源决策框架跑起来把状态机和行为树的代码读透然后用真实路采数据做回放迭代逐步理解“决策”和“预测”“规划”之间那些微妙的关系。等你真正经历过一次从决策错误到交通事故排查的过程你会明白决策算法这份工作的分量。最后分享一个小技巧每次做仿真测试时给决策算法故意注入一些感知错误例如某帧漏检一个前方静止车辆看系统能不能识别出“感知状态不可靠”并进入保守模式。这个测试绝大部分团队不做但一旦在实车上遇到感知漏检决策算法有保守兜底和没有保守兜底结果可能是天壤之别。这也是我至今保留的习惯。