rnn主要是用来解决时间序列信息的问题,当一个信息在不同的t上具有前后关系时,使用rnn可以学习到前后信息之间的联系。比如针对语音识别、文字识别这种具有上下文联系的问题,我们就可以利用rnn来进行学习。
rnn如何将上下文的信息联系起来呢?这里可以联系到之前学习到的时间序列分析的思想,一个简单的时间序列模型MA模型:就是根据之前p个时刻的信息来建立线性方程,从而预测t+1时刻的结果。rnn同样借助了这样的思路,t时刻的结果根据前一段时刻的特征以及t时刻的特征来进行预测,如此就利用到了上下文的信息。
但要利用到前多少个时刻的信息呢?这里如果要是像MA模型一样,对前面的每一个时刻都计算一个参数,显然是不好设计网络结构的,那么一个巧妙地思路就是构造一个辅助变量
每个都是根据上一个和该时间上的特征计算出来的,因此辅助变量就一轮一轮地结合到了之前每个时间上的特征,这样我们就可以根据来计算该时间上的预测结果了,也就是。
这里同样存在一个巧妙的思路就是,如果我们对每一个t都使用不同的W、U、V,那么如果我们的训练数据是长短不同的序列(比如语音识别、文字识别),那么这个模型就难以训练这样的数据,但事实上长短不同的序列是非常常见的,所以这里将每一个t时刻的W、U、V都设计成了相同的,这样的话不仅减少了使用的参数,而且适用于长短不同的序列。
但是这个简单的rnn结构存在一个问题,由于序列间是权重共享的,在t时刻对W进行求导时,需要用到1至t-1时刻的W的梯度信息,当时间序列较长时,梯度相乘的过程中这部分的梯度容易趋于0,也就是不能利用到较长时态的信息。
问:如何同时利用到较长时态和较短时态的信息?
答:可以对较长时态信息和较短时态信息分别进行计算,这样长时态信息就不会被短时信息所淹没。
如此就产生了lstm网络结构,lstm保留一个长时态信息在所有t之间进行传输,同时向其中添加短时态信息。
lstm有三个门来帮助结合长短信息,分别为遗忘门,输出门和输入门。
遗忘门: 将上一个时刻计算得到的和该时刻的结合起来计算一个[0,1]区间内的值,这个值代表了我们要保留多少长时态信息的百分比。 如果遗忘门得到的值为1,则表示所有长时态信息都可以通过,若为0,则表示所有的长时态信息都不通过。
输入门:输入门计算当前的短时态的信息。输入们的计算分成两部分。
根据遗忘门和输入门的结果我们可以对长时态信息进行更新:长时态信息一部分通过遗忘门没有被保留,另外根据输入门添加了一部分新的信息。从而我们对长时态信息进行了更新。
那么该时刻的输出是如何计算的呢?输出一方面根据更新后的长时态信息,一方面根据当前时态信息进行计算。
整体来说就是这样一个结构:
多层lstm:
下图来自https://blog.csdn.net/xiewenbo/article/details/80210850
多层lstm就是把第一层lstm在每一个t上得到的结果再输入第二层,如此构成多层lstm。
通过tensorflow建立一个基本的lstm单元:
这里的num_units代表隐藏层输出的值的维度,比如每个t输入的array的大小为3*1,我们设置num_units为5,那么lstm输出的array大小就是5*1。
lstm_cell = rnn.BasicLSTMCell(num_units=hidden_size, forget_bias=1.0, state_is_tuple=True)
如果要构造多层lstm,可以通过如下的函数,这里的 layer_num就是我们的sltm层数。
mlstm_cell = rnn.MultiRNNCell([lstm_cell] * layer_num, state_is_tuple=True)
lstm的函数源码:
'''code: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/rnn/python/ops/core_rnn_cell_impl.py'''
def __call__(self, inputs, state, scope=None):
"""Long short-term memory cell (LSTM)."""
with vs.variable_scope(scope or "basic_lstm_cell"):
# Parameters of gates are concatenated into one multiply for efficiency.
if self._state_is_tuple:
c, h = state
else:
c, h = array_ops.split(value=state, num_or_size_splits=2, axis=1)
concat = _linear([inputs, h], 4 * self._num_units, True, scope=scope)
# ** 下面四个 tensor,分别是四个 gate 对应的权重矩阵
# i = input_gate, j = new_input, f = forget_gate, o = output_gate
i, j, f, o = array_ops.split(value=concat, num_or_size_splits=4, axis=1)
# ** 更新 cell 的状态:
# ** c * sigmoid(f + self._forget_bias) 是保留上一个 timestep 的部分旧信息
# ** sigmoid(i) * self._activation(j) 是有当前 timestep 带来的新信息
new_c = (c * sigmoid(f + self._forget_bias) + sigmoid(i) *
self._activation(j))
# ** 新的输出
new_h = self._activation(new_c) * sigmoid(o)
if self._state_is_tuple:
new_state = LSTMStateTuple(new_c, new_h)
else:
new_state = array_ops.concat([new_c, new_h], 1)
# ** 在(一般都是) state_is_tuple=True 情况下, new_h=new_state[1]
# ** 在上面博文中,就有 cell_output = state[1]
return new_h, new_state
双向lstm:
先从相对简单的单层双向lstm说起,下面为示意图:
前向和后向分别进行计算,最后的输出结果y是综合了前后向的输出结果(前向,后向)
如果是多层的双向lstm,其实就是单层双向lstm的组合,将第一层双向lstm的结果作为下一层双向lstm的输入,如此进行第二层的计算。