基于LSTM的简单时间序列预测
流程介绍
1 注意事项
1.1 此项目为某运营商公司为预测未来某时间段,进行的序列预测。
1.2 数据已脱敏,用户信息已处理,可正常使用。
1.3 文章仅为技术分享,内容仅提供参考,不可作为项目开发需求。
2 模型实现思路
2.1 数据加工
正常使用中,对实际的数据,需要按照等长序列需求,将数据按照指定的时间段长度进行合并,即将数据处理为等长序列,简单演示如下:
原始数据:
| user | date | cnt |
|---|---|---|
| a | 2022/8/30 10:00 | 22 |
| a | 2022/8/30 12:11 | 1 |
| a | 2022/8/30 21:38 | 16 |
| a | 2022/8/31 15:22 | 36 |
| a | 2022/8/31 16:40 | 24 |
| a | 2022/9/1 7:13 | 83 |
| a | 2022/9/1 9:50 | 34 |
| a | 2022/9/1 11:02 | 12 |
| a | 2022/9/1 14:19 | 8 |
| a | 2022/9/1 19:38 | 41 |
加工后数据:
| user | date | cnt |
|---|---|---|
| a | 2022/8/30 0:00 | 39 |
| a | 2022/8/31 0:00 | 60 |
| a | 2022/9/1 0:00 | 178 |
2.2 模型需求
环境需求(仅供参考):
| # Name | Version |
|---|---|
| keras | 2.9.0 |
| pandas | 1.3.5 |
| python | 3.7.13 |
| scikit-learn | 1.0.2 |
| tensorflow | 2.9.1 |
数据介绍
链接失效时请邮件至邮箱:wangyang980818@163.com
原理介绍
LSTM 网络原理,详细内容请移步原作
Long Short Term Memory networks(以下简称LSTMs),一种特殊的RNN网络,该网络设计出来是为了解决长依赖问题。该网络由 Hochreiter & Schmidhuber (1997)引入,并有许多人对其进行了改进和普及。他们的工作被用来解决了各种各样的问题,直到目前还被广泛应用。
所有循环神经网络都具有神经网络的重复模块链的形式。 在标准的RNN中,该重复模块将具有非常简单的结构,例如单个tanh层。标准的RNN网络如下图所示
LSTMs也具有这种链式结构,但是它的重复单元不同于标准RNN网络里的单元只有一个网络层,它的内部有四个网络层。LSTMs的结构如下图所示。
在解释LSTMs的详细结构时先定义一下图中各个符号的含义,符号包括下面几种
图中黄色类似于CNN里的激活函数操作,粉色圆圈表示点操作,单箭头表示数据流向,箭头合并表示向量的合并(concat)操作,箭头分叉表示向量的拷贝操作
LSTMs的核心思想
LSTMs的核心是细胞状态,用贯穿细胞的水平线表示。
细胞状态像传送带一样。它贯穿整个细胞却只有很少的分支,这样能保证信息不变的流过整个RNNs。细胞状态如下图所示
LSTM网络能通过一种被称为门的结构对细胞状态进行删除或者添加信息。
门能够有选择性的决定让哪些信息通过。其实门的结构很简单,就是一个sigmoid层和一个点乘操作的组合。如下图所示
因为sigmoid层的输出是0-1的值,这代表有多少信息能够流过sigmoid层。0表示都不能通过,1表示都能通过。
一个LSTM里面包含三个门来控制细胞状态。
一步一步理解LSTM
前面提到LSTM由三个门来控制细胞状态,这三个门分别称为忘记门、输入门和输出门。下面一个一个的来讲述。
LSTM的第一步就是决定细胞状态需要丢弃哪些信息。这部分操作是通过一个称为忘记门的sigmoid单元来处理的。它通过查看 h(t-1) 和 xt 信输出一个0-1之间的向量,该向量里面的0-1值表示细胞状态中的哪些信息保留或丢弃多少。0表示不保留,1表示都保留。忘记门如下图所示。
下一步是决定给细胞状态添加哪些新的信息。这一步又分为两个步骤,首先,利用和通过一个称为输入门的操作来决定更新哪些信息。然后利用和通过一个tanh层得到新的候选细胞信息,这些信息可能会被更新到细胞信息中。这两步描述如下图所示。
下面将更新旧的细胞信息,变为新的细胞信息。更新的规则就是通过忘记门选择忘记旧细胞信息的一部分,通过输入门选择添加候选细胞信息的一部分得到新的细胞信息。更新操作如下图所示
更新完细胞状态后需要根据输入的和来判断输出细胞的哪些状态特征,这里需要将输入经过一个称为输出门的sigmoid层得到判断条件,然后将细胞状态经过tanh层得到一个-1~1之间值的向量,该向量与输出门得到的判断条件相乘就得到了最终该RNN单元的输出。该步骤如下图所示
时间序列原理,详细内容请移步原作
我们被随处可见的模式所包围,人们可以注意到四季与天气的关系模式,以交通量计算的交通高峰期的模式,你的心跳或者是股票市场和某些产品的销售周期。
分析时间序列数据对于发现这些模式和预测未来非常有用。有几种方法可以创建这类预测。
平稳序列
平稳时间序列是指统计特性,如均值、方差和自相关系数,随时间相对恒定的序列。因此,非平稳序列是统计特性随时间变化的序列。
在开始任何预测建模之前,都有必要验证这些统计属性是否是常量: · 常数均值 · 常数方差 · 自相关
常数均值
平稳序列在时间上具有一个相对稳定的均值,这个值没有减少或者增加的趋势。围绕常数均值的小的变化,使我们更容易推测未来。 在某些情况下,相对于平均值的变量比较小,使用它可以很好地预测未来。下图显示了变量与该常数平均值相对于时间变化的关系:
在这种情况下,如果序列不是平稳的,对未来的预测将是无效的,因为平均值周围的变量会显著偏离,如下图所示:
在上图中,我们可以明显看到上升的趋势,均值正在逐渐上升。在这种情况下,如果使用均值进行未来值的预测,误差将非常大,因为预测价格会总是低于实际价格。
常数方差 当序列的方差为常数时,我们知道均值和标准差之间存在一种关系。当方差不为常数时(如下图所示),预测在某些时期可能会有较大的误差,而这些时期是不可预测的。可以预测到,随着时间的推移直到未来,方差会保持不稳定。
为了减小方差效应,可以采用对数变换。在本例中,也可以使用指数变换,如Box-Cox方法,或者使用膨胀率调整。
自相关序列 当两个变量在时间上的标准差有相似的变化时,可以说这些变量是相关的。例如,体重会随着心脏疾病而增加,体重越大,心脏问题的发生率就越大。在这种情况下,相关性是正的,图形应该是这样的:
负相关序列
负相关的情况类似于:对工作安全措施的投入越多,工作相关的事故数量就越少。
当谈到自相关时,意思是某些先前时期与当前时期存在相关性,这种相关性是滞后的。例如,在以小时为单位的测量值序列中,今天12:00的温度与24小时前的12:00的温度非常相似。 如果你比较24小时内的温度变化,就会存在自相关,在本例中,我们将与第24小时前的时间存在自相关关系。
自相关是使用单个变量创建预测的一种情况,因为如果没有相关性,就不能使用过去的值来预测未来;当有多个变量时,则可以验证因变量和独立变量的滞后之间是否存在相关性。
如果一个序列不存在自相关关系,那么它就是随机且不可预测的,做预测的最佳方法通常是使用前一天的值。
脚本实现
import pandas as pd
import numpy as np
import random
import tensorflow as tf
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from keras.models import Sequential
from keras.layers import Dense,LSTM
# 创建随机种子
random.seed(18)
tf.random.set_seed(18)
# 根均方误差(RMSE),用于实际预测结果检测
def RMSE(predictions, targets):
return np.sqrt(((predictions - targets) ** 2).mean())
# 构建LSTM模型
def build_model():
model = Sequential()
model.add(LSTM(64, input_shape=(sequence_len,1),return_sequences=True))
model.add(LSTM(32))
model.add(Dense(1,activation="sigmoid"))
optimizer =tf.optimizers.Adam(learning_rate=0.01)
model.compile(loss='mean_squared_error', optimizer=optimizer, metrics=['mape'])
return model
# 数据读取,格式转化,缺失数据填充
data = pd.read_excel('gross.xlsx')
# 数据不可有非数值型
data = data.astype(float)
# 数据填充(填充方式依照实际需求)
data = data.fillna(0)
# 抽取单条序列
all_data = data.iloc[0,1:].values
# 创建标准化变换
stand_scaler = MinMaxScaler()
all_data = stand_scaler.fit_transform(all_data.reshape(-1,1))
# 参数1--sequence_len(步长):将原序列以指定步长切分
# [1,2,3,4,5]按照步长3切割,得到如下序列
# [[1,2,3],[2,3,4],[3,4,5]]
sequence_len = 31
X = []
Y = []
for i in range(len(all_data)-sequence_len):
X.append(all_data[i:i+sequence_len])
Y.append(all_data[i+sequence_len])
X = np.array(X)
Y = np.array(Y)
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.1)
lstm = build_model()
# 参数2--step(训练轮数):模型的训练轮数
step = 50
# 参数3--batch_sizes(抽取数量):模型在单轮训练中抽取的文件数量(此处为序列数)
batch_sizes = 20
# 网络训练
history = lstm.fit(
X_train,
Y_train,
epochs=step,
batch_size = batch_sizes,
validation_data = (X_test, Y_test),
# verbose=0
)
# 网络预测
Y_predict = lstm.predict(X_test)
# 将数据还原(训练过程中为标准化数据)
Y_predict_real = stand_scaler.inverse_transform(Y_predict.reshape(-1,1))
Y_test_real = stand_scaler.inverse_transform(Y_test.reshape(-1,1))
# 输出当前序列的预测效果
print(f"根均方误差(RMSE):{RMSE(Y_predict_real, Y_test_real)}")
上文演示中,针对的是单个用户进行建模预测,实际使用中,如果想要进行多个用户预测,可构建循环。