RNN的应用有很多,尤其是两个RNN组成的Seq2Seq结构,在时序预测、自然语言处理等方面有很大的用处,而每个RNN中一个节点是一个Cell,它是RNN中的基本结构。本文从如何使用RNN建模数据开始,重点解释RNN中Cell的结构,以及Keras中Cell相关的输入输出及其维度。我已经尽量解释了每个变量,但可能也有忽略,因此可能对RNN之前有一定了解的人会更友好,本文最主要的目的是描述Keras中RNNcell的参数以及输入输出的两个注意点。如有问题也欢迎指出,我会进行修改。
关于RNN模型的更加理论的解释,可以参考之前的文章:深度学习之RNN模型 | 数据学习者官方网站(Datalearner)
目录如下:
- 一、基于Seq2Seq来预测时序数据
- 二、RNN中的Cell如何对数据建模
- 三、Simple RNN Cell的建模理论数学说明
- 四、Keras中SimpleRNNCell的输入输出和参数
一、基于Seq2Seq来预测时序数据
我们知道多个RNN Cell连接就可以称为一个RNN模型,两个RNN相连接,可以组成一个Seq2Seq模型,即第一个RNN一般称为Encoder,第二个RNN称为Decoder,第一个RNN的最后一个输出作为第二个RNN的输入,这种Seq2Seq的结构在实际中有很多应用,包括时序数据预测、自然语言处理等。以时序数据为例,Encoder建模对象是过去N天的已知数据,如过去30天某个APP的流量,而Decoder建模对象就是未来M天的未知数据,如我们需要预测的未来7天某个APP的流量。
原理也很简单,就是说我们要用过去的数据预测未来,但是直接将过去的数据作为输入的话,形成回归或者分类模型的话,那么过去N天之间的关联关系我们就没有用到。例如,假如我们从历史数据中抽取了如下序列,来训练模型,预测未来:
这种数据传统回归模型并不好用,一方面历史数据之间的关联关系没有利用,另一方面基于这种可以说是多分类的模型去预测也准确性很低。
利用Seq2Seq模型则比较适合,我们把过去30天当作一个RNN模型,即Seq2Seq的Encoder,Encoder中每一个Cell都对历史的一天建模,且都使用前一天的输出来构建后一个Cell的输入,那么最后一个Cell的输出也就包含了历史的信息(由于本文仅关注原理,因此这里不讨论过长的序列导致模型训练困难等其它缺点)。再使用这个Encoder输出作为Decoder阶段的输入来预测未来,那么就避免了上述问题。
二、RNN中的Cell如何对数据建模
那么一个Cell如何对数据建模呢?以上述时序数据预测为例,那么一个Encoder中的Cell就对应历史某一天。这时候Cell的输入数据就是两个,一个是前一天Cell的状态输出,另一个是当前Cell的特征输入,特征就是今天时序特征,通常,在时序数据预测中,某一个时间点的特征就是如一年的第几天,第几个季度,7天前流量值等等,这些都可以是特征。
三、Simple RNN Cell的建模理论数学说明
下图是一个Simple RNN Cell的图示:
如前所述,一个Cell的输入包括两个:
一个是前一阶段的状态,即上图中a(0),初始的第一个Cell的前一阶段的状态一般来说可以随机生成。
另一个是当前Cell的特征输入,即上图的x(1)。也就是前面说的当前时间点的一些特征,如这是一年的第几天、第几个季度、是否是节假日、七天前的流量是多少、全年今天的流量是多少等等。
输出也是两个:一个是当前Cell的输出y(1),另一个是下一阶段的状态,即a(1)。
维度相关
以一个时间点的数据为例,如果当前数据特征维度是K,前一个状态a(0)的维度是L,那么输出状态a(1)的状态依然是L,输出y1的维度是1,当然Encoder阶段我们可能不太在意这个y输出,而Decoder阶段的y输出就是我们预测的结果了。
四、Keras中SimpleRNNCell的输入输出和参数
Keras中SimpleRNNCell的参数很多,包括权重参数如何初始化,是否添加bias,激活函数是啥等等。这些都是常见的参数,就不解释了,其中一个参数是units,这个定义容易混淆,原先我也以为这个unit是叠加多层RNN的输出,最后发现其实这个参数是隐状态的维度,也就是前面的a(0)和a(1)的维度。
SimpleRNNCell在创建的时候指定units就可以了,而使用的时候,它有两个输入参数,一个是当前阶段的特征输入,即input_x,另一个是前一个状态previous_states,注意,这里也有个坑,由于RNNCell的种类很多,包括后面的GRUCell或者是LSTMCell,这些更加复杂的Cell状态都不止一个,因此在代码中,Keras的状态并不单纯是一个state,它其实是一个列表。
我们看如下代码(tensorflow 1.15.x)
# 我们先定义一个cell,units=1,也就是隐状态的维度是1rnn_cell = tf.keras.layers.SimpleRNNCell(1, activation=None, kernel_initializer=tf.keras.initializers.Zeros())# 我们随机生成一个当前阶段的特征和前一阶段的状态input_x = tf.convert_to_tensor(np.asarray([[1, 1, 1], [2, 2, 2]]).astype(np.float32) )# 注意,这里state的维度是[1, 2, 1],因为Keras取前一阶段的状态会根据不同的Cell取不同的数量,如果是SimpleRNNCell,那么取第一个,如果是GRU,那么接着取,所以这里的第一个维度实际是第几种state,由于我们这里测试的是SimpleRNNCell,只有一个状态,那么用这个维度即可previous_states = tf.convert_to_tensor(np.asarray([[[0.1], [0.1]]]).astype(np.float32))# 输出测试rnn_output, next_states = rnn_cell(input_x, previous_states)init = tf.compat.v1.global_variables_initializer()with tf.compat.v1.Session() as sess: sess.run(init) rnn_output, next_states = sess.run([rnn_output, next_states]) print(rnn_output) print(next_states)
注意,这里state的维度是[1, 2, 1],因为Keras取前一阶段的状态会根据不同的Cell取不同的数量,如果是SimpleRNNCell,那么取第一个,如果是GRU,那么接着取,所以这里的第一个维度实际是第几种state,由于我们这里测试的是SimpleRNNCell,只有一个状态,那么用这个维度即可。之前测试过[2,1],结果报错,也就是这个原因。输出也是类似,输出的第一个是当前阶段的结果和状态,对应前面的y1和a(1)。注意a(1)是一个列表。这里列表中只有一个状态,该状态的维度是[2,1],和我们自己定义的previous_states变量都是一个意思。
当然,如果我们把units设置成其它数值如5,那么我们的previous_states构造就必须是一个列表,列表中有一个变量,其维度是[5,1],当然也可以构造成我们的[1,5,1]形式的变量。