本文将介绍如何使用Keras开源框架搭建基于LSTM的时间序列预测模型,并对美国COVID-19确诊人数时间序列进行预测。

引言

近些年来计算机运算能力飞速提高,机器学习领域的各项研究也产生了突破性的进展。1974年,Paul Werbos提出著名的反向传播算法(Backpropagation Algorithm, BP),随后被Y. LeCun等人应用与具有深度神经结构的网络中进行训练。BP神经网络根据损失函数计算输出层产生的预测误差,通过反向传播来更新网络中各个神经元的内部权重参数。然而在训练深层网络中,基于梯度下降的BP算法很容易陷入局部最优解而非全局最优解的问题中。2006年Hinton等人提出了深度神经网络(Deep Neural Network, DNN)的概念,自此深度学习(Deep Learning, DL)的方法逐渐流行了起来。

经过多年的发展,深度学习技术已经在众多领域取得了令人瞩目的成果。在数据分析领域,人们也开始利用深度学习技术来处理分析数据,时间序列预测就是其中一个重要应用。时间序列是按照时间顺序排列的一组数据,通常间隔为一段固定的时间,可以作为离散时间数据进行分析处理,同时也具有一定的连续性。作为深度学习的一个重要应用,时间序列预测在计量经济学、天气预报、数学金融等领域中被广泛应用,起到了十分重要的作用。深度学习技术可以建立十分复杂的模型,使得预测结果更加准确。在深度学习领域中最热门的分支是神经网络。神经网络通过梯度下降算法进行学习训练,可以通过增加神经元、堆叠多层网络来提高模型的学习能力。传统的神经网络模型无法对连续的时间序列进行学习。于是通过对其进行修改,产生了更善于处理连续序列的神经网络模型,即循环神经网络(Recurrent Neural Network, RNN)模型。1997年,Hochreiter和Schmidhuber进一步提出了长短期记忆网络(Long Short-Term Memory, LSTM),有效地解决了RNN存在的各种问题,提高了对序列数据的处理能力。

算法介绍

循环神经网络(RNN)

介绍长短期记忆网络之前,首先需要介绍一下循环神经网络。单个循环神经网络主要形式如图1(左)所示,其中$f$为循环神经网络,$x$为当前状态下输入的向量数据,$y$为当前状态下的输出向量,$h$表示接收的上一时间点的输出,$h'$表示传递到下一时间点的输出。以下三个公式为循环神经网络f的表达式,公式中的$W^h$、$W^i$和$W^0$表示不同的权重参数。

$$ h',y = f(h,x) \tag{1} $$

$$ h' = tanh(W^h h+W^i x) \tag{2} $$

$$ y = W^0 h' \tag{3} $$

当数据以序列的形式输入循环神经网络中时,网络会循环使用每个时间点的输出,得到如图1(右)所示形式的RNN。训练数据时,在时刻t神经元接收到输入的特征向量$x$,同时也接收到了t-1时刻隐藏层神经元输出的h。通过上述公式计算并记录时刻t下的输出$h'$以及输出向量$y$。最后根据损失函数L计算出模型预测结果和真实数据之间的误差,并将误差反向传播(Backpropagation),通过梯度下降(Gradient Descent)的方法更新各层神经元内部的参数。不断地循环这一过程,逐渐降低误差,最终训练出预测模型。正是得益于这种循环的结构,网络可以拥有“记忆”,学习数据的过程中会联系当前输入数据的前后文进行判断。因此循环神经网络十分擅长处理时间序列类型的数据。然而这种结构的循环神网络并不能很好地处理长期依赖问题,并且还会出现梯度消失、梯度爆炸的问题。

Figure-1
图1 (左)单个循环神经网络节点,(右)循环神经网络处理序列数据

长短期记忆网络(LSTM)

