之前的模型都是基于word单词作为基本单位的,但是其缺点是不能很好的解决out-of-vocabulary即单词不在词汇库里的情况,且对于单词的一些词法上的修饰(morphology)处理的也不是很好。一个自然的想法就是能够利用比word更基本的组成来建立模型,以更好的解决这些问题。
一种思路是将字符作为基本单元,建立Character-level model,但是由于基本单元换为字符后,相较于单词,其输入的序列更长了,使得数据更稀疏且长程的依赖关系更难学习,训练速度也会降低。Fully Character-Level Neural Machine Translation without Explicit Segmentation中利用了多层的convolution, pooling与highway layer来解决这一问题,其中encoder的结构如下图所示: 输入的字符先被映射到character embedding。然后与窗口大小不同的卷积核进行卷积操作再将输出联结起来,例如上图中有三种窗口大小分别为3,4,5的卷积核,相当于学习了基于字符的trigram, 4-grams, 5-grams。然后对卷积的输出进行max pooling操作,相当于选择最显著的特征产生segment embedding。由此我们从最基础的输入的character embedding得到了系统中认为语言学上有意义的segment embedding。然后将这些特征经过Highway Network(有些类似于Residual network,方便深层网络中信息的流通,不过加入了一些控制信息流量的gate)和双向的GRU,这样得到最终的encoder output。之后decoder再利用Attention机制以及character level GRU进行decode。
基本单元介于字符与单词之间的模型称作Subword Model。那么Subword如何选择呢?一种方法是Byte Pair Encoding,简称BPE。 BPE最早是一种压缩算法,基本思路是把经常出现的byte pair用一个新的byte来代替,例如假设(‘A’, ’B‘)经常顺序出现,则用一个新的标志’AB’来代替它们。
给定了文本库,我们的初始词汇库仅包含所有的单个的字符,然后不断的将出现频率最高的n-gram pair作为新的ngram加入到词汇库中,直到词汇库的大小达到我们所设定的某个目标为止。
例如,假设我们的文本库中出现的单词及其出现次数为 {‘l o w’: 5, ‘l o w e r’: 2, ‘n e w e s t’: 6, ‘w i d e s t’: 3},我们的初始词汇库为{ ‘l’, ‘o’, ‘w’, ‘e’, ‘r’, ‘n’, ‘w’, ‘s’, ‘t’, ‘i’, ‘d’},出现频率最高的ngram pair是(‘e’,‘s’) 9次,所以我们将’es’作为新的词汇加入到词汇库中,由于’es’作为一个整体出现在词汇库中,这时文本库可表示为 {‘l o w’: 5, ‘l o w e r’: 2, ‘n e w es t’: 6, ‘w i d es t’: 3},这时出现频率最高的ngram pair是(‘es’,‘t’) 9次,将’est’加入到词汇库中,文本库更新为{‘l o w’: 5, ‘l o w e r’: 2, ‘n e w est’: 6, ‘w i d est’: 3},新的出现频率最高的ngram pair是(‘l’,‘o’)7次,将’lo’加入到词汇库中,文本库更新为{‘lo w’: 5, ‘lo w e r’: 2, ‘n e w est’: 6, ‘w i d est’: 3}。以此类推,直到词汇库大小达到我们所设定的目标。这个例子中词汇量较小,对于词汇量很大的实际情况,我们就可以通过BPE逐步建造一个较小的基于subword unit的词汇库来表示所有的词汇。
谷歌的NMT模型用了BPE的变种,称作wordpiece model,BPE中利用了n-gram count来更新词汇库,而wordpiece model中则用了一种贪心算法来最大化语言模型概率,即选取新的n-gram时都是选择使得perplexity减少最多的ngram。进一步的,sentencepiece model将词间的空白也当成一种标记,可以直接处理sentence,而不需要将其pre-tokenize成单词。
还有一种思路是在大多数情况下我们还是采用word level模型,而只在遇到OOV的情况才采用character level模型。
其结构如下图所示,大部分还是依赖于比较高效的word level模型,但遇到例子中的"cute"这样的OOV词汇,我们就需要建立一个character level的表示,decode时遇到这个表示OOV的特殊标记时,就需要character level的decode,训练过程是end2end的,不过损失函数是word部分与character level部分损失函数的加权叠加。
在 Word Vector中,word vector也是基于word level来建立的,对于含有很多OOV的词汇的文本效果不好,那么我们可不可以采取类似于上面的subword的思路来产生更好的word embedding呢?FAIR的FastText就是利用subword将word2vec扩充,有效的构建embedding。其基本思路是将每个word表示成bag of character n-gram以及单词本身的集合,例如对于where这个单词和n=3的情况,它可以表示为 <wh,whe,her,ere,re>, ,其中"<",">"为代表单词开始与结束的特殊标记。假设对于word w ,其n-gram集合用 Gw 表示,每个n-gram的矢量表示为Zg ,则每个单词可以表示成其所有n-gram的矢量和的形式,而center word w 与context word c 的分数就可表示成 s ( w , c ) = ∑ g ∈ G w z g → T v c → s(w, c)=\sum_{g \in G_{w}} \overrightarrow{z_{g}}^{T} \overrightarrow{v_{c}} s(w,c)=g∈Gw∑zg Tvc 的形式,之后就可以按照经典的word2vec算法训练得到这些特征向量。
这种方式既保持了word2vec计算速度快的优点,又解决了遇到training data中没见过的oov word的表示问题,可谓一举两得。
原文