如何优雅地用 TensorFlow 预测时间序列:TFTS 库详细教程 | 雷锋网

如何用 TensorFlow 结合 LSTM 来做时间序列预测其实是一个很老的话题,然而却一直没有得到比较好的解决。如果在 Github 上搜索 “tensorflow time series”,会发现 star 数最高的 tgjeon/TensorFlow-Tutorials-for-Time-Series ( http://t.cn/Rpvepai)已经和 TF 1.0 版本不兼容了,并且其他的项目使用的方法也各有不同,比较混乱。 在刚刚发布的 TensorFlow 1.3 版本中,引入了一个 TensorFlow Time Series 模块(源码地址为:tensorflow/tensorflow ( http://t.cn/RpvebbN),以下简称为 TFTS)。TFTS 专门设计了一套针对时间序列预测问题的 API,目前提供 AR、Anomaly Mixture AR、LSTM 三种预测模型。

由于是刚刚发布的库,文档还是比较缺乏的,我通过研究源码,大体搞清楚了这个库的设计逻辑和使用方法,这篇文章是一篇教程帖,会详细的介绍 TFTS 库的以下几个功能:

  • 读入时间序列数据(分为从 numpy 数组和 csv 文件两种方式)
  • 用 AR 模型对时间序列进行预测
  • 用 LSTM 模型对时间序列进行预测(包含单变量和多变量)

先上效果图,使用 AR 模型预测的效果如下图所示,蓝色线是训练数据,绿色为模型拟合数据,红色线为预测值:

使用 LSTM 进行单变量时间序列预测:

使用 LSTM 进行多变量时间序列预测(每一条线代表一个变量):

文中涉及的所有代码已经保存在 Github 上了,地址是:hzy46/TensorFlow-Time-Series-Examples ( http://t.cn/RpvBIrU),以下提到的所有代码和文件都是相对于这个项目的根目录来说的。

时间序列问题的一般形式

一般地,时间序列数据可以看做由两部分组成:观察的时间点观察到的值。以商品价格为例,某年一月的价格为 120 元,二月的价格为 130 元,三月的价格为 135 元,四月的价格为 132 元。那么观察的时间点可以看做是 1,2,3,4,而在各时间点上观察到的数据的值为 120,130,135,132。

从 Numpy 数组中读入时间序列数据

如何将这样的时间序列数据读入进来?TFTS 库中提供了两个方便的读取器 NumpyReader 和 CSVReader。前者用于从 Numpy 数组中读入数据,后者则可以从 CSV 文件中读取数据。

我们利用 np.sin,生成一个实验用的时间序列数据,这个时间序列数据实际上就是在正弦曲线上加上了上升的趋势和一些随机的噪声:

如图:

横坐标对应变量 “x”,纵坐标对应变量 “y”,它们就是我们之前提到过的 “观察的时间点” 以及 “观察到的值”。TFTS 读入 x 和 y 的方式非常简单,请看下面的代码:

data={ tf.contrib.timeseries.TrainEvalFeatures.TIMES:x, tf.contrib.timeseries.TrainEvalFeatures.VALUES:y, } reader=NumpyReader(data)

我们首先把 x 和 y 变成 python 中的词典(变量 data)。变量 data 中的键值 tf.contrib.timeseries.TrainEvalFeatures.TIMES 实际就是一个字符串 “times”,而 tf.contrib.timeseries.TrainEvalFeatures.VALUES 就是字符串”values”。所以上面的定义直接写成 “data = {‘times’:x, ‘values’:y}” 也是可以的。写成比较复杂的形式是为了和源码中的写法保持一致。

得到的 reader 有一个 read_full() 方法,它的返回值就是时间序列对应的 Tensor,我们可以用下面的代码试验一下:

with tf.Session() as sess: full_data=reader.read_full() # 调用 read_full 方法会生成读取队列 # 要用 tf.train.start_queue_runners 启动队列才能正常进行读取 coord=tf.train.Coordinator() threads=tf.train.start_queue_runners(sess=sess,coord=coord) print(sess.run(full_data)) coord.request_stop()

不能直接使用 sess.run(reader.read_full()) 来从 reader 中取出所有数据。原因在于 read_full() 方法会产生读取队列,而队列的线程此时还没启动,我们需要使用 tf.train.start_queue_runners 启动队列,才能使用 sess.run() 来获取值。

我们在训练时,通常不会使用整个数据集进行训练,而是采用 batch 的形式。从 reader 出发,建立 batch 数据的方法也很简单:

train_input_fn=tf.contrib.timeseries.RandomWindowInputFn( reader,batch_size=2,window_size=10)

tf.contrib.timeseries.RandomWindowInputFn 会在 reader 的所有数据中,随机选取窗口长度为 window_size 的序列,并包装成 batch_size 大小的 batch 数据。换句话说,一个 batch 内共有 batch_size 个序列,每个序列的长度为 window_size。

以 batch_size=2, window_size=10 为例,我们可以打出一个 batch 内的数据:

with tf.Session() as sess: batch_data=train_input_fn.create_batch() coord=tf.train.Coordinator() threads=tf.train.start_queue_runners(sess=sess,coord=coord) one_batch=sess.run(batch_data[0]) coord.request_stop() print('one_batch_data:',one_batch)

这部分读入代码的地址为 https://github.com/hzy46/TensorFlow-Time-Series-Examples/blob/master/test_input_array.py

从 CSV 文件中读入时间序列数据

有的时候,时间序列数据是存在 CSV 文件中的。我们当然可以将其先读入为 Numpy 数组,再使用之前的方法处理。更方便的做法是使用 tf.contrib.timeseries.CSVReader 读入。项目中提供了一个 test_input_csv.py 代码 ( http://t.cn/RpvgxmE),示例如何将文件./data/period_trend.csv 中的时间序列读入进来。

假设 CSV 文件的时间序列数据形式为:

1,-0.6656603714 2,-0.1164380359 3,0.7398626488 4,0.7368633029 5,0.2289480898 6,2.257073255 7,3.023457405 8,2.481161007 9,3.773638612 10,5.059257738 11,3.553186083

CSV 文件的第一列为时间点,第二列为该时间点上观察到的值。将其读入的方法为:

# coding: utf-8 from__future__import print_function import tensorflow as tf csv_file_name='./data/period_trend.csv' reader=tf.contrib.timeseries.CSVReader(csv_file_name)

从 reader 建立 batch 数据形成 train_input_fn 的方法和之前完全一样。下面我们就利用这个 train_input_fn 来训练模型。

使用 AR 模型预测时间序列

自回归模型(Autoregressive model,可以简称为 AR 模型)是统计学上处理时间序列模型的基本方法之一。在 TFTS 中,已经实现了一个自回归模型。使用 AR 模型训练、验证并进行时间序列预测的示例程序为 train_array.py ( http://t.cn/Rpvdea4)。

先建立一个 train_input_fn:

x=np.array(range(1000)) noise=np.random.uniform(-0.2,0.2,1000) y=np.sin(np.pi*x/100)+x/200.+noise plt.plot(x,y) plt.savefig('timeseries_y.jpg') data={ tf.contrib.timeseries.TrainEvalFeatures.TIMES:x, tf.contrib.timeseries.TrainEvalFeatures.VALUES:y, } reader=NumpyReader(data) train_input_fn=tf.contrib.timeseries.RandomWindowInputFn( reader,batch_size=16,window_size=40)

针对这个序列,对应的 AR 模型的定义就是:

ar=tf.contrib.timeseries.ARRegressor( periodicities=200,input_window_size=30,output_window_size=10, num_features=1, loss=tf.contrib.timeseries.ARModel.NORMAL_LIKELIHOOD_LOSS)

这里的几个参数比较重要,分别给出解释。第一个参数 periodicities 表示序列的规律性周期。我们在定义数据时使用的语句是:“y = np.sin(np.pi * x / 100) + x / 200. + noise”,因此周期为 200。input_window_size 表示模型每次输入的值,output_window_size 表示模型每次输出的值。input_window_size 和 output_window_size 加起来必须等于 train_input_fn 中总的 window_size。在这里,我们总的 window_size 为 40,input_window_size 为 30,output_window_size 为 10,也就是说,一个 batch 内每个序列的长度为 40,其中前 30 个数被当作模型的输入值,后面 10 个数为这些输入对应的目标输出值。最后一个参数 loss 指定采取哪一种损失,一共有两种损失可以选择,分别是 NORMAL_LIKELIHOOD_LOSS 和 SQUARED_LOSS。

num_features 参数表示在一个时间点上观察到的数的维度。我们这里每一步都是一个单独的值,所以 num_features=1。

除了程序中出现的几个参数外,还有一个比较重要的参数是 model_dir。它表示模型训练好后保存的地址,如果不指定的话,就会随机分配一个临时地址。

使用变量 ar 的 train 方法可以直接进行训练:

ar.train(input_fn=train_input_fn, steps=6000)

TFTS 中验证 (evaluation) 的含义是:使用训练好的模型在原先的训练集上进行计算,由此我们可以观察到模型的拟合效果,对应的程序段是:

evaluation_input_fn=tf.contrib.timeseries.WholeDatasetInputFn(reader) evaluation=ar.evaluate(input_fn=evaluation_input_fn,steps=1)

如果要理解这里的逻辑,首先要理解之前定义的 AR 模型:它每次都接收一个长度为 30 的输入观测序列,并输出长度为 10 的预测序列。整个训练集是一个长度为 1000 的序列,前 30 个数首先被当作 “初始观测序列” 输入到模型中,由此就可以计算出下面 10 步的预测值。接着又会取 30 个数进行预测,这 30 个数中有 10 个数就是前一步的预测值,新得到的预测值又会变成下一步的输入,以此类推。

最终我们得到 970 个预测值(970=1000-30,因为前 30 个数是没办法进行预测的)。这 970 个预测值就被记录在 evaluation[‘mean’] 中。evaluation 还有其他几个键值,如 evaluation[‘loss’] 表示总的损失,evaluation[‘times’] 表示 evaluation[‘mean’] 对应的时间点等等。

evaluation[‘start_tuple’] 会被用于之后的预测中,它相当于最后 30 步的输出值和对应的时间点。以此为起点,我们可以对 1000 步以后的值进行预测,对应的代码为:

(predictions,)=tuple(ar.predict( input_fn=tf.contrib.timeseries.predict_continuation_input_fn( evaluation,steps=250)))

这里的代码在 1000 步之后又像后预测了 250 个时间点。对应的值就保存在 predictions[‘mean’] 中。我们可以把观测到的值、模型拟合的值、预测值用下面的代码画出来:

plt.figure(figsize=(15,5)) plt.plot(data['times'].reshape(-1),data['values'].reshape(-1),label='origin') plt.plot(evaluation['times'].reshape(-1),evaluation['mean'].reshape(-1),label='evaluation') plt.plot(predictions['times'].reshape(-1),predictions['mean'].reshape(-1),label='prediction') plt.xlabel('time_step') plt.ylabel('values') plt.legend(loc=4) plt.savefig('predict_result.jpg')

画好的图片会被保存为 “predict_result.jpg”

使用 LSTM 预测单变量时间序列

注意:以下 LSTM 模型的例子必须使用 TensorFlow 最新的开发版的源码。具体来说,要保证 “from tensorflow.contrib.timeseries.python.timeseries.estimators import TimeSeriesRegressor” 可以成功执行。

给出两个用 LSTM 预测时间序列模型的例子,分别是 train_lstm.py ( http://t.cn/RpvdcbO) 和 train_lstm_multivariate.py ( http://t.cn/RpvBek7)。前者是在 LSTM 中进行单变量的时间序列预测,后者是使用 LSTM 进行多变量时间序列预测。为了使用 LSTM 模型,我们需要先使用 TFTS 库对其进行定义,定义模型的代码来源于 TFTS 的示例源码 ( http://t.cn/RpvrF9L),在 train_lstm.py 和 train_lstm_multivariate.py 中分别拷贝了一份。

我们同样用函数加噪声的方法生成一个模拟的时间序列数据:

x=np.array(range(1000)) noise=np.random.uniform(-0.2,0.2,1000) y=np.sin(np.pi*x/50)+np.cos(np.pi*x/50)+np.sin(np.pi*x/25)+noise data={ tf.contrib.timeseries.TrainEvalFeatures.TIMES:x, tf.contrib.timeseries.TrainEvalFeatures.VALUES:y, } reader=NumpyReader(data) train_input_fn=tf.contrib.timeseries.RandomWindowInputFn( reader,batch_size=4,window_size=100)

此处 y 对 x 的函数关系比之前复杂,因此更适合用 LSTM 这样的模型找出其中的规律。得到 y 和 x 后,使用 NumpyReader 读入为 Tensor 形式,接着用 tf.contrib.timeseries.RandomWindowInputFn 将其变为 batch 训练数据。一个 batch 中有 4 个随机选取的序列,每个序列的长度为 100。

接下来我们定义一个 LSTM 模型:

estimator=ts_estimators.TimeSeriesRegressor( model=_LSTMModel(num_features=1,num_units=128), optimizer=tf.train.AdamOptimizer(0.001))

num_features = 1 表示单变量时间序列,即每个时间点上观察到的量只是一个单独的数值。num_units=128 表示使用隐层为 128 大小的 LSTM 模型。

训练、验证和预测的方法都和之前类似。在训练时,我们在已有的 1000 步的观察量的基础上向后预测 200 步:

estimator.train(input_fn=train_input_fn,steps=2000) evaluation_input_fn=tf.contrib.timeseries.WholeDatasetInputFn(reader) evaluation=estimator.evaluate(input_fn=evaluation_input_fn,steps=1) # Predict starting after the evaluation (predictions,)=tuple(estimator.predict( input_fn=tf.contrib.timeseries.predict_continuation_input_fn( evaluation,steps=200)))

将验证、预测的结果取出并画成示意图,画出的图像会保存成 “predict_result.jpg” 文件:

使用 LSTM 预测多变量时间序列

所谓多变量时间序列,就是指在每个时间点上的观测量有多个值。在 data/multivariate_periods.csv 文件中,保存了一个多变量时间序列的数据:

这个 CSV 文件的第一列是观察时间点,除此之外,每一行还有 5 个数,表示在这个时间点上的观察到的数据。换句话说,时间序列上每一步都是一个 5 维的向量。

使用 TFTS 读入该 CSV 文件的方法为:

与之前的读入相比,唯一的区别就是 column_names 参数。它告诉 TFTS 在 CSV 文件中,哪些列表示时间,哪些列表示观测量。

接下来定义 LSTM 模型:

estimator=ts_estimators.TimeSeriesRegressor( model=_LSTMModel(num_features=5,num_units=128), optimizer=tf.train.AdamOptimizer(0.001))

区别在于使用 num_features=5 而不是 1,原因在于我们在每个时间点上的观测量是一个 5 维向量。

训练、验证、预测以及画图的代码与之前比较类似,可以参考代码 train_lstm_multivariate.py ( http://t.cn/RpvBek7),此处直接给出最后的运行结果:

图中前 100 步是训练数据,一条线就代表观测量在一个维度上的取值。100 步之后为预测值。

总结

这篇文章详细介绍了 TensorFlow Time Series(TFTS)库的使用方法。主要包含三个部分:数据读入、AR 模型的训练、LSTM 模型的训练。文章里使用的所有代码都保存在 Github 上了,地址是:hzy46/TensorFlow-Time-Series-Examples ( http://t.cn/RpvBIrU)。如果觉得有帮助,欢迎点赞或 star~~~

原文发布于微信公众号 - AI研习社(okweiwu)

原文发表时间:2017-09-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AILearning

TF图层指南:构建卷积神经网络

TensorFlow layers模块提供了一个高级API,可以轻松构建神经网络。它提供了便于创建密集(完全连接)层和卷积层,添加激活函数以及应用缺陷正则化的方...

79950
来自专栏专知

【干货】计算机视觉实战系列01——用Python做图像处理

【导读】在当今互联网飞速发展的社会中,数量庞大的图像和视频充斥着我们的生活,让我们需要对图片进行检索、分类等操作时,利用人工手段显然是不现实的,于是,计算机视觉...

1.1K120
来自专栏AI研习社

按照这几个步骤操作,不实现 RNN 都难!

最近在看RNN模型,为简单起见,本篇就以简单的二进制序列作为训练数据,而不实现具体的论文仿真,主要目的是理解RNN的原理和如何在TensorFlow中构造一个简...

31270
来自专栏人工智能LeadAI

如何优雅地用TensorFlow预测时间序列:TFTS库详细教程

前言 如何用TensorFlow结合LSTM来做时间序列预测其实是一个很老的话题,然而却一直没有得到比较好的解决。如果在Github上搜索“tensorflow...

369120
来自专栏目标检测和深度学习

Keras官方中文版文档正式发布

机器之心整理 参与:思源 今年 1 月 12 日,Keras 作者 François Chollet‏ 在推特上表示因为中文读者的广泛关注,他已经在 GitHu...

35060
来自专栏用户2442861的专栏

Caffe学习系列(6):Blob,Layer and Net以及对应配置文件的编写

http://www.cnblogs.com/denny402/p/5073427.html

9210
来自专栏机器之心

终于!Keras官方中文版文档正式发布了

36360
来自专栏专知

【最新TensorFlow1.4.0教程02】利用Eager Execution 自定义操作和梯度 (可在 GPU 运行)

点击上方“专知”关注获取更多AI知识! 【导读】主题链路知识是我们专知的核心功能之一,为用户提供AI领域系统性的知识学习服务,一站式学习人工智能的知识,包含人工...

56760
来自专栏机器之心

资源 | 十倍模型计算时间仅增20%:OpenAI开源梯度替换插件

33290
来自专栏ATYUN订阅号

马尔可夫链文本生成的简单应用:不足20行的Python代码生成鸡汤文

提到自然语言的生成时,人们通常认为要会使用高级数学来思考先进的AI系统,然而,并不一定要这样。在这篇文章中,我将使用马尔可夫链和一个小的语录数据集来产生新的语录...

29060

扫码关注云+社区

领取腾讯云代金券