
简介结合车辆行为识别的轨迹预测算法压缩包面向动态交通环境下的智能车轨迹预测研究聚焦LSTM与驾驶行为识别联合建模适合交通运输、自动驾驶、智能车方向的算法研究者与硕士研究生参考。源代码覆盖数据处理、交互信息提取、模型构建、数据加载与训练全流程实验数据采用NGSIM公开数据集与实验室智能车采集的实车数据算法已在实际车辆运行中验证具备较强工程价值。压缩包大小约200.64MB包含Python源码、训练脚本、模型文件及数据接口目录结构清晰便于按模块学习与二次开发。已有159人学习下载可作为毕业论文核心章节复现的参照也能帮助读者快速梳理从原始轨迹数据清洗、上下文特征构建到行为识别与轨迹输出联合训练的完整研究链路。1. 行为识别先行的轨迹预测UB-LSTM 的定位纯 LSTM 做车辆轨迹预测有个常见的失败模式预测轨迹往车道中心线“收”换道场景终点平均偏移 0.6 米以上。原因不是 LSTM 不够强而是它把左换道、右换道、车道保持三种运动模式压进同一个隐空间平均掉了。UB-LSTM 的思路是先识别行为再用行为概率去调制轨迹解码。这个资源包是论文《基于 LSTM 的动态环境车辆轨迹预测》第四章的完整源码包含数据处理、交互信息提取、模型构建、数据加载训练全部链路同时支持 NGSIM 数据集和实车数据集并且作者已经在智能车上实际跑通。适合正在做轨迹预测毕设、或者想复现行为识别和预测联合模型的人直接上手。2. 数据管线从 NGSIM 原始轨迹到交互特征张量2.1 原始字段清洗与频率对齐NGSIM 最常用的两个路段 US-101 和 I-80 都是 10Hz 采样高频噪声很大。直接拿原始位置算速度和加速度抖动会传导到 LSTM 输入端。我一般先做两步第一步按 Vehicle_ID 和 Frame_ID 排序删除缺失 Frame_ID 的行。一个车辆在某帧消失又出现通常是被遮挡或被跟踪器弄丢这种片段要切断否则一个 LSTM 序列里会拼进两条不连续的轨迹。第二步对 Global_X / Global_Y 做 Savitzky-Golay 滤波窗口长度通常取 11多项式阶数取 3再反算速度和加速度。NGSIM 自带的 v_Vel 和 v_Acc 已经平滑过但如果你从别的数据集或实车传感器拿原始点必须自己重算。import pandas as pd from scipy.signal import savgol_filter df pd.read_csv(ngsim_processed.csv) df df.sort_values([Vehicle_ID, Frame_ID]).reset_index(dropTrue) def smooth_track(group): if len(group) 11: return group x savgol_filter(group[Global_X], window_length11, polyorder3, deriv0) y savgol_filter(group[Global_Y], window_length11, polyorder3, deriv0) group[smooth_x] x group[smooth_y] y group[v_x] group[smooth_x].diff() * 10 # 10Hz单位m/s group[v_y] group[smooth_y].diff() * 10 return group df df.groupby(Vehicle_ID, group_keysFalse).apply(smooth_track).reset_index(dropTrue)这段代码先按车辆和帧排序再按车辆分组对全局坐标做平滑。window_length11对应 1.1 秒窗口polyorder3表示用三阶多项式拟合既不会把真实加速度削平也能滤掉高频噪声。diff()*10是因为 10Hz 采样相邻帧间隔 0.1 秒差分后要乘 10 才是每秒速度变化量。注意 pandas 2.x 里groupby.apply默认会保留分组键这里显式加group_keysFalse避免后面拼接样本时多出一列分组信息。坐标转换方面NGSIM 的 Global_X/Y 是基于英尺的坐标要乘 0.3048 转成米。你需要先拟合出车道中心线然后把绝对坐标投影成纵向距离 s 和横向偏移 d。后面所有轨迹模块都用 (s, d) 而不是 (x, y)因为横向偏移对换道行为是强判别特征而纵向距离 s 直接反映跟车距离和速度关系。import numpy as np def frenet_transform(track_x, track_y, center_x, center_y): center_x/center_y: 当前车道中心线的密集采样点 返回纵向距离s和横向偏移d dx center_x - track_x dy center_y - track_y idx np.argmin(dx**2 dy**2) nearest_vec np.array([center_x[idx], center_y[idx]]) point_vec np.array([track_x, track_y]) tangent np.array([center_x[min(idx 1, len(center_x) - 1)], center_y[min(idx 1, len(center_y) - 1)]]) - nearest_vec normal np.array([-tangent[1], tangent[0]]) normal normal / (np.linalg.norm(normal) 1e-8) d float(np.dot(point_vec - nearest_vec, normal)) s float(np.sqrt(np.sum((point_vec - nearest_vec)**2))) return s, d这里的 s 只是到最近中心线点的欧氏距离严格做应该沿中心线累加弧长。资源里的实现通常会预设分段车道再把轨迹点匹配到最近的车道中心点上用该点已有弧长作为 s。我这里简化了真实训练不要直接用这个 s否则在弯道路段误差会很大。重点是横向偏移 d 的符号定义向量指向轨迹点与车道中心线法向量的夹角正负表示左右偏离后续行为分类会直接用到 d 的变化趋势。2.2 交互信息提取邻域车辆怎么找NGSIM 每一帧有几百台车不能把全部车辆都拼进模型中。UB-LSTM 这类方法通常只取目标车周围一定范围内的车辆作为交互对象。常见设置纵向范围 ±45 米横向范围左右各一个车道。找到候选车后提取相对特征。下面这组特征是我在复现时常用的特征名含义维度delta_s前车/后车相对纵向距离前正后负2delta_d左右邻车相对横向偏移2delta_v相对纵向速度2delta_a相对纵向加速度2ttc碰撞时间截断到 [-5, 5]2lane_gap目标车到左右车道线距离2注意不是把所有候选车特征拼成一个大向量而是先按车道和相对距离排序固定取最危险的前后两台车。交互车数量不够时用 0 填充并在输入向量里增加一个 mask 标记哪些位置是有效值。def find_neighbors(frame_df, target_id, range_lon45.0, range_lat3.75): target frame_df[frame_df[Vehicle_ID] target_id].iloc[0] cand frame_df[ (frame_df[s] target[s] - range_lon) (frame_df[s] target[s] range_lon) (frame_df[d] target[d] - range_lat) (frame_df[d] target[d] range_lat) (frame_df[Vehicle_ID] ! target_id) ] front cand[cand[s] target[s]].sort_values(s) back cand[cand[s] target[s]].sort_values(s, ascendingFalse) features [ extract_front_back(front, target), extract_lane_side(cand, target) ] return np.concatenate(features)这段代码按纵向 45 米、横向 3.75 米找邻居。front按 s 升序取最近前车back按 s 降序取最近后车。extract_front_back里计算 delta_s、delta_v、TTC 时会除以相对距离做归一化数值范围控制在 0 到 1 之间。extract_lane_side用的是当前帧车道线信息不需要邻居车也能算所以即使旁边没车这个分支依然有值。这样模型才能学到“旁边空着”也是一种安全状态而不是把缺失特征简单当成 0 处理。2.3 滑动窗口样本构造与数据划分训练样本用历史 3 秒30 帧预测未来 5 秒50 帧。因为 10Hz 采样一个窗口跨 8 秒。滑窗步长取 1 秒10 帧避免样本重叠太高导致验证指标虚高。构造时按同一辆车的轨迹先做车辆级划分80% 车辆做训练10% 验证10% 测试。注意不能随机切帧否则训练集和测试集会出现同一条轨迹的重叠片段跑出来的 ADE 会好看但不可信。def build_samples(vehicle_track, his30, fut50, stride10): samples [] for start in range(0, len(vehicle_track) - his - fut 1, stride): clip vehicle_track[start:start his fut] past clip[:his] future clip[his:] if len(past) his or len(future) fut: continue samples.append({ past: past[[s, d, v_s, v_d, behavior_prob]].values, future: future[[s, d]].values, interaction: build_interaction_sequence(past), behavior_label: int(future[behavior_label].mode()[0]) }) return samplesbehavior_label是后验行为标签资源里用的是未来 3 秒内横向位移最大变化量超过 0.5 米来判断左换道或右换道否则标成车道保持。这个标签不是预测值而是用来监督行为分类头的。stride10让相邻样本只重叠 2 秒历史数据量足够时我一般会把 stride 调到 20训练时间直接减半ADE 损失可以忽略。注意behavior_prob这一列在训练时并不作为输入它只是样本构造阶段临时用到的中间特征真正输入 LSTM 的是 s、d、速度、加速度和交互向量。3. 模型结构行为识别分支与 LSTM 解码头的配合3.1 行为分类器用短时运动状态做动作判断很多轨迹预测模型会把换道识别做成独立模块UB-LSTM 的做法是把行为识别做成一个辅助头和轨迹输出共用 LSTM 编码器。输入是过去 30 步的速度、加速度和横向偏移输出四分类车道保持、左换道、右换道、减速跟车。分类器结构非常简单两层 MLP中间用 ReLU输出层 4 个 logits。关键是把横向偏移 d 的窗口统计量也拼进去因为换道决策往往在横向速度起来之前就已经体现在横向偏移曲线的弯曲趋势里。import torch.nn as nn class BehaviorHead(nn.Module): def __init__(self, feat_dim, hidden64, num_classes4): super().__init__() self.fc1 nn.Linear(feat_dim, hidden) self.fc2 nn.Linear(hidden, num_classes) def forward(self, hidden_state): x torch.relu(self.fc1(hidden_state)) return self.fc2(x)feat_dim是 LSTM 隐藏状态维度加上窗口统计量后的拼接值。hidden_state取的是 LSTM 最后一个时间步的输出也就是整段历史轨迹的压缩表示。这里不单独训练一个动作识别网络而是让分类梯度和轨迹回归梯度共同影响 LSTM好处是 LSTM 编码器必须同时保留对运动学和语义都有用的信息而不是只被回归误差牵着走。行为类别定义类别定义车道保持未来 3 秒横向位移变化不超过 0.5 米左换道未来 3 秒横向位移向左变化超过 0.5 米且持续 1 秒以上右换道对称定义减速跟车纵向速度连续下降且速度差小于前车 2 m/s类别定义直接决定行为标签质量。资源里的阈值可能略有不同如果你换到实车采集的数据建议先统计横向位移分布再定阈值。阈值太大换道样本会被标成车道保持阈值太小正常车道内抖动会被标成换道行为分类头会学出大量假正例。3.2 行为嵌入如何影响轨迹解码拿到行为 logits 后不是直接 argmax而是先过 softmax 得到行为概率分布再用概率加权融合。具体做法是把概率向量作为权重对行为嵌入矩阵的每一行做加权求和得到一个行为上下文向量。这个向量与 LSTM 编码器输出拼接后输入给解码 LSTM。这样在轨迹预测时模型不是硬性选一个行为而是按概率混合多条行为轨迹换道不确定性自然被保留下来。解码器有两种常见设计。第一种是单解码器输入拼接行为上下文用自回归方式逐帧输出未来位置的 delta 值。第二种是多头解码器为每个行为类单独维护一个输出头最后按行为概率加权。源码包里的核心代码通常是第一种实现简单且梯度更稳。我建议刚上手先复现第一种论文实验也大多是第一种。class UBLSTM(nn.Module): def __init__(self, in_dim8, hidden128, layers2, num_behavior4, future_steps50): super().__init__() self.encoder nn.LSTM(in_dim, hidden, layers, batch_firstTrue) self.behavior_embed nn.Embedding(num_behavior, 16) self.decoder nn.LSTM(hidden 16, hidden, layers, batch_firstTrue) self.reg_head nn.Linear(hidden, 2) self.behavior_head BehaviorHead(hidden 2) self.future_steps future_steps def forward(self, hist, current_state): enc_out, (h, c) self.encoder(hist) last_h enc_out[:, -1] feat torch.cat([last_h, current_state[:, -2:]], dim-1) behavior_logits self.behavior_head(feat) behavior_prob torch.softmax(behavior_logits, dim-1) behavior_ctx behavior_prob self.behavior_embed.weight dec_input torch.cat([last_h, behavior_ctx], dim-1).unsqueeze(1) outputs [] for _ in range(self.future_steps): dec_out, (h, c) self.decoder(dec_input, (h, c)) delta self.reg_head(dec_out[:, -1]) outputs.append(delta) dec_input torch.cat([dec_out[:, -1][..., :hidden], behavior_ctx], dim-1).unsqueeze(1) return torch.stack(outputs, dim1), behavior_logitsbehavior_prob self.behavior_embed.weight是行为概率对嵌入矩阵的线性组合相当于软选择。解码过程把行为上下文和last_h拼在一起每步输出的是相对于当前坐标的 delta累加才能得到绝对位置。这里有一个容易踩的坑PyTorch 的nn.LSTM在batch_firstTrue时输出形状是[B, T, H]取enc_out[:, -1]才是最后一个时间步如果用了双向 LSTM拼接后维度要除以 2否则后面的线性层维度对不上。源码里的模型不支持双向那就没必要强行上双向行为识别任务对过去依赖强未来帧还没出现双向收益很低。3.3 损失函数与评估指标训练损失是行为分类损失和轨迹回归损失的加权和。ce_loss nn.CrossEntropyLoss()(behavior_logits, behavior_labels) reg_loss nn.SmoothL1Loss()(pred_traj, future_traj) loss 0.2 * ce_loss reg_loss0.2 的权重是我常用的初始值。如果数据里车道保持样本占 60% 以上行为分类器很容易把所有样本都推到车道保持类这时把ce_loss权重调到 0.5 也无法根治更有效的是给每个类按样本数倒数设置权重做成带权重的CrossEntropyLoss(weight...)。轨迹回归用 SmoothL1 而不是 MSENGSIM 里有不少急加速和急刹车的尾段MSE 对离群点太敏感SmoothL1 在误差大时梯度是常数收敛更稳。评估指标上轨迹预测最常看的是 ADE 和 FDEade torch.mean(torch.norm(pred_traj[:, :, :2] - future_traj[:, :, :2], dim-1)) fde torch.mean(torch.norm(pred_traj[:, -1, :2] - future_traj[:, -1, :2], dim-1))ADE 衡量所有时间步的平均偏差FDE 只看终点偏差。论文实验里还要分场景评估车道保持、换道、跟车分别统计因为换道场景的 ADE 通常比车道保持高 0.4 米以上。行为识别分支的准确率也要单独报告最好给出混淆矩阵能看出换道误分类是发生在左换和右换之间还是换道与车道保持之间。第 6 章会讲怎么快速做这个验证。4. 模型训练与超参数完整复现论文第四章4.1 数据加载器设计训练时最忌讳的是直接随机打乱所有样本因为同一辆车相邻窗口的样本会同时出现在训练集和验证集评估出来的 ADE 会虚低。正确做法是按车辆 ID 划分数据再为每辆车内部构造的样本建立索引映射。数据加载器返回四个对象历史轨迹、交互特征、未来轨迹、行为标签。from torch.utils.data import Dataset class TrajectoryDataset(Dataset): def __init__(self, samples, feature_cols): self.samples samples self.feature_cols feature_cols def __len__(self): return len(self.samples) def __getitem__(self, idx): sample self.samples[idx] past torch.tensor(sample[past][:, self.feature_cols], dtypetorch.float32) inter torch.tensor(sample[interaction], dtypetorch.float32) future torch.tensor(sample[future], dtypetorch.float32) label torch.tensor(sample[behavior_label], dtypetorch.long) return past, inter, future, label在这个 Dataset 里feature_cols可以灵活控制是否把速度、加速度、车道偏移一起输入。inter的形状一般是[T, 16]因为每个时间步都有一组邻居特征。future只保留 s 和 d 坐标训练回归头时直接对应 delta 输出。注意行为标签必须在样本构造时稳定下来不要用当前时刻的行为而要用未来 3 秒内真实发生的行为否则模型学不到“未来意图”。4.2 超参数清单与经验下面这张表是我用资源代码在 NGSIM US-101 上跑出来的基础配置可以直接照抄。超参数推荐值说明历史帧数303 秒10Hz预测帧数505 秒LSTM 隐藏维度128再大提升有限LSTM 层数21 层表达不足Dropout0.2只加在解码器输出侧行为嵌入维度164 类行为16 维够用Batch size256按 2080 Ti 显存设置学习率1e-3Adam预热后指数衰减行为分类损失权重0.2可调梯度裁剪5.0防止 5 秒自回归梯度爆炸资源里默认隐藏维度可能不一样我复现时从 64 加到 128ADE 降了约 0.07 米再从 128 加到 256 只降 0.01 米计算量却接近翻倍所以 128 是性价比最高的点。层数上两层 LSTM 已经能把速度变化趋势编码清楚三层在 30 步输入上容易过拟合验证集 ADE 反而变差。Batch size 影响行为分类头的稳定性太小的情况下每个 batch 里换道样本只有几个分类损失震荡明显建议至少 128。4.3 训练主循环与早停逻辑训练主循环除了常规的 batch 遍历还要记录每个 epoch 的 ADE、FDE 和分类准确率。我习惯每 5 个 epoch 在验证集上算一次完整指标并保存验证 ADE 最低的权重。早停 patience 设为 10也就是连续 10 次验证指标不下降就停止训练。optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma0.96) best_ade float(inf) patience, wait 10, 0 for epoch in range(100): model.train() train_loss 0.0 for past, inter, future, label in train_loader: optimizer.zero_grad() pred_traj, behavior_logits model(past, inter) loss compute_loss(pred_traj, future, behavior_logits, label) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step() scheduler.step() if epoch % 5 0: val_ade evaluate(model, val_loader) if val_ade best_ade: best_ade val_ade torch.save(model.state_dict(), best_ub_lstm.pt) wait 0 else: wait 1 if wait patience: print(fEarly stop at epoch {epoch}) breakclip_grad_norm_的max_norm5.0是解码器自回归训练的关键训练过程中一旦某一步输出 delta 过大后续所有时间步都会被带偏梯度范数会指数级增长。ExponentialLR每步乘 0.96学习率从 1e-3 经过 50 个 epoch 变成约 0.13e-3后期主要靠小学习率慢慢修正行为分类头的决策边界。验证时注意要把预测的 delta 累加回绝对坐标再算 ADE不能直接用 delta 序列算误差。这一步做错最终指标会虚高 0.1 米以上。5. 从 NGSIM 迁移到实验室实车UB-LSTM 部署关键点5.1 传感器坐标系到全局坐标的转换实车数据不像 NGSIM 那样提供现成的全局坐标。实验室智能车一般用 RTK-GNSS 接收自车位置用毫米波雷达或摄像头检测目标车辆。要做的第一件事是把传感器输出的目标相对于自车的极坐标转换到以自车航向角为基准的局部笛卡尔坐标再叠加自车的 UTM 坐标得到目标车的全局东向和北向坐标。这个坐标和 NGSIM 的 Global_X/Global_Y 同构后续 Frenet 投影逻辑可以直接复用。这里有一个坑NGSIM 的 Global_X/Y 使用北东坐标系UTM 本身也是北东向但很多 GNSS 设备输出的是经纬度。必须先调用pyproj从 WGS84 投影到对应 UTM 区带再做旋转对齐否则轨迹会产生固定的角度偏差训练好的 LSTM 直接失效。资源里的实车数据采集部分应该已经做了这个转换但如果你接入新传感器务必检查航向角基准是 0 度指向北还是指向东。import pyproj transformer pyproj.Transformer.from_crs(4326, 32650) # WGS84 - UTM zone 50N local_utm_x, local_utm_y transformer.transform(lat, lon) target_global_x ego_utm_x local_x * np.cos(yaw) - local_y * np.sin(yaw) target_global_y ego_utm_y local_x * np.sin(yaw) local_y * np.cos(yaw)32650是 UTM 50N 区的 EPSG 代码国内大部分地区落在 49N、50N、51N要按实际经度选择。旋转矩阵里的yaw是自车航向角单位弧度。这里最容易犯的错是忘了传感器坐标系的 x 轴指向车头还是指向右侧导致目标车辆位置在左右方向上整体翻转模型预测的换道方向全部相反。源码包里的实车预处理脚本已经处理过这个旋转但仍建议在接入新采集数据时先用一段直行匀速轨迹做自检保证目标车全局坐标没有固定角度偏差。5.2 数据流对齐和滑动窗口缓存实车测试时不能在某个时间点一次性拿到完整 30 帧历史。车载计算平台通常以固定频率接收结构化轨迹帧一般为 10Hz 或 20Hz。常见做法是维护一个环形缓冲区每来一帧就追加目标车状态滚动丢弃超过 3 秒的旧帧。推理触发条件有两种固定间隔预测或者当前帧检测到目标车进入自车前方 45 米范围时触发。from collections import deque tracker deque(maxlen31) # 历史30帧 当前帧 def on_track_frame(frame): tracker.append(frame[[s, d, v_s, v_d]].values) if len(tracker) 30: return None, None history np.stack(tracker) with torch.no_grad(): pred_traj, behavior_prob model.infer(history) return pred_traj, behavior_probdeque(maxlen31)会自动丢弃最旧帧省去手动管理时间戳。model.infer在 PyTorch 中用torch.no_grad()包裹并把输入改成 batch1。实车工控机如果没有 GPU可以用 ONNX 导出 LSTM。LSTM 在 ONNX 里有两种导出方式把 30 步整段输入或者用循环展开。前者在 GPU 上快后者在 CPU 上更稳定不会有 TensorRT 的循环展开问题。我一般优先用 ONNX 静态图直接整段推理因为模型需要完整历史上下文逐帧刷新状态反而更慢。5.3 分布偏移与微调策略NGSIM 数据是高速公路上稳定跟车流实验室智能车校园场景车速低、弯道多、有行人干扰。直接部署原模型FDE 会明显变大。我一般不会全部重新训练而是用实车数据微调两条路径冻结编码器 LSTM 的底层只更新行为分类头和顶层隐藏状态学习率降到 1e-4 以下用实车数据跑 10 个 epoch。因为编码器底层已经学到了通用的速度/加速度动态特征行为分类头却容易受场景影响。差异项NGSIM实车校园场景平均速度25 m/s8 m/s换道频率低高车道线质量清晰部分路段模糊目标车类型小汽车/货车行人与小型车混合数据频率10Hz稳定偶发丢帧实车数据采样频率往往不严格是 10Hz视觉检测算法会丢帧。在构造训练样本前要做时间戳插值把不规则间隔的轨迹重采样到固定 0.1 秒网格。线性插值对速度和加速度误差较大更稳的是用三次样条对 s 和 d 分别插值再求导得到平滑速度。这个预处理代码在资源包的数据处理目录里应该有类似实现检查一下是否支持自定义采样率。6. 验证行为识别分支的三种快捷手段6.1 用混淆矩阵定位误分类验证集上跑完一遍后先不要只看准确率要把预测行为标签和真实行为标签做成交叉表。重点看两类错误第一类是左换道和右换道互相错说明横向偏移 d 的正负符号处理不一致第二类是换道错成车道保持说明行为分类阈值太严或者历史窗口没有捕获到换道意图。资源里给出的误分类往往是第二类因为 NGSIM 的换道通常发生在 3 秒窗口最后阶段模型看不到完整换道动作。from sklearn.metrics import confusion_matrix y_true val_labels y_pred pred_behavior cm confusion_matrix(y_true, y_pred, labels[0, 1, 2, 3]) print(cm)如果 0 类车道保持行上 1 类左换道的漏判比例超过 15%我会把训练样本的行为标签判定窗口从 3 秒延长到 5 秒让标签更早反映换道意图。6.2 检查行为概率与换道点的时序关系只查混淆矩阵还不够要画行为概率随时间的变化曲线。选几个真实换道样本把左换道概率在时间轴上画出来看概率的上升点是否早于真实换道点。理想情况下行为分类头应该在横向速度明显变化前 0.5 到 1 秒就已经给出高概率。prob_history model.predict_behavior(history) # shape [T, 4] plt.plot(np.arange(len(prob_history)), prob_history[:, 1], labelleft lane change) plt.axvline(true_lane_change_time, colorred, linestyle--, labelground truth)如果概率上升点比真实换道点晚超过 1 秒说明行为分类头过度依赖横向速度没有利用早期横向偏移曲线的弯曲趋势。解决办法是在特征拼接时把横向偏移的二阶差分、前 1 秒平均斜率加进去。6.3 对行为概率做指数平滑后再解码实车推理时每一帧的行为概率都会抖动直接作为软权重会导致预测轨迹在换道/车道保持之间来回摆动。简单有效的手段是引入指数移动平均smooth_prob 0.8 * smooth_prob 0.2 * raw_prob平滑系数 0.8 意味着新的观测只贡献 20% 权重约 5 帧后才能明显改变概率分布。这样做会让换道预测起点滞后 0.3 秒左右但换来的是轨迹稳定性大幅提升。平滑后的概率不再直接参与训练只在推理时使用。这个技巧对实车部署尤为重要因为控制模块对预测轨迹的抖动容忍度很低与其每帧都输出不同方向不如让行为判断慢半拍但方向一致。本文还有配套的精品资源点击获取