
简介这份完整代码.zip是一份基于深度学习的轨迹预测项目源码面向Python开发者、数据科学学习者及相关方向的研究者适合用于理解循环神经网络在时序预测任务中的落地实现。压缩包共11个文件约723KB包含7个py脚本、LSTM与GRU模型文件、checkpoint训练状态文件、CSV格式轨迹数据以及TensorFlow框架下的索引与数据分块文件覆盖数据读取、模型构建、训练保存、对比评估等完整环节。项目实现了LSTM和GRU两种序列模型并附带逻辑回归作为基线对比实验便于观察传统机器学习与深度学习在轨迹预测上的效果差异checkpoint文件支持断点续训与模型复用data_read.py和轨迹.py分别负责数据预处理与轨迹特征提取metra.py等自定义模块则承担特定训练或分析功能相关代码可直接迁移到类似的时序预测场景。资源体积小巧但结构完整通过阅读源码可学习模型保存加载、实验对比设计等实用技巧。已有230人学习下载适合希望系统掌握序列模型建模流程、或需要参考完整项目结构的开发者。1. 轨迹预测一份能直接跑起来的完整工程代码做LBS、物流调度或自动驾驶仿真的开发者手头大概率攒过一批历史GPS轨迹。可当你想让程序根据已有轨迹预测车辆或行人下一个坐标时会发现论文里的算法离工程落地还有十万八千里数据要清洗、序列要切窗、模型要训练、结果要可视化每一步都藏着坑。这份轨迹预测代码包把整条链路串好了从原始坐标日志到预测结果输出一步到位装好依赖就能跑通训练和推理。它适合想在项目里快速落地轨迹预测能力的人也适合拿来当深度时序预测的入门模板二开起来不费劲。2. 架构设计与技术选型从原始坐标到预测结果2.1 技术栈为什么是 PyTorch 加 LSTM轨迹预测本质上是时序预测输入是一串按时间排序的坐标点输出是未来若干个时刻的位置。市面上的主流做法无非是 LSTM、Transformer 或者图神经网络但这份代码包选了一条最稳的路——LSTM。原因很直接LSTM 对中等长度的序列预测足够有效训练成本低而且换到 CPU 机器上也能跑不像 Transformer 那样需要堆算力。整套代码基于 Python 3.8 和 PyTorch 2.x 实现数据侧只用到了 Pandas 和 NumPy预处理部分用了 GeoPy 做坐标距离计算没有引入任何重量级依赖。选 PyTorch 而不是 TensorFlow主要看重两点一是动态图机制调试方便模型结构改了立刻能看到效果二是生态里现成的序列模型组件多后面想换 Transformer 或者加注意力机制改动成本很小。代码包里的模型定义、训练循环、推理逻辑全部解耦任何一个环节都能单独替换。这个设计对二次开发很友好我后面会详细说怎么换模型。2.2 目录结构代码包拆开看拿到压缩包之后先别急着跑花五分钟把目录结构过一遍。这套代码的目录划分是按「数据 → 模型 → 训练 → 推理」这条主线来的trajectory_prediction/ ├── config/ │ └── config.yaml # 全局配置超参数、路径、设备选择 ├── data/ │ ├── raw/ # 原始 GPS 日志放这里 │ ├── processed/ # 预处理后的训练样本 │ └── split/ # 训练集 / 验证集 / 测试集 ├── src/ │ ├── preprocess.py # 漂移点过滤、轨迹压缩、滑窗切分 │ ├── dataset.py # Dataset 封装和数据加载器 │ ├── model.py # LSTM 模型定义 │ ├── train.py # 训练入口日志、checkpoint、早停 │ ├── evaluate.py # 评估指标计算ADE / FDE │ └── predict.py # 推理脚本加载模型输出预测轨迹 ├── scripts/ │ └── run.sh # 一键训练到推理的脚本 └── requirements.txt每个模块的职责很清晰preprocess.py负责把原始日志变成干净的序列样本dataset.py负责在训练时按批次喂数据model.py定义网络结构train.py控制训练流程。配置文件独立成config.yaml所有超参数集中在里面改参数不用动代码。这个分层方式适合大多数中小型预测项目照着抄都不会翻车。2.3 数据流转链路一个样本从进入到返回的完整路径理解这套代码最有效的方式是追踪一条原始轨迹在系统里是怎么流转的。原始数据是典型的 CSV 格式每一行是一个 GPS 定位点包含设备编号、时间戳、经度、纬度。加载之后先做清洗剔除掉速度突变和坐标越界的漂移点接着按设备编号和时间连续性把长轨迹切分成互不重叠的短轨迹最后用滑窗把每条短轨迹切成固定长度的序列前 20 个点做输入后 10 个点做预测目标。# config.yaml 中的关键配置 input_seq_len: 20 # 输入序列长度用前 20 个点做预测 pred_seq_len: 10 # 预测序列长度输出后 10 个点的位置 trajectory_gap: 30 # 相邻点最大时间间隔秒超过则断开轨迹 min_trajectory_len: 30 # 最短轨迹长度太短的直接丢弃 batch_size: 128这三个长度参数是整个预处理的核心。input_seq_len和pred_seq_len决定模型的输入输出维度改这两个值会直接影响预测效果序列太短模型学不到运动趋势太长又会引入大量噪声。trajectory_gap是轨迹切分的依据——如果两个相邻点之间的时间间隔超过 30 秒说明设备可能关机或者信号丢了这时候硬把两段拼在一起会让模型学到错误的时序关系。这份配置是经过验证的默认值直接拿来用没问题按自己业务数据的采样频率调整即可。3. 核心模块拆解数据预处理与轨迹特征工程3.1 漂移点过滤先把脏数据洗干净GPS 日志里最常见的脏数据是漂移点某个点突然跳到几百米外速度算出来高达两三百公里每小时这明显是定位噪声而不是真实运动。如果不过滤这种点会作为输入喂给模型预测结果直接偏到天上去。代码包里用的过滤策略叫「速度阈值 中值滤波」两层把关# src/preprocess.py 片段 import numpy as np import pandas as pd from geopy.distance import geodesic def filter_drift_points(df, max_speed_kmh150.0): # 计算相邻点之间的距离米和时间差秒 df df.sort_values([device_id, timestamp]) coords list(zip(df[latitude], df[longitude])) distances [0.0] [ geodesic(coords[i], coords[i-1]).meters for i in range(1, len(coords)) ] time_diffs df[timestamp].diff().dt.total_seconds().fillna(0).tolist() # 速度 距离 / 时间超过阈值的点标记为漂移点 speeds [ d / t if t 0 else 0.0 for d, t in zip(distances, time_diffs) ] df[speed] speeds df[is_drift] df[speed] max_speed_kmh # 第二步对漂移点做中值滤波修正而不是直接删除 # 直接删会打断轨迹连续性用前后 5 个点的中位数替代更稳妥 for idx in df.index[df[is_drift]]: window df[speed].iloc[max(0, idx-5): idx5] df.loc[idx, [latitude, longitude]] ( df.loc[window.index[window max_speed_kmh], [latitude, longitude]].median() ) return df这段代码做了两件事先判断每个点是不是漂移点再对漂移点做修正而非删除。很多人第一次处理 GPS 数据时习惯把异常点直接删掉这是个常见的坑——删点会让轨迹的时间间隔变得不均匀后续滑窗切分时对不齐。用中值滤波把漂移点拉回正常位置既能保留原始轨迹的时序结构又不会让极端值污染序列。速度阈值 150km/h 是保守值普通场景下车辆和行人都远低于这个速度如果做无人机或高铁轨迹预测这个阈值要往上调到 300 以上。3.2 轨迹切分与压缩把连续轨迹变成训练样本清洗干净之后原始轨迹可能长达几千个点。直接用整条长轨迹做训练一方面太长的序列会让 LSTM 捕捉不到早期信息另一方面不同轨迹长度不一致也无法批量训练。代码包的策略是两步走先用时间间隔切分再用滑动窗口截取固定长度。# src/preprocess.py 片段 def split_trajectories(df, time_gap_sec30, min_len30): trajectories [] for device_id, group in df.groupby(device_id): group group.sort_values(timestamp) # 时间差超过阈值的位置作为切分点 group[gap] group[timestamp].diff().dt.total_seconds() segment_id (group[gap] time_gap_sec).cumsum() for seg in group.groupby(segment_id): if len(seg[1]) min_len: trajectories.append(seg[1]) return trajectories def build_sliding_windows(trajectories, input_len20, pred_len10): samples [] for traj in trajectories: coords traj[[latitude, longitude]].values for i in range(len(coords) - input_len - pred_len): x coords[i : i input_len] # 输入历史 20 个点 y coords[i input_len : i input_len pred_len] # 目标未来 10 个点 samples.append((x, y)) return samplessplit_trajectories返回的是切分后的轨迹列表build_sliding_windows再把每条轨迹切成输入-标签对。这里有个容易被忽略的细节滑窗的步长是 1意味着相邻两个样本之间有 19 个点是重叠的。这样生成的样本量会非常大一条 500 点的轨迹能切出 470 个样本但缺点是相邻样本高度相似模型容易过拟合。后来我在自己项目里把步长改成了 3样本相关性降了不少训练也更稳定。代码包里保留的是步长为 1 的默认实现想改的话在build_sliding_windows的参数里加一个step就行。3.3 坐标归一化为什么不能把经纬度直接喂进去经纬度是度数坐标经度 1 度的实际距离在不同纬度下差别很大直接把经纬度数值丢给 LSTM 会让模型学到虚假的刻度关系。代码包的做法是以每条轨迹的起点为原点做局部坐标系转换把经度和纬度转成相对起点的米制偏移量。这样既去了量纲又保留了轨迹的局部几何形状。# 归一化以轨迹起点为原点转换为相对偏移 def normalize_coordinates(traj_coords): # traj_coords: shape (seq_len, 2)列顺序为 [lat, lng] origin_lat, origin_lng traj_coords[0] normalized [] for lat, lng in traj_coords: dx geodesic((origin_lat, origin_lng), (origin_lat, lng)).meters dy geodesic((origin_lat, origin_lng), (lat, origin_lng)).meters # 根据相对方向给 dx/dy 加上正负号 if lng origin_lng: dx -dx if lat origin_lat: dy -dy normalized.append([dy, dx]) return np.array(normalized)这段归一化代码把纬度方向作为 y 轴、经度方向作为 x 轴算出的坐标是以米为单位的局部平面坐标轨迹的形状和方向都保留住了。注意代码里的geodesic函数是测地线距离比平面欧氏距离更准在低纬度地区两者差别不大但高纬度地区经度方向的差距很明显。这属于那种「不踩不会注意踩了才发现」的坑我第一次直接用经纬度训练模型在测试集上的 ADE 比别人公开的结果差了一大截换成局部归一化之后指标立刻正常了。4. 核心模块拆解LSTM 轨迹预测模型与训练指标4.1 模型结构从坐标序列到未来轨迹的映射模型结构走的是经典的 Encoder-Decoder 路线编码器用 LSTM 把历史轨迹编码成隐状态解码器用另一个 LSTM 逐步生成未来点。输入维度是 2局部坐标 x/y隐藏层维度是 128两层堆叠输出层接一个全连接把隐状态映射回二维坐标。# src/model.py 完整定义 import torch import torch.nn as nn class TrajectoryLSTM(nn.Module): def __init__(self, input_dim2, hidden_dim128, num_layers2, pred_len10): super().__init__() self.pred_len pred_len self.encoder nn.LSTM( input_sizeinput_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue ) self.decoder nn.LSTM( input_sizeinput_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue ) self.fc_out nn.Linear(hidden_dim, input_dim) def forward(self, src): # src: (batch, seq_len, 2)历史轨迹坐标 batch_size src.size(0) _, (hidden, cell) self.encoder(src) # 用编码器的最终隐状态作为解码器初始状态 decoder_input src[:, -1, :].unsqueeze(1) # (batch, 1, 2) outputs [] for t in range(self.pred_len): out, (hidden, cell) self.decoder(decoder_input, (hidden, cell)) coord self.fc_out(out) # (batch, 1, 2) outputs.append(coord) decoder_input coord # 把上一步输出喂回解码器 return torch.cat(outputs, dim1) # (batch, pred_len, 2)这个模型的核心思想是自回归生成每一步预测出的坐标点会作为下一步的输入模型在预测过程中能不断修正方向。要注意batch_firstTrue所以输入张量的形状是(batch, seq_len, 2)训练时千万不能把维度顺序搞错。hidden_dim128是经验值序列长度在 20 左右时这个规模足够如果你的业务序列更长或者要预测的点更多可以加到 256但训练时间也会相应拉长。解码器的初始隐状态直接复用编码器最后一步的输出这是一种常见的时序预测做法相当于把历史轨迹的语义压缩进了隐状态。缺点是预测距离越远误差越大因为后续每一步的输入都依赖前面的预测结果误差会累积。这个现象在轨迹预测里叫「误差传播」后面我会讲怎么缓解。4.2 损失函数预测坐标的误差怎么算模型输出的是经纬度对应的局部坐标损失函数直接算预测点和真实点的欧氏距离均方MSE是最简单的做法。但这里有个细节预测点在局部坐标系里而局部坐标系是相对每条轨迹起点建立的不同轨迹的起点不同直接算 MSE 时的数值范围是一致的。所以代码包用的是经过验证的加权 MSE——对预测序列中越靠后的点赋予更大的权重因为靠后的点误差天然更大不加权重模型会只顾着优化前几个点。# src/train.py 中的损失计算 def masked_mse_loss(pred, target, decay0.95): # pred / target: (batch, pred_len, 2) # 距离权重从 0.5 开始逐点衰减权重 weights torch.tensor( [0.5 * (decay ** t) for t in range(pred.size(1))], devicepred.device ).view(1, -1, 1) loss (pred - target) ** 2 * weights return loss.mean()这个权重设计值得解释一下。轨迹预测中第 1 个点的预测误差通常远小于第 10 个点的误差如果所有点权重相同大部分梯度会被远期误差主导模型会过度调整去拟合那些本身难以预测的点。按时间衰减分配权重后模型会优先学稳近期的运动模式再逐步优化远期预测。decay0.95意味着每往后一个点权重乘 0.95衰减幅度不大但也足够平衡。这是个细微但有效的工程技巧。4.3 评估指标ADE 和 FDE 到底在算什么模型训练完要看效果轨迹预测领域最常用的两个指标是 ADEAverage Displacement Error和 FDEFinal Displacement Error。ADE 是所有预测点与真实点之间距离的平均值衡量整体预测精度FDE 是最后一个预测点与真实终点的距离衡量最终到达位置的准确性。这两个指标在代码包中是一个独立的评估模块# src/evaluate.py 片段 import numpy as np def compute_ade_fde(pred, target): # pred / target: (num_samples, pred_len, 2)单位是米 diff pred - target distances np.linalg.norm(diff, axis2) # (num_samples, pred_len) ade np.mean(distances) fde np.mean(distances[:, -1]) return ade, fde这两个指标的数值可以直接对照论文结果但注意单位是米。如果你的业务场景更在意预测终点的准确性比如外卖配送预测送达位置就重点看 FDE如果做自动驾驶的轨迹规划每一步都不能偏太多那 ADE 更重要。评估脚本会把每个测试样本的预测轨迹和真实轨迹画在同一张图上方便直观检查模型在拐弯、直行等不同场景下的表现。5. 训练与推理把代码包跑起来5.1 环境准备与依赖安装先把依赖装好。代码包附带的requirements.txt里列出了全部依赖核心就四个torch、pandas、numpy、geopy。PyTorch 的安装注意匹配自己的 CUDA 版本如果只用 CPU 跑直接装 CPU 版就行——这套代码在 CPU 上训练两三万条样本也就几分钟不需要刻意上 GPU。cd trajectory_prediction pip install -r requirements.txt # 验证安装 python -c import torch, pandas, numpy, geopy; print(deps ok)5.2 准备数据与启动训练数据目录data/raw下放原始 GPS 日志格式是 CSV至少包含device_id、timestamp、latitude、longitude四列。时间戳格式最好是标准 ISO 格式预处理脚本会自动解析。准备好之后依次执行# 第一步预处理生成训练样本并切分数据集 python src/preprocess.py --config config/config.yaml # 第二步启动训练 python src/train.py --config config/config.yaml训练脚本会打印每个 epoch 的 ADE 和 FDE默认训练 50 个 epoch每 5 个 epoch 做一次验证。验证集上的 ADE 低于 20 米基本说明模型学出了目标的运动趋势。训练完成后模型权重会自动保存到data/processed/checkpoint_best.pth。5.3 推理与结果可视化预测脚本接收一段历史轨迹输出未来 10 个预测点# 预测并可视化 python src/predict.py \ --checkpoint data/processed/checkpoint_best.pth \ --input 39.9042,116.4074;39.9142,116.4174;... \ --output result.png可视化结果会把历史轨迹、真实未来轨迹和预测轨迹三条线画在同一张图上一眼就能看出预测偏离了多少。调试时推荐先跑这一步因为指标只能告诉你误差数值而图能告诉你误差产生的位置——是拐弯处偏了还是直行时速度估计不准。我第一次跑通时看到预测轨迹整体比真实轨迹短了一截后来才发现是因为训练数据里慢速点占多数模型学到了偏保守的速度估计。5.4 常见问题与排查训练和推理过程中有几个高频问题都是我在实际跑这套代码时踩过的列出来省得你再走一遍弯路。现象一预处理后样本量为 0。原因多半是原始数据的timestamp格式不对或者min_trajectory_len设得太大轨迹全被过滤掉了。解决先打印原始数据的行数、设备数再逐步调大trajectory_gap或调小min_trajectory_len确认切分逻辑生效。现象二损失下降但 ADE 不降甚至验证集 ADE 比训练集高很多。这是典型的过拟合信号。原因在于滑窗步长为 1相邻样本高度重合相当于用相似的样本反复训练。解决把滑窗步长改为 3 或 5同时把hidden_dim从 128 降到 64减少模型参数量。现象三预测轨迹基本不动近乎原地踏步。这个坑最玄学。原因是 LSTM 学到的是输出平均值——当训练数据里直行和折返混合时模型的「最安全策略」是预测一个中间位置。解决检查训练集是否存在严重的类别不平衡比如大量低速短轨迹可以按轨迹的位移距离过滤掉总长太短的样本或者把速度特征加入输入。现象四加载 checkpoint 时维度不匹配报错。多半是改了config.yaml里的input_seq_len或pred_seq_len但模型结构还是旧的。解决改任何序列长度参数后都要重新训练或者保证加载的 checkpoint 对应的模型配置与当前一致。这是改配置最容易翻车的地方强烈建议训练时把完整配置写入 checkpoint 文件加载时自动校验。6. 二次开发把轨迹预测改成你的业务拿到这套代码最值得做的改造有三件事按优先级排序接入真实业务数据源、替换模型结构、扩展预测输出目标。第一步把数据加载部分从 CSV 文件改成你的数据库或消息队列。dataset.py里TrajectoryDataset类的__init__接受的是文件路径你只需要让这个类改从数据库接口读数据保持返回的样本格式(history, future)不变就行。数据处理管道不用动改动集中在数据入口。第二步换模型。如果你想试试 Transformer 或注意力机制只需要继承TrajectoryLSTM的接口实现一个类forward接收(batch, seq_len, 2)的张量返回(batch, pred_len, 2)。训练和评估代码都不用改。代码包里train.py是通过import引用的模型类替换model.py里的定义即可。我后来在自己的项目里用 Transformer 替换了 LSTM序列长度从 20 加到 50 之后注意力机制的优势才体现出来——长序列下预测误差明显更稳定。第三步扩展预测目标。目前模型只输出坐标。如果你的业务需要预测到达时间ETA可以在preprocess.py里额外构建每个样本对应的耗时标签在模型输出层接一个分支网络同步输出坐标和时间。改造方法是在TrajectoryLSTM.forward的最后把fc_out的维度从 2 扩到 3用最后一个隐状态再过一层全连接输出时间值。训练时损失函数变成坐标损失和时间损失的加权和。从那以后我每次接轨迹预测项目都强制自己先跑一遍完整的 preprocess → train → predict 链路再动手改业务。手里的这套代码就像一份底稿不管换什么数据、换什么模型只要保持接口不变改造永远是局部替换而不是全局推倒。希望这份解析能帮你少走几步弯路跑通它再改成你自己的形状。本文还有配套的精品资源点击获取