概述
- 背景
时间序列是按照时间维度以固定的采样频率生成的一组或多组变量。时间序列预测是一种用历史数据来预测未来趋势或数值的方法。它在很多领域都有应用,比如气象、金融、医疗等领域。 - 我的工作
本项目以预测北京市的气温为例,使用lstm,gru和普通rnn,通过过去30天的气温,预测接下来一天的气温 - 核心目标
该项目主要对比了以上三种模型在预测北京市气温任务上的预测性能,并比较它们的优缺点 - 报告结构
本文按照以下结构展开:首先介绍lstm,gru和普通rnn各自的原理,接下来进行仿真实验,包括对实验数据的预处理和模型搭建,然后分析实验结构,对比它们各自的预测误差以及优缺点,最后进行总结和展望
算法原理
lstm长短期记忆网络
它的的设计灵感来自于计算机中的逻辑门。它包含输入门、遗忘门和输出门,示意图如下所示
它有三个输入,t-1时刻长期记忆(细胞状态ct-1)和短期记忆(隐状态ht-1),以及t时刻的输入向量xt,lstm的工作流程如下
- ht-1和xt拼接,进入第一个sigmoid函数,生成的结果叫做遗忘比例,ct-1乘上这个遗忘比例
- ht-1和xt拼接,进入第二个sigmoid函数,生成的结果作为生成新记忆的比例
- ht-1和xt拼接,进入第一个tanh函数,生成的结果作为新生成的记忆备选
- 2和3相乘,再和1相加,作为新生成的长期记忆ct
- ht-1和xt拼接,进入第三个sigmoid函数,生成的结果作为输出结果的比例
- ct进入第二个tan函数,生成的结果作为输出备选
- 5和6相乘,作为新的隐状态也就是输出ht
gru
主要有两个部分,重置门和更新门。
输入有2个,上一个时间的隐状态ht-1和当前的输入向量xt。
主要工作流程如下
- ht-1和xt合并作为X
- X进入第一个sigmoid作为重置门的比例
- X进入第二个sigmoid作为更新门的比例
- X进入重置门(与2的结果比例相乘)得到重置后的隐状态
- 重置后的隐状态进入tanh函数,作为候选隐状态
- 更新门乘ht-1
- 再加上(1-更新门)✖️候选隐状态
- 得到最终的隐状态
- PS:重置门仅作为候选隐状态的生成,更有助于捕获序列中的短期依赖关系
- 更新门有助于捕获序列中的长期依赖关系
仿真实验
实验数据
数据来源
本实验使用的数据是北京市的日平均气温,来自kaggle网站Daily Temperature of Major Cities
数据描述
本实验选取了1995-2020年北京市的日均气温,经过数据筛选,得到9266 条关于 'Beijing' 的记录
数据预处理
我们主要进行了以下三步
-
日期处理:原数据的日期格式为
Month Day Year 1 1 1995 ... ... ... 我们把它将年月日合并转化为标准日期格式,效果如下
Date 1995-01-01 -
数据缺失值处理,对于数据中的缺失值(比如温度为-99°F或NaN的情况),我们采用了先向前填充(用上一个数据补充当前的缺失数据),再向后填充的方法(用下一个数据补充当前的缺失数据)
-
特征选择,仅使用日平均气温作为我们学习与预测的特征
预处理完成后,前五行数据如下图所示
Date |
City |
AvgTemperature |
|---|---|---|
1995-01-01 |
Beijing |
|
1995-01-01 |
Beijing |
|
1995-01-01 |
Beijing |
|
1995-01-01 |
Beijing |
数据可视化
我们可以使用matplotlib作图,可视化我们的数据集如下所示,其中气温以℉为单位

