
时间序列模型
时间序列预测分析就是利用过去一段时间内某事件时间的特征来预测未来一段时间内该事件的特征。这是一类相对比较复杂的预测建模问题,和回归分析模型的预测不同,时间序列模型是依赖于事件发生的先后顺序的,同样大小的值改变顺序后输入模型产生的结果是不同的。
举个栗子:根据过去两年某股票的每天的股价数据推测之后一周的股价变化;根据过去2年某店铺每周想消费人数预测下周来店消费的人数等等
RNN 和 LSTM 模型
时间序列模型最常用最强大的的工具就是递归神经网络(recurrent neural network, RNN)。相比与普通神经网络的各计算结果之间相互独立的特点,RNN的每一次隐含层的计算结果都与当前输入以及上一次的隐含层结果相关。通过这种方法,RNN的计算结果便具备了记忆之前几次结果的特点。
典型的RNN网路结构如下:
右侧为计算时便于理解记忆而产开的结构。简单说,x为输入层,o为输出层,s为隐含层,而t指第几次的计算;V,W,U为权重,其中计算第t次的隐含层状态时为St = f(U*Xt + W*St-1),实现当前输入结果与之前的计算挂钩的目的。对RNN想要更深入的了解可以戳这里。
RNN的局限:
由于RNN模型如果需要实现长期记忆的话需要将当前的隐含态的计算与前n次的计算挂钩,即St = f(U*Xt + W1*St-1 + W2*St-2 + ... + Wn*St-n),那样的话计算量会呈指数式增长,导致模型训练的时间大幅增加,因此RNN模型一般直接用来进行长期记忆计算。
LSTM模型
LSTM(Long Short-Term Memory)模型是一种RNN的变型,最早由Juergen Schmidhuber提出的。经典的LSTM模型结构如下:
LSTM的特点就是在RNN结构以外添加了各层的阀门节点。阀门有3类:遗忘阀门(forget gate),输入阀门(input gate)和输出阀门(output gate)。这些阀门可以打开或关闭,用于将判断模型网络的记忆态(之前网络的状态)在该层输出的结果是否达到阈值从而加入到当前该层的计算中。如图中所示,阀门节点利用sigmoid函数将网络的记忆态作为输入计算;如果输出结果达到阈值则将该阀门输出与当前层的的计算结果相乘作为下一层的输入(PS:这里的相乘是在指矩阵中的逐元素相乘);如果没有达到阈值则将该输出结果遗忘掉。每一层包括阀门节点的权重都会在每一次模型反向传播训练过程中更新。更具体的LSTM的判断计算过程如下图所示:
LSTM模型的记忆功能就是由这些阀门节点实现的。当阀门打开的时候,前面模型的训练结果就会关联到当前的模型计算,而当阀门关闭的时候之前的计算结果就不再影响当前的计算。因此,通过调节阀门的开关我们就可以实现早期序列对最终结果的影响。而当你不不希望之前结果对之后产生影响,比如自然语言处理中的开始分析新段落或新章节,那么把阀门关掉即可。(对LSTM想要更具体的了解可以戳这里)
下图具体演示了阀门是如何工作的:通过阀门控制使序列第1的输入的变量影响到了序列第4,6的的变量计算结果。
黑色实心圆代表对该节点的计算结果输出到下一层或下一次计算;空心圆则表示该节点的计算结果没有输入到网络或者没有从上一次收到信号。
Python中实现LSTM模型搭建
Python中有不少包可以直接调用来构建LSTM模型,比如pybrain, kears, tensorflow, cikit-neuralnetwork等(更多戳这里)。这里我们选用keras。(PS:如果 *** 作系统用的linux或者mac,强推Tensorflow!!!)
因为LSTM神经网络模型的训练可以通过调整很多参数来优化,例如activation函数,LSTM层数,输入输出的变量维度等,调节过程相当复杂。这里只举一个最简单的应用例子来描述LSTM的搭建过程。
应用实例
基于某家店的某顾客的历史消费的时间推测该顾客前下次来店的时间。具体数据如下所示:
消费时间
2015-05-15 14:03:512015-05-15 15:32:462015-06-28 18:00:172015-07-16 21:27:182015-07-16 22:04:512015-09-08 14:59:56..
..
具体 *** 作:
1. 原始数据转化
首先需要将时间点数据进行数值化。将具体时间转化为时间段用于表示该用户相邻两次消费的时间间隔,然后再导入模型进行训练是比较常用的手段。转化后的数据如下:
消费间隔04418054..
..
2.生成模型训练数据集(确定训练集的窗口长度)
这里的窗口指需要几次消费间隔用来预测下一次的消费间隔。这里我们先采用窗口长度为3, 即用t-2, t-1,t次的消费间隔进行模型训练,然后用t+1次间隔对结果进行验证。数据集格式如下:X为训练数据,Y为验证数据。
PS: 这里说确定也不太合适,因为窗口长度需要根据模型验证结果进行调整的。
X1 X2 X3 Y0 44 18 044 18 0 54..
..
注:直接这样预测一般精度会比较差,可以把预测值Y根据数值bin到几类,然后用转换成one-hot标签再来训练会比较好。比如如果把Y按数值范围分到五类(1:0-20,2:20-40,3:40-60,4:60-80,5:80-100)上式可化为:
X1 X2 X3 Y0 44 18 044 18 0 4...
Y转化成one-hot以后则是(关于one-hot编码可以参考这里)
1 0 0 0 00 0 0 0 1...
3. 网络模型结构的确定和调整
这里我们使用python的keras库。(用java的同学可以参考下deeplearning4j这个库)。网络的训练过程设计到许多参数的调整:比如
需要确定LSTM模块的激活函数(activation fucntion)(keras中默认的是tanh);
确定接收LSTM输出的完全连接人工神经网络(fully-connected artificial neural network)的激活函数(keras中默认为linear);
确定每一层网络节点的舍弃率(为了防止过度拟合(overfit)),这里我们默认值设定为0.2;
确定误差的计算方式,这里我们使用均方误差(mean squared error);
确定权重参数的迭代更新方式,这里我们采用RMSprop算法,通常用于RNN网络。
确定模型训练的epoch和batch size(关于模型的这两个参数具体解释戳这里)
一般来说LSTM模块的层数越多(一般不超过3层,再多训练的时候就比较难收敛),对高级别的时间表示的学习能力越强;同时,最后会加一层普通的神经网路层用于输出结果的降维。典型结构如下:
如果需要将多个序列进行同一个模型的训练,可以将序列分别输入到独立的LSTM模块然后输出结果合并后输入到普通层。结构如下:
4. 模型训练和结果预测
将上述数据集按4:1的比例随机拆分为训练集和验证集,这是为了防止过度拟合。训练模型。然后将数据的X列作为参数导入模型便可得到预测值,与实际的Y值相比便可得到该模型的优劣。
实现代码
时间间隔序列格式化成所需的训练集格式
import pandas as pdimport numpy as npdef create_interval_dataset(dataset, look_back):""" :param dataset: input array of time intervals :param look_back: each training set feature length :return: convert an array of values into a dataset matrix. """
dataX, dataY = [], [] for i in range(len(dataset) - look_back):
dataX.append(dataset[i:i+look_back])
dataY.append(dataset[i+look_back]) return np.asarray(dataX), np.asarray(dataY)
df = pd.read_csv("path-to-your-time-interval-file")
dataset_init = np.asarray(df) # if only 1 columndataX, dataY = create_interval_dataset(dataset, lookback=3) # look back if the training set sequence length
这里的输入数据来源是csv文件,如果输入数据是来自数据库的话可以参考这里
LSTM网络结构搭建
import pandas as pdimport numpy as npimport randomfrom keras.models import Sequential, model_from_jsonfrom keras.layers import Dense, LSTM, Dropoutclass NeuralNetwork():def __init__(self, **kwargs):
""" :param **kwargs: output_dim=4: output dimension of LSTM layeractivation_lstm='tanh': activation function for LSTM layersactivation_dense='relu': activation function for Dense layeractivation_last='sigmoid': activation function for last layerdrop_out=0.2: fraction of input units to dropnp_epoch=10, the number of epoches to train the model. epoch is one forward pass and one backward pass of all the training examplesbatch_size=32: number of samples per gradient update. The higher the batch size, the more memory space you'll needloss='mean_square_error': loss functionoptimizer='rmsprop' """
self.output_dim = kwargs.get('output_dim', 8) self.activation_lstm = kwargs.get('activation_lstm', 'relu') self.activation_dense = kwargs.get('activation_dense', 'relu') self.activation_last = kwargs.get('activation_last', 'softmax') # softmax for multiple output
self.dense_layer = kwargs.get('dense_layer', 2) # at least 2 layers
self.lstm_layer = kwargs.get('lstm_layer', 2) self.drop_out = kwargs.get('drop_out', 0.2) self.nb_epoch = kwargs.get('nb_epoch', 10) self.batch_size = kwargs.get('batch_size', 100) self.loss = kwargs.get('loss', 'categorical_crossentropy') self.optimizer = kwargs.get('optimizer', 'rmsprop') def NN_model(self, trainX, trainY, testX, testY):
""" :param trainX: training data set :param trainY: expect value of training data :param testX: test data set :param testY: epect value of test data :return: model after training """
print "Training model is LSTM network!"
input_dim = trainX[1].shape[1]
output_dim = trainY.shape[1] # one-hot label
# print predefined parameters of current model:
model = Sequential() # applying a LSTM layer with x dim output and y dim input. Use dropout parameter to avoid overfitting
model.add(LSTM(output_dim=self.output_dim,
input_dim=input_dim,
activation=self.activation_lstm,
dropout_U=self.drop_out,
return_sequences=True)) for i in range(self.lstm_layer-2):
model.add(LSTM(output_dim=self.output_dim,
input_dim=self.output_dim,
activation=self.activation_lstm,
dropout_U=self.drop_out,
return_sequences=True)) # argument return_sequences should be false in last lstm layer to avoid input dimension incompatibility with dense layer
model.add(LSTM(output_dim=self.output_dim,
input_dim=self.output_dim,
activation=self.activation_lstm,
dropout_U=self.drop_out)) for i in range(self.dense_layer-1):
model.add(Dense(output_dim=self.output_dim,
activation=self.activation_last))
model.add(Dense(output_dim=output_dim,
input_dim=self.output_dim,
activation=self.activation_last)) # configure the learning process
model.compile(loss=self.loss, optimizer=self.optimizer, metrics=['accuracy']) # train the model with fixed number of epoches
model.fit(x=trainX, y=trainY, nb_epoch=self.nb_epoch, batch_size=self.batch_size, validation_data=(testX, testY)) # store model to json file
model_json = model.to_json() with open(model_path, "w") as json_file:
json_file.write(model_json) # store model weights to hdf5 file
if model_weight_path: if os.path.exists(model_weight_path):
os.remove(model_weight_path)
model.save_weights(model_weight_path) # eg: model_weight.h5
return model
这里写的只涉及LSTM网络的结构搭建,至于如何把数据处理规范化成网络所需的结构以及把模型预测结果与实际值比较统计的可视化,就需要根据实际情况做调整了。
在实际项目中,我们能接触到很多序列数据,比如用户的行为序列。在构建模型中,这些行为序列能为我们的模型提供更多维度的特征,同时行为序列本身也包含了用户的各种习惯。因为序列数据本身有先后关系,为了更好的建模这种先后关系,我们尝试用 LSTM 模型。
LSTM (Long Short Term Memory network),一种特殊的RNN网络,可以很好的学习长期依赖关系。LSTM 由 Hochreiter &Schmidhuber (1997) 提出,并在近期被 Alex Graves 进行了改良和推广。
其结构如下:
图中主要的计算如下
将行为序列用 id 编码,并填充至固定长度。并切分好训练集、验证集和测试集。
将序列数据用 embedding 转换,用 LSTM 来提取数据,后接 Dense 层,最后输出结果。
这里简要提一下 LSTM 的参数计算:
其中, , , ,所以最终参数为 。
人对一个问题的思考不会完全从头开始。比如你在阅读本片文章的时,你会根据之前理解过的信息来理解下面看到的文字。在理解当前文字的时候,你并不会忘记之前看过的文字,从头思考当前文字的含义。
传统的神经网络并不能做到这一点,这是在对这种序列信息(如语音)进行预测时的一个缺点。比如你想对电影中的每个片段去做事件分类,传统的神经网络是很难通过利用前面的事件信息来对后面事件进行分类。
而循环神经网络(下面简称RNNs)可以通过不停的将信息循环 *** 作,保证信息持续存在,从而解决上述问题。RNNs如下图所示
可以看出A是一组神经网络(可以理解为一个网络的自循环),它的工作是不停的接收 并且输出 。从图中可以看出A允许将信息不停的再内部循环,这样使得它可以保证每一步的计算都保存以前的信息。
这样讲可能还是有点晕,更好的理解方式,也是很多文章的做法,将RNNs的自循环结构展开,像是将同一个网络复制并连成一条线的结构,将自身提取的信息传递给下一个继承者,如下图所示。
这种链式的结构揭示了RNNs与序列和列表类型的数据密切相关。好像他们生来就是为了处理序列类型数据的。
谁说不是呢!在过去的几年里,RNNs在语音识别、文字建模、翻译、字幕等领域有很成功的应用。在Andrej Karpathy写的博客 The Unreasonable Effectiveness of Recurrent Neural Networks 中讨论了RNNs取得的惊人成果,这里就不详细讨论了。
很对成功的案例都有一个共性,就是都用了一种叫LSTMs的特殊的RNNs网络结构。下面就来看看什么是LSTMs。
从之前的描述可以看出来,RNNs理论上是可以将以前的信息与当前的任务进行连接,例如使用以前的视频帧来帮助网络理解当前帧。如果RNNs能做到这一点,那将会是非常的有用。但是他们能做到这点吗?答案是不一定。
有时候我们需要利用近期的信息来执行来处理当前的任务。例如,考虑用一个语言模型通过利用以前的文字信息来预测下一个文字。如果我们需要预测“the clouds are in the sky”这句话的最后一个字,我们不需要其他的信息,通过前面的语境就能知道最后一个字应该是sky。在这种情况下,相关信息与需要该信息的位置距离较近,RNNs能够学习利用以前的信息来对当前任务进行相应的 *** 作。如下图所示通过输入的 信息来预测出
假设现在有个更为复杂的任务,考虑到下面这句话“I grew up in France… I speak fluent French.”,现在需要语言模型通过现有以前的文字信息预测该句话的最后一个字。通过以前文字语境可以预测出最后一个字是某种语言,但是要猜测出French,要根据之前的France语境。这样的任务,不同之前,因为这次的有用信息与需要进行处理信息的地方之间的距离较远,这样容易导致RNNs不能学习到有用的信息,最终推导的任务可能失败。如下图所示。
理论上RNNs是能够处理这种“长依赖”问题的。通过调参来解决这种问题。但是在实践过程中RNNs无法学习到这种特征。 Hochreiter (1991) [German] 和 Bengio, et al. (1994) 深入研究过为什么RNNs没法学习到这种特征。
幸好LSTMs这种特殊的RNNs是没有这个问题的。
Long Short Term Memory networks(以下简称LSTMs),一种特殊的RNN网络,该网络设计出来是为了解决长依赖问题。该网络由 Hochreiter &Schmidhuber (1997) 引入,并有许多人对其进行了改进和普及。他们的工作被用来解决了各种各样的问题,直到目前还被广泛应用。
所有循环神经网络都具有神经网络的重复模块链的形式。 在标准的RNN中,该重复模块将具有非常简单的结构,例如单个tanh层。标准的RNN网络如下图所示
LSTMs也具有这种链式结构,但是它的重复单元不同于标准RNN网络里的单元只有一个网络层,它的内部有四个网络层。LSTMs的结构如下图所示。
在解释LSTMs的详细结构时先定义一下图中各个符号的含义,符号包括下面几种
图中黄色类似于CNN里的激活函数 *** 作,粉色圆圈表示点 *** 作,单箭头表示数据流向,箭头合并表示向量的合并(concat) *** 作,箭头分叉表示向量的拷贝 *** 作
LSTMs的核心是细胞状态,用贯穿细胞的水平线表示。
细胞状态像传送带一样。它贯穿整个细胞却只有很少的分支,这样能保证信息不变的流过整个RNNs。细胞状态如下图所示
LSTM网络能通过一种被称为门的结构对细胞状态进行删除或者添加信息。
门能够有选择性的决定让哪些信息通过。其实门的结构很简单,就是一个sigmoid层和一个点乘 *** 作的组合。如下图所示
因为sigmoid层的输出是0-1的值,这代表有多少信息能够流过sigmoid层。0表示都不能通过,1表示都能通过。
一个LSTM里面包含三个门来控制细胞状态。
前面提到LSTM由三个门来控制细胞状态,这三个门分别称为忘记门、输入门和输出门。下面一个一个的来讲述。
LSTM的第一步就是决定细胞状态需要丢弃哪些信息。这部分 *** 作是通过一个称为忘记门的sigmoid单元来处理的。它通过查看 和 信息来输出一个0-1之间的向量,该向量里面的0-1值表示细胞状态 中的哪些信息保留或丢弃多少。0表示不保留,1表示都保留。忘记门如下图所示。
下一步是决定给细胞状态添加哪些新的信息。这一步又分为两个步骤,首先,利用 和 通过一个称为输入门的 *** 作来决定更新哪些信息。然后利用 和 通过一个tanh层得到新的候选细胞信息 ,这些信息可能会被更新到细胞信息中。这两步描述如下图所示。
下面将更新旧的细胞信息 ,变为新的细胞信息 。更新的规则就是通过忘记门选择忘记旧细胞信息的一部分,通过输入门选择添加候选细胞信息 的一部分得到新的细胞信息 。更新 *** 作如下图所示
更新完细胞状态后需要根据输入的 和 来判断输出细胞的哪些状态特征,这里需要将输入经过一个称为输出门的sigmoid层得到判断条件,然后将细胞状态经过tanh层得到一个-1~1之间值的向量,该向量与输出门得到的判断条件相乘就得到了最终该RNN单元的输出。该步骤如下图所示
还是拿语言模型来举例说明,在预测动词形式的时候,我们需要通过输入的主语是单数还是复数来推断输出门输出的预测动词是单数形式还是复数形式。
之前描述的LSTM结构是最为普通的。在实际的文章中LSTM的结构存在各种变式,虽然变化都不会太大,但是也值得一提。
其中一个很受欢迎的变式由 Gers &Schmidhuber (2000) 提出,它在LSTM的结构中加入了“peephole connections.”结构,peephole connections结构的作用是允许各个门结构能够看到细胞信息,具体如下图所示。
上图描绘的是所有门都能看到细胞信息,还有一些变式是在其中的某些门引入细胞信息。
还有一种变式是在忘记门与输入门之间引入一个耦合。不同于之前的LSTM结构,忘记门和输入门是独立的,这个变式是在忘记门删除历史信息的位置加入新的信息,在加入新信息的位置删除旧信息。该结构如下图所示。
一种比其他形式变化更为显著的LSTM变式是由 Cho, et al. (2014) 提出的门循环单元(GRU)。它将忘记门和输入门合并成一个新的门,称为更新门。GRU还有一个门称为重置门。如下图所示
其中重置门为上图中前面那个门,决定了如何将新的输入信息与前面的记忆相结合。更新门为上图中后面那个门,定义了前面记忆保存到当前时间步的量。 由于该变式的简单有效,后来被广泛应用。
这里介绍的只是一些较为有名的LSTM变式,关于LSTM的变式其实还有很多种,像 Yao, et al. (2015) 提出的Depth Gated RNNs。还有其他用于解决长依赖问题的方法,如由 Koutnik, et al. (2014) 提出的 Clockwork RNNs。
至于哪种变式效果最好?各种差异对LSTM的影响有多少?这些问题 Greff, et al. (2015) 做了一些对比,结论是他们基本是一样的。 Jozefowicz, et al. (2015) 测试了一万多种RNN结构,发现在某些指定任务上有些变式还是由于标准LSTMs的。
之前也提到过RNNs取得了不错的成绩,这些成绩很多是基于LSTMs来做的,说明LSTMs适用于大部分的序列场景应用。
一般文章写法会堆一堆公式吓唬人,希望本文一步一步的拆分能有助于大家的理解。
LSTMs对于RNNs的使用是一大进步。那么现在还有个问题,是否还有更大的进步?对于很多研究者来说,但是是肯定的,那就是attention的问世。attention的思想是让RNN在每一步挑选信息的时候都能从更大的信息集里面挑选出有用信息。例如,利用RNN模型为一帧图片生成字母,它将会选择图片有用的部分来得到有用的输入,从而生成有效的输出。事实上, Xu, et al. (2015) 已经这么做了,如果你想更深入的了解attention,这会是一个不错的开始。attention方向还有一些振奋人心的研究,但还有很多东西等待探索......
在RNN领域attention并不是唯一一个可以研究的点。比如 Kalchbrenner, et al. (2015) 提出的Grid LSTMs, Gregor, et al. (2015) , Chung, et al. (2015) , 和 Bayer &Osendorfer (2015) 将RNNs用于生成模型的研究都非常有意思。
在过去几年RNNs方面的研究非常的多,相信以后的研究成果也会更为丰富。
同原文
欢迎加入OCR交流群:785515057(此群已满)
欢迎加入OCR交流群2:826714963
原文链接
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)