自然语言和音频都是前后相互关联的数据,对于这些序列数据需要使用循环神经网络(Recurrent Neural Network,RNN)来进行处理。
对于一个序列数据 x,用符号 x ⟨ t ⟩ x^{⟨t⟩} x⟨t⟩来表示这个数据中的第 t个元素,用 y ⟨ t ⟩ y^{⟨t⟩} y⟨t⟩来表示第 t个标签,用 T x T_x Tx 和 T y T_y Ty来表示输入和输出的长度。对于一段音频,元素可能是其中的几帧;对于一句话,元素可能是一到多个单词。
第 i 个序列数据的第 t 个元素用符号 x ( i ) ⟨ t ⟩ x^{(i)⟨t⟩} x(i)⟨t⟩,第 t 个标签即为 y ( i ) ⟨ t ⟩ y^{(i)⟨t⟩} y(i)⟨t⟩。对应即有 T x ( i ) T_x^{(i)} Tx(i) 和 T y ( i ) T_y^{(i)} Ty(i)。
想要表示一个词语,需要先建立一个词汇表(Vocabulary),或者叫字典(Dictionary)。将需要表示的所有词语变为一个列向量,可以根据字母顺序排列,然后根据单词在向量中的位置,用 one-hot 向量(one-hot vector)来表示该单词的标签:将每个单词编码成一个 R ∣ V ∣ ∗ 1 R^{|V|*1} R∣V∣∗1向量,其中 |V|是词汇表中单词的数量。一个单词在词汇表中的索引在该向量对应的元素为 1,其余元素均为 0。
例如,'zebra’排在词汇表的最后一位,因此它的词向量表示为:
w z e b r a = [ 0 , 0 , 0 , . . . , 1 ] T w^{zebra}=[0,0,0,...,1]^T wzebra=[0,0,0,...,1]T
补充:one-hot 向量是最简单的词向量。它的缺点是,由于每个单词被表示为完全独立的个体,因此单词间的相似度无法体现。例如单词 hotel 和 motel 意思相近,而与 cat 不相似,但是
( w h o t e l ) T w m o t e l = ( w h o t e l ) T w c a t = 0 (w^{hotel})^Tw^{motel}=(w^{hotel})^Tw^{cat}=0 (whotel)Twmotel=(whotel)Twcat=0
对于序列数据,使用标准神经网络存在以下问题:
为了解决这些问题,引入循环神经网络(Recurrent Neural Network,RNN)。一种循环神经网络的结构如下图所示:
当元素 x ⟨ t ⟩ x^{⟨t⟩} x⟨t⟩ 输入对应时间步(Time Step)的隐藏层的同时,该隐藏层也会接收来自上一时间步的隐藏层的激活值 a ⟨ t − 1 ⟩ a^{⟨t−1⟩} a⟨t−1⟩,其中 a ⟨ 0 ⟩ a^{⟨0⟩} a⟨0⟩ 一般直接初始化为零向量。一个时间步输出一个对应的预测结果 y ^ ⟨ t ⟩ \hat y^{⟨t⟩} y^⟨t⟩。
循环神经网络从左向右扫描数据,同时每个时间步的参数也是共享的,输入、激活、输出的参数对应为 W a x W_{ax} Wax、 W a a W_{aa} Waa、 W y a W_{ya} Wya。
下图是一个 RNN 神经元的结构:
前向传播过程的公式如下:
激活函数 g 1 g_1 g1通常选择 tanh,有时也用 ReLU; g 2 g_2 g2可选 sigmoid 或 softmax,取决于需要的输出类型。
为了进一步简化公式以方便运算,可以将 W a a W_aa Waa、 W a x W_ax Wax 水平并列为一个矩阵 W a W_a Wa,同时 a ⟨ t − 1 ⟩ a^{⟨t−1⟩} a⟨t−1⟩和 x ⟨ t ⟩ x^{⟨t⟩} x⟨t⟩ 堆叠成一个矩阵。则有:
为了计算反向传播过程,需要先定义一个损失函数。单个位置上(或者说单个时间步上)某个单词的预测值的损失函数采用交叉熵损失函数,如下所示:
将单个位置上的损失函数相加,得到整个序列的成本函数如下:
循环神经网络的反向传播被称为通过时间反向传播(Backpropagation through time),因为从右向左计算的过程就像是时间倒流。
某些情况下,输入长度和输出长度不一致。根据所需的输入及输出长度,循环神经网络可分为“一对一”、“多对一”、“多对多”等结构:
目前我们看到的模型的问题是,只使用了这个序列中之前的信息来做出预测,即后文没有被使用。可以通过双向循环神经网络(Bidirectional RNN,BRNN)来解决这个问题。
语言模型(Language Model)是根据语言客观事实而进行的语言抽象数学建模,能够估计某个序列中各元素出现的可能性。例如,在一个语音识别系统中,语言模型能够计算两个读音相近的句子为正确结果的概率,以此为依据作出准确判断。
建立语言模型所采用的训练集是一个大型的语料库(Corpus),指数量众多的句子组成的文本。建立过程的第一步是标记化(Tokenize),即建立字典;然后将语料库中的每个词表示为对应的 one-hot 向量。另外,需要增加一个额外的标记 EOS(End of Sentence)来表示一个句子的结尾。标点符号可以忽略,也可以加入字典后用 one-hot 向量表示。
对于语料库中部分特殊的、不包含在字典中的词汇,例如人名、地名,可以不必针对这些具体的词,而是在词典中加入一个 UNK(Unique Token)标记来表示。
将标志化后的训练集用于训练 RNN,过程如下图所示:
在第一个时间步中,输入的 a ⟨ 0 ⟩ a^{⟨0⟩} a⟨0⟩和 x ⟨ 1 ⟩ x^{⟨1⟩} x⟨1⟩都是零向量, y ^ ⟨ 1 ⟩ \hat y^{⟨1⟩} y^⟨1⟩是通过 softmax 预测出的字典中每个词作为第一个词出现的概率;在第二个时间步中,输入的 x ⟨ 2 ⟩ x^{⟨2⟩} x⟨2⟩是训练样本的标签中的第一个单词 y ⟨ 1 ⟩ y^{⟨1⟩} y⟨1⟩(即“cats”)和上一层的激活项 a ⟨ 1 ⟩ a^{⟨1⟩} a⟨1⟩,输出的 y ⟨ 2 ⟩ y^{⟨2⟩} y⟨2⟩表示的是通过 softmax 预测出的、单词“cats”后面出现字典中的其他每个词的条件概率。以此类推,最后就可以得到整个句子出现的概率。
在训练好一个语言模型后,可以通过采样(Sample)新的序列来了解这个模型中都学习到了一些什么。
在第一个时间步输入 a ⟨ 0 ⟩ a^{⟨0⟩} a⟨0⟩和 x ⟨ 1 ⟩ x^{⟨1⟩} x⟨1⟩为零向量,输出预测出的字典中每个词作为第一个词出现的概率,根据 softmax 的分布进行随机采样(np.random.choice
),将采样得到的 y ^ ⟨ 1 ⟩ \hat y^{⟨1⟩} y^⟨1⟩作为第二个时间步的输入 x ⟨ 2 ⟩ x^{⟨2⟩} x⟨2⟩。以此类推,直到采样到 EOS,最后模型会自动生成一些句子,从这些句子中可以发现模型通过语料库学习到的知识。
这里建立的是基于词汇构建的语言模型。根据需要也可以构建基于字符的语言模型,其优点是不必担心出现未知标识(UNK),其缺点是得到的序列过多过长,并且训练成本高昂。因此,基于词汇构建的语言模型更为常用。
The cat,which already ate a bunch of food, was full.
The cats,which already ate a bunch of food, were full.
对于以上两个句子,后面的动词单复数形式由前面的名词的单复数形式决定。但是基本的 RNN 不擅长捕获这种长期依赖关系。究其原因,由于梯度消失,在反向传播时,后面层的输出误差很难影响到较靠前层的计算,网络很难调整靠前的计算。
在反向传播时,随着层数的增多,梯度不仅可能指数型下降,也有可能指数型上升,即梯度爆炸。不过梯度爆炸比较容易发现,因为参数会急剧膨胀到数值溢出(可能显示为 NaN)。这时可以采用梯度修剪(Gradient Clipping)来解决:观察梯度向量,如果它大于某个阈值,则缩放梯度向量以保证其不会太大。相比之下,梯度消失问题更难解决。GRU 和 LSTM 都可以作为缓解梯度消失问题的方案。
GRU(Gated Recurrent Units, 门控循环单元)改善了 RNN 的隐藏层,使其可以更好地捕捉深层连接,并改善了梯度消失问题。
The cat,which already ate a bunch of food, was full.
当我们从左到右读上面这个句子时,GRU 单元有一个新的变量称为 c,代表记忆细胞(Memory Cell),其作用是提供记忆的能力,记住例如前文主语是单数还是复数等信息。在时间 t,记忆细胞的值 c ⟨ t ⟩ c^{⟨t⟩} c⟨t⟩等于输出的激活值 a ⟨ t ⟩ a^{⟨t⟩} a⟨t⟩; c ~ ⟨ t ⟩ \tilde c ^{⟨t⟩} c~⟨t⟩ 代表下一个 c 的候选值。 Γ u Γ_u Γu 代表更新门(Update Gate),用于决定什么时候更新记忆细胞的值。以上结构的具体公式为
当使用 sigmoid 作为激活函数 σ 来得到 Γ u Γ_u Γu时, Γ u Γ_u Γu 的值在 0 到 1 的范围内,且大多数时间非常接近于 0 或 1。当 Γ u Γ_u Γu=1时, c ⟨ t ⟩ c^{⟨t⟩} c⟨t⟩被更新为 c ~ ⟨ t ⟩ \tilde c ^{⟨t⟩} c~⟨t⟩,否则保持为 c ⟨ t − 1 ⟩ c^{⟨t-1⟩} c⟨t−1⟩。因为 Γ u Γ_u Γu 可以很接近 0,因此 c ⟨ t ⟩ c^{⟨t⟩} c⟨t⟩几乎就等于 c ⟨ t − 1 ⟩ c^{⟨t-1⟩} c⟨t−1⟩。在经过很长的序列后,c 的值依然被维持,从而实现“记忆”的功能。
以上实际上是简化过的 GRU 单元,但是蕴涵了 GRU 最重要的思想。完整的 GRU 单元添加了一个新的相关门(Relevance Gate) Γ r Γ_r Γr,表示 c ~ ⟨ t ⟩ \tilde c ^{⟨t⟩} c~⟨t⟩和 c ⟨ t ⟩ c^{⟨t⟩} c⟨t⟩的相关性。因此,表达式改为如下所示:
相关论文:
Cho et al., 2014. On the properties of neural machine translation: Encoder-decoder approaches
Chung et al., 2014. Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling
LSTM(Long Short Term Memory,长短期记忆)网络比 GRU 更加灵活和强大,它额外引入了遗忘门(Forget Gate) Γ f Γ_f Γf和输出门(Output Gate) Γ o Γ_o Γo。其结构图和公式如下:
将多个 LSTM 单元按时间次序连接起来,就得到一个 LSTM 网络。
以上是简化版的 LSTM。在更为常用的版本中,几个门值不仅取决于 a ⟨ t − 1 ⟩ a^{⟨t−1⟩} a⟨t−1⟩和 x ⟨ t ⟩ x^{⟨t⟩} x⟨t⟩,有时也可以偷窥上一个记忆细胞输入的值 c ⟨ t − 1 ⟩ c^{⟨t−1⟩} c⟨t−1⟩,这被称为窥视孔连接(Peephole Connection)。这时,和 GRU 不同, c ⟨ t − 1 ⟩ c^{⟨t−1⟩} c⟨t−1⟩和门值是一对一的。
c 0 c^0 c0 常被初始化为零向量。
相关论文:Hochreiter & Schmidhuber 1997. Long short-term memory
PS:GRU只有两个门,计算速度快,更容易创建出一个更大的网络。LSTM有三个门,更强大和灵活。LSTM是首选。
单向的循环神经网络在某一时刻的预测结果只能使用之前输入的序列信息。双向循环神经网络(Bidirectional RNN,BRNN)可以在序列的任意位置使用之前和之后的数据。其工作原理是增加一个反向循环层,结构如下图所示:
因此,有
这个改进的方法不仅能用于基本的 RNN,也可以用于 GRU 或 LSTM。缺点是需要完整的序列数据,才能预测任意位置的结果。例如构建语音识别系统,需要等待用户说完并获取整个语音表达,才能处理这段语音并进一步做语音识别。因此,实际应用会有更加复杂的模块。