尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深度卷积网络多模态轨迹预测:从设计到落地的工程实践

深度卷积网络多模态轨迹预测:从设计到落地的工程实践 自动驾驶轨迹预测这个方向我从早期做规则-based的卡尔曼滤波跟踪开始到后来转深度学习方案踩过的坑确实不少。今天想聊的这个项目核心是用深度卷积网络做多模态轨迹预测——说白了就是让车不仅能猜出前方行人或车辆下一步往哪走还要给出多种可能的走向而不是只给一条死板的预测线。这件事为什么重要因为现实世界里一个行人站在路边他可能继续站着、可能突然横穿、也可能往回走如果你只预测一条轨迹下游的规划模块很容易做出错误决策。多模态预测就是要把这些可能性都覆盖到并且给出每种可能性的概率。这个项目适合谁看如果你正在做自动驾驶感知或预测模块的工程落地或者你在做多模态融合、时序数据建模相关的研究再或者你只是想搞清楚深度卷积网络怎么用在轨迹预测这种时序问题上那这篇内容应该能给你一些可以直接参考的东西。我会从整体设计思路讲起然后拆解核心细节、实操过程最后把我自己遇到过的问题和排查方法整理出来。1. 项目整体设计与思路拆解1.1 为什么选深度卷积网络而不是RNN或Transformer轨迹预测本质上是一个时序建模问题。输入是过去若干帧的目标位置、速度、加速度等信息输出是未来若干帧的轨迹分布。很多人第一反应是用LSTM或者Transformer这没错但深度卷积网络在这个任务上有它独特的优势。卷积网络的核心能力是提取局部模式。在轨迹预测场景里目标的运动轨迹在时间维度上具有强烈的局部相关性——相邻几帧之间的运动变化是平滑的突然的剧烈变化往往意味着异常。一维卷积沿时间轴滑动可以非常高效地捕捉这种局部时序模式。而且卷积是并行计算的训练速度比RNN快很多这在需要大规模数据集迭代的时候非常关键。另一个考虑是卷积网络对输入长度的适应性更好。RNN在处理变长序列时有天然的递归依赖容易出现梯度消失或爆炸。卷积网络通过堆叠多层、使用膨胀卷积来扩大感受野可以灵活控制模型看到多长的历史信息。我在实际项目里试过用膨胀卷积堆到感受野覆盖过去3秒的轨迹效果比同等参数量的LSTM稳定不少。当然卷积网络也有短板。它对全局依赖的建模能力不如Transformer所以在一些需要长距离交互的场景比如多车博弈里纯卷积可能不够。我的做法是在卷积主干之后接一个轻量的注意力模块既保留了卷积的局部建模优势又补上了全局交互的能力。这个混合结构在多个数据集上都比纯卷积或纯Transformer要好。1.2 多模态输出的实现方式多模态轨迹预测的核心难点在于如何让模型输出多个合理的未来轨迹而不是只输出一个平均值。如果直接用MSE损失训练模型会倾向于输出所有可能轨迹的均值这在多模态场景下是灾难性的——比如行人可能向左也可能向右均值就是直着走这显然不合理。常见的多模态输出方案有三种。第一种是Mixture Density Network让模型输出多个高斯分布的参数每个分布对应一个模态。第二种是Multiple Choice Learning训练多个预测头每个头负责一个模态通过竞争机制让不同的头专注于不同的模式。第三种是Anchor-based方法预定义一组轨迹锚点模型预测每个锚点的偏移量和概率。我在这个项目里用的是第二种和第三种结合的方式。具体来说我预设了K个轨迹锚点K通常取6到8每个锚点代表一种典型的运动模式比如直行、左转、右转、急停等。模型在卷积特征的基础上为每个锚点预测一个偏移量和一个置信度。这样做的好处是训练稳定而且锚点的设计可以融入领域知识——比如根据车道线的方向来初始化锚点让模型从一开始就有合理的先验。注意锚点的数量不是越多越好。我试过K16结果很多锚点退化成了几乎相同的轨迹反而增加了计算量。K6到8在大多数场景下已经能覆盖主要的运动模式。1.3 多模态融合的输入设计这个项目的输入不是单一模态的数据而是融合了多种信息源。具体包括目标历史轨迹过去2秒的位置序列采样频率10Hz共20帧。目标属性类型行人、自行车、车辆、尺寸、朝向。局部地图信息目标周围一定范围内的车道线、人行横道、路沿等静态元素。交互信息周围其他目标的历史轨迹和当前状态。这些信息通过不同的编码分支处理后在特征层面进行融合。历史轨迹用一维卷积编码地图信息用二维卷积编码成鸟瞰图特征交互信息用图神经网络或者简单的池化操作处理。融合的方式我试过早期融合、晚期融合和中期融合最终选择的是中期融合——每个分支先独立编码然后在中间层拼接再经过几层卷积做进一步的特征交互。为什么选中中期融合早期融合在输入层就拼接不同模态的数据尺度和语义差异太大模型很难学好。晚期融合在输出层才合并各分支之间缺乏交互对于需要跨模态推理的场景比如根据地图判断轨迹是否合理效果不好。中期融合兼顾了两者既保留了各模态的独立编码能力又提供了足够的交互空间。2. 核心细节解析与实操要点2.1 数据准备与预处理的关键细节数据是这个项目里最耗时的部分没有之一。我用的数据集主要是Argoverse和nuScenes这两个都是自动驾驶领域常用的公开数据集。Argoverse的轨迹预测赛道提供了详细的轨迹和地图数据nuScenes则在传感器配置和场景多样性上更丰富。预处理的第一步是坐标归一化。原始数据里的坐标是全局坐标系下的绝对位置直接输入网络会导致数值范围过大训练不稳定。我的做法是以目标当前时刻的位置为原点朝向为x轴正方向建立一个局部坐标系把所有历史轨迹和地图元素都转换到这个坐标系下。这样输入数值都在一个合理的范围内而且模型学到的模式与目标的绝对位置无关泛化能力更强。第二步是轨迹采样和插值。不同数据集的采样频率不一样Argoverse是10HznuScenes是2Hz。为了统一我需要把nuScenes的数据插值到10Hz。这里用的是三次样条插值比线性插值更平滑能更好地保留运动的高阶信息。插值之后还要做一次平滑滤波去掉传感器噪声带来的抖动。第三步是地图栅格化。把目标周围一定范围我设的是左右各30米前后各60米内的地图元素渲染成一张鸟瞰图。车道线、人行横道、路沿用不同的通道表示形成多通道的栅格图像。分辨率我设的是0.2米/像素这样一张图大概是300x600的大小既能保留足够的细节又不会让计算量爆炸。实操心得地图栅格化的分辨率需要根据实际场景调整。城市道路场景下0.2米/像素够用但如果是高速公路场景目标移动速度快可能需要降低到0.5米/像素来覆盖更大的范围。2.2 网络结构设计与参数选择网络的主干是一个多层一维卷积网络处理历史轨迹序列。具体结构是输入层接收20帧x 6维的特征位置x、y速度vx、vy加速度ax、ay经过三层一维卷积每层的卷积核大小分别是5、3、3通道数分别是64、128、256。每层后面接BatchNorm和ReLU激活。三层之后感受野已经覆盖了全部20帧的历史信息。地图分支用的是二维卷积网络输入是3通道的鸟瞰图经过四层卷积每层后面接最大池化最终得到一个空间特征图。然后通过全局平均池化得到一个固定长度的特征向量。交互分支我用的是一种简化的图注意力机制。每个目标作为一个节点节点特征包括历史轨迹编码和属性信息。边的关系根据目标之间的距离动态构建距离越近的目标之间边权重越大。经过两层图注意力层之后每个目标得到一个融合了周围目标信息的特征向量。三个分支的输出拼接在一起经过两层全连接层然后分成K个预测头每个头输出一个轨迹偏移量和一个置信度分数。轨迹偏移量是一个T x 2的矩阵T是预测的未来帧数我设的是30帧对应3秒2是x和y方向的偏移。置信度经过softmax归一化保证所有模态的概率和为1。损失函数用的是Winner-Takes-All加上分类损失。具体来说对于每个样本只计算与真实轨迹最接近的那个模态的回归损失同时所有模态都计算分类损失让模型学会给正确的模态更高的置信度。回归损失用的是Smooth L1比MSE对异常值更鲁棒。2.3 训练策略与调参经验训练这个网络有几个关键点。首先是学习率的设置我用的是余弦退火加上warmup。前5个epoch做线性warmup学习率从1e-5升到1e-3然后余弦退火到1e-6。这样做的原因是卷积网络在训练初期对大的学习率比较敏感warmup可以让模型先稳定下来。Batch size我设的是64在单卡上刚好能跑满显存。如果显存不够可以用梯度累积来模拟更大的batch size。优化器用的是AdamW权重衰减设的是1e-4。AdamW比Adam在卷积网络上的泛化性能更好这一点在多个任务上都得到了验证。训练轮数我设的是100个epoch但实际上在60到70个epoch的时候验证集损失就基本不再下降了。我加了早停机制如果连续10个epoch验证损失没有改善就停止训练。最终模型大概在65个epoch左右收敛。注意多模态预测的评估指标不能只看ADE和FDE。这两个指标衡量的是预测轨迹与真实轨迹的平均距离和终点距离但多模态场景下更重要的是minADE和minFDE也就是在所有预测模态中取与真实轨迹最接近的那个来计算。另外还要看Miss Rate衡量的是所有模态都没有预测准的比例。3. 实操过程与核心环节实现3.1 环境搭建与依赖安装这个项目的代码基于PyTorch实现我用的版本是1.13CUDA版本是11.7。以下是核心依赖的安装步骤conda create -n traj_pred python3.9 conda activate traj_pred pip install torch1.13.1cu117 torchvision0.14.1cu117 -f https://download.pytorch.org/whl/torch_stable.html pip install numpy pandas scikit-learn matplotlib pip install tensorboard pip install argoverse-apiArgoverse的API需要单独安装它提供了数据加载和地图可视化的工具。nuScenes的数据加载我用的是nuscenes-devkit安装方式和Argoverse类似。环境搭好之后先跑一个小的数据加载测试确认数据路径和格式没问题。这一步看起来简单但我见过太多人因为路径配置错误浪费半天时间。建议写一个简单的脚本加载一个样本把轨迹和地图可视化出来肉眼确认数据是对的。3.2 数据加载器的实现数据加载器是整个训练流程的入口它的效率直接影响训练速度。我实现了一个自定义的Dataset类核心逻辑如下class TrajectoryDataset(Dataset): def __init__(self, data_root, modetrain, obs_len20, pred_len30): self.data_root data_root self.obs_len obs_len self.pred_len pred_len self.samples self._load_samples(mode) def _load_samples(self, mode): # 加载轨迹数据、地图数据、交互数据 # 返回样本列表 pass def __getitem__(self, idx): sample self.samples[idx] # 坐标归一化 obs_traj, pred_traj self._normalize(sample) # 地图栅格化 map_feat self._rasterize_map(sample) # 交互特征 inter_feat self._extract_interaction(sample) return obs_traj, map_feat, inter_feat, pred_traj def __len__(self): return len(self.samples)这里有几个优化点。第一地图栅格化是比较耗时的操作我在训练前会预计算好所有样本的地图特征存成npy文件训练时直接加载省去了重复计算。第二交互特征的提取涉及到目标之间的配对我用了一个空间索引来加速最近邻搜索比暴力遍历快了一个数量级。DataLoader的num_workers我设的是8prefetch_factor设的是4。这两个参数需要根据CPU核心数和内存大小调整。如果num_workers设得太大反而会因为进程切换开销导致速度下降。3.3 模型定义与训练循环模型定义的核心部分如下class TrajectoryPredictor(nn.Module): def __init__(self, obs_len20, pred_len30, num_modes6): super().__init__() self.obs_encoder nn.Sequential( nn.Conv1d(6, 64, kernel_size5, padding2), nn.BatchNorm1d(64), nn.ReLU(), nn.Conv1d(64, 128, kernel_size3, padding1), nn.BatchNorm1d(128), nn.ReLU(), nn.Conv1d(128, 256, kernel_size3, padding1), nn.BatchNorm1d(256), nn.ReLU() ) self.map_encoder MapEncoder() self.interaction_encoder InteractionEncoder() self.fusion nn.Sequential( nn.Linear(256 128 128, 512), nn.ReLU(), nn.Linear(512, 256), nn.ReLU() ) self.traj_heads nn.ModuleList([ nn.Linear(256, pred_len * 2) for _ in range(num_modes) ]) self.confidence_head nn.Linear(256, num_modes) def forward(self, obs_traj, map_feat, inter_feat): obs_feat self.obs_encoder(obs_traj.permute(0, 2, 1)) obs_feat obs_feat.mean(dim-1) map_feat self.map_encoder(map_feat) inter_feat self.interaction_encoder(inter_feat) fused self.fusion(torch.cat([obs_feat, map_feat, inter_feat], dim-1)) trajs torch.stack([head(fused) for head in self.traj_heads], dim1) confs self.confidence_head(fused) return trajs, confs训练循环里每个batch的处理逻辑是前向传播得到K个轨迹和对应的置信度计算每个模态与真实轨迹的L2距离选出距离最小的模态作为winner只对winner计算回归损失对所有模态计算分类损失。分类损失用的是交叉熵标签是winner的索引。def train_step(model, batch, optimizer, criterion): obs_traj, map_feat, inter_feat, pred_traj batch trajs, confs model(obs_traj, map_feat, inter_feat) # 计算每个模态与真实轨迹的距离 distances torch.norm(trajs - pred_traj.unsqueeze(1), dim-1).mean(dim-1) winner_idx distances.argmin(dim1) # 回归损失只计算winner reg_loss criterion(trajs[range(len(winner_idx)), winner_idx], pred_traj) # 分类损失计算所有模态 cls_loss F.cross_entropy(confs, winner_idx) loss reg_loss 0.5 * cls_loss optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() return loss.item()梯度裁剪是必须的多模态训练有时候会出现梯度爆炸的情况max_norm设1.0比较稳妥。3.4 评估与可视化评估部分我实现了minADE、minFDE和Miss Rate三个指标。minADE的计算方式是对于每个样本计算K个预测轨迹与真实轨迹的ADE取最小值。minFDE类似只是计算终点距离。Miss Rate是minFDE大于2米的比例。可视化我用matplotlib实现了一个简单的工具把历史轨迹、预测轨迹、真实轨迹和地图元素画在一起。预测轨迹用不同颜色表示不同的模态透明度表示置信度。这个工具在调试的时候非常有用可以直观地看到模型到底学到了什么。实操心得可视化的时候一定要把地图画出来。我遇到过模型预测的轨迹穿过了人行横道或者冲上了路沿但只看轨迹数据是发现不了的。加上地图之后这些问题一目了然。4. 常见问题与排查技巧实录4.1 训练损失不下降或震荡这是最常见的问题。可能的原因和排查方法如下问题现象可能原因排查方法解决方案损失从一开始就不下降学习率过大或过小打印梯度范数观察是否爆炸或消失调整学习率加warmup损失下降后震荡Batch size太小观察不同batch的损失方差增大batch size或用梯度累积损失突然变成NaN梯度爆炸检查梯度范数加梯度裁剪降低学习率验证损失上升过拟合对比训练和验证损失曲线加Dropout增大权重衰减我遇到过一次损失震荡特别严重的情况排查后发现是数据加载器里的坐标归一化有问题。部分样本的朝向角度计算错误导致归一化后的轨迹方向完全乱了。修复之后损失曲线就平滑了。所以数据预处理一定要仔细检查最好写单元测试。4.2 多模态退化问题多模态退化是指模型输出的K个模态变得几乎一样失去了多样性。这个问题在多模态预测里非常普遍。我试过以下几种解决方法第一种是增加多样性损失。在损失函数里加一项惩罚不同模态之间的相似度。具体来说计算两两模态之间的距离如果距离太小就加惩罚。这个方法有效但权重需要仔细调太大会影响预测精度。第二种是使用Multiple Choice Learning的变体。不是只让winner更新而是让top-k个模态都更新但权重不同。这样可以让多个模态都有机会学习到不同的模式。第三种是锚点初始化。我前面提到的锚点方案本质上就是给模型一个多样性的先验。锚点初始化得好模态退化的问题会轻很多。实测下来锚点初始化加上轻度的多样性损失效果最好。模态之间的差异明显而且预测精度没有明显下降。4.3 推理速度优化自动驾驶对推理速度的要求很高通常需要在100毫秒以内完成一次预测。我最初的模型在V100上推理一次要200多毫秒后来做了以下优化第一把卷积层的通道数从256降到192精度损失不到1%但速度提升了约20%。第二把地图分支的全局平均池化改成自适应池化减少了计算量。第三用TensorRT对模型进行量化FP16精度下速度提升了近一倍精度损失在可接受范围内。最终模型在V100上的推理时间降到了约60毫秒满足了实时性要求。如果部署在边缘设备上可能还需要进一步压缩比如用MobileNet风格的轻量卷积替换标准卷积。注意量化之后一定要重新评估精度。我遇到过量化后minADE上升了0.3米的情况虽然整体指标还能接受但在一些关键场景比如行人密集区域的预测质量明显下降。所以量化后的模型需要在各种场景下都做充分测试。4.4 跨数据集泛化问题在Argoverse上训练好的模型直接拿到nuScenes上测试性能会下降不少。主要原因是两个数据集的场景分布不一样——Argoverse主要是城市道路nuScenes包含了更多的高速和停车场场景。另外两个数据集的传感器配置和标注规范也有差异。我的解决方法是做领域自适应。具体来说在训练时同时使用两个数据集但给每个数据集一个可学习的领域嵌入向量加到特征上。这样模型可以学到领域相关的特征同时共享底层的运动模式。另外我还用了数据增强比如随机旋转、缩放、加噪声来提升模型的鲁棒性。实测下来加了领域自适应之后跨数据集的minADE下降了约15%。如果目标场景有少量标注数据还可以做微调效果会更好。4.5 常见问题速查表问题排查方向快速解决训练不收敛学习率、数据归一化、损失函数检查数据预处理调整学习率模态退化多样性损失、锚点设计加多样性损失重新设计锚点推理太慢模型大小、量化、算子优化减小通道数用TensorRT量化跨数据集性能下降领域差异、数据增强加领域嵌入做数据增强预测轨迹不合理地图特征、物理约束检查地图编码加物理约束损失显存不够Batch size、模型大小减小batch size用梯度累积最后分享一个我在实际项目中总结的小技巧多模态轨迹预测的评估不能只看数字指标一定要做可视化。我遇到过minADE指标很好但可视化后发现模型在某些场景下预测的轨迹虽然距离真实轨迹很近但明显违反了交通规则比如逆行。这种问题只有通过可视化才能发现。所以建议在评估流程里强制加入可视化环节每个epoch都随机抽一些样本画出来看看。
返回列表