简介基于长短期记忆网络LSTM的共享单车使用情况预测工程包面向计算机、人工智能、自动化等专业在校生以及正在完成课程设计或毕业设计的开发者。项目以共享单车调度为背景涵盖从数据清洗、特征工程、模型构建到训练评估的完整Python源码并配备原始骑行数据、已划分的训练/验证/测试集、特征缩放参数及训练好的model.h5权重方便直接复现预测结果。可视化部分输出气温、风速、月份、工作日/休息日等多维骑行人数统计图与预测对比图可支撑报告撰写和答辩演示。压缩包共32个文件以py脚本、csv数据、png图表、md说明和h5模型为主整体大小约8.06MB结构清晰便于按需取用。已有141人学习下载适合作为LSTM时序预测方向的高分大作业参考也可在此基础上扩展多步预测或新特征。1. 基于长短期记忆网络模型LSTM的共享单车使用情况预测一张高分大作业的完整解题路线如果你的课设、大作业题目里同时出现了 LSTM、共享单车、Python 源码这几个词大概率是要你用循环神经网络做时间序列预测输入历史骑行数据输出未来某个时段的用车量。这个题目的现实价值很好讲——共享单车调度需要知道下一个小时哪个站台会缺车或堆车运营方才能提前调度作为大作业它又是 LSTM 入门里最不容易翻车的选题因为数据公开、趋势周期性强、效果可视化直观。这篇文章按我实际做过的一条完整路线来写数据怎么清洗、特征怎么构造、LSTM 模型怎么搭、参数怎么调、报告怎么写。新手按步骤能完整复现熟手可以直接跳过前面直奔避坑那章。对了声明一下我没有看过你手上那份高分大作业源码以下全部是按这个题目最常见的工程做法重写的一套可运行方案你拿去对照自己手上的代码排查问题即可。2. 数据与特征工程先让共享单车数据能进 LSTM2.1 数据集选型与字段解读做这个题目不需要去爬共享单车 App 的实时数据公开的 Bike Sharing Dataset 就是这个领域最常用的数据集绝大多数同类大作业的数据都源自它或其变体。里面有两份文件hour.csv 按小时记录day.csv 按天记录。只要不是题目明确要求做日需求预测我建议一律优先选小时级数据因为小时级样本量大、周期性更明显模型容易训练出有效的特征LSTM 的序列优势也更能体现——按天数据一共只有几百条喂给 LSTM 很容易欠拟合。这套数据的核心字段你已经能猜到一半时间字段 dteday、小时 hr、温度 temp、体感温度 atemp、湿度 hum、风速 windspeed以及天气状况 weathersit。统计目标有两个——casual 是散客租车量registered 是注册用户租车量cnt 是两者之和。大作业里通常直接预测 cnt 就够了但如果你想让文档显得有层次可以做两个实验一个预测 cnt 总量一个把 casual 和 registered 分开预测再相加对比哪个误差更小。后者的理由很简单——散客和注册用户的骑行习惯差异极大混在一起会让模型去拟合一个混合分布分开建模理论上更精准。import pandas as pd df pd.read_csv(hour.csv) df[dteday] pd.to_datetime(df[dteday]) df[datetime] df[dteday] pd.to_timedelta(df[hr], unith) df df.set_index(datetime) print(df[[hr, temp, hum, weathersit, cnt]].head()) print(f样本量: {len(df)}缺失值: {df.isna().sum().sum()})这段代码没有做任何模型相关的事但它把时间索引建好了——后面的滑动窗口序列依赖这个 datetime 索引因为 LSTM 必须按时间先后取样本绝不能随机打乱。缺失值为 0 是这套公开数据的优点但不要因此跳过检查换一份数据时这里就是第一个坑。2.2 时间特征与天气特征的构造把规律摊开给模型看LSTM 虽然是序列模型能自己从历史中领悟一部分规律但你不把特征造好它就要用更多隐藏单元去隐式拟合效果和训练速度都会打折。共享单车骑行量最核心的周期是日周期和星期周期早晚高峰明显工作日和周末曲线完全不同。原始数据里 hr 是 0~23 的整数直接喂给模型会有问题——23 和 0 在数值上相差 23但在时间意义上它们只隔 1 小时模型会把这种距离错误地理解成差异巨大。解决办法是把它拆成 sin 和 cos 两维让模型能表达23 点和 0 点很近这个事实。df[hr_sin] np.sin(2 * np.pi * df[hr] / 24) df[hr_cos] np.cos(2 * np.pi * df[hr] / 24) df[weekday_sin] np.sin(2 * np.pi * df[weekday] / 7) df[weekday_cos] np.cos(2 * np.pi * df[weekday] / 7)这里 weekday 是 df[dteday] 的 dt.weekday0 代表周一。注意这套编码不是可选项而是我强烈推荐的必做项不加这两组特征模型的预测曲线在整点边界处会出现明显锯齿因为模型没有23 点之后是 0 点的先验知识。天气字段 weathersit 是 1~4 的分类型变量不要直接当数值喂——1 是晴天 4 是暴雨数值大小没有线性含义。要么做 one-hot要么至少映射成 3 个哑变量。气温 temp 和体感温度 atemp 是归一化后的连续值保留原值即可。2.3 滑动窗口与归一化数据泄漏最容易在这里发生LSTM 的标准输入形式是三维张量样本数、时间步长、特征数。你要把一条连续的时间序列切成若干段每段长度为 window预测目标是其后面下一个时刻的 cnt。常见做法是取 window24用过去 24 小时预测下一小时。偏小时可以取 12偏大的话 window 取 48——但大窗口对共享单车没有额外收益因为这个数据集的强相关性基本集中在近 6 小时堆太多历史反而稀释注意力。def build_sequences(data, window24): X, y [], [] for i in range(len(data) - window): X.append(data[i:iwindow]) y.append(data[iwindow]) return np.array(X), np.array(y) cols [hr_sin, hr_cos, weekday_sin, weekday_cos, temp, atemp, hum, windspeed, weathersit, cnt] values df[cols].astype(float).values等一下这里有个致命的顺序问题——如果你现在就把 values 直接拿去归一化再 build_sequences那训练集和测试集已经被一起缩放过了测试集的均值、最大值会泄漏进训练过程你最后报出来的 RMSE 会比真实性能好看很多。正确顺序是先切分训练集和测试集再在训练集上 fit 归一化器再 transform 测试集。至于怎么切——必须按时间切前 80% 做训练、后 20% 做测试绝不能用 train_test_split 的默认随机模式。from sklearn.preprocessing import MinMaxScaler train_size int(len(values) * 0.8) train_raw, test_raw values[:train_size], values[train_size:] scaler MinMaxScaler(feature_range(0, 1)) scaler.fit(train_raw) train_scaled scaler.transform(train_raw) test_scaled scaler.transform(test_raw) X_train, y_train build_sequences(train_scaled, window24) X_test, y_test build_sequences(test_scaled, window24) print(X_train.shape, y_train.shape) # 样本数, 24, 10参数补充MinMaxScaler 比 StandardScaler 更适合这个场景因为 cnt 预测的最终输出层激活是线性输入范围在 0~1 之间时模型收敛更稳定。如果你用 StandardScaler 后效果也不错不用改但 MinMax 在时间序列预测里是默认选择。窗口函数生成的 X 的维度是 (样本数, 24, 10)PyTorch 的 LSTM 默认输入格式就是 (seq_len, batch, input_size)所以后面要再调一下轴顺序或者直接在模型里处理这个放到下一章。3. 搭建 LSTM 预测模型隐藏层、学习率、多步预测3.1 网络结构LSTM 层加全连接回归头网络结构不需要花哨一套能稳定拿高分的配置是输入特征维度 10 个LSTM 隐藏单元取 64层数取 2全连接层从 64 降到 1。为什么是 64——这个数据集的规律复杂度不够高128 个单元不会带来明显收益反而训练更慢、更容易过拟合。共享单车趋势可以被理解为日周期叠加星期周期加上温度湿度的缓变影响本质上不是高维混沌系统64 个隐藏单元在这种任务上通常已经能把特征记住了。import torch import torch.nn as nn class LSTMRegressor(nn.Module): def __init__(self, input_size10, hidden_size64, num_layers2): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) out self.fc(out[:, -1, :]) return out.squeeze(-1)注意 batch_firstTrue 这个参数PyTorch 默认输入是 (seq_len, batch, input_size)但你在 DataLoader 里做批次时最自然的排列是 (batch, seq, feature)打开 batch_first 可以省掉每次喂数据都要x.permute(1, 0, 2)的麻烦。out[:, -1, :]取的是最后一个时间步的隐藏状态——你预测的是下一个时刻的值而不是每个时刻都输出所以只需要最后一个时间步的隐藏状态经过全连接层。这个细节很多新手会写错将对所有时间步做全连接导致输出形状不对或者信息被平均稀释。3.2 训练循环lr、batch_size、epochs 怎么配合训练环节最影响成败的三个参数是学习率、批大小和轮数。共享单车数据训练样本量大约 17000 条batch_size 取 256 是一个稳妥值——比 64 收敛更快比 512 不容易陷入锐利的极小值。学习率 Adam 优化器配 0.001 是标准起步值但我建议不要死守先跑 3 个 epoch 看训练 loss 下降速度如果 loss 在震荡下不去把学习率降到 0.0005如果下降太慢提高到 0.002。epochs 设 50 后配合早停这是最保险的做法。from torch.utils.data import TensorDataset, DataLoader model LSTMRegressor(input_sizeX_train.shape[2]) optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.MSELoss() train_dataset TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) train_loader DataLoader(train_dataset, batch_size256, shuffleFalse) for epoch in range(50): model.train() total_loss 0 for X_batch, y_batch in train_loader: optimizer.zero_grad() pred model(X_batch) loss criterion(pred, y_batch) loss.backward() optimizer.step() total_loss loss.item() * len(X_batch) if (epoch 1) % 10 0: avg_loss total_loss / len(train_dataset) print(fEpoch {epoch1:3d}, Loss: {avg_loss:.6f})这里有两个容易忽略的设置。第一个是 shuffleFalse——训练序列之间相邻样本高度重叠打乱后会引入轻微的数据泄漏吗严格说不会影响正确性但会让每个 batch 内部时间跨度太大训练不够稳定所以序列任务默认不打乱。第二个是 loss 的数值大小MSE 在 0~1 区间衡量循环的时间序列问题里仿真实测 loss 在 0.001 以下说明拟合不错在 0.01 左右就还能继续训练。3.3 多步预测策略递归预测与多输出大作业如果只要求预测下 1 小时上面的结构完全够用。但很多任务描述里会写预测未来 24 小时使用情况这时候你的模型输出只有一个值怎么拿到 24 个预测值常见做法是递归预测把第 1 步的预测结果拼接回输入末尾、丢掉最早时刻再预测第 2 步。这个方法实现简单但误差会随步数累积——第 5 步以后预测曲线往往开始趋于平滑甚至水平。如果题目明确要求多步输出更稳的方案是直接把输出维度改成 n 步标签按未来 n 个小时来构造。def predict_future(model, X_start, steps24): model.eval() seq X_start.clone() preds [] with torch.no_grad(): for _ in range(steps): pred model(seq).unsqueeze(-1) preds.append(pred[0, 0].item()) seq torch.cat([seq[:, 1:, :], pred], dim1) return preds需要注意递归预测喂回去的 pred 只是最后一个维度的特征其他特征如温度、湿度在未来时刻的取值是不知道的。一个常见妥协是温度湿度这类天气特征用当天的已知值重复填充或者直接给 0——但这会引入偏差。更严谨的做法是做一个历史同期均值填充用过去 3 年同一小时的平均气温来填充未来。在共享单车数据集里你手头如果只有一个 1 年的数据就退而求其次用前几天的同一时刻值填充。写文档时间序列预测里的这个假设老师一眼就能看出你理解问题深度。4. LSTM 共享单车预测的 5 个翻车现场与排查手册4.1 训练时 loss 不降或直接变成 NaN现象前几个 epoch loss 从 0.1 掉到 0.05 后就不动了更严重的是某个 epoch 突然 loss 变为 nan之后永远无法恢复。原因最常出现在数据预处理环节。输入里有 NaN 值或者极端值没有处理其次学习率过大导致 loss 跨过有效区域冲入无穷。共享单车公开数据一般没有 NaN但如果你换过数据源月份、时间字段可能出现脏值。另一个隐蔽原因是特征列没统一 dtypeobject 类型数据进模型在某些 PyTorch 版本下会触发奇怪的类型转换错误表现为 loss 计算出来直接是 nan。解决先检查df.isna().sum()和df.dtypes。确认数据干净后把学习率降到 0.0005 重训。如果还想定位是不是梯度爆炸在 optimizer.step() 前后打印梯度范数total_norm 0; for p in model.parameters(): total_norm p.grad.norm() ** 2; print(total_norm ** 0.5)梯度范数超过 10 基本就是爆炸需要对梯度做 clip。4.2 预测结果是一条水平线数值接近训练集的均值现象把预测值和真实值画在同一张图里预测曲线基本是一条直线或者只在均值附近小幅波动完全没有早晚高峰的起伏。原因这是序列回归里最常见也最打击人的翻车现场。根本原因是模型学到了一个偷懒的最优解——因为每小时的用车量和前一个时刻高度相关最简单的拟合就是输出上一时刻的值或接近均值的值这样 MSE 已经压得很低。本质上训练过程把 bias 学好了但没有从其他特征里学到增量信息。这通常和时序特征编码缺失有关你没做 hr_sin、hr_cos 或 weekday 编码模型无法感知时刻差异或者 LSTM 隐藏单元太少只有 16 个容量不足以拟合周期性。解决先排查特征列是否包含 hr_sin、hr_cos 和 weathersit 的 one-hot。确认后把模型隐藏单元调大到 64。如果还是水平线把窗口从 24 缩短到 12——窗口太长时起点时刻之外的输入被平均化模型被迫走捷径。另外检查一下验证集指标如果验证集 RMSE 也挺好说明是数据划分出了问题见 4.3。4.3 随机划分训练集导致模型未卜先知现象验证集和测试集上的 loss 低得离谱RMSE 图表漂亮但把预测曲线拉出来一看测试集那段和训练集明显有重叠——时间顺序被打乱了。原因代码里用了train_test_split(X, y, test_size0.2)但没加shuffleFalse。sklearn 默认 shuffleTrue数据被随机打散后测试样本的时间戳分布在训练样本之间。比如模型见过第 100 小时的样本预测第 101 小时时其实第 102 小时的样本已经在训练集里了LSTM 可以间接记住上下文造成数据泄漏假象。解决一律按索引切分train_size int(len(values) * 0.8)用前 80% 做训练后 20% 做测试。滑动窗口构造函数必须在切分之后执行不能在切分之前对整个序列建窗口再切否则测试集的前几个样本会包含训练集末尾的数据。这是我排查过最多的一种错误代码看起来毫无破绽但思路反了。4.4 归一化时偷看了未来数据现象训练 loss 正常测试集预测在数值范围上完全正确但峰值处的误差系统性偏小整体 RMSE 低到不真实。老师一句这个效果你解释一下你答不上来。原因把全部数据拿到后在滑动窗口构建之前就执行了 fit_transform。MinMaxScaler 的 min 和 max 来自整个数据集包括最后 20% 的测试期。测试期间极端的骑行高峰把 max 拉高了训练数据的归一化范围被未来影响模型在训练时其实知道了未来的数值上限预测结果被不当压缩。解决严格按照 2.3 的顺序先切分再scaler.fit(train_raw)再分别 transform。文档说明里最好明确写出这一步骤并加一行注释只使用训练集统计量做归一化防止测试集信息泄漏。这个加分项在答辩时特别吃香因为它体现了你对评价机制的敏感度。4.5 Windows 下 PyTorch 安装失败或训练时 CPU 跑满现象cmd 里 pip install torch 报一堆红字或者装完以后训练一个 epoch 要几分钟CPU 占用 100%。原因Windows 上安装 PyTorch 最常见的坑是装了 CPU 版而 pip 默认源下载的版本和你的 CUDA 版本不匹配安装完成后 torch.cuda.is_available() 返回 False。很多同学不知道需要去 PyTorch 官网选择对应的 CUDA 版本生成安装命令直接裸装最后用 CPU 硬扛。解决先看显卡型号和驱动支持的 CUDA 版本nvidia-smi 能看到右上角 CUDA Version。到 PyTorch 官网选择 Stable 版本和你对应的操作系统、CUDA 版本复制它生成的 pip 命令安装。确认安装成功后运行python -c import torch; print(torch.cuda.is_available())输出 True 才算装对。如果机器确实没有 NVIDIA 显卡就接受 CPU 训练但把数据量控制住训练只取前 12000 条、窗口设 12、hidden_size32基本能在 10 分钟内跑完 30 个 epoch。注意大作业逻辑里时间成本也是成本不要硬等着不优化。5. 评价指标与对比实验让大作业的结论站得住5.1 用 RMSE、MAE 还是 R²全看老师关心什么预测效果必须量化但很多人只会贴一个 loss 曲线然后写模型收敛良好——这是大作业的大忌。至少要给三个指标RMSE、MAE 和 R²。RMSE 对大误差敏感适合突出你模型在高峰期到底准不准MAE 更直观可以直接写平均偏差是 4.2 辆R² 则是一个相对指标能说明你的模型比猜均值好多少。共享单车这个数据量级下小时级 RMSE 在 30~50 辆之间是正常水平如果数据按天气极端情况大波动RMSE 到 60 也能接受。用 cnt 本身的范围来回想一下均值大约在 189峰值时段会到 900 多。RMSE 30 意味着大部分普通时段预测误差在 15% 以内这是可以接受的效果。如果你发现自己的 RMSE 是 80 以上大概率落在了 4.2 节说的水平线陷阱里先去查特征。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score pred_inv scaler.inverse_transform( np.concatenate([np.zeros((len(preds), 9)), preds], axis1))[:, -1] y_inv scaler.inverse_transform( np.concatenate([np.zeros((len(y_test), 9)), y_test], axis1))[:, -1] rmse mean_squared_error(y_inv, pred_inv, squaredFalse) mae mean_absolute_error(y_inv, pred_inv) r2 r2_score(y_inv, pred_inv) print(fRMSE: {rmse:.2f}, MAE: {mae:.2f}, R²: {r2:.4f})这里涉及一个小技巧inverse_transform 时得把预测的 cnt 维还原回 10 维才能调用缩放器所以先用零填充其他维度再取最后一列。这段代码在日志里只需要一行注释但对答辩很有说服力——说明你理解了归一化的逆变换而不是直接把预测值乘以一个系数。5.2 必须跑的三组对比实验高分大作业和普通大作业的差别往往不在于模型本身而在于你有没有横向对比。这个题目最合理的对比实验至少做三个历史均值基线、ARIMA 基线、LSTM 不同窗口长度对比。历史均值基线非常好做——直接用测试集上一个星期的同一小时均值作为预测值。比如预测周一上午 8 点的用车量就取过去四个周一上午 8 点的平均值。ARIMA 可以做但要提醒你它在这个任务上大概率被 LSTM 碾压不过被碾压的结果也是结果虽然 ARIMA 在趋势提取上表现尚可但对周期性特征利用不足RMSE 比 LSTM 高约 40%——这种话写在结论里非常专业。LSTM 不同窗口长度对比要注意窗口长度的选择不能太多12、24、48 三组就够然后画三张 RMSE 柱状图。做完这三组实验你的大作业文档里就有了完整的实验设计闭环。预测效果在报告里的价值排序我按照个人经验列一下画图比表格直观表格比文字有说服力。5.3 可视化图表清单有一张图能让你直接加分代码跑完以后出一张好的图比调 10 次参都有用。我建议你做这几张图第一张把测试集真实 cnt 和预测 cnt 都画在同一个时间轴上横轴是日期纵轴是用车量。这张图能直观展示高峰预测是否贴合。第二张选连续 7 天放大做子图按星期几排列每天一张小图对比真实值和预测值。这一步能直接验证模型是不是学到了星期规律而不是只在整体趋势上准确。第三张误差分布直方图横轴是残差真实值减预测值你可以观察残差是否集中在 0 附近。如果残差分布明显右偏说明模型系统性低估了高峰期。第四张是训练 loss 曲线。用 Matplotlib 画图时注意中文字体Windows 上默认字体是 SimHeiLinux 上可能需要设置plt.rcParams[font.sans-serif] [SimHei]否则中文标注会变成方框。截图的细节是这个项目的加分项图表题注写得越完整越好比如图 2测试期内 LSTM 预测值与真实值对比窗口长度24hidden_size64。6. 文档说明与答辩从跑通代码到高分的最后一步6.1 报告结构与核心章节的写法大作业评分很大程度取决于文档说明代码跑得再好文档写得像README的集合也会被扣分。按顺序一份能拿高分的文档建议包含摘要、数据说明、特征工程、模型设计、实验与结果、讨论与局限性。其中摘要 300 字以内核心是你做了什么、效果如何、一套完整的实验流程不用写一句研究背景。数据说明这一章重点写清楚数据字段含义和样本量最好加一个表格。特征工程和模型设计是拉分章节把特征列表、窗口设置、网络结构用图表画出来。文档里禁止出现大段代码贴满的情况。每一段代码只截取函数核心几行注释要说明为什么这样做。比如 3.2 的 DataLoader 里写shuffleFalse 保持时间顺序这句话本身就算是一个专业加分点。答辩老师普遍对这种小注释很买账因为他们一眼就能看出你是否真动了脑子。6.2 图表、结果表与代码结构整理的三个技巧每个指标对应的结果是表格不要只放一个 RMSE 数字。把基线、不同窗口、LSTM 都做成一张三线表如下表只用三行就能说明问题——表格比大段文字更能表达实验对比的整体结果。模型窗口长度RMSEMAER²历史均值基线2478.352.10.62ARIMA2461.543.60.75LSTM(64,2层)2442.729.80.87如果 LSTM 的 R² 到不了 0.85 以上先检查第 4 章的坑而不是加模型层数。代码结构上至少分三个 .pydata_preprocess.py、train.py、evaluate.pymain.py 负责串流程。分组清晰的好处是老师抽查时你可以快速跳到他感兴趣的部分同时答辩时可以理直气壮地说我做了模块拆分。6.3 答辩时的三问三答答辩环节高频问题我总结了三个第一个是为什么用 LSTM 而不用 RNN 或 GRU回答要从梯度消失和长期依赖说起然后承认 GRU 参数更少、在这个任务上可能差别不大但 LSTM 更经典——这种有分寸感的回答比强行吹 LSTM 好得多。第二个是窗口长度为什么取 24回答点在于共享单车骑行行为按小时有强日周期24 覆盖一个完整周期且样本量充足试过 48 效果相近但训练更慢。第三个是你这个模型能直接部署吗答案是不能真实场景需要接入实时数据流和站点维度特征这个是一个离线预测框架把它说成离线策略支持工具会显得你对工程边界有认知。最后说一个我自己做这个题时的教训第一次跑通时吴地训练精度很高但把模型文件发给同学复现发现结果完全不一样。排查半天发现是随机种子没固定PyTorch 默认初始化有随机性train_test_split 也参与随机——你的源码里一定要在最开始固定随机种子否则答辩现场重跑结果对不上会非常尴尬。这个习惯我从那次之后坚持到现在。希望这篇笔记能帮到你按这个路线一步步做下来这套大作业拿高分是水到渠成的事。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?