GluonNLP 0.3.3 新功能及重现报告

谨以此文纪念为 GluonNLP 的模型重现赴汤蹈火,积极踩坑的小哥们。

GluonNLP 第一弹中炼丹师小A的遭遇和深度学习那些坑扎了许多朋友的心,比如知乎网友RickZ评论称 “正在被中文数据预处理教做人🙃”。在第一弹发布后,我们 GluonNLP 的小哥们马上又投入到紧张的论文复 (cai) 现 (keng) 工作中。今天,我们新发布的 GluonNLP 0.3.3 为大家带来了又一批新鲜出炉的模型:

  • 语言模型
    • Cache Language Model by Grave, E., et al. “Improving neural language models with a continuous cache”. ICLR 2017
  • 机器翻译
    • Transformer Model by Vaswani, Ashish, et al. “Attention is all you need”. NIPS 2017
  • 词向量训练
    • FastText models by Bojanowski, P., Grave, E., Joulin, A., & Mikolov, T. (2017). “Enriching Word Vectors with Subword Information”. TACL, 5, 135-146.
    • Word2Vec by Mikolov, T., Sutskever, I., Chen, K., Corrado, G. S., & Dean, J. (2013). “Distributed representations of words and phrases and their compositionality”. NIPS 2014

开发过程中,各位 NLP 小哥坚持踏踏实实踩坑,在哪个坑跌倒就从哪个坑爬起来,终于攒出大招发了这一波模型。下面我们就来听听各位 GluonNLP 小哥们从前方发来干货满满的踩坑报道。

语言模型篇

踩坑小哥: 晨光 @cgraywang (@home)

当时年幼无知的你看到杰伦唱:“就是开不了口,让她知道”,心里的潜台词一定是这样的:

杰伦究竟为啥开不了口?不行我来,我们感到很生气(暴露了年龄orz)。现在做了GluonNLP的我终于明白,原来是杰伦的语言模型不行。如果您也有类似症状😀新版本GluonNLP V0.3提供了最先进的治疗方案,即目前市面上表现最佳的Cache语言模型,了解一下。

这次新发布的Cache语言模型为啥用过的都说好?因为效果好,能把perplexity (PPL) 从之前最优的 69.74 提升到 54.51(PPL是公认的语言模型评价指标,越低越好)。本质上Cache语言模型是记性更好的深度循环神经网络。要进一步解释Cache语言模型原理,我们就得言归正传一下,首先传统语言模型的正式定义是:给定一系列词,预测接下来的一个词。传统的语言模型是n元语言模型,简单的来说,就是计算给定n-1个词,预测第n个词的概率(没错~就是条件概率),然后把一个句子中所有的n-gram概率相乘,就是整个句子或者语言的概率。例如二元语言模型(bigram),就是给定1个词,预测第2个词的概率,然后就能计算出某个句子的语言概率。Cache语言模型的基本假设是:如果一个词已经在一篇文章里面出现过了,那么这个词相对于其他词就更有可能出现在同一篇文章中。例如,老虎这个词在维基百科上老虎标题的页面中出现的频率是2.8%,而在整个维基百科文章中出现的频率则是0.0037%。Cache语言模型就是通过对语言中存在的词之间的长关联进行更好的建模,从而提升语言模型的效果。换句话说,Cache语言模型有一个叫做Cache的记忆单元,其中包含近期出现过的词(比如同一篇文章,或者某种宽度的窗口中)。简单从模型的实现细节上来讲,Cache存储了长短期记忆(LSTM)模型某段的隐状态(hidden states)与已经看到的词,然后将这段隐状态作为查询关键词,与当前隐状态进行简单的向量之间的内积运算,得到已经看到的词在Cache中的概率分布。该概率分布将直接作用到传统语言模型的概率分布上(例如上面提到的二元语言模型),从而提升语言模型的性能。Cache语言模型主要有三个好处:

  • 可以帮助把已经在某个领域的数据上训练好的语言模型直接应用到新的领域;
  • 能够更好的预测词典之外的词(OOV),因为只要是见到一次词典之外的词,Cache就能记住;
  • 对于语言中的长关联现象有更好的把握。

不过这里要特别说明一下,因为 Cache 语言模型需要把真实的近期出现过的词存储到 Cache 中,所以无法很直接的扩展到生成类的任务(例如,机器翻译或问答系统)。这是因为生成类任务中往往生成时所依赖的前文也是模型自己生成的,而非真实数据。若我们假设过去出现的真实的词是能够很容易拿到的,那么Cache语言模型也就能够直接用到生成类的任务中。对于拿不到真实上文的模型,GluonNLP 中已经有了其他各种语言模型,例如基于循环神经网络(RNN)的语言模型,以及功能强大的AWD语言模型。在这里做个预告,我们将会在后续的博客中针对语言模型跟大家进行更细致全面的分享~

于是自从有了GluonNLP语言模型后的杰伦,不再开不了口,而是变身诗人(LAOSIJI):

机器翻译篇

踩坑小哥: 帅哥 @szhengac (@home)