长短期记忆网络是一种优化的循环神经网络,其核心是在每一个时间点加入了记忆细胞的状态,能够选择性地删除或保留状态信息。这个能力是因为内部引入了如图2所示的“输入门(input gate)”、“输出门(output gate)”以及“遗忘门(forget gate)”这三个“门(gate)”结构。也正是因此长短期神经网络具备了解决RNN存在的问题的能力。这三个门结构都通常使用sigmoid函数作为激活函数,以此来控制“门”结构的开启和关闭。输入门决定当前输入的特征向量能否进入记忆细胞,遗忘门决定记忆细胞中的值是否更新或重置,而输入门则决定最终是否能将结果输出。因此这种网络工作时需要输入四种参数,分别是输入向量、输入门控制信号、输出门控制信号以及遗忘门控制信号,通过这些参数控制长短期记忆网络能够按照理想的方式工作。

Figure-2
图2 长短期记忆网络的门结构

由于其内部复杂的构造,长短期记忆网络的计算过程也相比循环神经网络复杂了许多。如图3所示,我们考虑单个长短期记忆网络单元在时间点t时的计算过程。其中$C_{t-1}$表示上一时刻传递下来的细胞状态,$h_{t-1}$表示上一时刻传递来的输出,$x_t$表示当前时刻输入的向量。首先使用$x_t$和$h_{t-1}$这两个输入向量共同计算出以下四个状态。

$$ z=tanh⁡(W⋅[h_{t-1},x_t]+b) \tag{4} $$

$$ z_i=σ(W_i⋅[h_{t-1},x_t]+b_i) \tag{5} $$

$$ z_f=σ(W_f⋅[h_{t-1},x_t]+b_f) \tag{6} $$

$$ z_o=σ(W_o⋅[h_{t-1},x_t]+b_o) \tag{7} $$

通过计算以上公式得到的$z_f$、$z_i$、$z_o$是由$h_{t-1}$和$x_t$的拼接向量乘以权重矩阵($W_f$、$W_i$、$W_o$)后,再通过$sigmoid$激活函数转换控制信号来控制各个“门”结构的状态。$z$是把拼接向量通过$tanh$激活函数转换成-1到1之间的值。接下来将在长短期记忆网络内部使用这四个状态。

$$ C_t=z_f⊙C_(t-1)+z_i⊙z \tag{8} $$

$$ h_t=z_o⊙tanh⁡(c_t) \tag{9} $$

长短期记忆网络单元内部将会进行三个主要阶段。首先是忘记阶段,这个阶段主要是通过计算得到的$z_f$状态作为“遗忘门”控制信号,控制LSTM单元对上一个时间点时传递进来的输入$C_{t-1}$进行选择性忘记。下一阶段是选择记忆阶段,这一阶段会有选择的记忆当前时间的输入$x_t$。把通过公式(5)得出的状态$z_i$作为“输入门”的控制信号,将有选择记忆后的结果和上一阶段保留的记忆相加,从而得到传输给下一时间的状态$C_t$,如公式(8)所示。最后一个阶段是输出阶段,这个阶段将决定哪些信息可以作为当前状态的输出。这一阶段对当前细胞状态$C_t$用$tanh$激活函数进行处理,再把$z_o$状态作为输出门控制信号,控制“输出门”确定输出的部分,最终得到当前时间的输出$h_t$。如果该单元位于长短期记忆网络的输出层,那么将会把当前状态的输出$h_t$作为网络的输出向量$y_t$。

Figure-3
图3 单个长短期记忆网络单元示意图

时间序列处理

滑动窗口技术

时间序列是一种有序的序列,在对其进行处理的过程中,滑动窗口技术是最常见的处理方法之一。滑动窗口技术是在大规模的数据不断流入的情况下,保持窗口的数据不断更新的技术,其更新采样的过程如图4所示。保持采样窗口时间步长为k+1,其中前k个时间点的数据为模型的输入数据,第k+1个数据为这一组数据的标签,即时间序列预测的真实值。采样后采样窗口仍保持时间步长不变,往后滑动一个时间点,继续采样。保持上述过程不断采样,直到采样窗口的终点时刻滑动到数据终点时刻,采样结束。这样可以保证每次采样的过程中采样窗口内会更新一个最新的数据并且删除一个过去数据。

