
简介面向计算机相关专业学生与从业者的采用长短期记忆网络的高速公路车辆轨迹预测项目基于PyTorch框架实现经过严格调试可直接运行。资源完整覆盖数据预处理、模型训练、测试评估与可视化流程既可作为期末课程设计与课程大作业也能支撑毕业设计中的轨迹预测任务。压缩包共15个文件包含9个Python脚本、5张流程示意图及1份教程说明整体仅311KB轻量易用。脚本涵盖MTF-LSTM核心算法及其多种测试变体配套NGSIM数据集处理模块教程文本详细说明了数据划分与模型搭建步骤图示则直观展示了各阶段运行效果资源包内目录结构清晰算法模块与数据模块分离便于二次开发与对比实验。已有5214人学习下载适合需要快速上手深度学习轨迹预测项目的学生与开发者参考。1. LSTM高速公路车辆轨迹预测拿过来就能跑的PyTorch源码项目做自动驾驶方向课程设计的人多少都会碰到轨迹预测这道坎。第一次接触的人以为它很高深实际落地之后你会发现它就是一个典型的时间序列预测问题把车辆过去几秒的位置、速度喂给模型让模型把未来几秒的轨迹猜出来。LSTM在这个场景里几乎是最稳的选择不用像Transformer那样堆大量数据也不像卡尔曼滤波那样对运动模型有强假设。这套资源就是我把整个流程打包好的一份完整版本PyTorch写的LSTM轨迹预测源码、完整的训练与推理脚本、整理过的高速路车辆轨迹数据集训练、验证、测试环节都能直接跑通。拿来交期末大作业、做毕业设计或者当课程设计交付物都够用。代码结构不复杂一条数据管线跑完就能看到Loss下降曲线和最终的预测轨迹图。2. 轨迹数据预处理从原始CSV到LSTM输入序列动手写模型之前先把数据这个问题掰扯清楚。轨迹预测项目最后翻车一大半原因不是模型不行而是数据预处理出了岔子。这里的预处理不只是读CSV、做归一化这么简单关键在怎么把一条条连续轨迹切成长度一致的样本以及切分时如何防止信息泄漏。顺序搞错后面所有环节都会跟着错。2.1 数据集字段与读取检查这份资源自带的数据集是典型的表格结构每一行记录某一帧时刻某辆车的状态。高速公路轨迹数据的常见字段如下字段名含义示例值vehicle_id车辆唯一编号3frame_id帧号按时间递增120x横向位置15.2y纵向位置312.7v瞬时速度m/s23.6lane_id所在车道编号2字段名称可能略有不同但核心信息就这些。CSV里的行不是按车按时间排好的读取之后第一件事是排序和分组否则后面滑窗切出来的根本就不是连续轨迹。import pandas as pd df pd.read_csv(data/tracks.csv) df df.sort_values([vehicle_id, frame_id]) print(df.head(10)) print(车辆总数:, df[vehicle_id].nunique()) print(帧范围:, df[frame_id].min(), -, df[frame_id].max())这段代码做了两件事按车辆ID和帧号排序保证后续按组遍历时轨迹是时间连续的然后输出车辆总数和帧范围确认数据规模。跑完这一步你应该对数据集有个直观概念多少辆车、多少帧、采样间隔是否均匀。注意帧间隔。很多高速场景数据集是0.1秒一帧也就是10Hz采样相邻帧差1代表0.1秒如果frame_id跨度更大要先换算成实际时间。这个间隔决定后面obs_len和pred_len的物理含义也会直接影响训练效果先确认清楚再往下走。2.2 滑窗采样与DataLoader构造LSTM的输入是定长序列一条轨迹几百帧不能整段塞进模型。正确做法是滑动窗口切块取前obs_len帧作为观测输入紧接着的pred_len帧作为预测目标窗口每次向后挪一帧。这样一条长轨迹能切出大量短样本相当于把数据集放大了。import numpy as np def sliding_window(df, obs_len8, pred_len6): X, Y [], [] for veh_id, group in df.groupby(vehicle_id): group group.sort_values(frame_id) coords group[[x, y]].values for i in range(len(coords) - obs_len - pred_len 1): X.append(coords[i:i obs_len]) Y.append(coords[i obs_len:i obs_len pred_len]) return np.asarray(X), np.asarray(Y)函数返回两个数组X的形状是(N, obs_len, 2)Y的形状是(N, pred_len, 2)N是滑窗切出来的样本总数。代码重点在range那一行i从0走到len(coords) - obs_len - pred_len保证每个窗口有前8帧和后6帧不多不少。一条200帧的轨迹大约切出186个样本数据量直接被放大一个数量级。obs_len和pred_len是后续最常调的两个参数。obs_len8代表观测8个时间步如果帧间隔0.1秒就是0.8秒历史pred_len6则是预测0.6秒。课程设计题目如果要求预测未来1秒把pred_len改成10。obs_len没必要太长LSTM在短序列上的表现往往比长序列更稳训练也更快。切出来的样本要通过DataLoader组织成batch。注意shuffle的位置训练集要shuffle验证和测试不要。from torch.utils.data import TensorDataset, DataLoader import torch X_tensor torch.FloatTensor(X) Y_tensor torch.FloatTensor(Y) dataset TensorDataset(X_tensor, Y_tensor) loader DataLoader(dataset, batch_size64, shuffleTrue)shuffleTrue让每个epoch的batch组成不同帮助模型收敛。batch_size64在轨迹预测这种小样本任务里比较稳妥样本少的时候batch设太大一个epoch的梯度更新次数太少模型容易欠拟合。2.3 归一化与集合切分时序任务的信息泄漏红线轨迹坐标是绝对位置数值范围可能到几百米直接喂给LSTM会让梯度计算吃力所以必须归一化。但时序任务和普通分类有一个本质区别训练集、验证集、测试集必须按车辆ID切绝不能随机按行打乱再切。同一辆车的前后帧如果被分到两个集合测试时的未来其实已经被模型在训练时见过指标会虚高到离谱。vehicle_ids df[vehicle_id].unique() train_ids vehicle_ids[: int(len(vehicle_ids) * 0.7)] val_ids vehicle_ids[int(len(vehicle_ids) * 0.7):int(len(vehicle_ids) * 0.85)] test_ids vehicle_ids[int(len(vehicle_ids) * 0.85):] from sklearn.preprocessing import MinMaxScaler scaler_x MinMaxScaler() scaler_y MinMaxScaler() train_df df[df[vehicle_id].isin(train_ids)] scaler_x.fit(train_df[[x]]) scaler_y.fit(train_df[[y]])这段代码把车辆ID顺序切出70%训练、15%验证、15%测试然后只在训练集上fit归一化器。scaler的fit只能看到训练集验证集和测试集的极值不参与计算否则等于让模型在训练阶段就看到了未来数据的分布。切分比例不是死的数据量大可以放宽到80/10/10。但70%是底线再低模型学不到足够多的运动模式。另外注意切分前把vehicle_ids随机打乱一次避免数据集本身按车道或时间段排序导致三个集合分布不均衡。注意如果你后续换用NGSIM这类公开数据先确认坐标单位。NGSIM原始坐标是英尺要乘0.3048换算成米再归一化。单位不一致时后续ADE/FDE指标会成倍偏移和论文结果完全没法比这是轨迹预测项目里最隐蔽的单位坑。3. LSTM轨迹预测模型网络结构、训练与推理配置数据准备到位后核心就是模型部分。这套资源用的是两层LSTM加一个全连接输出层输入历史8帧坐标输出未来6帧坐标。结构不算复杂但作为课程设计或期末作业已经能拿到很扎实的指标而且代码量小、答辩时容易讲清楚每个模块的作用。3.1 模型定义与维度设计LSTM神经网络处理轨迹预测的标准做法观测序列走LSTM编码取最后一个时间步的隐藏状态作为整段历史轨迹的浓缩特征再接全连接层一次性输出未来所有坐标点。import torch import torch.nn as nn class TrajectoryLSTM(nn.Module): def __init__(self, input_dim2, hidden_dim128, num_layers2, pred_len6): super().__init__() self.pred_len pred_len self.lstm nn.LSTM( input_dim, hidden_dim, num_layers, batch_firstTrue, dropout0.2 ) self.fc nn.Linear(hidden_dim, pred_len * 2) def forward(self, x): # x: (batch, obs_len, 2) out, (h_n, c_n) self.lstm(x) last_hidden h_n[-1] # last_hidden: (batch, hidden_dim) pred self.fc(last_hidden) # pred: (batch, pred_len * 2) pred pred.view(-1, self.pred_len, 2) return predforward里的逻辑可以拆成三步看。第一步把观测序列x传入LSTMPyTorch返回的out是每个时间步的隐藏状态序列形状(batch, obs_len, hidden_dim)h_n是最后一层在最后一个时间步的隐藏状态形状(num_layers, batch, hidden_dim)。第二步取h_n[-1]也就是最后一层LSTM的最终输出这一步是整个模型的关键假设LSTM的隐藏状态已经足够浓缩车辆在观测窗口内的运动模式。第三步全连接层把hidden_dim维向量映射成pred_len*2个值对应未来每个时间步的x和y坐标view再拆成(batch, pred_len, 2)。input_dim2表示输入只有x、y坐标。如果数据集里有速度、加速度字段把input_dim改成4喂入的每个时间步向量拼上对应字段模型通常会收敛更快预测高速变道这种工况时优势更明显。hidden_dim128适合几千条样本的中小数据集样本量上万可以加到256。num_layers2是经过验证的折中层数再多反而容易在小数据集上过拟合。3.2 损失函数与优化器配置轨迹预测最常用的损失函数是MSE也就是所有预测时间步上坐标差的平方均值。MSE对误差大的点更敏感模型会优先纠正偏差最大的那一两个时间步。criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3)MSE有个缺点对离群点过于敏感。如果数据集里存在坐标跳变的异常轨迹训练loss会被少数几个离群点主导。遇到这种数据把损失函数换成SmoothL1Loss更稳criterion nn.SmoothL1Loss(beta1.0)beta1.0表示误差小于1时使用平方误差大于1时退化为线性误差对离群点更温和。切换只需要改一行验证集指标往往比MSE更平滑。优化器选Adamlr1e-3是稳妥起点。训练集样本只有几千条时这个学习率跑30到50个epoch足够收敛。如果loss震荡不降先把lr降到5e-4做对比实验。3.3 训练循环与模型保存策略训练环境上PyTorch的GPU版和CPU版都能跑这份代码。数据集不大CPU训练也就几分钟到十几分钟GPU只是更快不影响结果。关键在训练循环本身有几个细节值得注意。def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, total_num 0.0, 0 for x, y in loader: x, y x.to(device), y.to(device) optimizer.zero_grad() pred model(x) loss criterion(pred, y) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() * x.size(0) total_num x.size(0) return total_loss / total_num每个batch先清零梯度前向传播算预测值MSE算loss反向传播后做一次梯度裁剪再更新参数。total_loss累计时乘上batch大小最后除以总样本数得到按样本加权的平均loss。梯度裁剪这行很多人会漏但对LSTM这类序列模型来说反向传播路径长梯度范数容易突然暴增表现为loss曲线跳崖式升高。clip_grad_norm_把梯度范数限制在1.0以内就是防这一手。loss稳定时去掉也行留着一版对结果影响很小。主循环与模型保存best_val float(inf) for epoch in range(50): train_loss train_one_epoch(model, train_loader, optimizer, criterion, device) val_loss evaluate(model, val_loader, criterion, device) print(fepoch{epoch1:03d} train_loss{train_loss:.5f} val_loss{val_loss:.5f}) if val_loss best_val: best_val val_loss torch.save(model.state_dict(), best_model.pth)保存策略是看验证集loss不是训练集loss。验证loss最低的那一轮参数通常泛化性最好。训练完再用测试集跑一次评估拿测试集结果作为最终指标不要拿验证集结果顶替。加载测试时有个高频错误容易犯model TrajectoryLSTM(input_dim2, hidden_dim128, num_layers2, pred_len6) model.load_state_dict(torch.load(best_model.pth)) model.eval()eval()会把模型切到推理模式dropout和batchnorm的行为随之变化。不写这一行推理结果每跑一遍都可能不一样这是很多人复现结果对不上的原因之一。3.4 推理与反归一化训练完成后的推理流程数据路径必须和训练时完全一致包括归一化、滑窗、维度排列。with torch.no_grad(): obs_tensor torch.FloatTensor(obs[:8]).unsqueeze(0) # obs_tensor: (1, 8, 2) pred_norm model(obs_tensor).squeeze(0).numpy() # pred_norm: (6, 2) pred_real np.zeros_like(pred_norm) pred_real[:, 0] scaler_x.inverse_transform(pred_norm[:, 0].reshape(-1, 1)).ravel() pred_real[:, 1] scaler_y.inverse_transform(pred_norm[:, 1].reshape(-1, 1)).ravel()模型输出的是归一化坐标画图、算误差指标之前必须用训练时那套scaler反变换回真实坐标。这里最坑的地方是scaler必须是训练时fit的同一个对象或持久化保存的对象不能在推理脚本里重新fit一次否则统计量不同反归一化结果整体偏移。4. 避坑手册轨迹预测实战最容易翻车的五个场景这一章写的是我在做轨迹预测项目时真实踩过的坑每一个都让结果出过问题。按照现象、原因、解决的思路整理你在复现时如果遇到类似情况直接对照排查。4.1 顺序没排序滑窗切出假连续序列现象训练loss能正常下降验证集指标也还能看但可视化预测轨迹时画出来的点序列在空间里乱跳完全不像一辆车的行驶路径。原因CSV原始数据不按时间排序滑窗前没有对每个车辆ID单独排序。groupby取出来的坐标序列是乱序的滑窗切出来的连续帧实际混杂了不同时刻甚至不同车道的点。解决排序必须写在滑窗函数内部每次调用都执行不给遗漏机会。我在2.2节代码里就在groupby之后先sort_values(frame_id)再取坐标。这条看似简单实际是轨迹预测项目里最隐蔽的翻车点。4.2 数据泄漏测试集的未来被训练集提前看到现象训练集和验证集指标都不错但进一步分析发现模型预测轨迹基本是复读最后观测位置没有体现出车辆的速度趋势和变道意图。指标虚高模型实际没学到东西。原因切分训练/测试时用了随机按行打乱同一辆车的轨迹前后帧被分到两个集合。模型在训练时已经见过测试样本的历史和未来测试指标自然好看但模型并没有真正的泛化能力。解决切分必须以vehicle_id为粒度同一辆车的所有帧只能归属一个集合。用70/15/15比例切好之后再开始归一化和滑窗。这条是时序任务的红线不守住后面所有评估都没有意义。4.3 反归一化不一致预测结果整体漂移现象模型输出的坐标范围在0到1之间inverse_transform之后某个轴还是0到1另一个轴变成了几百预测轨迹和真实轨迹错位明显。原因反归一化时把x和y的scaler搞反了或者加载了一个重新fit过的scaler或者只对部分坐标做了反变换。训练和推理环节的统计量不一致结果必然错位。解决把scaler和模型一起持久化保存。用python的pickle模块在训练结束时把scaler_x、scaler_y存成一个pkl文件推理脚本里load这个文件绝不重新fit。从那以后我每次都强制走这条流程再没出过反归一化错位。4.4 异常轨迹没过滤loss出现NaN现象训练到某个epoch后loss突然变成nan之后再也回不来重启训练也没用。原因数据集中存在坐标跳变或重复帧的异常轨迹。归一化时某些坐标接近0方差产生极大数值LSTM的反向传播梯度在数值上溢出权重被破坏。解决滑窗前加一道过滤逻辑。轨迹长度不足obs_len pred_len的车辆直接丢弃再用diff检测帧间坐标跳变超过阈值就剔除整条轨迹。for veh_id, group in df.groupby(vehicle_id): if len(group) obs_len pred_len: df df[df[vehicle_id] ! veh_id] continue diff_x group[x].diff().abs().max() diff_y group[y].diff().abs().max() if diff_x 5.0 or diff_y 5.0: df df[df[vehicle_id] ! veh_id]阈值5.0按坐标单位调整0.1秒帧间隔下车辆帧间位移超过5米基本就是异常点。这步过滤放预处理最前面能省掉后面大量排查时间。4.5 ADE/FDE指标算错结果对比全乱套现象模型跑通后输出的ADE/FDE数值和论文里同量级的数字差好几倍或者FDE的曲线变化和训练loss趋势对不上。原因坐标单位不一致或者FDE的定义理解有偏差。FDE只取预测终点那一个点与真实终点的距离被当成所有预测点的平均误差是常见错误。解决先确认坐标单位统一成米。再按标准定义计算ADE是预测轨迹所有点的平均位移误差FDE是最后一帧的位移误差。具体实现放在5.1节两个指标共用一套函数避免重复代码造成偏差。5. 评估与调优从能跑通到预测得更准模型能跑只是第一步。课程设计和毕业设计的评分差距往往体现在指标是否规范、有没有对比实验、能不能解释清调参过程。这一章把评估和调优串起来讲。5.1 ADE与FDE轨迹预测的两个核心指标轨迹预测领域最常用的两个指标是ADE和FDE。ADE是Average Displacement Error预测轨迹上所有时间步与真实轨迹的平均欧氏距离FDE是Final Displacement Error只取最后一个预测点与真实终点的欧氏距离。两者配合使用ADE衡量整体贴合度FDE衡量终点准确性。def compute_ade_fde(y_true, y_pred): # y_true, y_pred: (batch, pred_len, 2) dist torch.sqrt(((y_true - y_pred) ** 2).sum(dim-1)) ade dist.mean().item() fde dist[:, -1].mean().item() return ade, fdedist的形状是(batch, pred_len)每个样本每个预测时刻的位移误差。ade对dist全部元素求平均fde只取最后一列再求平均。注意输入的单位必须已经是米且是反归一化后的真实坐标。这套计算逻辑在所有实验里保持一致对比才有意义。测试集评估的完整流程加载saved模型遍历测试DataLoader每个batch算pred和y_true一起传进compute_ade_fde最后汇总所有batch的均值和标准差。标准差也值得报出来答辩时如果老师问为什么有的轨迹预测好有的差标准差能帮你解释误差分布。5.2 超参调优与消融对比调参要有方向不能靠玄学。下面这张表是我在类似规模数据集上比较常用的参数起点和调整方向参数初始值调优方向说明obs_len86/10/12观测窗口长度过短缺上下文过长引入噪声pred_len65/8/12由题目要求的预测时长决定hidden_dim12864/256样本量小时用小维度防过拟合num_layers21/31层快但表达能力弱3层易过拟合lr1e-35e-4/1e-4loss震荡时优先降lrbatch_size6432/128小数据集用32更稳dropout0.20.1/0.3过拟合时加大欠拟合时减小消融实验是答辩加分项。课程设计不要求做完整消融但至少做一个baseline对比恒速模型加上LSTM。恒速模型用最后两个观测点算速度匀速外推未来位置完全不依赖神经网络。对比它和LSTM的ADE/FDE能直观说明LSTM学到了什么。def constant_velocity_baseline(obs_coords, pred_len): # obs_coords: (obs_len, 2) v obs_coords[-1] - obs_coords[-2] start obs_coords[-1] future [start v * (i 1) for i in range(pred_len)] return np.asarray(future)把这条基线跑在同一个测试集上通常恒速模型的FDE不会太差但ADE会被LSTM明显拉开尤其在有变道和加减速的片段里。这个对比结果放进答辩PPT比任何文字解释都有力。5.3 把训练过程记录下来科学调参的基建调参最忌讳凭记忆。每个实验跑完记录一组参数和两个指标坚持几轮之后你才能看出规律。我一般用CSV记录结构简单Python直接写import csv with open(experiments.csv, a, newline) as f: writer csv.writer(f) writer.writerow([epoch, hidden_dim, num_layers, lr, batch_size, ade, fde])每次实验追加一行累积20条之后扫一眼表格哪些参数组合在ADE上有优势一目了然。这也避免了同一个参数组合重复跑的问题。答辩时如果被问到怎么确定的最终参数直接把CSV记录展示出来说是做了这些对比实验后选的比空口说我试了试觉得这个好要可信得多。6. 轨迹可视化与答辩演示让结果自己说话模型调完最后一步是把效果可视化。一张预测轨迹对比图比任何指标数字都直观。用matplotlib画三条线历史轨迹、真实未来轨迹、LSTM预测未来轨迹。import matplotlib.pyplot as plt obs np.array(history_coords) # (8, 2) true np.array(true_coords) # (6, 2) pred np.array(pred_coords) # (6, 2) plt.figure(figsize(8, 6)) plt.plot(obs[:, 0], obs[:, 1], b-o, label历史轨迹) plt.plot(true[:, 0], true[:, 1], g--s, label真实未来轨迹) plt.plot(pred[:, 0], pred[:, 1], r-^, labelLSTM预测轨迹) plt.legend() plt.xlabel(x / m) plt.ylabel(y / m) plt.title(LSTM轨迹预测效果) plt.savefig(prediction_result.png, dpi150)画图前确认坐标是反归一化后的真实坐标否则图里数值范围对不上。随机抽3条测试集轨迹一条直线行驶、一条变道、一条加减速有代表性答辩时讲起来更有说服力。答辩PPT我建议放三张核心图第一张是训练loss曲线证明收敛过程干净第二张是3条轨迹对比图展示预测效果第三张是ADE/FDE对比表放恒速基线和LSTM的数据。三张图配一个简短的文字说明项目的完整度和可信度就立起来了。这套源码和数据集我整理在同一个压缩包里解压后按第2章的预处理顺序走一遍再到第3章跑训练最后的可视化和指标输出都是现成脚本。我自己做轨迹预测项目的习惯是每次跑完训练强制把模型权重、scaler、实验结果图和CSV记录统一归档命名带日期。答辩前再完整跑一遍测试流程确认环境没变、结果可复现。这些血泪经验总结成的流程建议你也直接沿用。希望帮到你。本文还有配套的精品资源点击获取