在新的版本里,我们加入了Transformer模型,目前我们的NMT系统支持WMT14和16的英文到德文的互译。Transformer不同于RNN模型,由于整个模型由Attention构成,所有的timestamp的计算可以同时进行,这个大大的加速了训练速度。

在训练Transformer的过程中我们踩了很多的坑。首先是数据的不同, WMT14测试数据newstest2014 英文到德文的翻译有2个版本,一个有3003对样本,另一个有2737对样本;Batch size不再指的是Sentence的个数而是token的个数;再接着是BLEU的计算,这个比较复杂,我们在后面细说。

目前Gluonnlp支持三种BLEU计算:WMT官方的BLEU计算;基于International Tokenization的BLEU;用于重复论文结果的BLEU计算。大家看到这里可能惊讶论文里的BLEU计算方式和官方的算法不一样。这个是我们踩了很久的一个坑。BLEU的计算会由于预处理和tokenization的不同结果千变万化,BLEU的不同可能会有2到3个值。而这个不同其实并不能反应翻译的好坏,因为不同来自于标点符号的处理不同。这大大加了重复论文结果的难度,因为我们可能不知道这篇论文是怎么进行处理的。经过和T2T和Sockeye的作者沟通以及查阅他们的代码,我们才了解到论文里是怎么计算BLEU的。按照论文里的计算方式,我们的Base Transformer模型在newstest2014数据上英文到德文的翻译可以达到28.74的BLEU。这个已经远远超过了论文里报告的Large Transformer的27.3的BLEU,甚至超过了Large Transformer 的28.4的BLEU。而如果按照WMT官方的BLEU计算方式,BLEU的值是26.81。加上International Tokenization的结果是27.65。这里可以看到结果的变化是巨大的。

词向量篇

踩坑小哥: Leo @leezu (@home)

翻译小哥: 查晟

有木有好奇过电脑是怎么理解自然语言的? 感兴趣的话可以看看 GluonNLP 中的 “using pre-trained word embeddings in Gluon NLP“里的介绍。如果已经熟悉其中内容了请继续看。

对于最基本的情况,GluonNLP中的 TokenEmbedding 中只保留每个已知单词的向量表征。模型在查询时 TokenEmbedding 像查字典一样,根据一个词的完整拼写来查询,一个字母都不能错。但如果一个词没有见过肿么办? 在 v0.3 版以前,GluonNLP 和其他许多深度学习自然语言处理框架一样,会把这些词替换成一个特殊值 “<unknown>“,然后把这些没见过的词统统假装是这个特殊值统一来学习。然鹅,这样做的话会丢失很多信息。已经有许多方法可以为这些没见过的词提供更好的表征,比如 fastText 会用每个词中不同长度的子序列的表征累加成一个词的表征。v0.3开始,GluonNLP 支持在遇到生词时,自动地用包含 fastText 这样的基于子序列生成插值的模型来产生生词的词向量。对于 fastText,用户只需要在载入已有的 fastText 向量时声明 loadngrams=True,GluonNLP 包就会自动帮你载入啦。

除此之外,为了让使用预训练词向量更容易,GluonNLP 现在提供词向量的训练脚本,用来在用户自己的数据集上训练。我们提供了 word2vec 和 fastText 模型,并且直接将他们与已有的 TokenEmbedding 整合。用户也可以很容易地改动或自己定义模型,从而能快速实验改善词向量的新想法。要知道,facebook 提供的 fastText 本来是一个专为 CPU 优化的C++的库。为了能让用户更容易地在 Python 中开发词向量训练模型,并且利用 GPU 加快实验速度,小哥我也做了不少改进工作。下面简单介绍下:

  1. 批量化 首先,为了能充分利用GPU达到快速训练的目的,我们需要能让模型做批量化的训练 (Batching),这与 word2vec 和 fastText 的异步多线程的处理方式不同。然而,在新模式下直接使用一般词向量训练最常用的 SGD 做优化效果并不好,所以在尝试后,我们使用了 adagrad 而达到了很好的效果。

  2. 哈希函数 fastText 的支持需要使用哈希函数帮助特征表征。但是,我们在进行相关工作的时候发现有个小坑,就是原实现中使用的哈希函数的实现方式对不同的编译器会有行为不同,因而是达不到原作者声称的可移植性的。我们因此选择了最常见的 x86 上最常见的编译器的默认行为,并且把上面的问题汇报给了原作者们。

  3. 数据管道 与许多深度学习任务不同的是,词向量训练时用的 SkipGram 或者 CBOW 训练目标下使用的浅神经网络计算代价很低,因此我们尤其需要注意数据管道的效率以避免其成为训练过程的瓶颈。我们在 GluonNLP 中使用了 numba 的 just-in-time (JIT) 编译来帮助提高效率。

接下来

看过小哥们的踩坑报告,相信大家对小哥们的(吐槽)功力有了更深的认识。在享受新功能的同时,也记得给小哥们点个赞!

最新的 GluonNLP 发布在 gluon-nlp.mxnet.io。项目开发都在 Github 的 gluon-nlp。我们会持续加入新的特性和模型。如果你也跃跃欲试,欢迎一起来搞事情!

讨论请点这里