Figure-4
图4 滑动窗口技术

数据随机化

无论是长短期记忆网络还是线性回归模型都属于神经网络模型,训练时都是采用梯度下降的方式更新内部参数。这种算法对输入数据的随机性十分敏感,如果输入数据是有序的,在反向更新时梯度下降的方向会是固定的,而随机无序的数据集上梯度下降的方向也是随机的。随机的梯度下降方向可以帮助模型更迅速地找到局部最优解,从而提高模型训练效率。

因此在对数据进行采样后,要对其进行随机化处理,以提高接下来模型训练的效率和效果。

数据标准化

由于我们要处理的数据变化范围巨大,而这种形式的数据是不利于神经网络进行处理的。因此下一步我们将对数据进行放缩到合适的形式,使用的方法为标准化处理。先通过公式(10)(11)求出数据的均值向量和标准差向量,再通过公式(12)将数据放缩到到围绕0上下波动的数据。

$$ mean(x)=\frac{1}{n}{\sum_{i=1}^nx} \tag{10} $$

$$ std(x)=\sqrt{\frac{1}{n}\sum_{i=1}^n(x-mean)^2} \tag{11} $$

$$ x'=\frac{x-mean}{std} \tag{12} $$

数据划分

为了验证模型最终的训练成果,当模型训练结束后,让模型在未训练过的测试数据集上进行测试。因此要对数据进行划分。取其中80%的数据作为训练集数据,另外20%作为测试集数据。

基于LSTM的时间序列预测模型

设计的模型如图5所示,模型隐藏层是一个单层的LSTM层,使用$tanh$函数作为激活函数,用于分析处理输入的时间序列数据。由于模型预测的结果是后一天的确诊人数,即输出的值只有一个,所以输出层只需要一个神经元,无激活函数。

Figure-5
图5 基于LSTM的时间序列预测模型

训练时将通过计算损失函数来比较预测值与真实值之间的差距,从而对网络进行训练。比较常用的损失函数有交叉熵(Cross-entropy)损失函数、均方差(Mean Square)损失函数等。其中交叉熵损失函数用于分类问题,而均方差损失函数适用于回归、预测等问题,因此本次实验采用均方差损失函数。该损失函数公式如(13)所示,其中$y_i$表示神经网络通过正向传播计算出的预测值,$\hat{y_i}$表示真实值。

$$ L=\frac{1}{N}\sum_{i=1}^N(y_i-\hat{y_i})^2 \tag{13} $$

为了提高模型训练的效率,可以使用优化算法对训练过程进行优化。常用的优化算法有随机梯度下降法(SGD)、RMSprop、Adam等。本次实验选择了Adam作为优化器的优化方法。Adam算法是通过对随机梯度下降算法进行优化而来,已被广泛应用。传统梯度下降算法采用的是某一固定学习率来更新神经元内参数,而Adam在每一次训练时都会通过计算该点梯度的一阶矩估计和二阶矩估计来为网络内不同参数分别设置独立的学习率。这种算法结合了适应性梯度算法(AdaGrad)和均方根传播(RMSProp)两种优化算法的优点,从而能够极大地提高模型训练效率。

将训练集数据输入至搭建好的模型中,同时将测试集数据也输入到模型中,让模型每完成一轮训练后,都在测试集上进行一次测试,从而能够观测到每轮训练后模型在测试集上的效果。随后让模型训练100次,并且在每轮训练后分别记录下训练集和测试集上的平均绝对值误差(Mean Absolute Error, MAE),来作为我们评判每轮训练结果好坏的标准。平均绝对值误差越小,说明预测值与真实值越接近,预测结果越准确。MAE的计算方法如公式(14)所示,其中$y_i$表示神经网络预测值,$\hat{y_i}$表示真实值。

