关于rnn的学习

rnn主要是用来解决时间序列信息的问题,当一个信息在不同的t上具有前后关系时,使用rnn可以学习到前后信息之间的联系。比如针对语音识别、文字识别这种具有上下文联系的问题,我们就可以利用rnn来进行学习。

rnn如何将上下文的信息联系起来呢?这里可以联系到之前学习到的时间序列分析的思想,一个简单的时间序列模型MA模型:就是根据之前p个时刻的信息来建立线性方程,从而预测t+1时刻的结果。rnn同样借助了这样的思路,t时刻的结果根据前一段时刻的特征以及t时刻的特征来进行预测,如此就利用到了上下文的信息。

但要利用到前多少个时刻的信息呢?这里如果要是像MA模型一样,对前面的每一个时刻都计算一个参数,显然是不好设计网络结构的,那么一个巧妙地思路就是构造一个辅助变量_{_S{t}}

每个_{_S{t}}都是根据上一个_{_S{t}}和该时间上的特征计算出来的,因此辅助变量_{_S{t}}就一轮一轮地结合到了之前每个时间上的特征,这样我们就可以根据_{_S{t}}来计算该时间上的预测结果了,也就是_{_O{t}}

这里同样存在一个巧妙的思路就是,如果我们对每一个t都使用不同的W、U、V,那么如果我们的训练数据是长短不同的序列(比如语音识别、文字识别),那么这个模型就难以训练这样的数据,但事实上长短不同的序列是非常常见的,所以这里将每一个t时刻的W、U、V都设计成了相同的,这样的话不仅减少了使用的参数,而且适用于长短不同的序列。

但是这个简单的rnn结构存在一个问题,由于序列间是权重共享的,在t时刻对W进行求导时,需要用到1t-1时刻的W的梯度信息,当时间序列较长时,梯度相乘的过程中这部分的梯度容易趋于0,也就是不能利用到较长时态的信息。

问:如何同时利用到较长时态和较短时态的信息?

答:可以对较长时态信息和较短时态信息分别进行计算,这样长时态信息就不会被短时信息所淹没。

如此就产生了lstm网络结构,lstm保留一个长时态信息在所有t之间进行传输,同时向其中添加短时态信息。

lstm有三个门来帮助结合长短信息,分别为遗忘门,输出门和输入门。

遗忘门: 将上一个时刻计算得到的_{_h{t-1}}和该时刻的_{_x{t}}结合起来计算一个[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的输入,如此进行第二层的计算。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章