资讯动态

LSTM预测共享单车全流程:特征工程、训练调参与迭代预测

发布时间:2026/9/10 11:48:17 来源:尧图企业网站定制
简介一套基于LSTM神经网络模型的共享单车使用情况预测代码面向深度学习与人工智能方向的高校学生尤其适合需要完成时间序列预测类课程大作业的读者。项目覆盖数据预处理、LSTM模型构建、训练评估与预测可视化的完整流程所有Python脚本均可直接运行且方案经过实际评审并以95分以上成绩通过代码规范性和完成度较高。压缩包共31个文件大小仅8.07MB包含4个Python源码、8个CSV训练与测试数据集、H5格式模型权重、JSON特征缩放参数及多张PNG可视化结果图。多张图表具体展示了预测值与真实值对比、工作日与休息日、四季及温湿度风速等特征对骑行人数的影响配合LSTM原理笔记和依赖清单便于深入理解模型机制与复现环境。目前已有229人学习下载适合希望快速掌握LSTM实战、搭建共享单车预测项目的读者作为参考。1. 用 LSTM 预测共享单车难的不是模型是数据怎么喂接过“基于 LSTM 神经网络模型实现的共享单车使用情况预测”这类大作业的同学第一反应通常是找个开源代码改改 loss 和网络层数结果跑出来误差大得离谱最后只能硬调hidden_size碰运气。一个反直觉的事实是LSTM 在共享单车这类强周期、有外部因素干扰的任务上并不天然比 XGBoost 或随机森林更准它真正的价值体现在“把时间顺序和上下文信息建模进特征空间”这件事上。也就是说模型只占三四成工作量剩下六七成花在窗口构建、特征处理、数据切分和评估方式上。这篇博客就是顺着“拿到一份 95 分大作业源码之后你能从里面拆出什么、改什么、踩过什么坑”来写的。适合正在做 LSTM 时间序列预测课程设计的学生也适合想快速评估 LSTM 是否适合自己的业务数据、又不想从零搭环境的工程师。我不会把某一份源代码复述一遍而是把这类项目里最常见、最可靠的实现方式拆开数据怎么清洗、序列窗口怎么滑、LSTM 的输入输出怎么对应、训完怎么调参和验证每一步都可以直接抄走。2. 共享单车数据的时序特征与 LSTM 输入格式构建2.1 原始数据里有哪些字段哪些能直接进 LSTM共享单车数据集最常见的开放来源是 UCI Bike Sharing Dataset但大作业里更可能是老师给的 CSV字段通常包括instant记录序号、dteday日期、season季节、yr年份、mnth月份、hr小时、holiday是否节假日、weekday星期几、workingday是否工作日、weathersit天气情况、temp标准化温度、atemp体感温度、hum湿度、windspeed风速以及cnt该小时租借总数。这份数据有几个明显特点。第一cnt是按小时统计的一天 24 条记录天然适合做小时级序列预测第二变量里有大量周期性特征比如小时、星期、季节它们和cnt有强相关但又不能用“上一时刻的租借量”代替第三weathersit是类别变量码值 1 到 4直接喂进 LSTM 会让模型学到“天气数值越大越差”的错误规律。因此原始字段不能直接进入模型需要先完成类型转换和特征变换。我一般会在数据清洗后做一轮“特征身份”标记把它们分成三组放入开发文档特征分组字段处理方式时序目标cnt预测目标使用前必须做标准化周期特征hr, weekday, mnth, season转成 sin/cos 编码或 one-hot 编码外部事件holiday, workingday二值化后直接输入环境变量temp, atemp, hum, windspeed标准化到 0-1 区间冗余字段instant, dteday, yr直接删除instant是计数序号对预测无意义2.2 用滑动窗口把表格数据变成“序列样本”LSTM 吃进去的不是一行一行记录而是一个三维张量形状是(样本数, 时间步长, 特征数)。所以第一步是把扁平的数据表切成长度为lookback的窗口。假设lookback24用前 24 小时的特征去预测第 25 小时的cnt这相当于把时间关系编码进样本结构里。下面用 PyTorch 的Dataset封装这个逻辑import torch from torch.utils.data import Dataset import numpy as np class BikeSharingDataset(Dataset): def __init__(self, features, target, lookback24): self.features features self.target target self.lookback lookback def __len__(self): return len(self.features) - self.lookback def __getitem__(self, index): x self.features[index:index self.lookback] y self.target[index self.lookback] return torch.tensor(x, dtypetorch.float32), torch.tensor(y, dtypetorch.float32)这段代码的逻辑是用连续lookback个时间点的特征去预测下一个时间点的值。这里有个容易踩的坑index是不断后移的窗口之间有重叠如果直接用shuffleTrue会破坏时间顺序模型会“偷看”到未来信息。因此训练集和验证集必须按时间顺序切割而不是随机抽样。再延伸一个细节lookback的取值直接决定模型能看到多长的历史。共享单车的使用有明显日内周期24 小时是默认首选但如果你要预测明天同一小时的租借量窗口只设 24 是不够的最好把前 7 天同时段的数据作为额外特征拼进去或者直接把lookback拉长到 16824×7。当然窗口越长样本数量越少训练耗时也越长这个需要在实验里权衡。2.3 用 MinMaxScaler 做标准化避免 LSTM 梯度爆炸LSTM 内部使用 sigmoid 和 tanh 激活函数对输入数值范围非常敏感。温度、风速、租借量三者的量纲完全不同如果不做标准化梯度更新会被大数值特征主导。常见做法是用sklearn.preprocessing.MinMaxScaler把每个特征列映射到[0, 1]区间。from sklearn.preprocessing import MinMaxScaler scaler_x MinMaxScaler(feature_range(0, 1)) scaler_y MinMaxScaler(feature_range(0, 1)) scaled_features scaler_x.fit_transform(features) scaled_target scaler_y.fit_transform(target.reshape(-1, 1))注意这里有两个独立的 scaler一个用于输入特征x一个用于目标值y。原因在于反预测时需要把模型输出的值还原成真实的租借量如果x和y共用一个 scaler还原会引入偏差。fit_transform只应在训练集上调用验证集和测试集必须用已训练好的 scaler 做transform否则会信息泄漏。具体写法是先fit训练集再对验证集和测试集分别transform不能把全部数据混在一起 fit。3. 构建并训练 LSTM 神经网络预测模型3.1 网络结构设计输入层、LSTM 层、全连接层的参数匹配共享单车预测任务是典型的回归任务输出是一个连续值租借量因此 LSTM 网络最后不需要接 softmax而是接一个线性层把隐藏状态映射到 1 维输出。用 PyTorch 搭建一个两层 LSTM 回归网络的典型代码如下import torch.nn as nn class LSTMRegressor(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.2): super(LSTMRegressor, self).__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.regressor nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Dropout(dropout), nn.Linear(32, 1) ) self.initializer() def initializer(self): for name, param in self.lstm.named_parameters(): if weight_ih in name: nn.init.xavier_uniform_(param) elif weight_hh in name: nn.init.orthogonal_(param) def forward(self, x): out, _ self.lstm(x) # 取最后一个时间步的隐藏状态作为序列表示 out out[:, -1, :] return self.regressor(out)这里需要理解两个关键选择的理由。一是batch_firstTrue这样输入张量的形状是(batch, lookback, feature_size)对调试和打印形状更友好二是取out[:, -1, :]因为 LSTM 每一步都会输出一个隐藏状态而我们要预测的是窗口结束之后的下一个时间点理应只保留最后一个隐藏状态。如果你改成取所有时间步的平均池化效果通常会差一些因为模型被迫把历史各时刻的“印象”平均化丢失了临近时刻的权重优势。input_size是你特征工程后实际的列数。比如第 2 章处理后输入为小时编码、星期编码、天气 one-hot、连续环境变量和外部事件标记假设最终合计 18 列那input_size18。hidden_size64和num_layers2是常见起点但不是绝对最优后文会讲怎么调。3.2 训练循环里的关键超参数与梯度裁剪LSTM 在长序列上容易出现梯度爆炸或梯度消失。虽然通过残差连接和门控机制缓解了一部分但训练时仍建议加上梯度裁剪确保梯度范数不超过阈值。下面是训练循环的关键部分import torch.optim as optim from torch.utils.data import DataLoader dataset BikeSharingDataset(scaled_features, scaled_target, lookback24) loader DataLoader(dataset, batch_size128, shuffleFalse) model LSTMRegressor(input_sizescaled_features.shape[1]) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr0.001) scheduler optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, factor0.5, patience5) for epoch in range(50): model.train() total_loss 0.0 for x_batch, y_batch in loader: optimizer.zero_grad() y_pred model(x_batch) loss criterion(y_pred, y_batch.unsqueeze(1)) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() * x_batch.size(0) epoch_loss total_loss / len(loader.dataset) scheduler.step(epoch_loss) print(fEpoch {epoch1}: loss{epoch_loss:.6f})这里shuffleFalse是关键批内按时间顺序取数据而不是随机打散后训练。batch_size128是中等偏大的起点好处是训练稳定坏处是如果数据量特别大、序列特别长容易超出显存入门时可以先设为 32 或 64逐步往上加。ReduceLROnPlateau是验证集 Loss 不再下降时自动将学习率减半能有效避免后期收敛过慢。注意我用它监听的是训练集 Loss严格做法应该监听验证集 Loss即每个 epoch 结束后跑一次验证流程再决定是否裁减学习率。对于大作业来说先用训练 Loss 做粗调没有大问题但提交时建议改成验证 Loss。3.3 验证集划分与“未来数据泄漏”防范共享单车预测里最常见的评估错误是随机抽出 20% 的行当测试集。这会导致同一个时间段在训练集和测试集里都出现模型实际是在预测已经见过的数据。正确做法是按时间点切分比如把前 80% 的记录作为训练集后 20% 作为测试集。这样训练阶段完全看不到未来数据模型学到的规律才是真正可泛化的时间规律。具体切分时还要注意一个隐含问题滑动窗口在切分边界处会截断样本。假设总记录数为N窗口长度为lookback切分点为split_index那么训练集应只用到scaled_features[:split_index]和scaled_target[:split_index]这样从第split_index到split_index lookback这一段不会混入训练集样本构造中。最常见的一个深入原因是如果你直接把整段数据喂给BikeSharingDataset再切分最后一个训练样本的输入会包含测试集前lookback-1个时间点验证集效果虚高。train_size int(len(scaled_features) * 0.8) train_features scaled_features[:train_size] train_target scaled_target[:train_size] val_features scaled_features[train_size - lookback:] val_target scaled_target[train_size:] train_dataset BikeSharingDataset(train_features, train_target, lookback) val_dataset BikeSharingDataset(val_features, val_target, lookback)这里把验证集的起始点前移了lookback目的是保证第一个验证样本的历史窗口完整落在可用的历史范围内。如果没有这段前移第一个验证样本会永远缺一段历史特征模型的第一个预测会异常偏大或偏小。4. 模型评估与隐藏层、学习率等核心参数调优4.1 用 RMSE 和 MAE 双重指标判断预测质量LSTM 回归模型只用 loss 高低来判断好坏是不够的因为 loss 是标准化空间里的值普通人看不出来这个误差意味着什么。我一般会在验证集上同时计算 RMSE均方根误差和 MAE平均绝对误差并把它们还原到真实租借量尺度。from sklearn.metrics import mean_squared_error, mean_absolute_error model.eval() predictions [] ground_truth [] with torch.no_grad(): for x_batch, y_batch in val_loader: y_pred model(x_batch) predictions.extend(y_pred.squeeze().tolist()) ground_truth.extend(y_batch.tolist()) pred_inverse scaler_y.inverse_transform(np.array(predictions).reshape(-1, 1)) true_inverse scaler_y.inverse_transform(np.array(ground_truth).reshape(-1, 1)) rmse mean_squared_error(true_inverse, pred_inverse, squaredFalse) mae mean_absolute_error(true_inverse, pred_inverse) print(fRMSE: {rmse:.2f} 辆/小时, MAE: {mae:.2f} 辆/小时)RMSE 对异常值敏感如果模型在某几个晚上的预测偏差特别大RMSE 会明显增大MAE 更鲁棒反映整体平均误差。实际观测中高峰时段早晚通勤的误差通常远高于凌晨时段。一个合格模型在小时级预测上RMSE 应控制在真实租借量均值 15% 以内大作业评“95 分以上”的源码一般还会附带一张真实值和预测值的时间序列对比图。4.2 hidden_size、num_layers、dropout 的调整顺序与观察点LSTM 调参不需要瞎猜建议按“先定层数再调隐藏单元最后调 dropout”的顺序进行。num_layers从 1 层开始如果训练 Loss 和验证 Loss 都偏高说明欠拟合再加到 2 层2 层仍不足时再尝试 3 层。但共享单车数据量通常只有几千到两万条记录3 层以上很容易过拟合。判断过拟合的硬指标是训练 Loss 持续下降验证 Loss 在第 10 个 epoch 左右开始反弹。这是加 Dropout 或调低hidden_size的信号。hidden_size的合理区间是 32 到 128。小于 16 时模型表达能力不足连基本的周期规律都学不到大于 256 时参数量剧增训练时间变长但精度提升极其有限。我一般会做一个试点实验固定num_layers2比较hidden_size为 32、64、128 三组实验的验证集 RMSE。dropout一般取 0.2 到 0.5。Dropout 只应用于 LSTM 层之间的连接以及全连接层不影响隐藏状态内部传播。注意PyTorch 的nn.LSTM在num_layers1时设置dropout参数会被忽略必须至少两层才有实际效果这是新手最容易忽略的一个点。4.3 利用早停和学习率衰减终止训练防止过拟合LSTM 训练曲线通常在前 20 个 epoch 快速下降30 个 epoch 后进入平台期。如果固定训练 50 个 epoch很容易在后期过拟合。常见做法是保存验证集 Loss 最优的模型权重并设置早停计数连续 8 个 epoch 验证 Loss 不下降就停止训练。best_val_loss float(inf) patience 8 trigger_times 0 for epoch in range(epochs): # 训练循环省略 val_loss evaluate(model, val_loader, criterion) if val_loss best_val_loss: best_val_loss val_loss trigger_times 0 torch.save(model.state_dict(), best_lstm_checkpoint.pt) else: trigger_times 1 if trigger_times patience: print(fEpoch {epoch1}: early stopping) break早停的唯一目的是防止模型过拟合不是省时间。最后用于测试的模型应为best_lstm_checkpoint.pt而不是最后一个 epoch 的权重因为最后一个 epoch 的权重通常已经在验证集上表现变差了。训练结束后把耗时、参数、验证集 RMSE 记录在一张表格里这也是大作业加分项。5. 预测未来 24 小时租借量的迭代多步预测与验证训练完模型后「预测下一个小时」只是入门操作。大作业里常常要预测未来一天甚至一周的单车需求这时候必须用迭代预测也就是把当前预测出来的值再当作输入的一部分去预测下一个时间步。具体做法是先用历史lookback个点预测出第lookback1个值然后丢弃窗口最早的 1 个点把预测值追加到窗口末尾形成新的滑动窗口循环执行。def multi_step_predict(model, initial_window, steps, scaler_y): model.eval() window initial_window.clone().float() predictions [] for _ in range(steps): with torch.no_grad(): pred model(window.unsqueeze(0)) pred_value pred.squeeze(0).squeeze(0).item() predictions.append(pred_value) # 构造新的输入窗口: 丢弃最早时间步加入预测值 new_row window[-1].clone() new_row[0] pred_value # 假设第0列是cnt字段 window torch.cat([window[1:], new_row.unsqueeze(0)], dim0) return scaler_y.inverse_transform(np.array(predictions).reshape(-1, 1))这里的核心风险是误差累积。第 1 步预测的误差会混入第 2 步的输入窗口导致第 3 步、第 4 步的误差越来越大。所以验证多步预测效果时不要只看第 1 步的 RMSE要画一条“预测时效 vs 误差”曲线。如果误差随时间步长快速爆炸说明模型的学习更多来自记忆近期值而不是理解周期性规律需要回看特征工程是否补足了小时、星期等周期信号。实际执行时建议做个对比用“真实值滚动窗口”和“预测值滚动窗口”各跑一遍 24 步预测两者的误差差距能直观反映模型的误差累积情况。差距在 20% 以内说明模型鲁棒差距超过 50% 就说明预测结果只能作为短期参考不宜直接用于调度决策。最后可以用matplotlib把“未来 24 小时预测值 vs 真实值”叠画在同一张图里这也是能直接放进大作业报告里的可视化验证结果。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价