UB-LSTM行为识别轨迹预测:从NGSIM数据到实车部署实践
2026/9/10 6:49:21 网站建设 项目流程

简介:结合车辆行为识别的轨迹预测算法压缩包,面向动态交通环境下的智能车轨迹预测研究,聚焦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(drop=True) def smooth_track(group): if len(group) < 11: return group x = savgol_filter(group["Global_X"], window_length=11, polyorder=3, deriv=0) y = savgol_filter(group["Global_Y"], window_length=11, polyorder=3, deriv=0) 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_keys=False).apply(smooth_track).reset_index(drop=True)

这段代码先按车辆和帧排序,再按车辆分组对全局坐标做平滑。window_length=11对应 1.1 秒窗口,polyorder=3表示用三阶多项式拟合,既不会把真实加速度削平,也能滤掉高频噪声。diff()*10是因为 10Hz 采样相邻帧间隔 0.1 秒,差分后要乘 10 才是每秒速度变化量。注意 pandas 2.x 里groupby.apply默认会保留分组键,这里显式加group_keys=False,避免后面拼接样本时多出一列分组信息。

坐标转换方面,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前车/后车相对纵向距离,前正后负2
delta_d左右邻车相对横向偏移2
delta_v相对纵向速度2
delta_a相对纵向加速度2
ttc碰撞时间,截断到 [-5, 5]2
lane_gap目标车到左右车道线距离2

注意不是把所有候选车特征拼成一个大向量,而是先按车道和相对距离排序,固定取最危险的前后两台车。交互车数量不够时用 0 填充,并在输入向量里增加一个 mask 标记哪些位置是有效值。

def find_neighbors(frame_df, target_id, range_lon=45.0, range_lat=3.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", ascending=False) 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, his=30, fut=50, stride=10): 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 samples

behavior_label是后验行为标签,资源里用的是未来 3 秒内横向位移最大变化量超过 0.5 米来判断左换道或右换道,否则标成车道保持。这个标签不是预测值,而是用来监督行为分类头的。stride=10让相邻样本只重叠 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, hidden=64, num_classes=4): 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_dim=8, hidden=128, layers=2, num_behavior=4, future_steps=50): super().__init__() self.encoder = nn.LSTM(in_dim, hidden, layers, batch_first=True) self.behavior_embed = nn.Embedding(num_behavior, 16) self.decoder = nn.LSTM(hidden + 16, hidden, layers, batch_first=True) 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, dim=1), behavior_logits

behavior_prob @ self.behavior_embed.weight是行为概率对嵌入矩阵的线性组合,相当于软选择。解码过程把行为上下文和last_h拼在一起,每步输出的是相对于当前坐标的 delta,累加才能得到绝对位置。这里有一个容易踩的坑:PyTorch 的nn.LSTMbatch_first=True时输出形状是[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_loss

0.2 的权重是我常用的初始值。如果数据里车道保持样本占 60% 以上,行为分类器很容易把所有样本都推到车道保持类,这时把ce_loss权重调到 0.5 也无法根治,更有效的是给每个类按样本数倒数设置权重,做成带权重的CrossEntropyLoss(weight=...)。轨迹回归用 SmoothL1 而不是 MSE,NGSIM 里有不少急加速和急刹车的尾段,MSE 对离群点太敏感,SmoothL1 在误差大时梯度是常数,收敛更稳。

评估指标上,轨迹预测最常看的是 ADE 和 FDE:

ade = 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], dtype=torch.float32) inter = torch.tensor(sample["interaction"], dtype=torch.float32) future = torch.tensor(sample["future"], dtype=torch.float32) label = torch.tensor(sample["behavior_label"], dtype=torch.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 加到 128,ADE 降了约 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(), lr=1e-3) scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.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_norm=5.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(f"Early stop at epoch {epoch}") break

clip_grad_norm_max_norm=5.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(maxlen=31) # 历史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_prob

deque(maxlen=31)会自动丢弃最旧帧,省去手动管理时间戳。model.infer在 PyTorch 中用torch.no_grad()包裹,并把输入改成 batch=1。实车工控机如果没有 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], label="left lane change") plt.axvline(true_lane_change_time, color="red", linestyle="--", label="ground truth")

如果概率上升点比真实换道点晚超过 1 秒,说明行为分类头过度依赖横向速度,没有利用早期横向偏移曲线的弯曲趋势。解决办法是在特征拼接时把横向偏移的二阶差分、前 1 秒平均斜率加进去。

6.3 对行为概率做指数平滑后再解码

实车推理时每一帧的行为概率都会抖动,直接作为软权重会导致预测轨迹在换道/车道保持之间来回摆动。简单有效的手段是引入指数移动平均:

smooth_prob = 0.8 * smooth_prob + 0.2 * raw_prob

平滑系数 0.8 意味着新的观测只贡献 20% 权重,约 5 帧后才能明显改变概率分布。这样做会让换道预测起点滞后 0.3 秒左右,但换来的是轨迹稳定性大幅提升。平滑后的概率不再直接参与训练,只在推理时使用。这个技巧对实车部署尤为重要,因为控制模块对预测轨迹的抖动容忍度很低,与其每帧都输出不同方向,不如让行为判断慢半拍但方向一致。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询