$$ MAE=\frac{1}{n}\sum_{i=1}^n|y_i-\hat{y_i}| \tag{14} $$

实践分析

实践部分,我这里使用的环境是Python3.7+Anaconda4.8+TensorFlow2.0(Keras已经集成为TensorFlow的一个高级API)。另外还用到了Numpy、Pandas、Matplotlib等库。使用的编辑器是Jupyter,这个编辑器可以逐行运行代码,还可以使用一些魔法命令,在数据处理领域比较方便。

导入各种开源库

这里需要注意的是,由于我是用的Tensorflow2.0的Keras API并且使用了GPU加速,所以不能直接导入Keras库。

import tensorflow as tf
physical_devices = tf.config.experimental.list_physical_devices('GPU')
tf.config.experimental.set_memory_growth(physical_devices[0], enable=True)

from tensorflow import keras
from tensorflow.keras import models
from tensorflow.keras import layers
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

获取数据

COVID-19的数据很容易获得。国内数据可以通过丁香园、国家卫建委等网站获得,国际数据可以参考WHO官网每日发布的数据。同时还有许多整合了多方信息的第三方提供的数据源,如著名的数学建模和数据分析竞赛平台Kaggle等。这里我们就使用的是GitHub上找到的一个数据接口。按照这个项目提供的readme.md介绍,只需要如下一行代码即可获得COVID-19全球各个国家从疫情开始至今的全部数据。

data = pd.read_csv("https://raw.githubusercontent.com/canghailan/Wuhan-2019-nCoV/master/Wuhan-2019-nCoV.csv")

通过data.head()这个命令可以看到前五行数据,如图6所示。

Figure-6
图6 获取到的COVID-19数据

数据预处理

数据补全

上文我们获得到的数据一共包涵213个国家,数据的内容包括日期、国家、国家代码等等,很多是我们不需要的。我们只需要保留美国的确诊人数和对应的日期数据。然后再把日期作为数据的索引,使得这串数据变成以日期为索引的时间序列数据。

us_data = data[data.country == '美国']
us_data = us_data.set_index('date')
us_data = us_data[['confirmed']]

然后通过us_data.head()us_data.shape()看一下我们制作好的时间序列数据,如图7所示。

Figure-7

这里就会发现一个问题,获得的数据是从2020年1月22日开始至今,应该一共是168天,但是我们这里获得的数据只有166条。经过按月排查发现,获得到的数据缺失了2月4日和2月5日这两天的记录。我们暂且假设这两天的确诊人数和2月3日相同,将数据补全,便于后续处理预测。

dict_add = {'confirmed': [5, 5]}

df_add = pd.DataFrame(dict_add, index=["2020-02-04", "2020-02-05"])

data_new = pd.concat([us_data[:13], df_add])
data_new = pd.concat([data_new, us_data[13:]])

us_data = data_new

数据采样

使用滑动窗口技术对数据进行采样,这里我们将采样窗口步长设为11。

sequence_length = 10 # 读取10天数据
delay = 1 # 预测后一天数据

data_ = []
for i in range(len(us_data) - sequence_length - delay +1):
    data_.append(us_data.iloc[i: i + sequence_length + delay])

data_ = np.array([df.values for df in data_])
data_copy = data_.copy()

数据随机化

经过上一步采样后的数据集还是按照时间有序排列的,因此我们这一步要对数据进行随机化处理。

np.random.shuffle(data_)

数据标准化

使用us_data.plot()可以通过Matplotlib库将数据绘制成图像,从而直观地看出整体数据的变化趋势,如图8所示。

Figure-8
图8 美国确诊人数变化

可以很明显地看出,确诊人数变化幅度巨大。正如我们上文所说,这是不便于神经网络处理的。因此我们对其进行标准化处理。

mean = data_.mean(axis=0)
std = data_.std(axis=0)
data_ = (data_ - mean)/std

