做了两年多端到端自动驾驶的仿真实验,各种模型架构也差不多摸了一遍。说实话,很多论文里的方案,真到自己动手复现的时候,总会觉得差点意思,要么是训练不稳定,要么是泛化能力不行,在仿真器里跑得挺溜,换条路就原形毕露。但Transfuser这个模型,是我见过少有的,把“一段式端到端”做到既能跑通、又符合直觉的方案。这个“一段式端到端”的定位很关键。它的意思是,从传感器原始数据输入,到最终输出车辆控制指令,整个过程是一个完整的神经网络模型,中间没有拆成独立的感知、预测、规划模块,也没有人为定义中间结果。当年我刚开始接触这个方向时,业内主流还是两段式甚至三段式的方案,感知出障碍物框和车道线,然后交给规则规划器去算轨迹,最后再映射成控制指令。这种方案的好处是每个环节都好解释、好调试,但坏处也明显——感知误差会一路传导下去,而且感知结果丢失了大量原始信息,比如目标的细微运动趋势、路面材质对抓地力的影响,这些信息对驾驶决策其实很重要,但级联架构里根本传不到后端。Transfuser让我印象最深的一点,是它在“一段式”这个框架下,没有牺牲可解释性。它不是把多传感器数据一股脑丢进一个巨型网络里让它自己学,而是用一个注意力特征融合模块,把摄像头图像和LiDAR点云的特征在多个尺度上做交互。这种做法既保留了端到端学习的全局优化优势,又能在特征层面显式建模两种模态之间的对应关系,从设计思路上就比那些单纯堆算力的方案高明不少。这篇文章我打算把Transfuser从原理到实操完整拆一遍,包括它的架构设计细节、训练技巧、以及我在复现和改造过程中踩过的坑。如果你正在做自动驾驶相关的科研或者工程落地,或者对多模态融合、端到端控制感兴趣,这篇文章应该能帮你省下不少摸索的时间。1. 为什么端到端方案值得赌一把:先搞清楚两类方案的优劣在聊Transfuser的具体结构之前,我得先把端到端这个话题的来龙去脉讲清楚。因为很多初学者一上来就纠结“用CNN还是用Transformer”,但实际上这个问题的前提是——你为什么要选端到端?传统方案,我习惯叫它“模块化级联方案”,就是把自动驾驶拆成感知、预测、规划、控制四个独立模块,每个模块单独训练、单独优化。感知模块负责检测目标、分割可行驶区域,预测模块负责推测其他交通参与者的意图,规划模块生成一条安全可行的轨迹,控制模块再把轨迹跟踪成油门、刹车、方向盘的操作。听起来很合理,每个模块都有明确的输入输出和评价指标,学术界也积累了大量成熟工具。但这个方案有个根本性的问题:模块之间的信息传递有瓶颈。感知模块输出的目标框,已经丢失了目标内部像素的细节;预测模块输出的轨迹分布,又丢失了目标历史运动的细微信号;等到规划模块拿到手的,只是这些被反复“压缩”过的抽象表示。现实世界的复杂情况,比如一个行人突然从盲区冲出来、一个塑料袋被风吹到路面上,这些长尾场景在级联架构里很难被捕获,因为每个模块都是在用自己的“归纳偏置”去理解世界,而归纳偏置本身就是一种信息过滤器。端到端的思路完全不同。它不做模块划分,而是把从传感器到控制指令的映射,直接建模成一个函数逼近问题。网络自己决定要关注哪些视觉特征、如何把图像和点云的信息结合起来、最终输出什么样的控制量。这样做的好处是,信息损失最小化,而且模型有可能学到人类驾驶员那种“直觉式”的驾驶行为——不需要显式地算出目标框,只要知道“那个位置有东西在动,我得减速”就够了。当然,端到端方案也有自己的难题。最典型的问题有三个:需要的训练数据量巨大,没有海量数据很难覆盖长尾场景;模型的可解释性差,出了事故很难追溯原因;训练稳定性难以保证,损失函数设计不当很容易出现灾难性遗忘或者模式崩溃。Transfuser的贡献在于,它在这两个极端之间找到了一个平衡点。它的整体框架是端到端的,但内部结构有很强的先验设计——用Transformer做跨模态融合,用自回归的方式预测路径点,用安全建议网络约束控制输出。这些设计让它既享受了端到端的好处,又在一定程度上缓解了端到端的通病。这也是我一直推荐拿它当入门端到端驾驶模型的原因:它不是一个“黑盒”,而是一个“看得见的端到端”。2. Transfuser架构拆解:注意力融合如何解决多模态对齐问题Transfuser的架构,简单来说由三部分组成:两个模态专用的编码器、一个注意力特征融合模块、以及一个自回归路径点预测头。我再把它拆细一点,每个部分都讲清楚它是干什么的、为什么这么设计。2.1 双编码器设计:为什么图像用ResNet,LiDAR用PointNet输入方面,Transfuser同时接收两个模态的数据:前视摄像头的RGB图像,和LiDAR点云数据。这里涉及到第一个设计选择——用什么网络结构来编码这两种模态。图像数据用的是ResNet的变体,具体来说是一个经过修改的ResNet34,去掉了最后的全连接层和平均池化层,保留空间分辨率较高的特征图。图像编码器的设计很好理解,因为图像是稠密的网格状数据,局部纹理、边缘、颜色过渡这些信息对驾驶决策都有用,卷积网络天然的局部感受野和平移等变性非常契合图像特点。LiDAR点云的处理就麻烦一些。点云是稀疏的、无序的3D点集合,直接用卷积网络处理不现实。Transfuser的做法是,先把点云投影到鸟瞰图视角,划分成规则的网格,然后每个网格里统计点云的数量、高度最大值、强度最大值等特征,这样就把稀疏点云转换成了稠密的2D特征图。基于这个BEV表示,再用一个CNN编码器提取特征。你可能注意到这里用的不是PointNet而是卷积网络,原因很简单:后续的特征融合需要两种模态的表征在空间上可以对标,BEV表示是固定尺寸、规则排列的,和图像特征图在结构上更接近,融合起来更自然。当然,点云投影本身会丢失一些信息,比如垂直方向的细节分布、单点的精确3D位置。但Transfuser用多尺度的方式弥补了这一点,它在不同分辨率下分别提取BEV特征,浅层特征保留细节位置信息,深层特征提取语义抽象信息,这两类信息在后级的融合模块里都被用上了。2.2 注意力特征融合模块:不同模态的信息是怎么“对齐”的这是Transfuser的核心创新点,也是它区别于早期端到端模型的地方。先说为什么需要融合。摄像头图像分辨率高,有丰富的颜色和纹理信息,能看清交通标志、红绿灯、远处的小目标,但它缺乏精确的距离度量。LiDAR点云虽然稀疏,但能提供厘米级精度的3D几何信息,尤其对近距离障碍物的位置估计非常准。如果只靠图像,模型很难判断“前面那个障碍物到底在5米还是10米外,我需不需要急刹车”;如果只靠点云,模型很难读懂“这个路牌上写着限速多少”这样的语义信息。所以融合是必须的。早期方案是late fusion,也就是图像和点云分别走完自己的编码器,最后直接把两个特征向量拼接在一起,接一个全连接层输出控制量。这种做法实现简单,但有个致命缺陷:两种模态的特征在空间上完全没有对齐。图像里的“前方20米”和点云里的“前方20米”是两组完全独立的数值,模型需要自己从这两组毫不相关的数字里去发现对应的模式,学习难度非常大。Transfuser用了一种gated cross-modal attention来实现融合。它的做法是:把图像特征作为查询Query,把BEV点云特征作为键Key和值Value,计算注意力权重。这个设计的直觉是,让图像中的每个空间位置,主动去查询LiDAR特征中它对应的区域有什么几何信息,然后根据注意力权重,把有用的几何信息“拿”过来,增强了图像特征在深度维度上的表达能力。这个机制的生物学类比是,它有点像人类驾驶时的行为:视觉告诉你前面有个物体,但你无法直接从视网膜上判断距离,这时候你会不自觉地依靠一些几何线索(比如路面纹理的透视收缩、物体的遮挡关系)来估计距离。Transformer注意力机制做的正是让视觉特征主动去“问”几何特征:“我看到的这个东西,到底离我多远?它有多高多大?” 然后把几何答案融合到视觉特征里,作为后续决策的依据。更妙的是,Transfuser不是把这个fusion模块放在编码器的最后一层做一次,而是在多个尺度上做多次。每个尺度的融合结果,都会作为下一层编码器的输入,继续提取更抽象的特征。这种多尺度融合的好处是,浅层的几何关系和深层的语义关系都被捕捉到了,模型既能理解“那里有个东西挡着路”,也能理解“那是一个静止的施工区域,我应该变道绕过去”。最终,在BEV特征图上加权求和,得到全局语义上下文向量,作为路径点预测头的输入。2.3 从感知到规划:自回归路径生成和安全约束融合特征之后,Transfuser要做的是生成驾驶决策。它不直接输出方向盘角度和油门值,而是输出一个自回归路径点序列。这一点设计我觉得特别值得学习:每条路径由一系列离散点组成,涵盖未来2秒的轨迹,每个点包含两个值:横向位置和纵向位置。用自回归的方式逐点生成,当前点的输出会作为预测下一点的额外输入。这相当于在时序维度上也做了注意力式的信息传递,让路径生成更平滑、更连贯。路径点生成之后,再接一个控制转换模块,把路径点映射成实际的转向和油门刹车信号。这里用了一个比较简单的PID控制器或者线性映射,具体实现不复杂,但很有效——它相当于做了一个“轨迹跟踪器”的工作,把高层规划结果落地到低层控制指令上。这里有个容易被忽略的细节:Transfuser还引入了一个安全建议网络。它的作用是,用规则或者轻量级模型估算当前状态下车辆的“安全控制边界”,比如前方有障碍物时最大允许速度是多少、当前曲率下方向盘转角不要超过多少,然后把这个安全边界作为约束条件,去修正自回归路径生成的结果。这样即便模型在某些极端场景下给出了激进的路径,安全建议网络也能把它拉回到安全范围内。这个安全建议网络是模块化方案留下的“最后一道防线”,也是Transfuser作为一段式端到端方案中“不完全端到端”的成分。但我个人认为这是一个工程上非常明智的妥协:在可解释性和安全性的刚需面前,保留一个显式的安全约束是值得的。你可以不好解释网络为什么在这个路口左转,但你至少需要解释清楚——模型没有让你撞车。2.4 目标损失怎么设计:控制精度优先还是轨迹平滑优先训练Transfuser时,损失函数由两部分组成:路径点回归损失和控制指令回归损失。路径点回归损失用的是L1损失,即模型预测的轨迹点和数据集中的真实轨迹点的逐点距离之和。之所以用L1而不是常用的L2,是因为L1损失对离群点和噪声的鲁棒性更好,不容易被个别偏差很大的样本带偏整个训练方向。在驾驶场景里,数据集中难免会有一些标注不准或者极端不典型的样本,L1损失能让训练更稳定。控制指令回归损失则是对比模型输出的转向角、油门、刹车和数据集记录的真实控制指令。这里有一个细节:在训练阶段,Transfuser使用了教师强迫策略,即在每一步预测当前路径点时,都把上一步的真实路径点作为输入,而不是模型自己预测的路径点。这样做的好处是训练收敛快且稳定,但代价是训练出的模型在推理阶段会面临训练与推理不一致的问题。经典做法是用计划抽样逐步降低教师强迫的比例,让模型逐渐学会在自回归模式下也能正确预测路径点。我在复现时加了这样一个退火过程:预训练100个epoch用纯教师强迫,然后每20个epoch将教师强迫比例降低10%,最终降到0。这个经验我试过很多次,确实能显著降低推理阶段的误差累积问题,但代价是训练时间变长了大约40%。3. 从零到一:搭建环境、准备数据和开始训练3.1 仿真环境选择:为什么建议用模拟器而不是真实赛道端到端自动驾驶的数据收集,如果放到真实车辆上去做,成本高、风险大、周期长,而且很难在可控范围内复现极端场景。所以绝大多数研究和实践都会选择在仿真环境里跑。我自己的实验是在某开源自动驾驶模拟器上做的,它提供道路场景、天气条件、传感器仿真、动态交通流等功能,很多端到端驾驶领域的论文都用它做基准测试。你如果第一次接触端到端驾驶,对仿真器可能有个误解——觉得模拟器数据跟真实数据差距大,训练出来的模型到真实场景估计就废了。其实不用太担心,这个仿真器里传感器的物理模型建模得比较精细,包括相机的畸变、自动曝光、点云强度衰减这些细节都有模拟。更重要的是,在端到端驾驶研究中,仿真器提供了一个可以大规模复现实验的标准平台,这可比在真实道路上跑几千公里要靠谱得多。Transfuser原论文在仿真器的两条道路上做训练和测试,一条训练路线,一条测试路线,两条路线在城市布局上完全不同。这样设计的目的很明确:测试模型在没见过的场景下的泛化能力,而不是在训练集上过拟合的“背诵能力”。我在复现时还额外加了一条测试路线,三条路线轮换使用,就是为了观察模型到底有没有真正学会“开车”这件事。3.2 数据管线:乱用数据集会让模型学到错误先验仿真环境搭好之后,下一步是采集训练数据。这看起来是简单的事情,但实际操作中有两个很隐蔽的坑。第一个坑是驾驶行为数据的分布问题。如果你只是让一个固定的驾驶策略盲目地在路上跑,采到的数据绝大多数是“直行、保持车道、跟车减速”这类简单工况。真正对模型训练有价值的,往往是那些站占比很低但很重要的样本——紧急刹停、变道超车、路口让行。训练集里这些样本太少了,模型会慢慢把所有场景都判定成“直行就完事了”。为了缓解这个问题,我在采集数据时加入了随机扰动策略,每隔一段时间就给车辆参数加一个小的偏移量,同时配上一个有多风格驾驶策略库的专家系统来生成控制指令。这样采出来的数据里面,转弯、变道、避障的频率高很多,训练出来的模型明显更“机灵”。第二个坑是数据关联问题。图像、点云、控制指令这三者必须在时间戳上严格对齐。仿真器里采集频率不一致是常有的事,比如相机是20帧,LiDAR是10Hz,控制指令日志又是另一个频率。直接拿来训练会出现“看到的是2秒前的画面,却要用它来预测此刻的转向”这种错位情况。我写了一个预处理的脚本,统一做插值和同步,确保训练样本里的每一个图像-点云对对应的都是同一时刻的道路状态。3.3 训练配置:一套能跑通全文的详细参数下面是我在复现Transfuser时用到的训练配置,直接写出来供你参考。硬件环境是4张某品牌的专业计算卡,每张显存32G,PyTorch框架,分布式数据并行模式。没这么多显存也能跑,把batch size和图像分辨率调小就行,但收敛效果可能会打一些折扣。图像输入尺寸:256×256,RGB三通道BEV点云尺寸:192×192,单通道优化器:Adam,初始学习率1e-4学习率调度:余弦退火,最低降到1e-5训练总步数:100万步批量大小:512(4卡并行,每卡128)损失权重:路径点损失1.0,控制指令损失0.5训练时长方面,我做了一个估算模型参数规模在3000万左右,4卡并行训练,大概需要4到5天时间。如果你用的卡性能差一些,时间可能会翻倍。建议训练的时候搭配定期验证的流程,每5000步留出10个场景的蒸馏策略验证一下模型能不能完成指定的驾驶任务,否则跑完100万步才发现收敛方向错了,那就太浪费时间了。在CARLA模拟器上,每完成一次1公里的驾驶任务,可以记录任务成功率、碰撞率、时间消耗等指标。评估时注意,即使模型“正确地冲向障碍物”这个行为,也会被记为一次碰撞,所以要分清楚是任务失败还是单纯的评估指标定义问题。3.4 关键代码结构解读:训练和推理主流程这里放下我改写后的核心代码结构,方便你对照理解模型训练流程的骨架。我简化了部分细节,但整体流程是完整可跑的。# 训练主循环伪代码 for step in range(1000000): # 采样一个batch的数据 batch = sample_data() # 前向传播:图像 + BEV特征 -> 路径点 + 控制量 image_input = batch['image'].to(device) pointcloud_input = batch['pointcloud_bev'].to(device) expert_path = batch['expert_path'] expert_control = batch['expert_control'] # 模型预测 pred_path, pred_control = model(image_input, pointcloud_input, teacher_path=expert_path) # 损失计算 loss_path = path_loss(pred_path, expert_path) # L1损失 loss_ctrl = control_loss(pred_control, expert_control) # MSE损失 loss_total = loss_path + 0.5 * loss_ctrl # 反向传播与参数更新 optimizer.zero_grad() loss_total.backward() optimizer.step() # 周期性评估 if step % 5000 == 0: success_rate = evaluate_in_simulator(model) log(step, success_rate)推理阶段的流程更简单,因为推理时没有教师强迫了,路径点预测会切换到自回归模式:def inference(model, image_input, pointcloud_input, max_path_len=10): path = [] x = None for _ in range(max_path_len): pred_point, x = model.forward_iterative(image_input, pointcloud_input, x) path.append(pred_point) control = model.control_layer(path) return path, control这段代码的关键在于forward_iterative函数。它在每一步接收上一步的隐状态,输出当前步的路径点,再把该路径点作为下一步的输入。因为训练阶段已经做了计划抽样,推理阶段的误差累积被控制在了能接受的范围。我在实际验证时也确认了,纯自回归推理加合理的轨迹平滑后处理(比如移动平均滤波),跑起来的效果和教师强迫模式差别不太大。4. 常见问题与排查技巧实录4.1 模型总是直行,路口不知道拐弯怎么办这是我在复现时遇到的第一个问题。模型训练了好几天,验证的时候发现它在碰到需要左转的十字路口时,路径点几乎不产生横向偏移,车辆一直往前开,直到撞到路边围栏或者卡在路沿上。排查思路是不急着调网络结构,先看数据。我统计了训练集里路径点横向位移的分布,发现绝度大多数样本的横向位移都在很小范围内波动,说明数据集里直行工况占了压倒性比例。网络在梯度更新时会倾向于学到一个“平均行为”,也就是尽量不转方向盘,因为这样能降低大多数样本的损失。解决办法就是上一条提到的数据均衡策略——提高转弯、变道样本在训练集中的比例,同时可以使用类别平衡采样,让每个batch中转弯样本占比不低于40%。这个问题调了数据分布之后,路径点横向位移的方差立刻变大了,模型在路口的行为也明显丰富起来。4.2 训练损失下降,但验证碰撞率不降,怎么回事训练损失一直在降,看起来很乐观,但验证阶段碰撞率居高不下,这种“训练成功但评价失败”的割裂现象,是端到端驾驶模型训练中最让人头疼的问题之一。我复盘后发现主要有两个原因。第一个原因是过拟合到了训练路线。模型把训练场景的“时空特征”记住了,比如某个路口的树木形态、地面纹理、光线的固定模式,一旦到了测试路线,这些特征全变了,模型就不知道怎么处理了。针对这个问题,我在训练时加了强数据增强:随机改变光照条件、添加噪声、遮挡摄像头的一部分等。实测下来,加了数据增强之后验证碰撞率下降了将近三分之一。第二个原因是评估时的路径点平滑不足。自回归生成的路径点在不加平滑时,会出现相邻两步之间横向跳变,这种剧烈波动传到控制层,会让车辆的转向命令频繁来回切换,在高速状态下非常容易失稳。我在后处理阶段给路径点做了一次Savitzky-Golay滤波,保留轨迹趋势的同时滤掉高频毛刺。加上以后,车辆在直线路段几乎不画龙了,碰撞率又降了一个档次。4.3 多卡训练时损失函数震荡很剧烈,怎么稳定多卡并行训练时,每个卡的batch数据分布可能存在差异,尤其是转弯样本在不同卡上的比例不一致,会导致梯度方向来回拉扯,损失函数出现周期性的陡升陡降。我之前用分布式数据并行训练时也遇到过这个情况。解决办法不复杂:在每个卡上固定数据重放种子,保证不同卡上的batch是同步采样,并且不同卡之间数据分布保持一致。同时,我把批次数据做了全局打乱,而不是每个卡各自打乱一次。这个调整看起来不起眼,却让训练的稳定性有了质的提升。另外需要检查的一点是,BatchNorm在不同卡之间的统计信息在分布式模式下默认是不同步的,如果模型里用了BatchNorm,建议打开同步BN开关,否则每卡统计的均值方差会有偏差,特征分布就乱了。4.4 排行榜复盘:真实场景下效果差一个等级的原因我拿自己训练的Transfuser在几个公开的端到端驾驶排行榜上跑过,结论是:如果只看榜单分数,会觉得它跟最先进的方案差距不大;一旦跳到用户自己上传的真实场景视频里做测试,效果差的等级就显现出来了。差在哪里?榜单里的测试路线通常是有一定结构化的道路,车道线清晰、路况可控。真实场景里,反而是各种“不按套路出牌”的情况:无车道线的非结构化路段、施工区域临时改道、路面上乱七八糟的障碍物堆积。Transfuser在这些场景下表现不佳,根本原因是训练数据里这类样本就不够。所以说,想提升端到端模型在真实复杂场景里的泛化能力,光靠调整网络结构是不够的,核心要害还是在数据覆盖度和特征学习策略上。我后来做的是一个增加了一个对比学习分支,让模型在预训练阶段先学会区分“不同场景下路面的走向趋势”,在这个基础上再去做端到端控制,效果会比直接用原始训练集训练高一截。5. 复现后的思考:Transfuser还能往哪里走有一段话我想放在最后。很多同学复现完Transfuser之后,会觉得“就这?好像也没什么特别厉害的地方”。我一开始也有这种感觉,但后来我意识到,Transfuser真正的价值不在于它有多惊艳的性能,而在于它给端到端驾驶模型的设计提供了一个相当完整且可扩展的范式。它证明了,用Transformer做跨模态融合可以显著提升端到端模型在长尾场景下的鲁棒性;它证明了,自回归路径生成再加上安全建议网络约束,这种“端到端为主、规则兜底”的混合架构,在实际部署中更稳定;它还证明了,一段式端到端模型可以在不丢失可解释性的前提下完成从感知到控制的完整映射。我也基于Transfuser做了几个方向的扩展,简单分享一下效果。一个是换更强的图像编码器,把ResNet换成带Transformer编码块的混合网络,实验下来在夜间和逆光场景下的控制性能提升比较明显,原因是更强的图像特征对光照变化更鲁棒。另一个是给融合模块加一个“注意力掩码”分支,根据场景动态决定两种模态的融合权重,比如在隧道里把点云权重调高、在光照充足的开放道路上把图像权重调高,这个小改动让模型在路况切换时过渡得更自然。最后再分享一个小技巧。训练Transfuser时,图像增强这部分不要做得太花哨,重点放在HSV颜色抖动和随机遮挡上。我一开始为了提升泛化能力,加了随机裁剪和旋转,结果模型在最基本的车道保持任务上反而变差了,原因是我把车道线这种对裁剪旋转非常敏感的信息给破坏掉了。所以做视觉增强前,一定要先想清楚哪些特征是你的任务真正依赖的。
做了两年多端到端自动驾驶的仿真实验,各种模型架构也差不多摸了一遍。说实话,很多论文里的方案,真到自己动手复现的时候,总会觉得差点意思,要么是训练不稳定,要么是泛化能力不行,在仿真器里跑得挺溜,换条路就原形毕露。但Transfuser这个模型,是我见过少有的,把“一段式端到端”做到既能跑通、又符合直觉的方案。这个“一段式端到端”的定位很关键。它的意思是,从传感器原始数据输入,到最终输出车辆控制指令,整个过程是一个完整的神经网络模型,中间没有拆成独立的感知、预测、规划模块,也没有人为定义中间结果。当年我刚开始接触这个方向时,业内主流还是两段式甚至三段式的方案,感知出障碍物框和车道线,然后交给规则规划器去算轨迹,最后再映射成控制指令。这种方案的好处是每个环节都好解释、好调试,但坏处也明显——感知误差会一路传导下去,而且感知结果丢失了大量原始信息,比如目标的细微运动趋势、路面材质对抓地力的影响,这些信息对驾驶决策其实很重要,但级联架构里根本传不到后端。Transfuser让我印象最深的一点,是它在“一段式”这个框架下,没有牺牲可解释性。它不是把多传感器数据一股脑丢进一个巨型网络里让它自己学,而是用一个注意力特征融合模块,把摄像头图像和LiDAR点云的特征在多个尺度上做交互。这种做法既保留了端到端学习的全局优化优势,又能在特征层面显式建模两种模态之间的对应关系,从设计思路上就比那些单纯堆算力的方案高明不少。这篇文章我打算把Transfuser从原理到实操完整拆一遍,包括它的架构设计细节、训练技巧、以及我在复现和改造过程中踩过的坑。如果你正在做自动驾驶相关的科研或者工程落地,或者对多模态融合、端到端控制感兴趣,这篇文章应该能帮你省下不少摸索的时间。1. 为什么端到端方案值得赌一把:先搞清楚两类方案的优劣在聊Transfuser的具体结构之前,我得先把端到端这个话题的来龙去脉讲清楚。因为很多初学者一上来就纠结“用CNN还是用Transformer”,但实际上这个问题的前提是——你为什么要选端到端?传统方案,我习惯叫它“模块化级联方案”,就是把自动驾驶拆成感知、预测、规划、控制四个独立模块,每个模块单独训练、单独优化。感知模块负责检测目标、分割可行驶区域,预测模块负责推测其他交通参与者的意图,规划模块生成一条安全可行的轨迹,控制模块再把轨迹跟踪成油门、刹车、方向盘的操作。听起来很合理,每个模块都有明确的输入输出和评价指标,学术界也积累了大量成熟工具。但这个方案有个根本性的问题:模块之间的信息传递有瓶颈。感知模块输出的目标框,已经丢失了目标内部像素的细节;预测模块输出的轨迹分布,又丢失了目标历史运动的细微信号;等到规划模块拿到手的,只是这些被反复“压缩”过的抽象表示。现实世界的复杂情况,比如一个行人突然从盲区冲出来、一个塑料袋被风吹到路面上,这些长尾场景在级联架构里很难被捕获,因为每个模块都是在用自己的“归纳偏置”去理解世界,而归纳偏置本身就是一种信息过滤器。端到端的思路完全不同。它不做模块划分,而是把从传感器到控制指令的映射,直接建模成一个函数逼近问题。网络自己决定要关注哪些视觉特征、如何把图像和点云的信息结合起来、最终输出什么样的控制量。这样做的好处是,信息损失最小化,而且模型有可能学到人类驾驶员那种“直觉式”的驾驶行为——不需要显式地算出目标框,只要知道“那个位置有东西在动,我得减速”就够了。当然,端到端方案也有自己的难题。最典型的问题有三个:需要的训练数据量巨大,没有海量数据很难覆盖长尾场景;模型的可解释性差,出了事故很难追溯原因;训练稳定性难以保证,损失函数设计不当很容易出现灾难性遗忘或者模式崩溃。Transfuser的贡献在于,它在这两个极端之间找到了一个平衡点。它的整体框架是端到端的,但内部结构有很强的先验设计——用Transformer做跨模态融合,用自回归的方式预测路径点,用安全建议网络约束控制输出。这些设计让它既享受了端到端的好处,又在一定程度上缓解了端到端的通病。这也是我一直推荐拿它当入门端到端驾驶模型的原因:它不是一个“黑盒”,而是一个“看得见的端到端”。2. Transfuser架构拆解:注意力融合如何解决多模态对齐问题Transfuser的架构,简单来说由三部分组成:两个模态专用的编码器、一个注意力特征融合模块、以及一个自回归路径点预测头。我再把它拆细一点,每个部分都讲清楚它是干什么的、为什么这么设计。2.1 双编码器设计:为什么图像用ResNet,LiDAR用PointNet输入方面,Transfuser同时接收两个模态的数据:前视摄像头的RGB图像,和LiDAR点云数据。这里涉及到第一个设计选择——用什么网络结构来编码这两种模态。图像数据用的是ResNet的变体,具体来说是一个经过修改的ResNet34,去掉了最后的全连接层和平均池化层,保留空间分辨率较高的特征图。图像编码器的设计很好理解,因为图像是稠密的网格状数据,局部纹理、边缘、颜色过渡这些信息对驾驶决策都有用,卷积网络天然的局部感受野和平移等变性非常契合图像特点。LiDAR点云的处理就麻烦一些。点云是稀疏的、无序的3D点集合,直接用卷积网络处理不现实。Transfuser的做法是,先把点云投影到鸟瞰图视角,划分成规则的网格,然后每个网格里统计点云的数量、高度最大值、强度最大值等特征,这样就把稀疏点云转换成了稠密的2D特征图。基于这个BEV表示,再用一个CNN编码器提取特征。你可能注意到这里用的不是PointNet而是卷积网络,原因很简单:后续的特征融合需要两种模态的表征在空间上可以对标,BEV表示是固定尺寸、规则排列的,和图像特征图在结构上更接近,融合起来更自然。当然,点云投影本身会丢失一些信息,比如垂直方向的细节分布、单点的精确3D位置。但Transfuser用多尺度的方式弥补了这一点,它在不同分辨率下分别提取BEV特征,浅层特征保留细节位置信息,深层特征提取语义抽象信息,这两类信息在后级的融合模块里都被用上了。2.2 注意力特征融合模块:不同模态的信息是怎么“对齐”的这是Transfuser的核心创新点,也是它区别于早期端到端模型的地方。先说为什么需要融合。摄像头图像分辨率高,有丰富的颜色和纹理信息,能看清交通标志、红绿灯、远处的小目标,但它缺乏精确的距离度量。LiDAR点云虽然稀疏,但能提供厘米级精度的3D几何信息,尤其对近距离障碍物的位置估计非常准。如果只靠图像,模型很难判断“前面那个障碍物到底在5米还是10米外,我需不需要急刹车”;如果只靠点云,模型很难读懂“这个路牌上写着限速多少”这样的语义信息。所以融合是必须的。早期方案是late fusion,也就是图像和点云分别走完自己的编码器,最后直接把两个特征向量拼接在一起,接一个全连接层输出控制量。这种做法实现简单,但有个致命缺陷:两种模态的特征在空间上完全没有对齐。图像里的“前方20米”和点云里的“前方20米”是两组完全独立的数值,模型需要自己从这两组毫不相关的数字里去发现对应的模式,学习难度非常大。Transfuser用了一种gated cross-modal attention来实现融合。它的做法是:把图像特征作为查询Query,把BEV点云特征作为键Key和值Value,计算注意力权重。这个设计的直觉是,让图像中的每个空间位置,主动去查询LiDAR特征中它对应的区域有什么几何信息,然后根据注意力权重,把有用的几何信息“拿”过来,增强了图像特征在深度维度上的表达能力。这个机制的生物学类比是,它有点像人类驾驶时的行为:视觉告诉你前面有个物体,但你无法直接从视网膜上判断距离,这时候你会不自觉地依靠一些几何线索(比如路面纹理的透视收缩、物体的遮挡关系)来估计距离。Transformer注意力机制做的正是让视觉特征主动去“问”几何特征:“我看到的这个东西,到底离我多远?它有多高多大?” 然后把几何答案融合到视觉特征里,作为后续决策的依据。更妙的是,Transfuser不是把这个fusion模块放在编码器的最后一层做一次,而是在多个尺度上做多次。每个尺度的融合结果,都会作为下一层编码器的输入,继续提取更抽象的特征。这种多尺度融合的好处是,浅层的几何关系和深层的语义关系都被捕捉到了,模型既能理解“那里有个东西挡着路”,也能理解“那是一个静止的施工区域,我应该变道绕过去”。最终,在BEV特征图上加权求和,得到全局语义上下文向量,作为路径点预测头的输入。2.3 从感知到规划:自回归路径生成和安全约束融合特征之后,Transfuser要做的是生成驾驶决策。它不直接输出方向盘角度和油门值,而是输出一个自回归路径点序列。这一点设计我觉得特别值得学习:每条路径由一系列离散点组成,涵盖未来2秒的轨迹,每个点包含两个值:横向位置和纵向位置。用自回归的方式逐点生成,当前点的输出会作为预测下一点的额外输入。这相当于在时序维度上也做了注意力式的信息传递,让路径生成更平滑、更连贯。路径点生成之后,再接一个控制转换模块,把路径点映射成实际的转向和油门刹车信号。这里用了一个比较简单的PID控制器或者线性映射,具体实现不复杂,但很有效——它相当于做了一个“轨迹跟踪器”的工作,把高层规划结果落地到低层控制指令上。这里有个容易被忽略的细节:Transfuser还引入了一个安全建议网络。它的作用是,用规则或者轻量级模型估算当前状态下车辆的“安全控制边界”,比如前方有障碍物时最大允许速度是多少、当前曲率下方向盘转角不要超过多少,然后把这个安全边界作为约束条件,去修正自回归路径生成的结果。这样即便模型在某些极端场景下给出了激进的路径,安全建议网络也能把它拉回到安全范围内。这个安全建议网络是模块化方案留下的“最后一道防线”,也是Transfuser作为一段式端到端方案中“不完全端到端”的成分。但我个人认为这是一个工程上非常明智的妥协:在可解释性和安全性的刚需面前,保留一个显式的安全约束是值得的。你可以不好解释网络为什么在这个路口左转,但你至少需要解释清楚——模型没有让你撞车。2.4 目标损失怎么设计:控制精度优先还是轨迹平滑优先训练Transfuser时,损失函数由两部分组成:路径点回归损失和控制指令回归损失。路径点回归损失用的是L1损失,即模型预测的轨迹点和数据集中的真实轨迹点的逐点距离之和。之所以用L1而不是常用的L2,是因为L1损失对离群点和噪声的鲁棒性更好,不容易被个别偏差很大的样本带偏整个训练方向。在驾驶场景里,数据集中难免会有一些标注不准或者极端不典型的样本,L1损失能让训练更稳定。控制指令回归损失则是对比模型输出的转向角、油门、刹车和数据集记录的真实控制指令。这里有一个细节:在训练阶段,Transfuser使用了教师强迫策略,即在每一步预测当前路径点时,都把上一步的真实路径点作为输入,而不是模型自己预测的路径点。这样做的好处是训练收敛快且稳定,但代价是训练出的模型在推理阶段会面临训练与推理不一致的问题。经典做法是用计划抽样逐步降低教师强迫的比例,让模型逐渐学会在自回归模式下也能正确预测路径点。我在复现时加了这样一个退火过程:预训练100个epoch用纯教师强迫,然后每20个epoch将教师强迫比例降低10%,最终降到0。这个经验我试过很多次,确实能显著降低推理阶段的误差累积问题,但代价是训练时间变长了大约40%。3. 从零到一:搭建环境、准备数据和开始训练3.1 仿真环境选择:为什么建议用模拟器而不是真实赛道端到端自动驾驶的数据收集,如果放到真实车辆上去做,成本高、风险大、周期长,而且很难在可控范围内复现极端场景。所以绝大多数研究和实践都会选择在仿真环境里跑。我自己的实验是在某开源自动驾驶模拟器上做的,它提供道路场景、天气条件、传感器仿真、动态交通流等功能,很多端到端驾驶领域的论文都用它做基准测试。你如果第一次接触端到端驾驶,对仿真器可能有个误解——觉得模拟器数据跟真实数据差距大,训练出来的模型到真实场景估计就废了。其实不用太担心,这个仿真器里传感器的物理模型建模得比较精细,包括相机的畸变、自动曝光、点云强度衰减这些细节都有模拟。更重要的是,在端到端驾驶研究中,仿真器提供了一个可以大规模复现实验的标准平台,这可比在真实道路上跑几千公里要靠谱得多。Transfuser原论文在仿真器的两条道路上做训练和测试,一条训练路线,一条测试路线,两条路线在城市布局上完全不同。这样设计的目的很明确:测试模型在没见过的场景下的泛化能力,而不是在训练集上过拟合的“背诵能力”。我在复现时还额外加了一条测试路线,三条路线轮换使用,就是为了观察模型到底有没有真正学会“开车”这件事。3.2 数据管线:乱用数据集会让模型学到错误先验仿真环境搭好之后,下一步是采集训练数据。这看起来是简单的事情,但实际操作中有两个很隐蔽的坑。第一个坑是驾驶行为数据的分布问题。如果你只是让一个固定的驾驶策略盲目地在路上跑,采到的数据绝大多数是“直行、保持车道、跟车减速”这类简单工况。真正对模型训练有价值的,往往是那些站占比很低但很重要的样本——紧急刹停、变道超车、路口让行。训练集里这些样本太少了,模型会慢慢把所有场景都判定成“直行就完事了”。为了缓解这个问题,我在采集数据时加入了随机扰动策略,每隔一段时间就给车辆参数加一个小的偏移量,同时配上一个有多风格驾驶策略库的专家系统来生成控制指令。这样采出来的数据里面,转弯、变道、避障的频率高很多,训练出来的模型明显更“机灵”。第二个坑是数据关联问题。图像、点云、控制指令这三者必须在时间戳上严格对齐。仿真器里采集频率不一致是常有的事,比如相机是20帧,LiDAR是10Hz,控制指令日志又是另一个频率。直接拿来训练会出现“看到的是2秒前的画面,却要用它来预测此刻的转向”这种错位情况。我写了一个预处理的脚本,统一做插值和同步,确保训练样本里的每一个图像-点云对对应的都是同一时刻的道路状态。3.3 训练配置:一套能跑通全文的详细参数下面是我在复现Transfuser时用到的训练配置,直接写出来供你参考。硬件环境是4张某品牌的专业计算卡,每张显存32G,PyTorch框架,分布式数据并行模式。没这么多显存也能跑,把batch size和图像分辨率调小就行,但收敛效果可能会打一些折扣。图像输入尺寸:256×256,RGB三通道BEV点云尺寸:192×192,单通道优化器:Adam,初始学习率1e-4学习率调度:余弦退火,最低降到1e-5训练总步数:100万步批量大小:512(4卡并行,每卡128)损失权重:路径点损失1.0,控制指令损失0.5训练时长方面,我做了一个估算模型参数规模在3000万左右,4卡并行训练,大概需要4到5天时间。如果你用的卡性能差一些,时间可能会翻倍。建议训练的时候搭配定期验证的流程,每5000步留出10个场景的蒸馏策略验证一下模型能不能完成指定的驾驶任务,否则跑完100万步才发现收敛方向错了,那就太浪费时间了。在CARLA模拟器上,每完成一次1公里的驾驶任务,可以记录任务成功率、碰撞率、时间消耗等指标。评估时注意,即使模型“正确地冲向障碍物”这个行为,也会被记为一次碰撞,所以要分清楚是任务失败还是单纯的评估指标定义问题。3.4 关键代码结构解读:训练和推理主流程这里放下我改写后的核心代码结构,方便你对照理解模型训练流程的骨架。我简化了部分细节,但整体流程是完整可跑的。# 训练主循环伪代码 for step in range(1000000): # 采样一个batch的数据 batch = sample_data() # 前向传播:图像 + BEV特征 -> 路径点 + 控制量 image_input = batch['image'].to(device) pointcloud_input = batch['pointcloud_bev'].to(device) expert_path = batch['expert_path'] expert_control = batch['expert_control'] # 模型预测 pred_path, pred_control = model(image_input, pointcloud_input, teacher_path=expert_path) # 损失计算 loss_path = path_loss(pred_path, expert_path) # L1损失 loss_ctrl = control_loss(pred_control, expert_control) # MSE损失 loss_total = loss_path + 0.5 * loss_ctrl # 反向传播与参数更新 optimizer.zero_grad() loss_total.backward() optimizer.step() # 周期性评估 if step % 5000 == 0: success_rate = evaluate_in_simulator(model) log(step, success_rate)推理阶段的流程更简单,因为推理时没有教师强迫了,路径点预测会切换到自回归模式:def inference(model, image_input, pointcloud_input, max_path_len=10): path = [] x = None for _ in range(max_path_len): pred_point, x = model.forward_iterative(image_input, pointcloud_input, x) path.append(pred_point) control = model.control_layer(path) return path, control这段代码的关键在于forward_iterative函数。它在每一步接收上一步的隐状态,输出当前步的路径点,再把该路径点作为下一步的输入。因为训练阶段已经做了计划抽样,推理阶段的误差累积被控制在了能接受的范围。我在实际验证时也确认了,纯自回归推理加合理的轨迹平滑后处理(比如移动平均滤波),跑起来的效果和教师强迫模式差别不太大。4. 常见问题与排查技巧实录4.1 模型总是直行,路口不知道拐弯怎么办这是我在复现时遇到的第一个问题。模型训练了好几天,验证的时候发现它在碰到需要左转的十字路口时,路径点几乎不产生横向偏移,车辆一直往前开,直到撞到路边围栏或者卡在路沿上。排查思路是不急着调网络结构,先看数据。我统计了训练集里路径点横向位移的分布,发现绝度大多数样本的横向位移都在很小范围内波动,说明数据集里直行工况占了压倒性比例。网络在梯度更新时会倾向于学到一个“平均行为”,也就是尽量不转方向盘,因为这样能降低大多数样本的损失。解决办法就是上一条提到的数据均衡策略——提高转弯、变道样本在训练集中的比例,同时可以使用类别平衡采样,让每个batch中转弯样本占比不低于40%。这个问题调了数据分布之后,路径点横向位移的方差立刻变大了,模型在路口的行为也明显丰富起来。4.2 训练损失下降,但验证碰撞率不降,怎么回事训练损失一直在降,看起来很乐观,但验证阶段碰撞率居高不下,这种“训练成功但评价失败”的割裂现象,是端到端驾驶模型训练中最让人头疼的问题之一。我复盘后发现主要有两个原因。第一个原因是过拟合到了训练路线。模型把训练场景的“时空特征”记住了,比如某个路口的树木形态、地面纹理、光线的固定模式,一旦到了测试路线,这些特征全变了,模型就不知道怎么处理了。针对这个问题,我在训练时加了强数据增强:随机改变光照条件、添加噪声、遮挡摄像头的一部分等。实测下来,加了数据增强之后验证碰撞率下降了将近三分之一。第二个原因是评估时的路径点平滑不足。自回归生成的路径点在不加平滑时,会出现相邻两步之间横向跳变,这种剧烈波动传到控制层,会让车辆的转向命令频繁来回切换,在高速状态下非常容易失稳。我在后处理阶段给路径点做了一次Savitzky-Golay滤波,保留轨迹趋势的同时滤掉高频毛刺。加上以后,车辆在直线路段几乎不画龙了,碰撞率又降了一个档次。4.3 多卡训练时损失函数震荡很剧烈,怎么稳定多卡并行训练时,每个卡的batch数据分布可能存在差异,尤其是转弯样本在不同卡上的比例不一致,会导致梯度方向来回拉扯,损失函数出现周期性的陡升陡降。我之前用分布式数据并行训练时也遇到过这个情况。解决办法不复杂:在每个卡上固定数据重放种子,保证不同卡上的batch是同步采样,并且不同卡之间数据分布保持一致。同时,我把批次数据做了全局打乱,而不是每个卡各自打乱一次。这个调整看起来不起眼,却让训练的稳定性有了质的提升。另外需要检查的一点是,BatchNorm在不同卡之间的统计信息在分布式模式下默认是不同步的,如果模型里用了BatchNorm,建议打开同步BN开关,否则每卡统计的均值方差会有偏差,特征分布就乱了。4.4 排行榜复盘:真实场景下效果差一个等级的原因我拿自己训练的Transfuser在几个公开的端到端驾驶排行榜上跑过,结论是:如果只看榜单分数,会觉得它跟最先进的方案差距不大;一旦跳到用户自己上传的真实场景视频里做测试,效果差的等级就显现出来了。差在哪里?榜单里的测试路线通常是有一定结构化的道路,车道线清晰、路况可控。真实场景里,反而是各种“不按套路出牌”的情况:无车道线的非结构化路段、施工区域临时改道、路面上乱七八糟的障碍物堆积。Transfuser在这些场景下表现不佳,根本原因是训练数据里这类样本就不够。所以说,想提升端到端模型在真实复杂场景里的泛化能力,光靠调整网络结构是不够的,核心要害还是在数据覆盖度和特征学习策略上。我后来做的是一个增加了一个对比学习分支,让模型在预训练阶段先学会区分“不同场景下路面的走向趋势”,在这个基础上再去做端到端控制,效果会比直接用原始训练集训练高一截。5. 复现后的思考:Transfuser还能往哪里走有一段话我想放在最后。很多同学复现完Transfuser之后,会觉得“就这?好像也没什么特别厉害的地方”。我一开始也有这种感觉,但后来我意识到,Transfuser真正的价值不在于它有多惊艳的性能,而在于它给端到端驾驶模型的设计提供了一个相当完整且可扩展的范式。它证明了,用Transformer做跨模态融合可以显著提升端到端模型在长尾场景下的鲁棒性;它证明了,自回归路径生成再加上安全建议网络约束,这种“端到端为主、规则兜底”的混合架构,在实际部署中更稳定;它还证明了,一段式端到端模型可以在不丢失可解释性的前提下完成从感知到控制的完整映射。我也基于Transfuser做了几个方向的扩展,简单分享一下效果。一个是换更强的图像编码器,把ResNet换成带Transformer编码块的混合网络,实验下来在夜间和逆光场景下的控制性能提升比较明显,原因是更强的图像特征对光照变化更鲁棒。另一个是给融合模块加一个“注意力掩码”分支,根据场景动态决定两种模态的融合权重,比如在隧道里把点云权重调高、在光照充足的开放道路上把图像权重调高,这个小改动让模型在路况切换时过渡得更自然。最后再分享一个小技巧。训练Transfuser时,图像增强这部分不要做得太花哨,重点放在HSV颜色抖动和随机遮挡上。我一开始为了提升泛化能力,加了随机裁剪和旋转,结果模型在最基本的车道保持任务上反而变差了,原因是我把车道线这种对裁剪旋转非常敏感的信息给破坏掉了。所以做视觉增强前,一定要先想清楚哪些特征是你的任务真正依赖的。