
1. 为什么在自动驾驶决策模块里塞进一棵搜索树第一次把蒙特卡洛搜索树Monte Carlo Tree Search下文简称 MCTS往自动驾驶决策模块里塞的时候我心里是打鼓的。规划控制那边已经有成熟的采样规划器、有基于优化的方法甚至有些场景直接上规则状态机也能跑得挺稳为什么还要引入一个靠随机模拟来选动作的东西这个问题如果不先想清楚后面写出来的代码一定是照猫画虎参数调不动出了问题也不知道从哪儿查。先说清楚 MCTS 到底解决的是什么问题。自动驾驶的决策层本质上是在一个部分可观测、多智能体、连续状态空间的环境里做序贯决策当前这一刻选左变道还是保持车道会影响下一秒周围车辆的反应而周围车辆的反应又反过来影响我下一步能选什么。这跟下棋很像——你落一子对手会应一手你要考虑的是若干步之后的局面而不是眼前这一步的即时收益。MCTS 正是为这类带对手、带未来推演的决策问题设计的。那为什么不用传统的博弈树搜索因为博弈树的规模会指数爆炸。城市道路上一个四岔路口假设每个决策周期有 6 个候选动作加速、减速、左转、右转、保持、微调周围有 4 辆关键交互车每辆车也有类似的动作空间那么往前推 5 步节点数就是 (6×6^4)^5 这个量级天文数字穷举根本不可能。MCTS 的聪明之处在于它不追求穷举而是把有限的计算预算有偏地花在看起来更有希望的分支上用统计的方式逼近最优决策。这里有个很多人一开始会误解的点MCTS 不是随机乱试。它的随机只是手段核心是用随机模拟的结果去反向更新每个节点的价值估计再根据价值估计决定下一次往哪个分支多花时间。这个选择—扩展—模拟—回传的循环才是它的灵魂。我在项目里踩的第一个坑就是早期版本只做了随机 rollout 却没有正确的回传和选择策略结果搜索出来的动作跟随机选差不多白白浪费算力。从工程角度看MCTS 在自动驾驶里最适合的场景是交互密集、规则难以穷举、但又需要可解释决策的地方比如无保护左转、匝道汇入、环岛通行、窄路会车。这些场景里纯规则容易写出一堆 if-else 还覆盖不全纯学习的方法又缺乏可解释性和安全兜底。MCTS 恰好卡在中间它的搜索树本身就是一份决策依据你能看到它为什么倾向某个动作这对功能安全论证非常友好。提示如果你的场景里交互车辆很少、规则非常明确比如封闭园区直线巡航别硬上 MCTS规则状态机或采样规划器更省事、更可控。MCTS 的价值在交互复杂这四个字上。2. 把 MCTS 的四步循环翻译成驾驶决策语言2.1 选择从根节点往下走走哪条路有讲究选择阶段要做的事是从当前搜索树的根节点出发一路往下选子节点直到到达一个还没完全展开的节点。选哪个子节点靠的是UCTUpper Confidence Bound for Trees公式UCT Q/N c * sqrt( ln(N_parent) / N )其中 Q 是该节点的累计价值N 是该节点被访问的次数c 是探索常数。这个公式的直觉是第一项代表利用——历史表现好的节点多走第二项代表探索——访问次数少的节点给点机会。c 越大越爱探索越小越保守。在驾驶场景里这个公式的每个量都要重新定义。Q 不能是简单的胜负分而应该是多目标加权安全性、通行效率、舒适性、合规性。我一般把安全性做成硬约束碰撞直接给极大负值效率和舒适性做成软加权。N 的计数也要注意同一个动作在不同交通状态下被访问价值可能完全不同所以状态表征必须足够细否则会把直行畅通时的加速和前车急刹时的加速混为一谈。c 的取值我实测下来在驾驶决策里比围棋那种纯博弈场景要小一些通常 0.5 到 1.2 之间。原因很简单驾驶动作的后果差异没有围棋那么剧烈过大的探索会导致决策抖动车开起来一顿一顿的。这个参数没有理论最优值必须结合你的动作空间粒度和决策周期来调。2.2 扩展什么时候该长出新叶子扩展阶段是在选中的节点上把还没试过的动作加进树里。这里有个关键设计动作空间怎么离散化。连续的方向盘转角和加速度不能直接喂给 MCTS必须离散成有限个候选动作。我常用的做法是分层离散纵向用 {急减速, 减速, 保持, 加速, 急加速} 五档横向用 {左变道, 左微调, 保持, 右微调, 右变道} 五档组合起来 25 个候选。听起来不多但配合周围车辆的动作组合树还是会很快膨胀。所以实际工程里我会做动作剪枝根据当前车道线、障碍物、交通规则先把物理上不可行或违规的动作砍掉通常能砍掉一半以上。扩展的时机也很重要。不是每个节点一访问就立刻展开所有子节点而是访问到一次才展开一个。这样树是渐进生长的计算预算不会被浪费在明显没希望的分支上。我见过有同学一上来就把所有子节点全建出来结果内存爆了搜索深度还上不去。2.3 模拟rollout 策略决定了搜索质量的上限模拟阶段也叫 rollout是从新扩展的节点开始用某个默认策略一路走到终止条件得到一个结果。这一步是 MCTS 里最玄学也最影响效果的部分。最朴素的 rollout 是纯随机选动作但在驾驶场景里这基本不可用——随机选动作大概率立刻撞车模拟出来的价值全是垃圾。我的做法是用一个轻量级启发式策略做 rollout比如保持当前车道、跟车距离维持在安全阈值、遇到前车减速就减速。这个策略不需要很聪明但必须保证模拟出来的轨迹是物理可行、基本安全的否则价值估计就没有参考意义。rollout 的深度也要控制。理论上要模拟到 episode 结束但驾驶场景没有明确的结束所以我会设一个滚动时域比如往前模拟 3 到 5 秒或者模拟到某个关键事件发生比如完成变道、通过路口。模拟步长通常跟决策周期对齐0.1 到 0.2 秒一步。这里有个经验rollout 策略越强需要的模拟次数越少。早期我用纯随机要跑几千次才收敛换成启发式策略后几百次就能得到稳定决策。算力紧张的车载平台这个优化非常值。2.4 回传价值怎么一路传回根节点模拟得到一个结果后要沿着刚才走过的路径把结果反向更新到每个节点的 Q 和 N 上。这一步看似简单但有个细节容易错价值的方向。如果 Q 是从自车视角定义的那么对手节点的价值应该是自车价值的反面零和假设还是各自独立非零和驾驶场景其实是混合动机的有些车会让行有些车会抢道不是纯粹的零和博弈。我一般不做零和假设而是给每个智能体维护独立的价值自车节点更新自车价值对手节点更新对手价值。这样虽然计算量翻倍但决策更符合真实交通。回传还有一个工程细节折扣因子。越远的未来价值折扣越多。驾驶里我通常取 0.95 到 0.99取决于决策周期。周期短0.1 秒可以取大一点周期长0.5 秒取小一点避免过度看重远期不确定的收益。3. 状态表征与动作空间决定成败的隐形地基3.1 状态里到底该放什么MCTS 的搜索质量很大程度上取决于状态表征是否抓住了决策的关键信息。放少了树会把不同情况混为一谈放多了状态空间爆炸搜索效率骤降。我在项目里总结了一套最小充分状态集经过多轮迭代后稳定下来大概是这些维度维度类别具体内容说明自车状态位置、速度、加速度、航向角、所在车道基础运动学必须有周围车辆相对位置、相对速度、相对加速度、车道归属只保留一定范围内的关键交互车道路结构车道线曲率、路口拓扑、限速、停止线决定动作可行性交互历史最近若干帧的相对运动趋势判断对方意图的隐含线索任务信息目标车道、导航意图、剩余距离决定长期目标关键交互车的筛选是个技术活。全放进来不现实我一般用影响域筛选只保留那些在未来 3 秒内可能与自车产生冲突的车辆。判断方法很简单做一次匀速外推看轨迹是否相交或距离小于阈值。这样通常能把十几辆车筛到 3 到 5 辆搜索规模立刻可控。3.2 动作离散化的粒度怎么定动作空间太粗决策不精细车开起来一顿一顿太细搜索树膨胀算力扛不住。我试过几种粒度最后落在纵向 5 档、横向 5 档这个配置上理由是纵向加速度范围通常取 [-4, 2] m/s²5 档对应约 1.5 m/s² 的间隔这个间隔下乘客基本感觉不到档位切换的突兀。横向用车道级加微调变道是离散事件微调用于车道内避让粒度够用。如果场景需要更精细的控制比如窄路避障我会在 MCTS 输出粗动作后再接一层局部轨迹优化把粗动作平滑成连续轨迹。这样 MCTS 负责决策选哪条路优化器负责这条路怎么走得舒服分工明确。3.3 状态聚合与泛化纯 MCTS 有个天然缺陷它不泛化。同一个决策问题换个绝对位置就得重新搜。解决办法是状态相对化所有坐标都转成以自车为原点的相对坐标所有速度都转成相对速度。这样不同地点、不同绝对速度下的相似场景能共享搜索经验。更进一步我会做一个状态哈希表把相似状态映射到同一个节点。哈希的粒度要小心太粗会把不同场景混一起太细又起不到聚合作用。我的经验是用关键特征的离散桶做哈希键比如相对距离分 10 档、相对速度分 10 档、车道关系分几类组合起来做键。4. 并行化与实时性车载平台上的算力博弈4.1 为什么单线程 MCTS 在车上跑不动MCTS 是** anytime 算法**给的时间越多决策越好。但车载平台的算力是有限的决策周期通常要求 100 毫秒以内出结果。单线程跑 MCTS几百次模拟就要几十毫秒稍微复杂点的场景根本来不及。我最早的单线程版本在无保护左转场景下100 毫秒只能跑 200 次左右的模拟决策质量勉强能用但遇到多车交互就明显不够。后来做了并行化同样时间能跑到 1500 次以上决策稳定性和通行效率都有肉眼可见的提升。4.2 根并行、树并行与叶子并行怎么选并行 MCTS 有三条主流路线我逐个试过根并行是最简单的每个线程独立跑一棵完整的树最后投票或加权融合。优点是实现简单、无锁竞争缺点是内存占用翻倍且各线程的搜索不共享效率有浪费。适合线程数少4 到 8的场景。树并行是共享一棵树多线程同时访问不同节点。效率最高但需要处理节点访问的同步问题。我用的是原子操作加虚拟损失的方案一个线程选中某节点时先给它加一个虚拟损失让其他线程暂时避开模拟完再撤销。这样避免了锁竞争开销小。叶子并行是把 rollout 阶段并行化因为 rollout 通常是最耗时的部分。适合 rollout 策略复杂、单次模拟成本高的场景。我最终的选择是树并行加叶子并行混合树结构共享rollout 批量并行。实测在 8 核平台上加速比能到 5 倍以上比纯根并行高不少。4.3 时间预算怎么分配MCTS 的 anytime 特性意味着你必须设一个停止条件。常见的有三种固定模拟次数、固定时间、以及基于收敛判断的自适应停止。固定模拟次数的问题是不适应场景复杂度简单场景浪费算力复杂场景不够用。固定时间更实用但要注意留出安全余量因为搜索完之后还有轨迹生成、安全检查等环节。我一般把决策周期的 60% 到 70% 留给 MCTS剩下的留给后处理。自适应停止是我后来加上的优化监控根节点最优动作的价值方差方差小于阈值就提前停。这样简单场景能省下大量算力复杂场景则跑满预算。注意并行 MCTS 的随机性会导致决策抖动——同一场景两次搜索可能给出不同动作。解决办法是加决策平滑新决策与上一周期决策差异过大时做一次一致性检查除非有充分理由比如突发障碍否则倾向保持。这个在实车上非常关键否则乘客会觉得车犹豫不决。5. 奖励函数设计安全、效率、舒适怎么权衡5.1 安全性必须是硬约束而非软奖励奖励函数是 MCTS 的指挥棒设计不好搜索出来的动作再优也是危险的。我的第一条原则是安全性用硬约束表达不用软奖励。什么意思如果碰撞只是给一个很大的负奖励MCTS 在探索时仍可能试探碰撞路径因为它的价值估计是统计的偶尔的碰撞可能被平均掉。正确做法是在动作生成阶段就把会导致碰撞的动作直接剔除让搜索空间里根本不存在碰撞选项。这样 MCTS 只需要在安全动作里优化效率和舒适。具体实现上我会对每个候选动作做一次快速前向安全检查用简化的运动学模型外推 1 到 2 秒看是否与障碍物包围盒相交。相交的直接剪掉。这一步计算量很小但能极大提升搜索的安全性。5.2 效率与舒适的加权艺术安全动作确定后剩下的就是效率和舒适的权衡。我的奖励函数大致长这样R w1 * 进度奖励 w2 * 速度奖励 - w3 * 加速度惩罚 - w4 * jerk 惩罚 - w5 * 偏离目标惩罚进度奖励鼓励朝目标前进速度奖励鼓励保持合理车速加速度和 jerk 惩罚保证舒适偏离目标惩罚防止绕路。权重 w1 到 w5 需要根据场景调没有万能值。我调参的经验是先保证安全再调效率最后调舒适。因为舒适权重调大了车会变得过于保守通行效率下降效率权重调大了车会激进乘客不适。通常效率权重是舒适权重的 2 到 3 倍比较平衡。5.3 不同驾驶风格的参数化同一套算法不同用户可能想要不同风格。我做了风格参数化把权重暴露成几个高层参数激进/均衡/保守底层自动映射到具体权重。这样一套代码能适配多种需求也方便做 A/B 测试。风格切换时要注意平滑过渡不能这一秒激进下一秒保守否则决策会跳变。我的做法是风格参数本身做低通滤波缓慢变化。6. 实车调试中那些文档不会写的坑6.1 搜索树规模失控与内存管理实车跑久了搜索树会越来越大内存占用持续上升。原因是每帧都在扩展新节点旧节点没及时释放。我踩过这个坑跑半小时后内存告警。解决办法是每帧重建树而不是跨帧复用。虽然损失了一些跨帧经验但内存可控实现也简单。如果确实想复用必须做节点淘汰超过一定深度或一定时间没被访问的节点定期清理。6.2 决策抖动与一致性处理前面提过抖动问题这里展开说。抖动的根源是 MCTS 的随机性加上并行竞争。表现是车在两条车道间犹豫或者速度忽高忽低。我的处理分三层第一层是增加模拟次数次数越多价值估计越稳定第二层是决策平滑新决策与旧决策做一致性检查第三层是迟滞机制动作切换需要连续若干周期都倾向新动作才真正执行。三层叠加后抖动基本消除。6.3 对手意图建模的简化陷阱MCTS 假设对手会按某个策略行动但真实交通里对手意图是未知的。我早期用对手保持当前运动这个最简假设结果在对手突然变道时决策失效。后来改成多意图假设给每个对手维护几种可能的意图保持、加速、减速、变道在搜索时对每种意图分别模拟最后按概率加权。这样虽然计算量增加但鲁棒性大幅提升。意图概率可以用简单的贝叶斯更新根据对手历史运动推断。6.4 与下游规划控制的接口对齐MCTS 输出的是离散动作下游规划控制需要的是连续轨迹。这个接口如果没对齐会出现决策说变道、规划说不行的矛盾。我的做法是让 MCTS 输出带置信度的动作序列规划器根据置信度决定是否执行。置信度低时规划器可以降级到保守策略。同时规划器反馈的可行性信息要回传给 MCTS作为动作剪枝的依据形成闭环。7. 从仿真到实车的验证路径7.1 仿真环境里要重点验证什么仿真阶段不能只跑通就完事要重点验证几件事决策一致性相似场景决策是否稳定、边界处理极端场景是否安全、算力占用最坏情况下是否超时。我一般会构造一批对抗性场景前车急刹、旁车强行并线、路口突然窜出障碍物等看 MCTS 的应对。这些场景在仿真里可以反复跑统计成功率。7.2 实车上的降级与兜底实车必须有兜底。MCTS 超时、异常、或决策置信度过低时要能无缝降级到规则策略或安全停车。这个降级逻辑要独立于 MCTS不能依赖它的输出。我还会加一个安全监控器独立检查 MCTS 的决策是否违反安全约束违反就拦截。这个监控器逻辑要极简保证自身不出错。7.3 数据回灌与持续迭代实车跑出来的数据要回灌到仿真里复现问题场景迭代算法。我建了一套场景库把实车遇到的困难场景提取出来作为回归测试用例。每次算法更新都跑一遍场景库确保不退化。这套流程跑下来MCTS 的决策成功率从最初的七成多提升到九成以上剩下的主要是感知不确定性导致的不是决策本身的问题。8. 一些个人体会MCTS 在自动驾驶里不是银弹它的价值在交互密集场景在规则难以穷举的地方。用对了场景它能给出可解释、可论证的决策用错了场景它就是白白烧算力。我最大的体会是MCTS 的效果七分靠状态表征和奖励设计三分靠搜索本身。很多人一上来就调 c 参数、调模拟次数其实方向错了。状态里少了一个关键维度或者奖励函数权重失衡再怎么调搜索参数也救不回来。另一个体会是并行化和工程优化的重要性不亚于算法本身。同样的算法工程做得好实时性达标就能上车工程做得差算法再漂亮也只能停在仿真里。最后分享一个小技巧调试 MCTS 时把搜索树可视化出来看它实际访问了哪些分支、价值怎么分布。很多时候问题一眼就能看出来——比如某个明显该探索的分支访问次数是零那多半是动作剪枝剪错了或者状态哈希把不同情况混了。可视化比看日志高效得多。