数据划分

最后我们将数据划分为训练集和测试集

split_boundary = int(data_.shape[0] * 0.8) # 80%的数据作为训练集,20%作为测试集

x = data_[:, :-delay]
y = data_[:, -1]

train_x = x[: split_boundary]
test_x = x[split_boundary:]

train_y = y[: split_boundary]
test_y = y[split_boundary:]

模型搭建、训练

终于到了最关键的一步,我们接下来要使用Keras搭建上文设计好的时间序列预测模型,并按照前文描述的方法进行训练,训练100次。

model_lstm = models.Sequential()
model_lstm.add(layers.LSTM(1024, input_shape=(train_x.shape[1:])))
model_lstm.add(layers.Dense(1))
model_lstm.compile(optimizer='adam',
              loss='mse',
              metrics=['mae']
)
history_lstm = model_lstm.fit(train_x, train_y,
                   epochs = 100,
                   validation_data = (test_x, test_y)
)

结果分析

训练好后我们通过Matplotlib库将模型在训练集和测试集上产生的MAE记录绘制成图像,便于我们直观地观察,绘制出的图像如图9所示。

plt.plot(history_lstm.epoch, history_lstm.history['mae'], c='g', label='train')
plt.plot(history_lstm.epoch, history_lstm.history['val_mae'], c='r', label='test')
plt.legend()
plt.xlabel('times of training')
plt.ylabel('MAE')
plt.show()

Figure-9
图9 模型在训练集和测试集上产生的MAE

简单地看图说话一下,可以看出,前20轮训练时,预测出的值平均绝对值误差变化十分明显,虽然有波动但是总体趋势是迅速下降的。约20次训练后,产生的MAE已经十分接近于0,并且其变化幅度十分平缓。可以大致推测出,模型在前20次训练中快速学习到了某些“知识”,使得其可以越来越准确地进行预测,快速降低MAE。

接下来,我们将测试训练好的模型的能力。把全部数据按顺序输入到已经训练好的模型里,将其预测值与真实值绘制到同一张图上,绘制出的图像如图10所示。

data_copy = (data_copy-mean)/std
x = data_copy[:, :-delay].astype(np.float32)
y_p_lstm = model_lstm.predict(x)
y_heat = data_copy[:, -1]
plt.plot(y_heat, c='g', label='real data')
plt.plot(y_p_lstm, c='r', label='LSTM')
plt.legend()
plt.xlabel('date')
plt.ylabel('amount of confirmed after standardization')
plt.show()

Figure-10
图10 模型预测结果与真实值

从图中可以很直观地发现两条曲线几乎完全重合,这表明长短期记忆网络经过100次训练后,已经可以相当完美地拟合出真实的COVID-19病毒感染确诊人数变化曲线。这充分且直观地验证了模型对时间序列预测的准确性。

总结

本文基于长短期记忆网络搭时间序列预测模型。对LSTM算法和模型以及训练过程进行了详细的介绍。同时将设计的模型应用于COVID-19时间序列,对模型预测的过程以及产生的结果进行分析,验证了理论的正确性。后续可以通过提高长短期记忆网络的层数或增加每一层的神经元、长短期记忆网络与其他神经网络模型组合、阅读相关文献了解有关COVID-19更多的信息等方法进一步提高模型预测的准确度。

基于LSTM的时间序列预测是我大四的毕业设计题目,本文内容是其中的主要内容之一。作为刚接触深度学习没多久的新手,还有很多没有搞明白的地方。从文章中也许可以看出很多的纰漏,也请各位大佬批评指正。接下来我将继续对深度学习的研究,以后也会随着知识的提升而产出更多更有质量的文章。同时也希望本文能够帮助到和我一样刚刚入门的新人,帮助他们更容易地理解深度学习和时间序列预测的方法。

参考文献

Last modification:July 24th, 2020 at 11:31 am
如果觉得我的文章对你有用,请随意赞赏