语言生成:搜索 or 采样,that is the question

  • 2019 年 12 月 6 日
  • 筆記

目前,文本自然语言处理任务中,大体可分为 NLU(自然语言理解)NLG (自然语言生成)两大方向。

对于 NLU,现在已经找到了它的杀手锏,正是时不时刷榜一次的预训练模型,只要模型够大,数据够多,训练够长,屠榜都不是问题。在有些 NLU 任务上已毫无悬念地超过了人类,当然是否真的理解还有待探索,可参考近期博文 NLP's Clever Hans Moment has Arrived

虽然预训练模型在 NLU 有如此大进展,但如何用 BERT 系列模型做生成仍然是一个问题,其中也有些尝试,但都感觉有些不尽人意,特别是开放域。

现在已知预训练模型在语言生成任务上表现好的,有比如 UniLMMASS。其中 MASS 在今年机器翻译大赛 WMT 上还取得了冠军。

然而机器翻译只是语言生成的一端,在语言生成的另一端,还存在着诸如聊天机器人,故事生成这样,现在尚未表现很好的任务。如果将这些语言生成任务放在一个坐标上就如下图。

在坐标轴左边可看作生成更注重准确性,一般也都有比较好的评判指标,比如 BLEU,对于这种任务,通过大模型大数据也能够解决。而在坐标轴右边则可看作更注重生成的创造性更加开放,也没有合适评估指标,只能用人来评测。

对于这些创造性生成任务,我认为其中关键可能并不在于 bigger and more,而在于对语言以及心理的更多理解,在生成时用各种策略来迎合人类。

比如说今年 ACL 中 Facebook 一篇论文《What makes a good conversation? How controllable attributes affect human judgments》,就通过控制生成时的几个属性,而让一个两层的 LSTM seq2seq 模型在一些评判上取得了用巨大 GPT 精调出的对话系统一样效果。

对生成进行控制也可分为训练时与生成时,关于训练时的策略最近有篇很有意思的论文,我也有写相关的介绍《神经语言生成的非似然训练》

而这里就来介绍目前在生成时常用到的策略,首先是对坐标轴左端很有效的搜索(Search),然后是对坐标轴最右端很有效的采样(Sample)

首先假定已经有一个训练好的生成器,无论结构,可根据当前输入,给出对应位置预测的概率分布

搜索

贪婪搜索

对于语言生成,最简单的做法便是 Greedy Search(贪婪搜索),想法也便理所当然地对于每个时步(time step)直接取概率最大的预测

该方法可以作为基线来看模型训练是否有问题,或者要求不太高的生成。

但该方法,会因为太短视,导致容易陷入局部最优,最容易理解的例子便是俗语翻译,比如说“鹤蚌相争,渔翁得利”,如果用 Greedy Search,开始给“鹤蚌”这样字的概率会比较低,只有当看到是俗语时,才会给它高概率。

束搜索

为了解决上述问题提出了 Beam Search(束搜索)。

首先理解什么是 beam,可以把它看作是一个候选容器,放着当前探索中概率最高的一些候选,而容器容量就叫做 beam size。更简单的理解 beam size,可以把它当作是能够派遣出去探索的侦察兵,对可能性大的地方我们就会多派些兵力。当 beam size 等于 1 时,其实就是贪婪搜索了。

这样能让生成时有更多探索空间,更不易陷入局部最优。

具体做法是,假设 beam size 为 k:

  1. 选取当前分数最大的 k 个候选放入 beam
  2. 然后开始看每个候选下一步预测
  3. 从所有预测中取总分最大 k 个候选放入 beam
  4. 当候选中有生成结束符,将候选拿出,beam size 减 1
  5. 继续下一步预测 …
  6. 直到 beam size 为 0,结束搜索,取分最高为结果。

非常推荐吴教授关于 beam search 的讲解 Beam Search.

我自己画的简单示意图:

关于束搜索中排序用的分数,图中为了简单直接用概率加和,但一般会用 logprob(也就是log(prob)) 的加和。但这样会出现一个问题,因为 logprob 是负的,如果加和,生成结果中短句分数就会比较大,于是倾向于生成短句,而这当然不是我们想要的。

于是针对束搜索生成较长句就有好几种策略,比较经典的包括

  1. 分数直接除以生成长度
  2. 谷歌长度惩罚项,除以下列项(其中m是长度,alpha 为系数)
  1. 词奖励法,让分数加上 r*m,其中 r 是对每个词的奖励,m 为句长。

一般来说 2,3 效果要比 1 好些。

虽然通过惩罚项解决束搜索输出过短问题,但它还存在很多其他问题,特别是对像聊天机器人这样任务。

束搜索的问题

首先因为现在训练语言模型用的是最大似然法,当在大量对话语料上训练,然后用束搜索这样取最大概率方式来生成对话时,就会出现生成对话太普通了,没有多样性,不太像人对话

比如说研究人员通过列出束搜索和真实人类对话中词概率分布就发现,相比起人的对话,束搜索生成的对话更缺少意外性。举个很常见的例子,简单束搜索的生成式对话系统总会说,“我也是”,“好的”这样没营养的话。

其实也理所当然,因为本身束搜索的定义就是每次只取概率最高几个。

因此就需要通过采样来解决该问题。

采样

为解决搜索生成太乏味,可以通过采样来增加随机性,也就是上面所要的意外性。但增加随机性同时,会出现另一个问题,那就是生成可能会出现语法错误

举个栗子,假如说对全体词按照预测概率来采样,就可能出现采样到低概率词,从而在语法上导致整句话出现问题。

那么怎样避免该情况发生呢?可以通过强化顶部词的概率,然后只对最有可能的一些词进行采样,这样就能够在增加随机性的同时,又保证不出现一般性的错误

强化顶部词概率,可以通过对模型输出的 logits 除以一个小于 1 的温度(Temperature,T)。

这样就能在过 softmax 后使得分布更加尖锐,大概率的词概率更大。

之后根据获得概率对顶部词先进行挑选,然后再采样,这样直接杜绝了低概率词出现的可能性。

而这里挑选的策略,目前最主流的便是,TopKTopP.

TopK 采样

关于 TopK 采样,就是挑选概率最高 k 个 token,然后重新过 softmax 算概率,之后根据获得概率进行采样,接着进行下一步生成,不断重复。

一个简单的示例如下,假设 K=2:

但关于 TopK 有可能会出现一个问题,那便是,假如说遇上一种情况,模型对当前生成非常肯定,比如说概率最高的 token 的概率就有 0.9,而剩下的 token 概率都很低了。而如果这个时候,还单纯的用 topk 采样的话,就会导致之前想避免的采样到低概率情况仍然发生。

因此我们需要对顶部 token 的累计概率进行限制,这就是 TopP 采样

TopP(Nucleus)采样

和 TopK 单纯限制取顶部 k 个不同,TopP 是先设置一个概率界限,比如说 p=0.9,然后从最大概率的 token 往下开始取,同时将概率累加起来当取到大于等于 p 也就是 0.9 时停止

以上一节提到来说,如果最大 token 概率就已经有 0.9 了,那么就只取最大的一个 token。

关于 TopP 简单的示意图如下:

最后关于 TopP 和 TopK 实际应用,还有参数设置,借鉴 GPT2 中的设置,一般设置参数为 temperature 设 0.9,TopK 和 TopP 一起使用,k 取 40,p 取 0.9,至于效果,看了 GPT2 生成的结果可能也都了解。


本文转载自公众号:安迪的写作间,作者:Andy