RNN

RNN

前馈神经网络只能处理fixed-size input features,这让它有一定的局限性。同时,前馈神经网络没有temporal的概念,不能体现输入特征在时间上的变化。虽然sliding-window前馈神经网络能利用一部分的时间上的特征,如上下文信息,但也只能利用窗大小的上下文中的信息,而不能更加灵活地使用任意距离的上下文信息。

TDNN和RNN的区别在哪些地方?

我们将前馈神经网络的输入看做是一个一维的向量,每给定一个输入,前馈神经网络都会给出一个对应的输出。那么RNN的输入,则可以看成是n个一维的向量按时间排序组合成的矩阵,RNN按照时间发展依次处理每个时间维度上的一维向量,每个时间维度上的输出,都跟之前的时间维度上的输出有关系。

feedforward_rnn_input

RNN是一个比较宽泛的概率,任何任一时刻输出依赖于前一个输入的神经网络都可以划分到RNN中。最简单的RNN是Elman network,也就是我们常说的RNN。

RNN的网络结构

rnn_architecture

如上图(摘自slp3),前一时刻的信息,通过隐层传递给下一层。共享三个参数,U,隐层的投影矩阵;W,输入的投影矩阵;V,输出的投影矩阵。
$$
h_i = g(U \cdot h_{i-1} + W \cdot x_i) \\
y_i = f(V \cdot h_i)
$$

注意:$y_t$是给定$x_t$和上一个隐状态$h_{t-1}$的输出,而$y_t$是$x_t$的下一个单词,注意角标

$h_0$一般选0,因为最开始没有学习到任何信息,故为0.

如何使用bp来进行gradient descent

先定义一下符号:$L$代表loss function,$z$代表线性投影,$a$代表线性投影经过activation function之后的结果。假设用RNN进行多分类任务,模型的输出为最后一层的输出。

首先,feed forward计算出所有计算节点的值
$$
L = g(h_t \cdot V)
$$
对于V来说,只有最后时刻的输出涉及到了V,
$$
\frac{\partial L}{\partial V} = \frac{\partial L}{\partial a} \frac{\partial a}{\partial z} \frac{\partial z}{\partial V} = \frac{\partial L}{\partial a} \frac{\partial a}{\partial z} h_t
$$

TO BE CONTINUED!

RNN语言模型

用RNN来构建语言模型,可以避免像n-gram语言模型和sliding window版的前馈神经网络受限于context size。在预测下一个单词的时候,用当前单词的embedding,和上一时刻的hidden state,最后通过softmax,求出对于每一个单词的概率。RNN语言模型的训练,也是根据给定的训练语料,loss function是交叉熵,然后最优化参数。训练好语言模型之后,给定一个测试集,可以计算出概率。

在用该语言模型随机生成文本的过程中,每一个单词的选择都会影响下一个单词的预测,通过影响当前时刻的隐状态。其大概流程:首先,输入开始字符,在生成的分布中,随机采样(选一个单词);然后用选定的单词作为输入,继续预测下一个单词的分布,随机采样;直到出现结束字符,或者达到指定的长度。

RNN做序列标注

序列标注如POS tag,即词性标注;spans of text,即从一个文本序列中,找出一些phrase或者段落,前者如命名体识别,后者如阅读理解,从一段文章中找出对应的句子或段落来回答问题。还有structure prediction。

比如用RNN做pos tag,每一个单词选择的tag,不会影响下一个单词的tag。这跟HMM有点不一样,在HMM中,每一个单词的tag的选择都会影响下一个单词的tag的选择,因此在求解最优的pos tag序列的时候,需要通过维特比算法,寻找最佳路径。而在RNN中,给定文本序列之后,hidden state就确定了,最后的tag都是根据隐状态计算softmax得到,因此,每个单词的tag选取不会影响彼此。

用RNN做序列标注的时候存在的一个问题是,不能保证标注的tag是合理的。比如用IOB进行命名体识别中,很有可能出现I在O之后的情况,这是不符合逻辑的。解决方案一般是在RNN之后,加一层CRF层???????

RNN分类器

只用最后时刻的隐状态作为输出,其他时刻的隐状态只服务于下一个时刻,在隐状态之后加一层FF,然后利用softmax进行分类。

为何最后要加上一个全链接层,不能直接在最后的隐状态上进行softmax吗?书上给的图貌似还是两层的全链接层。

stacked RNN

RNN还可以堆叠起来,形成多层的RNN,前一层的RNN的ouput作为下一层RNN的输入,用最后一层的RNN的输出作为最终的输出。

stacked_rnn

类似于CNN,不同的卷积核可以学习不同的pattern,Stacked RNN不同层也能学习到不同的特征表示。

Bidirectional RNN

单向RNN只能利用过去时刻的信息,而不能利用未来时刻的信息,双向RNN就是用来解决这个问题的。双向RNN是将一个forward的RNN和一个backword的RNN结合起来,双向RNN的输出为这两个RNN的输出的结合,组合方式可以是拼接、addition或者相乘。

