简介这份资源面向深度学习入门与进阶学习者尤其是需要完成人工智能、数据挖掘类期末大作业或课程设计的高校学生。项目以纽约出租车流量预测为赛题场景提供从数据处理、模型搭建到训练评估的完整Python实现帮助读者理解时序预测任务的工程落地流程。压缩包共31个文件约1.22MB包含9个py源码文件、7个pyc缓存、6个xml配置、3张png训练指标图、2个npz数据集及说明文档等覆盖模型定义、数据加载与可视化模块。源码均经本地编译验证可运行评审分达95分以上难度适中内容经助教老师审定。读者可获得可直接复用的项目结构、CNN-GRU与CNN-LSTM等对比模型实现、训练曲线图及数据说明文档便于快速复现实验并撰写报告。目前已有249人学习下载适合作为课程设计参考与深度学习实践练手素材。1. 纽约出租车流量预测从95分大作业到能跑通的工程链路纽约出租车流量预测这个题目在深度学习课程大作业里出现的频率极高。它天然具备三个优势数据公开、时序特征明显、评价指标清晰。但真正动手做过的人都知道从原始CSV到一张能看的预测曲线中间隔着的不是模型结构而是数据清洗、时间粒度对齐、空间划分和特征工程这四道坎。我见过太多人直接拿LSTM硬套结果RMSE比均值预测还差。这篇笔记面向两类人一是正在做这个作业、想拿高分的学生二是想用这套流程练手时序预测的工程师。我会把数据预处理、模型选型、训练调参和避坑经验按可复现的顺序讲清楚代码基于Python和PyTorch数据集用NYC TLC公开的出租车行程记录。读完你应该能独立跑通一条从原始数据到预测结果的完整链路并且知道每个参数为什么这么设。2. 数据准备纽约出租车数据集的清洗与时间粒度对齐2.1 原始数据长什么样为什么不能直接喂给模型NYC TLC的出租车数据按月发布单月Parquet文件通常在几百万行量级字段包括上下车时间、上下车经纬度、乘客数、行程距离、费率类型等。直接读进来会发现几个致命问题时间范围跨月甚至跨年、经纬度有大量异常值比如0,0或者超出纽约边界、部分记录行程距离为负或为零。更关键的是流量预测需要的是「每个时间片内某个区域的订单数」而不是单条行程记录。所以第一步必须做聚合把行程级数据转成「时间片×区域」的流量矩阵。常见做法是选一个时间粒度比如15分钟或30分钟再把纽约市按经纬度网格划分成若干区域。网格太细会导致大量零值太粗则失去空间区分度。我一般先用0.01度经纬度网格试大约对应1公里左右然后根据零值比例调整。import pandas as pd import numpy as np # 读取单月数据只保留必要列 df pd.read_parquet(yellow_tripdata_2023-01.parquet, columns[tpep_pickup_datetime, pickup_longitude, pickup_latitude, passenger_count]) # 过滤异常经纬度纽约市大致范围 df df[(df[pickup_longitude].between(-74.05, -73.75)) (df[pickup_latitude].between(40.60, 40.90))] # 时间片对齐到15分钟 df[time_bin] df[tpep_pickup_datetime].dt.floor(15min) # 经纬度网格化0.01度一格 df[grid_x] ((df[pickup_longitude] 74.05) / 0.01).astype(int) df[grid_y] ((df[pickup_latitude] - 40.60) / 0.01).astype(int) # 聚合为流量矩阵 flow df.groupby([time_bin, grid_x, grid_y]).size().reset_index(namecount) print(flow.head()) print(时间片数量:, flow[time_bin].nunique()) print(网格数量:, flow[[grid_x, grid_y]].drop_duplicates().shape[0])这段代码做了三件事过滤异常坐标、把上车时间对齐到15分钟边界、把经纬度映射到整数网格。floor(15min)保证同一时间片内的记录被归到一起astype(int)做的是向下取整所以网格边界是左闭右开。聚合后的flow表每一行代表「某个15分钟片内某个网格的订单数」。如果零值比例超过70%说明网格太细可以把0.01改成0.02再试。2.2 构建时间序列样本滑动窗口与训练集划分聚合完只是有了流量矩阵模型需要的是「用过去N个时间片预测未来M个时间片」这样的样本对。这里有两个参数要定输入窗口长度和预测步长。对于15分钟粒度我一般用过去8个片2小时预测未来1个片15分钟或者过去12个片预测未来4个片。窗口太短抓不到周期性太长则引入噪声且增加计算量。# 把流量矩阵转成 时间片 × 网格 的二维数组 pivot flow.pivot_table(indextime_bin, columns[grid_x, grid_y], valuescount, fill_value0) pivot pivot.asfreq(15min, fill_value0) # 补齐缺失时间片 data pivot.values # shape: [T, num_grids] # 滑动窗口构造样本 def make_samples(data, input_len8, pred_len1): X, y [], [] for i in range(len(data) - input_len - pred_len 1): X.append(data[i:iinput_len]) y.append(data[iinput_len:iinput_lenpred_len]) return np.array(X), np.array(y) X, y make_samples(data, input_len8, pred_len1) print(样本形状:, X.shape, y.shape) # 按时间顺序划分不能随机打乱 split int(len(X) * 0.8) X_train, X_val X[:split], X[split:] y_train, y_val y[:split], y[split:]asfreq(15min, fill_value0)这一步很关键原始数据里某些时间片可能完全没有订单pivot之后会缺行必须补齐否则滑动窗口会跨过缺失时间段导致时间错位。划分训练集时一定按时间顺序切不能train_test_split随机打乱否则验证集的信息会泄漏到训练集指标虚高但实际部署时崩掉。提示如果内存吃紧可以只保留订单量排名前200的网格其余归入「其他」类这样特征维度从几千降到200左右训练速度提升明显。3. 模型选型LSTM、GRU还是TCN纽约出租车流量预测该用哪个3.1 时序预测模型的对比与选择理由纽约出租车流量数据有三个特点强日周期早晚高峰、强周周期工作日vs周末、空间相关性相邻网格流量此消彼长。选模型时要看它能不能同时捕捉这三类模式。LSTM和GRU擅长时序依赖但对空间关系无感知TCN通过膨胀卷积扩大感受野训练比LSTM快但同样不建模空间ST-ResNet这类时空网络能同时处理时空但实现复杂度高大作业场景下性价比不高。我的建议是如果只做单网格预测用GRU就够了参数量比LSTM少三分之一训练更快效果差异通常在1%以内。如果要做多网格联合预测可以在GRU前面加一层一维卷积做空间平滑或者直接用TCN。下面给一个GRU的基线实现输入是[batch, input_len, num_grids]输出[batch, pred_len, num_grids]。import torch import torch.nn as nn class TaxiGRU(nn.Module): def __init__(self, num_grids, hidden_dim128, num_layers2, pred_len1): super().__init__() self.gru nn.GRU(input_sizenum_grids, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue, dropout0.2) self.fc nn.Linear(hidden_dim, num_grids * pred_len) self.pred_len pred_len self.num_grids num_grids def forward(self, x): # x: [batch, input_len, num_grids] out, _ self.gru(x) out out[:, -1, :] # 取最后一个时间步 out self.fc(out) return out.view(-1, self.pred_len, self.num_grids)hidden_dim128是起点如果网格数超过500可以加到256。num_layers2配合dropout0.2是防止过拟合的常规组合再多层收益递减且容易梯度消失。fc层把隐藏状态映射到pred_len × num_grids维最后view成预测形状。损失函数用MSE或HuberHuber对异常流量更鲁棒。3.2 训练循环与关键参数设置训练时最容易翻车的地方是学习率和batch size。时序数据梯度噪声大学习率设1e-3容易震荡我一般从5e-4开始配合ReduceLROnPlateau在验证损失不降时减半。batch size用64或128太小则梯度方差大太大则泛化差。from torch.utils.data import DataLoader, TensorDataset from torch.optim.lr_scheduler import ReduceLROnPlateau device torch.device(cuda if torch.cuda.is_available() else cpu) model TaxiGRU(num_gridsX.shape[2], pred_len1).to(device) optimizer torch.optim.Adam(model.parameters(), lr5e-4) scheduler ReduceLROnPlateau(optimizer, modemin, factor0.5, patience3) criterion nn.HuberLoss() train_loader DataLoader(TensorDataset( torch.FloatTensor(X_train), torch.FloatTensor(y_train)), batch_size64, shuffleTrue) for epoch in range(50): model.train() train_loss 0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step() train_loss loss.item() # 验证 model.eval() with torch.no_grad(): val_pred model(torch.FloatTensor(X_val).to(device)) val_loss criterion(val_pred, torch.FloatTensor(y_val).to(device)) scheduler.step(val_loss) print(fEpoch {epoch}: train{train_loss/len(train_loader):.4f}, val{val_loss:.4f})clip_grad_norm_是时序模型训练的后悔药梯度爆炸在RNN类模型里太常见了加上它训练稳定性提升一个档次。ReduceLROnPlateau的patience3意味着验证损失连续3轮不降就减半学习率这个参数别设太小否则学习率降太快模型还没收敛就停了。注意如果验证损失从第一轮就比训练损失高很多大概率是训练集和验证集分布不一致检查一下划分点是不是落在节假日或异常天气日。4. 特征工程与评估让纽约出租车流量预测的RMSE真正降下来4.1 时间特征与外部特征的引入方式只靠历史流量模型很难区分「周三下午3点」和「周六下午3点」。把时间特征显式编码进去RMSE通常能降5%到10%。具体做法是构造几个周期编码小时的正弦余弦、星期的正弦余弦、是否周末、是否节假日。这些特征不参与GRU的时序输入而是拼接在GRU输出后面一起送进全连接层。def time_features(timestamps): hour timestamps.hour timestamps.minute / 60.0 weekday timestamps.weekday() feats np.stack([ np.sin(2 * np.pi * hour / 24), np.cos(2 * np.pi * hour / 24), np.sin(2 * np.pi * weekday / 7), np.cos(2 * np.pi * weekday / 7), (weekday 5).astype(float) ], axis-1) return feats # [T, 5]正弦余弦编码的好处是周期边界连续比如23点和0点在数值上接近而直接用小时数0和23差很远。(weekday 5)标记周末节假日可以用holidays库生成一个布尔序列。这些特征维度低不会显著增加计算量但信息增益明显。4.2 评估指标与结果解读流量预测常用的指标是RMSE、MAE和MAPE。MAPE在流量接近零时会爆炸所以纽约出租车场景下我主要看RMSE和MAE。另外建议按时间段分组统计早高峰、晚高峰、平峰、夜间分别算RMSE。如果模型在平峰表现好但高峰差说明容量不够或者输入窗口太短。指标含义合理范围15分钟粒度RMSE均方根误差单网格5-15MAE平均绝对误差单网格3-8高峰RMSE早晚高峰RMSE比整体高20%-40%夜间RMSE0-6点RMSE比整体低30%-50%如果RMSE比「用上一个时间片直接当预测」的基线还高说明模型没学到东西回去检查数据对齐和损失函数。如果训练损失降但验证损失不降加dropout或减模型容量。5. 避坑与排查纽约出租车流量预测里最容易翻车的5个地方现象一验证损失比训练损失低很多。原因通常是验证集里包含大量零流量时间片模型预测全零就能拿低损失。解决方法是评估时只看非零时间片或者在损失函数里给零值降权。现象二预测曲线整体平移了一个时间片。这是滑动窗口构造时input_len和pred_len的边界没对齐检查make_samples里y的起始索引是不是iinput_len而不是iinput_len-1。现象三训练到一半loss突然变成nan。梯度爆炸检查输入数据里有没有极大值没归一化。流量数据建议做log1p变换再送进模型预测后再expm1还原。现象四不同网格的预测效果差异巨大。流量大的网格RMSE绝对值高但相对误差小流量小的网格相反。解决方法是按网格流量分层评估或者对每个网格单独做标准化。现象五换个月份的数据测试效果断崖式下跌。纽约出租车流量有季节性和趋势性1月训练7月测试肯定崩。要么做差分消除趋势要么在训练集里覆盖多个季节。提示每次改完数据预处理先跑一个「预测下一个时间片等于当前时间片」的基线确认基线合理后再上模型否则你根本不知道模型是在学习还是在制造噪声。6. 进阶技巧用PyTorch Lightning重构训练流程并做超参搜索前面给的训练循环适合理解原理但实际做实验时反复改超参很痛苦。我后来习惯用PyTorch Lightning把训练逻辑和模型定义分离再配合Optuna做超参搜索。这样换模型、换学习率、换窗口长度只需要改配置不用动训练代码。import pytorch_lightning as pl import optuna class TaxiLit(pl.LightningModule): def __init__(self, num_grids, hidden_dim128, lr5e-4, pred_len1): super().__init__() self.save_hyperparameters() self.model TaxiGRU(num_grids, hidden_dim, pred_lenpred_len) self.criterion nn.HuberLoss() def training_step(self, batch, _): x, y batch loss self.criterion(self.model(x), y) self.log(train_loss, loss) return loss def validation_step(self, batch, _): x, y batch loss self.criterion(self.model(x), y) self.log(val_loss, loss) def configure_optimizers(self): opt torch.optim.Adam(self.parameters(), lrself.hparams.lr) return {optimizer: opt, lr_scheduler: {scheduler: ReduceLROnPlateau(opt, patience3), monitor: val_loss}} def objective(trial): hidden trial.suggest_categorical(hidden_dim, [64, 128, 256]) lr trial.suggest_float(lr, 1e-4, 1e-2, logTrue) model TaxiLit(X.shape[2], hidden_dimhidden, lrlr) trainer pl.Trainer(max_epochs30, enable_progress_barFalse) trainer.fit(model, train_loader, val_loader) return trainer.callback_metrics[val_loss].item() study optuna.create_study(directionminimize) study.optimize(objective, n_trials20) print(study.best_params)Lightning的save_hyperparameters()自动记录超参self.log统一管理指标换设备、加混合精度、做早停都是改一行参数的事。Optuna的logTrue让学习率在对数空间采样比线性采样更合理。20次试验通常能比手动调参找到更好的组合而且整个过程可复现。我自己的习惯是任何时序预测任务先花半天把数据管道和基线跑通再用Optuna搜一轮最后拿最优参数训一个长模型。这套流程在纽约出租车流量预测上单网格RMSE能从12左右降到8以下。别一上来就堆Transformer数据对齐和特征工程才是大头。希望帮到你。本文还有配套的精品资源点击获取