由上图可以看出北京气温呈现冬冷夏热的趋势,并且有季节周期性
实验步骤
实验环境
我们使用的软件环境 Python版本为,cuda版本为
pytorch版本为硬件环境为显卡RTX3050 laptop,CPU i5 11300h
数据准备
- 我们得到上述数据后,由于温度波动太大,所以要先把这些温度数据进行归一化,使用
sklearn包里的MinMaxScaler把温度数据放缩到[0, 1]区间。 - 构建时间序列样本,设置回顾窗口大小
look_back为30,即用过去30天的气温预测下一天的气温,将这些样本作为总数据集 - 把数据集按照训练集:测试集=4:1的比例进行划分
模型构建
- LSTM的搭建,我们使用pytorch.nn.modules 里的lstm类,在正文里构建lstm类,其中我们的参数设置,输入层大小为1,隐藏层神经元数量hidden_dim为64,LSTM层数num_layers为2,输出层大小为1。解释一下,输入层大小就是我们每天温度的维度,显然为1,隐藏层神经元数量就是我们记忆的数据维度,层数就是我们可以堆叠的层数,输出就是最后一个时间步的输出,也就是第31个输出。
- GRU模型结构设置为:输入层大小为1,隐藏层神经元数量hidden_dim为[64],GRU层数num_layers为[2],输出层大小为1。”
- rnn神经元作为baseline模型,参数如代码所示
input_dim_rnn = 1
hidden_dim_rnn = 64 # 和LSTM/GRU用一样的参数设置,方便对比
num_layers_rnn = 2
output_dim_rnn = 1
模型训练
本次实验采用Adam优化器,损失函数MSE,学习率0.001,轮次epochs=50
# “教材”:均方误差损失函数 (Mean Squared Error Loss)
# 它会计算模型预测的温度和真实温度之间的差距,差距越小说明学得越好
criterion = nn.MSELoss()
# “教练”:优化器
# 它会根据模型在“教材”上的表现,调整模型的“学习方法”(权重参数)
learning_rate = 0.001学习率
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
评价指标
通过均方根误差RMSE和平均绝对误差MAE对模型进行评估,值越小,说明预测越精确
结果分析
定性分析
我们可以看到训练时各模型损失的下降曲线

从损失下降速度来看,gru略快于lstm,可能是因为gru相对更简单,但之后都趋于较低损失值,稳定
以及预测值与真实值的对比曲线图,以测试集的前300个点为例

从图x来看,两种模型都能较好预测下一天的气温变化,从细节来看gru预测不如lstm激进(变化更平稳,但也导致了一部分误差),这一现象在真实值达到波峰、波谷时更为明显。
定量分析
具体指标如下表所示
| 模型 | RMSE(℉) | MAE(℉) | 参数数量(模型复杂度) |
|---|---|---|---|
| LSTM | 4.48 | 3.45 | 50497 |
| gru | 4.60 | 3.66 | 37889 |
| rnn | 4.54 | 3.54 |
从上表可以看出gru的模型复杂度更低,但误差较高,lstm复杂度高,但预测更精准我们还可以对lstm的预测结果进行详细误差分析
计算每个时间点的误差 (真实值 - 预测值)


误差统计:平均误差: 0.13°F, 误差标准差: 4.47°F
观察误差图:误差集中在0附近,没有明显的正偏或负偏(模型系统性高估或低估)
观察误差直方图:误差分布接近正态分布
总结
本项目使用了rnn普通神经元作为baseline模型,并使用lstm和gru两种循环神经网络对北京市气温进行预测,对比他们各自的优缺点
实验结果表明lstm模型训练误差较低,其rmse指标为4.48℉,但其因为复杂度较高,导致损失降低速度略慢
而gru模型较lstm训练误差升高,其rmse指标为4.60,但他的模型复杂度较低
当然本实验也存在局限性,比如仅预测1天时间还是太短,如果能预测14天就跟好了,
二是仅使用了北京市的数据进行建模,可能存在偶然性
三是没有调整更多超参数
未来希望使用一些改进的模型,可以加上transformer吗,并且融合温度之外的更多气象特征,探索更多的城市,进行多城市对比研究
参考文献
pytorch官方文档
kaggle数据集Daily Temperature of Major Cities
动手学深度学习