双向RNN还有一个优点。普通的RNN如果用来做分类的时候,最后一层的输出中,早期时刻的特征信息会少于后期时刻的特征信息,导致信息编码的不平衡。使用双向RNN,将forward RNN和backward RNN的最终输出拼接起来(或者相加、相乘),作为最终的representation用于分类。

改进版的RNN

RNN两个比较明显的问题:

  1. hidden state对于较远的信息学习不如较近的信息,
  2. 在bp的过程中,容易出现梯度消失的情况

因此,引入了更加精心设计的网络结构,

LSTM

LSTM,长短记忆模型,将上下文信息的管理分成了两个过程:第一个是移除历史上下文中不需要的信息,第二个是引入新的信息。

普通的RNN中有input,hidden state,在LSTM还引入一个变量,context。对于每一个变量的取舍,都通过门来控制。门的设计原理大致如下:先用一层feed-forward作用于变量(hidden state和input),然后用sigmoid激活函数,生成一个mask,然后将该mask与需要取舍的变量进行element-wise相乘,得到门的输出。通过sigmoid生成的mask,值在0到1之间,当值趋近1的时候,该位置对应的信息就保存下来了,当值趋近于0的时候,这部分的信息就被遗忘掉了。mask乘以历史变量,得到的就是保留下来(遗忘了一部分信息)的信息;mask乘以新变量,得到的就是需要添加进新context里面的信息。

LSTM引入了三个门:遗忘门,作用于context;新增门,作用于输入和hidden state之上;输出门,作用于新的context。所有的们都是通过input和上一时刻的hidden state构建的。

lstm

遗忘门:
$$
f_t = \sigma(U_fh_{t-1} + W_f x_t) \\
k_t = c_{t-1} \odot f_t
$$
其中,$f_t$是遗忘门,$k_t$是需要保留下来的context信息

新增门:
$$
i_t = \sigma(U_ih_{t-1} + W_i x_t) \\
g_t = \tanh (U_gh_{t-1} + W_gx_t) \\
j_t = g_t \odot i_t
$$
其中,$i_t$是新增门,$g_t$是新增量,$j_t$是需要添加到context中的新信息

输出门:
$$
o_t = \sigma(U_oh_{t-1} + W_ox_t) \\
c_t = k_t + j_t \\
h_t = o_t \odot \tanh (c_t)
$$
其中,$o_t$是输出门,$c_t$是新的context,$h_t$是新的hidden state。

$h_0, c_0$是如何训练的?是不是训练好模型之后,这两个变量都是定值?

GRU

LSTM是对SRN(simple recurrent network)的一个升级,解决了SRN的几个比较严重的问题。但LSTM本身因为参数过多(4对U,W),训练起来比较困难。为了缓解这个问题,GRU(gated recurrent units)被提出来了。GRU没有引入单独的context变量,同时门的数量也只有两个,一个reset,一个update。

GRU

重置门和更新门的构造方式跟LSTM一样:
$$
r_t = \sigma (U_rh_{t-1} + W_rx_t) \\
z_t = \sigma(U_zh_{t-1} + w_zx_t)
$$
首先,通过重置门,决定上一个hidden state有多少信息,可以用于当前信息的推断(当前信息的推断需要结合历史信息);然后,更新门通过对上一时刻的hidden和当前生成的hidden进行加权,得出新生成的hidden state。
$$
\tilde h_t = \tanh (U(r_t \odot h_{t-1}) + Wx_t) \\
h_t = (1-z_t)h_{t-1} + z_t \tilde h_t
$$

总结LSTM和GRU

虽然LSTM和GRU相较于SRN复杂需要,但好在我们可以将其模块化,我们将每一个unit看成一个计算单元,只考虑其输入和输出,具体的实现和bp过程,交给计算单元自己。

模块化的LSTM和GRU

理解$h_t$,$x_t$

$h_{t-1}$表征的是历史信息,$x_t$是当前输入,在预测$y_t$的时候,我们需要先得到隐状态$h_t$。隐状态跟当前输入和历史数据有关,但是并不是所有的历史信息都跟当前的预测相关,因此引入reset门,只采用$h_{t-1}$中相关的量,再协同$x_t$,计算当前的隐变量$\tilde h_t$。但$\tilde h_t$只是当前的隐变量,如果我们打算接着利用隐变量代表历史信息的话,我们已经遗漏了历史信息,因此,需要将历史信息加上。此时引入了update门,取历史信息和当前新生成的新变量$\tilde h_t$。考察reset门,我们怎么知道哪些历史信息有用哪些历史信息无用呢?可想而知,哪些历史信息有用跟当前所处的时刻有关,而$h_{t-1}$和$x_t$刚好是当前所处时刻的表现量。