受控文本生成2

昨天的文章介绍了在输出空间对文本生成进行控制的两篇论文,今天介绍一篇在隐空间控制的论文。 隐空间方法也分为两个流派,一种认为在隐空间里可以把内容和特性的向量表示分开(disentangled),然后通过修改特性向量来实现对特性的控制;另一种则无须这种假设。下面分别介绍一篇相关论文。 Style Transfer from Non-Parallel Text by Cross-Alignment disentangled representation的代表作之一,发表在2017年的NIPS上,如今引用已经接近300多。 Style Transformer: Unpaired Text Style Transfer without Disentangled Latent Representation 复旦大学NLP组发表于2019年的文章,已经被引用20次,文章标题中明确写了他们的方法without Disentangled Latent Representation。 在文章的introduction部分列举了一些Disentangled表示的问题,我觉得比较重要的两条是: 难以评价Disentangled表示的质量,纯粹的Disentangled表示也很难获得; 分离不是必要的,有文章已经论证了靠解码器就可以overwrite风格。 这篇文章总的来说是对抗生成网络的思路。模型分两大块,一块是典型的encoder-decoder结构的transformers,用来作为风格迁移器,另一块是判别器,用来解决由于没有平行语料带来的训练问题。 判别器训练 文中提出了两种判别器,从结果上看多分类判别器对BLEU指标更友好,而条件判别器对迁移后的风格更友好。 多分类判别器 这种方法比较好理解,即采用K+1类的多类分类器作为判别器,输入只有句子。后K类对应K种风格,第0类对应$f_\theta(\rm{x},\rm{\hat{s}})$,即假样本。在训练时判别器时,将原始语料和用原风格重建后的句子都标为对应的风格,假样本标为第0类。在训练风格转换器的时候,我们希望转换器能尽量提高$f_\theta(\rm{x},\rm{\hat{s}})$被判别为$\rm{\hat{s}}$类的概率,即能骗过判别器,使判别器不认为生成的是个假样本。 条件判别器 输入包括句子和风格,判别器需要判断句子是否含有风格(二分类)。训练判别器时将原始语料和重建句子$f_\theta(\rm{x},\rm{s})$标注为正样本,将变换后的句子$f_\theta(\rm{x},\rm{\hat{s}})$标注为负样本。 判别器训练算法 {: .align-caption style=“text-align:center;font-size:smaller”} 风格迁移器训练 风格迁移器有三个重要的任务,一个是自重建(Self Reconstruction),一个是循环重建(Cycle Reconstruction),以及风格控制(Style Controlling)。 自重建就是输入句子$\rm{x}$以及其原本风格的控制变量$\rm{s}$,让他生成自己。这个任务是可以使用监督学习来完成的,loss计算公式如下 $$ L_{\rm{self}}=-p_\theta(\rm y=\rm x|\rm x,\rm s) $$ 循环重建是先输入$\rm{x}$和一个其他的风格控制变量$\rm{\hat{s}}$,生成$\rm\hat y$,再用$\rm\hat y$和$\rm s$生成一个$\rm y$。此时$\rm y$应该和$\rm{x}$无论内容及风格都一致,并且可以使用监督学习来计算loss: $$ L_{\rm{cycle}}=-p_\theta(\rm y=\rm x|f_\theta(\rm x,\rm\hat s), \rm s) $$ 前面两个任务虽然解决了没有平行语料带来的训练问题,但思考一下就会发现这两个任务并不会起效果。模型可以完全只学习返回原句子就可以“蒙混过关”。解决的办法就是检验一下循环的中间结果$f_\theta(\rm x,\rm\hat s)$,这个中间结果应该尽可能含有$\rm\hat s$风格。因此引入了第三个风格控制任务,这个任务根据判别器的不同也分成两种情况:...

July 23, 2020 · 1 min · Yuanhao

受控文本生成1

关于文本生成的话题聊得比较少,印象中我们之前只有一期多轮问句改写涉及到了文本生成,受控文本生成就更少了。 受控文本生成是在保证内容的前提下对文本的特性,例如情绪、文风等,进行控制。典型的任务有文本风格迁移。图片和声音的风格迁移都已经商用落地了,例如之前很火的几个应用例如Prisma和FaceApp,相比起来文本风格迁移的发展要慢一些。 名噪一时的Prisma是图像风格迁移的代表性应用 {: .align-caption style=“text-align:center;font-size:smaller”} 文本风格迁移很像翻译任务(语言也可以认为是文本特性的一种),但相比于机器翻译,风格迁移任务几乎没有平行语料,所以要困难一些。如果你对这个方向有兴趣,强烈推荐北大付振新同学整理的这个Repo。 受控文本生成因为是文本生成的高级版,通过学习相关技术可以很好地加深对文本生成的理解。受控文本生成从技法上来讲主要有两类,第一类在隐空间做文章,第二类在生成器的输出空间上做文章。 相比于在隐空间施加控制,我感觉在输出空间施加控制在方法上更简单一些。今天先从这个流派开始。我认为在输出空间进行控制又可以细分出两种形式,一种是在概率空间,一种是在离散空间,下面分别用一篇文章来举例。 Delete, Retrieve, Generate: A Simple Approach to Sentiment and Style Transfer 来自斯坦福NLP组,发表在2018年的NAACL,目前引用150,可以说是这个方向的经典论文了。 这篇其实包含了四种方法,但我感觉最有代表性的是在token空间进行的方法(即后面的template based方法),可解释性强,效率也高。 DRG的四种迁移方法 {: .align-caption style=“text-align:center;font-size:smaller”} 这篇文章的思路很简单,因为它基于一个假设:通常文本特征迁移可以通过改变一些标志词或短语(attribute markers)来完成。 在做风格迁移时,首先要找到这些attribute markers。找的方法也很简单,就是考虑某个n-gram在不同风格语料中出现的概率。如果有显著(salience, s)差异,那它就很可能是个attribute marker,显著性的计算公式如下,$u$是某个term,$v$是某种风格,$\mathcal{D}_v$是某种风格的所有语料,$\lambda$是个平滑系数。公式简单,大家一看便懂,计算出数值后根据阈值最终确定所有的attribute marker。 $$ s(u,v)=\frac{\text{count}(u, \mathcal{D}v)+\lambda}{(\sum{v’\in \mathcal{V},v’\neq v}\text{count}(u, \mathcal{D}_{v’}))+\lambda} $$ 围绕这些attribute marker(后文简称AM),后续将进行如文字标题所写的三种核心操作:delete, retrieve和generate。 Delete Delete的目的是要删除句子中的AM,留下内容。用$a(x, v^{\text{src}})$表示源句子x中所有的AM,删除AM后的x表示为$c(x, v^{\text{src}})$,即不含AM的句子内容。 Retrieve 这一步是要在源句子中插入目标特性的AM。论文的策略是先使用$c(x, v^{\text{src}})$在目标特性句子集合中检索一个内容最接近的句子$x^{\text{tgt}}$。内容接近程度的评价可以使用任意的距离函数来完成。 Generate 这是最后一步,即获得最终的结果。文章里有四种策略 Retrieve Only 直接返回第二步的结果。这么做生成的句子在语言角度应该是正确的且带有目标特性,但可能在内容上和源句有出入。 Template Based 直接把$a(x, v^{\text{src}})$替换成$a(x^{\text{tgt}}, v^{\text{tgt}})$。这么做简单粗暴,可能产生不通顺的句子。 Delete Only 把$c(x, v^{\text{src}})$交给一个RNN进行编码,再拼上特性$v^{\text{tgt}}$的embedding,最后交由一个解码器解码。 Delete And Retrieve 和上一种相似,但不是拼上特性$v^{\text{tgt}}$的嵌入,而是用另一个RNN编码得到的$a(x^{\text{tgt}}, v^{\text{tgt}})$的表示向量。 前两种方法是不需要训练的,后两种则需要训练。对于Delete Only,使用重建句子任务(即训练一个自编码器)来训练。对于Delete And Retrieve则复杂一些,为了防止特征迁移的能力退化成句子拼接(c+a)的能力,作者在这里训练一个降噪自编码器,具体地说就是随机替换a里的AM。...

July 22, 2020 · 1 min · Yuanhao

Tweet Sentiment Extraction比赛总结

这是前段时间结束的Kaggle比赛,之前在文档问答的文章中我也有提到过,最终我们队获得了第七名,这篇文章分享一下我的参赛收获和感受。 首先感谢队友,特别是曹老师,如果没有曹老师我肯定中途就弃赛了。至于弃赛的原因,在感受部分会详细介绍。我的代码已经传到Github上了,感兴趣的朋友可以看一看,里面包含了我所有的commits,可以完整看到我方案的演进。 Repo: https://github.com/thuwyh/Tweet-Sentiment-Extraction 赛题回顾 比赛叫做Tweet Sentiment Extraction,对于给定的tweet和情感极性,需要选手从文本中找出支撑情感的部分。例如下面这条数据 "My ridiculous dog is amazing." [sentiment: positive] 模型应该返回amazing这个词。比赛的评价指标是word-level Jaccard score,它的含义看下面的实现就一目了然了。 def jaccard(str1, str2): a = set(str1.lower().split()) b = set(str2.lower().split()) c = a.intersection(b) return float(len(c)) / (len(a) + len(b) - len(c)) Baseline及一些改进 在比赛的初期讨论区和kernel分享区基本就定下了解题思路的基调,即用机器阅读理解(MRC)的方法来做span prediction。具体的说,就是把数据提供的情感词作为question,把tweet作为context,把预测对象作为answer。 模型也很简单,在RoBERTa后面接一个questionAnswering head预测start和end位置就可以了。这道题一个比较神奇的地方就是RoBERTa的效果比普通的BERT要好一些。 在这个框架下,大家也都做了一些改进,例如: 在语言模型输出后面加dropout; concat语言模型的多层输出结果; 引入FGM等对抗训练方法 以上都是一些比较常规的操作,也比较容易实现,类似FGM是比较稳定能提分的。还有一些稍微复杂一点的trick,例如: 在词级别进行数据增强,例如同义词替换,随机删词 在token级别的增强 label smoothing 蒸馏 因为是span prediction任务,数据增强如果做成随机动态的,需要考虑到改词后对label的影响,这是实现的一个小难点。英文的同义词替换可以使用wordnet来做,相比中文的一些同义词库来讲质量是比较高的。 label smoothing和蒸馏是很相关的两个技术,因为他们都需要用到KL散度作为损失函数。我也是趁这个比赛补了一补相关的知识,感觉还蛮有趣的,感兴趣的朋友可以参考这篇文章。做QA任务通常是对位置用CrossEntropyLoss,但是如果label不是一个确定的位置而是平滑过或者是teacher model预测得到的分布,就需要使用KLDivLoss。 这里在做标签平滑的时候遇到了一个小问题,蛮值得思考的。最开始是Google在Imagenet上用这个技巧,对于这个分类问题标签的种类是确定的K=1000类,所以在Inception论文里直接用一个系数来控制平滑的强度,即 $$ q’(k) = (1-\epsilon)\delta_{k,y}+\frac{\epsilon}{K} $$ 但是如果用同样方法在这些长短不一的句子上做平滑,其实是不合适的。每个位置的平滑概率反比于句子的长度,也就是K,所以我认为更好的确定平滑强度的方法是先确定一个单位平滑强度,再根据句子总长来确定原标签的权重。 针对数据特点的方法 这次的数据总体质量很差,噪声(其实是错误)很多,给参赛者带来了很多困扰。主要的噪声模式有两种,一种是把整个句子都标注成了支撑情感的selected_text,第二种是数据中有大量“断头词”出现在标签中。下图给出了一些例子。 对于第一种整句都是标签的情况,早期很多参赛者就发现了对于neutral类型的情感,绝大部分selected_text都和text一样;但对于其他情感,我们在人工审阅数据之后没有发现什么规律。我只好设计了一个辅助的分类任务让模型自己学习,实测下来有些微的提升,但并不明显。 对于“断头词”的情况,我们在比赛的末期终于发现了其规律。这种情况应该是由于标注环境不一致导致的。例如Twitter数据里有很多@用户的情况,这份比赛数据集会把相关的文本删除,但由于删除脚本的问题会导致文本中多出一个空格。我们猜测标注者看到的数据应该是没有多余空格的,类似于是使用' '.join(text.split())处理过的。这就会导致标出来的span相对于原text的位置产生了位移。且位移的大小就等于多余空格的数量。...

July 8, 2020 · 1 min · Yuanhao

跨语种语言模型

在著名的科幻电影《银河系漫游指南》里有一种叫巴别鱼的神奇生物。将它塞进耳朵里你就能听懂任何语言。多语种语言模型做得事情和巴别鱼很像,人们希望这个模型能用来处理所有的语言。举个例子,大家常用的中文bert有很强的中文处理能力以及一定的英文处理能力,但基本也就只能处理这两种语言;而目前的SOTA多语种模型XLM-RoBERTa能够处理104种语言。 巴别鱼,体型很小,黄色,外形像水蛭,很可能是宇宙中最奇异的事物。它靠接收脑电波的能量为生,并且不是从其携带者身上接收,而是从周围的人身上。它从这些脑电波能量中吸收所有未被人察觉的精神频率,转化成营养。然后它向携带者的思想中排泄一种由被察觉到的精神频率和大脑语言中枢提供的神经信号混合而成的心灵感应矩阵。所有这些过程的实际效果就是,如果你把一条巴别鱼塞进耳朵,你就能立刻理解以任何形式的语言对你说的任何事情。 数据集 训练跨语种语言模型会用到两种语料。一种是单语种(monolingual)语料,另一种是平行(parallel)语料。所谓平行语料就是源语言与译文“对齐”的语料。所谓对齐也有好几种级别,最常见的是句子级对齐,也有按词进行对齐的文本。可想而知,平行语料的获取相比于单语种语料要困难许多。如何充分借助单语种语料来提升模型能力是XLM研究的一个重点。 跨语种语言模型的评价一般有两个大方向,一个是其语义理解能力,另一个是文本生成能力。语义理解能力通常借助XNLI数据集,它提供了15种语言的平行文本,每种语言7500对的NLI语料。文本生成通常用翻译任务来评估,感兴趣的朋友可以自己查阅相关资料。 模型 下表列出了常见的单语种和多语种预训练语言模型。接下来我们将分析其中的mBERT、XLM和XLM-R三个模型。 Multilingual Bert(mBERT) 模型来自于这论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》,你没有看错,就是发表于2018年大名鼎鼎的BERT论文。 2018年11谷歌就放出了支持104种语言的多语种版本预训练模型,规格是BERT base。这个模型的较新版本是uncased版,即没有对输入文本进行规范化。使用WordPiece算法进行tokenization,词典大小是110k。其他的训练方法和普通的BERT一样,采用的是MLM和NSP两个loss,语料是Wikipedia。 XLM 模型来自于论文《Cross-lingual lan- guage model pretraining》,来自于FAIR,发表在NIPS2019。 XLM使用BPE算法进行tokenization,并且词典大小比mBERT更大,达到200k。论文指出Shared sub-word vocabulary对模型性能有很大的影响,在训练BPE算法的过程中他们使用了特殊的采样方式来避免低资源语种被进行字符集切分。 模型训练使用了三种不同的目标函数,在单语种语料上使用非监督的CLM和MLM。MLM就是masked language modeling,大家比较熟悉,在此就不再赘述了。CLM全称是Causal Language Modeling,简单的说就是用前面的词预测当前词,更详细的介绍大家可以参考我们之前UniLM和MASS的文章。在平行语料上使用的目标称为Translation Language Modeling (TLM)。其训练方式如下图所示,是将平行句子拼接后随机mask,希望让模型能借助另一种语言的信息来还原出被遮蔽的词。从图中可以看出模型用language embedding替换了BERT里的type embedding,并且在做TLM任务时position embedding在两个语言间是对应的。 我们来看一下XLM在XNLI上的表现。这张表很有意思,首先对这个数据集有3种处理方式:translate-train,translate-test和直接测试,即zeroshot。第一种是把英语的MNLI数据集机器翻译成XNLI内的15种语言用于训练,在XNLI测试集上测试;第二种是把XNLI测试集的15种语言翻译成英文。本文的对照组就是上面的mBERT。 可以看到效果最好的是翻译训练集,平均精度达到了76.7%,zero-shot次之,最差的是翻译测试集。在相同的实验设定下XLM稳定优于mBERT,甚至在zero-shot下的XLM也比finetune过的mBERT强。另外MLM+TLM也稳定优于只用MLM的方式。 XLM-RoBERTa 模型来自于论文《Unsupervised Cross-lingual Representation Learning at Scale》,和上文一样来自FAIR,已经被ACL 2020接收。 XLM-R使用了比XLM更大的词典,达到了250k。它也没有辜负RoBERTa的血统,使用了比Wikipedia大得多的cc100数据集。XLM-R只使用单语种语料,训练目标也只有MLM一个。 Tokenizer换成了sentence piece算法,在构建时也进行了采样,并且调整了系数使得各语言更加平衡。模型层面去掉了language embedding,变得更加简洁。我感觉用“重剑无锋”来形容XLM-R再合适不过了。 这篇论文总结了几个影响多语种模型的重要因素,可能会对大家有所启发: 当处理的语种变多的时候模型的能力会下降(嗯,符合常识)。增大模型可以一定程度对抗这种效应。 模型能力主要受词典大小、训练集大小、语种的采样频率影响 增大词典规模可以提高模型性能 sentence piece可以提高模型的通用性 下面这种图可以让大家对这些结论有更直观的印象 最后来看一下XLM-RoBERTa的实力。下表是在XNLI数据集上的结果对比,设定和XLM论文中差不多,其中Devlin et al.指的是mBERT,Lample and Conneau指的是XLM。可以看出XLM-R相比于XLM又前进了一大步。 顺便再提一嘴,论文作者还在GLUE数据集上对比了XLM-R和XLNET、RoBERTa等单语种语言模型,XLM-R超过了BERT-large,略低于XLNET和RoBERTa。也就是说XLM-R不仅获得了多语种能力,而且没有牺牲英文上的水平。 总结一下,从2018年的mBERT到2020年的XLM-R,跨语种预训练语言模型获得了长足的发展,地球语言范围内的巴别鱼指日可待。最近在Kaggle上正在进行一场跨语种文本分类的比赛,如果有想体验XLM最新进展的朋友可以去试试身手。 今天的文章就到这里,下期再见👋

May 11, 2020 · 1 min · Yuanhao

十分钟读懂beam search-2

在上一篇文章中我们介绍了基础版的beam search,这篇文章是对它的一个扩展,可以在模型不改的情况下获得更好的生成结果。今天的介绍围绕的也是一篇蛮新的论文,《The Curious Case of Neural Text Degeneration》,根据这篇论文的版面内容,它应该已经被ICLR 2020接收了。 Beam Search的问题 先解释以下什么要对Beam Search进行改进。因为Beam Search虽然比贪心有所改进,但还是会生成出空洞、重复、前后矛盾的文本。如果你有文本生成经验,一定对这些现象并不陌生。在语言模型还不像如今的BERT、GPT这么厉害的时候,这种现象更加明显。 没有经验也没关系,我们来看一个论文里面的例子。输入模型的引文(context) “The study, published in the Proceedings of the They were cattle called Bolivian Cavalleros; they live in a National Academy of Sciences of the United States of remote desert uninterrupted by town, and they speak huge, America (PNAS), was conducted by researchers from the beautiful, paradisiacal Bolivian linguistic thing. They say, Universidad Nacional Autónoma de México (UNAM) and...

March 23, 2020 · 4 min · Yuanhao

十分钟读懂beam search-1

最近研究了一下用基于BERT的encoder-decoder结构做文本生成任务,碰巧管老师昨天的文章也介绍了以生成任务见长的GPT模型,于是决定用两篇文章大家介绍一下在文本生成任务中常用的解码策略Beam Search(集束搜索)。 解码及贪心搜索 生成式任务相比普通的分类、tagging等NLP任务会复杂不少。在生成的时候,模型的输出是一个时间步一个时间步依次获得的,而且前面时间步的结果还会影响后面时间步的结果。也就是说,每一个时间步,模型给出的都是基于历史生成结果的条件概率。为了生成完整的句子,需要一个称为解码的额外动作来融合模型多个时间步的输出,而且使得最终得到的序列的每一步条件概率连乘起来最大。 在文本生成任务中,每一个时间步可能的输出种类称为字典大小(vocabulary size,我们用$v$表示),进行T步随机的生成可能获得的结果总共有$v^T$种。拿中文文本生成来说,$v$的值大约是5000-6000,即常用汉字的个数。在如此大的基数下,遍历整个生成空间是不现实的。 最容易想到的策略是贪心搜索,即每一个时间步都取出一个条件概率最大的输出,再将从开始到当前步的结果作为输入去获得下一个时间步的输出,直到模型给出生成结束的标志。例如下图,每一个时间步都取出了条件概率最大一个结果,生成了序列[A,B,C]。 很明显,这样做将原来指数级别的求解空间直接压缩到了与长度线性相关的大小。由于丢弃了绝大多数的可能解,这种关注当下的策略无法保证最终得到的序列概率是最优的。 Beam Search 而beam search是对贪心策略一个改进。思路也很简单,就是稍微放宽一些考察的范围。在每一个时间步,不再只保留当前分数最高的1个输出,而是保留num_beams个。当num_beams=1时集束搜索就退化成了贪心搜索。 下图是一个实际的例子,每个时间步有ABCDE共5种可能的输出,即$v=5$,图中的num_beams=2,也就是说每个时间步都会保留到当前步为止条件概率最优的2个序列。 在第一个时间步,A和C是最优的两个,因此得到了两个结果[A],[C],其他三个就被抛弃了; 第二步会基于这两个结果继续进行生成,在A这个分支可以得到5个候选人,[AA],[AB],[AC],[AD],[AE],C也同理得到5个,此时会对这10个进行统一排名,再保留最优的两个,即图中的[AB]和[CE]; 第三步同理,也会从新的10个候选人里再保留最好的两个,最后得到了[ABD],[CED]两个结果。 可以发现,beam search在每一步需要考察的候选人数量是贪心搜索的num_beams倍,因此是一种牺牲时间换性能的方法。 以上就是Beam Search的基本概念,下面我们解析一种高效率实现方式。 Beam Search代码解析 Beam Search的原理虽然简单,但实际实现的时候却有很多细节要考虑。下面要解析这个实现出自于NLP界著名Python包Transformers,我为了说明方便做了一些改动。 一个正确且高效的算法需要处理的问题大概有两个: 充分利用硬件,可以处理批量数据,且尽量使用并行计算少用循环 处理好长短不同的生成结果 下面是基础版的beam search函数定义。其中context是编码器编码获得的向量,batch_size是每批数据中包含的样本量,bos_token_id是句子开头标志的token id,pad_token_id是用于填充的token id,eos_token_id是句子结束标志的token id。这里给参数填上的默认值和我们后面讲解时使用的例子是一致的。 def beam_search_generate(context, batch_size=3, max_length=20, min_length=2, num_beams=2, bos_token_id=101, pad_token_id=0, eos_token_id=102, ): pass 在函数中主要执行以下三个步骤: 准备初始输入 在当前生成的序列长度未达到max_length时扩展生成序列 准备最终输出的序列 下面我们分别解析。 准备初始输入 # 建立beam容器,每个样本一个 generated_hyps = [ BeamHypotheses(num_beams, max_length, length_penalty, early_stopping=early_stopping) for _ in range(batch_size) ] # 每个beam容器的得分,共batch_size*num_beams个 beam_scores = torch.zeros((batch_size, num_beams), dtype=torch.float, device=encoder_input_ids....

March 20, 2020 · 4 min · Yuanhao

最近邻搜索,MIPS,LSH和ALSH

上一篇介绍REALM的文章对文中提到的Maximum Inner Product Search没有作充分的介绍。发出去的标题已经没法改了,这篇文章介绍一下MIPS和最近邻搜索问题,以及两个相关的算法。 问题定义 MIPS的定义很简单,假设你有一堆d维向量,组成集合X,现在输入了一个同样维度的查询向量q(query),请从X中找出一个p,使得p和q的点积在集合X是最大的。用公式写出来就是 $$p=\mathop{\arg\max}_{x \in X}x^Tq$$ 这个问题和最近邻问题很像,最近邻问题只要把上面的定义改成找一个p使得p和q的距离最小,假设这个距离是欧氏距离,则 $$p = \mathop{\arg\min}_{x \in X}\left |q-x\right|^2=(\left | x \right |^2 – 2q^Tx)$$ 如果X中的向量模长都一样,那两个问题其实是等价的。然而在很多实际场景例如BERT编码后的句向量、推荐系统里的各种Embedding等,这个约束是不满足的。 最近邻搜索其实应用非常广泛,如图片检索、推荐系统、问答等等。以问答匹配为例,虽然我们可以用BERT这样的大型模型获得很好的准确度,但如果用BERT直接对语料库中的所有问题进行计算,将耗费大量的时间。所以可以先用关键词检索或者向量检索从语料库里召回一些候选语料后再做高精度匹配。 朴素的算法 对于MIPS问题,一个直观的蛮力算法就是计算出所有相关的内积,然后将内积排序,找到最大的那个。对于最近邻问题其实也类似,即使X中向量模长各不相同,也可以提前计算出来,并不会增加排序的时间复杂度。 内积的计算可以转换成一个矩阵乘法,在CPU和GPU上都有大量的高效实现。当X中有N个向量时,时间复杂度是O(Nd),当N不大的时候是可以接受的,但是通常在工业界的大规模系统中,X的规模往往很大,朴素算法就显得力不从心。 Locality-sensitive hashing 对于某些距离度量(例如欧式距离,cosine距离)下的最近邻问题,可以使用LSH算法来解决。LSH的思路就像下图示意的那样,用hash函数把高维空间的点分到几个桶里去,从而减少距离的计算量。 跟普通的哈希函数不同,这个哈希函数是Locality-sensitive的。具体地说就是它有一个神奇的特点:在空间中离得近的点被分到同一个桶的概率大,离得远的点则大概率被分到不同的桶里去。或者说对于两个点x和y,他们被哈希函数分到同一个桶的概率随着距离的增大单调递减。 这样在查询的时候,只需要精确地比较和查询向量q处在同一个桶里的那些x。如果桶足够多,那便可以将N大大降低,从而提高查询速度。但需要注意的是,LSH是一个近似算法,有可能产生桶内的向量其实都不是最优解的情况,不同哈希函数发生这种情况的概率都不一样,也是作为评价哈希函数好坏的重要依据之一,对这部分感兴趣的朋友可以读参考文献。 下面举一个具体的例子来解释一下LSH。假设某个最近邻问题考虑的距离度量是cosine距离,有一个满足要求的LSH函数(变换),称为Random Projection。 如上图所示,其过程很好理解: 随机取一个空间中的超平面将空间分为两半,X内位于某一半的点标为0,其他标为1; 重复第一步K次。 完成之后,X中的每个点便得到了一个由K个0,1组成的表示(signature)。例如重复了K=32次,那每个点都被分到了一个用一个int32类型的整数编号的桶里。如果这些点在空间中分布足够均匀,那么我们将可以期望每个桶里只有N/2^K个点,当K~logN,则查询的时间复杂度就约为O(dlogN)。整个过程构建出了一张哈希表,由于LSH可能会错过最优解,一个可行的增强鲁棒性的做法是用同样的方法多构造几张哈希表,借助随机的力量来降低犯错的概率。这里有一个讲解LSH的视频,可谓短小精悍,直观易懂,推荐给大家。 LSH看上去相对于朴素算法确实前进了一大步。但别高兴得太早,要达到O(dlogN)的效果必须服从那个很强的假设。而点在空间中分布足够均匀往往是不太现实的。除此之外,一个LSH只能适用于某些距离度量,对于MIPS,找不到符合要求的LSH。 Asymmetric LSH(ALSH) 论文里证明了找不到可用于MIPS问题的LSH函数,但他们发现对LSH稍作点改动即可将MIPS问题转变为欧式距离下的最近邻搜索问题。改动的关键就在于Asymmetric这个词。在LSH算法中,对查询向量q和X中的向量做的是同样(对称) 的变换,而在ALSH中作者对两者使用了 不同(非对称) 的变换。简单起见,假设查询向量q的模长是1。对于X,先做一个放缩变换使得X中所有向量x的所有元素都小于1。然后对X中的向量进行变换P(x),对查询向量q做变换Q(x),P和Q的定义如下: $$P(x) = [x; \left | x \right |_2^{2^1}; \left | x \right |_2^{2^2},…,\left | x \right |_2^{2^m}]\ Q(x) = [x;\frac{1}{2};\frac{1}{2},…,\frac{1}{2}]$$ 可以发现,P和Q虽然变换不同,但都会使输入向量增加m维。进一步观察可以得到 $$Q(q)^TP(x)=q^Tx+\frac{1}{2}(\left | x \right |_2^{2^1}+ \left | x \right |_2^{2^2}]+…+\left | x \right |_2^{2^m})\ \left | P(x) \right |_2^2=\left | x \right |_2^{2^1}+ \left | x \right |_2^{2^2}]+…+\left | x \right |_2^{2^{m+1}}$$...

March 7, 2020 · 1 min · Yuanhao

REALM: Retrieval-Augmented Language Model Pre Training

知识就是力量 ——培根 背景 去年可以说是语言模型快速发展的一年,BERT、XLNET、Albert等等模型不断刷新各个NLP榜单。在NLP榜单中比较引人注目的应该属于阅读理解型的任务,例如SQuAD等等。以SQuAD为例,模型需要阅读一段给定的文本,然后回答几个问题,问题如果存在答案,答案一定可以在文章中找到。所以说虽然叫阅读理解,但其实和序列标注有点相像,是在给定序列中标出答案段。而这篇论文针对的问题叫开放领域问答(Open-domain QA),对于一个问题Q,模型需要从包含大量文档的知识库中找到答案,而不是像SQuAD数据集一样从一篇文章中寻找。 大部分的语言模型都采用一种称为masked language model,简称MLM的任务来训练,让模型学会类似完形填空一样的能力。通过在大规模语料上的训练,预训练语言模型如BERT实际上已经隐含了一些知识。例如输入一句“The is the currency of the United Kingdom”,BERT很有可能会填入单词"pound"。虽然他还是根据词的共现信息学习和推理的,但看上去就像具有所谓的知识一样。从去年开始就有越来越多的研究从单纯语言模型转换为带有知识嵌入的语言模型,例如清华和百度提出的两个同名模型ERNIE。 但上面说的这种隐含知识不好把握,也难以扩展。这篇论文则提出了一种更加模块化且可解释性更强的知识嵌入方法。总的来说,他的方法是训练一个独立的“语境知识抽取器”(contextual knowledge retriever),通过这个抽取器来决定应该在推理时使用哪些知识。而且这个抽取器和语言模型一起进行非监督预训练大大提高模型性能。 方法 如上图所示,整篇论文涉及两个任务,左边是语言模型预训练任务MLM,右边是QA任务。下图是预训练任务一个更加完整的流程图,我们由此切入进行介绍。 整个过程分为两个关键步骤。先看第一步,即neural knowledge retriever,它负责计算p(z|x)。要实现这个过程首先需要对z和x进行编码。论文采用的是BERT,对于问题x,直接输入BERT,取[CLS] token的输出作为编码向量,而对于文档z,则将标题和正文用[SEP]连接后输入BERT,同样去[CLS] token的输出。论文中还对BERT的输出向量进行了降维处理。即 对于文档库中的某一个z,则 其中f是问题和文档的相关性, 以上部分就称为neural knowledge retriever,通过他每篇z都会得到一个p。现在可以进行第二步,综合x和z求y。上图是一个预训练的例子,y是抠掉的词。利用z的方式是将z的正文和x拼在一起来提供上下文信息,然后优化下面的目标 其中j指第j个masked token。 在做QA的时候稍有不同。由于此时是针对某个具体的z,所以作者将开放域问答任务退化成了像SQuAD一样在文档中找答案的阅读理解任务。 这一部分就是knowledge-augmented encoder。 训练 上面已经描述了预训练阶段和QA finetune阶段的任务。训练的过程都是最大化正确y对应的logp(y|z,x),而且以上描述的两个任务都是可以端到端优化的。 但这里面对一个问题,上面有个公式需要对整个知识库中所有的文档z的相关概率求和,这是很困难的。作者提出将这一步用只对概率最高的k个文档计算来近似,因为绝大部分文档由于与问题不相关,p(z|x)都非常小。但问题还没有解决,如何找到概率最高的k个文档呢。 观察公式可以发现p(z|x)是正比于两个编码后的内积的,由于大家的分母都一样,分子的顺序就是整个分数的顺序。所以可以用最大内积搜索算法(Maximum Inner Product Search, MIPS,并不知道是什么,维基百科都没有)来解决。但要构建一个快速检索的索引又要求两个编码后的向量是确定的,而由于编码器是不断训练的,所以这个条件无法满足。为了追求一个平衡,作者决定每隔几百步才更新一下编码器,并重新构建索引。而且这只发生在预训练语言模型的时候,在finetune QA任务的时候只使用语言模型得到的编码器编码一次所有的z和x并构建索引。 额外策略 在研究过程中作者发现了一些能让模型更好训练的策略。 只训练真正需要知识的词(通常是实体和日期)来训练MLM 在topk文档外添加一个虚拟的null document 避免让x出现在z中(因为x被mask过,如果它来源于z,那答案就暴露了!) 避免冷启动的retriever太渣导致的恶性循环,他们用了一个以ICT作为任务的模型来初始化retriever 结果对比 这篇论文的对手主要是原来sparse retriever+神经阅读理解模型的组合,例如大名鼎鼎的DrQA。所谓sparse retriever就是指用例如TFIDF之类的特征进行检索的模型。还有一些跟本文很像的neural retriever+neural reader的组合。其中提到了一个ORQA,跟这篇非常像,只是这篇增加了预训练的步骤。最后是一些生成式模型,例如finetune后的T5(可怕!) 在Natural Questions-Open(NQ)、Web Questions(WQ)和Curated Trec(CT)三个数据集上的结果如下 总之一句话,非常牛逼!而且这里模型只取了top 5文档,其他模型可能取了20-80篇,还是打不过他。注意到ours的括号内有两个数据,Z是知识库,很好理解,X是指预训练用的语料。而且通过文章的Ablation Analysis部分可以知道预训练其实是非常关键的一个步骤,对performance的贡献非常大。 后记 我感觉这篇论文和他提到的ORQA还是很厉害的,知识嵌入也从去年的实体粒度的嵌入发展到了如今句子、篇章级别的嵌入。试想一下,这项技术发展起来之后,如今基于词的Sparse搜索引擎应该很快会发展成对NN更友好的Dense搜索引擎,所有的内容也许都会被映射到一个向量空间。各路神经网络将在这个向量空间尽情驰骋~莫非得encoder者得天下?! 论文链接:https://kentonl.com/pub/gltpc.2020.pdf

March 6, 2020 · 1 min · Yuanhao

能跟你聊DOTA的神经对话模型:Meena&DialoGPT

提到对话机器人或者聊天机器人,国内的朋友可能先想到的是微软小冰或者苹果Siri。这两个机器人由于需要完成一些功能性的任务,都采用了模块化设计,虽然神经网络在其中起到了重要作用,但输出结果并不是从输入语句“端到端”产生的。而且用过的朋友都知道,他们的聊天能力并不是很令人满意。 今天介绍的神经对话模型则是一步到位,将对话历史作为输入,让模型直接生成下一轮的回复。学术一点说,神经对话模型本质上是在解决文本生成的问题。相比于很多聊天机器人(包括我们的晴天一号)目前使用的语料库+检索算法的架构,生成式(包括改写等等)算法能给对话带来更多的可能性和趣味性。最近,得益于大规模预训练语言模型,文本生成任务得到了长足的发展。本文的主角就代表了目前神经对话模型的最高水平,分别是19年十一月由微软发布的DialoGPT和20年一月谷歌发布的Meena。 在开始正文之前,先展示一段机器人生成的对话,让大家对技术现状有个直观感受。 模型 两篇论文都没有在模型方面有什么重大的创新,他们使用的都是如下图所示基于Transformer的seq2seq模型。这里大致介绍一下所谓seq2seq模型的工作原理。每生成一个单词的过程如下:将对话的历史信息输入进编码器(左下角inputs),模型已经生成的当前轮结果输入解码器(右下角outputs,没错,outputs是也用来input的~),然后模型将会综合两者信息输出它预测的下一个词(上方output)。而生成一句完整的回复只需要将刚才新生成的词拼接到当前轮已有结果的后面(图中shifted right的含义),重复上述过程直到模型输出句子结束符。 DialoGPT采用的是标准的GPT-2模型,也就是上图模型的加大号版本;而Meena采用的是去年Google通过神经网络结构搜索(NAS)方法得到的进化版transformer模型(Evolved Transformer)。Meena由1个ET编码器和13个ET解码器构成,ET解码器和标准Transformer解码器的对比如下图所示,两者似乎差别不大,论文注脚说一个ET Decoder Block大概是标准Transformer Decoder Block的两倍深。 虽然Meena和DialoGPT都是Transformer模块搭建的模型,但两者规模的差别非常巨大。Meena使用了极其巨大的隐层规模(2560 hidden size,32 attention heads),总参数量达到了惊人的2.6B。相比之下微软的模型就克制很多,最大规模的DialoGPT参数量为762M,表现最好的模型参数量是345M。作为参照,目前地表最强——google的T5模型参数量是11B,BERT large则只有340M。 数据 数据则是两篇论文提升performance的重中之重,特别是对于模型大小比较正常的DialoGPT来说。 DialoGPT论文里明确说数据是从Reddit上搞下来的。通过解析Reddit上的讨论帖,可以获得“树状”的对话数据,然后把每一条从根节点到叶节点的路径拎出来都能获得一个完整的对话。假设一个对话共说了K轮,每次取出当前轮作为标签,之前轮的对话作为输入,总共可以获得K-1条训练数据。Meena的数据来自于public domain social media conversations,他也是从树状对话中获得的数据,我猜测这些对话的形式应该和reddit这种论坛比较相似。 他们的数据量都是非常之巨大的,DialoGPT的总session数是147116725(147M),总词数是1.8B。再来看Meena,他们的总训练数据量是867M,总词数是40B,存成文本文件是341GB。这里可以对比一下之前的模型,BERT的训练数据是16GB,GPT-2的训练数据是40GB,曾经的数据狂魔Roberta是160GB。谷歌再次定义了什么叫大力出奇迹。我在维基百科看到人的平均阅读速度是每分钟200词,按这样的速度1 Billion单词需要一个人不眠不休阅读大概347天。从这个角度看,目前的训练数据量似乎又显得比较合理了。 两篇论文都提到要对数据做非常大刀阔斧的清洗,例如删掉太长或太短的、重复词语多的、含有URL的、不含有常见词的等等。总之,要保证数据质量足够高。 顺便提一下,DialoGPT尝试了从预训练模型迁移和从对话数据集从头训练两种方式。结果是迁移的表现明显好于从头训练。我认为这个现象非常的合理,使用预训练模型就好比教一个学过好几年语文的小朋友在网上聊天,这应该好过一个从小就只通过网络聊天学语文的小朋友。但Meena好像采用的是从头训练的模式,也许他们预训练一把能得到更好的结果。 由于使用了极大的模型和数据集,两个模型都是使用了豪华的硬件来进行训练。DialoGPT使用16个V100,而Meena则使用了2048个TPU v3核训练了30天。512个v2 TPU组成的POD三年协议价是12万美元一个月,v3应该更贵,所以你如果想复现这个模型,至少要花费48万美元。有钱真好:) 解码方式 解码是生成式任务里很重要的一个部分。因为正如前面介绍的,生成模型在推理时每一个时间步只会生成下一个词,而且后面的结果会依赖前面的结果,所以就需要一个好的解码策略来保证这一个个token最后能组成高质量的句子。去年有不少关于解码的工作,希望让机器人产生更有意义、有个性的回复。 DialoGPT没有采用什么独特的解码方式,而是使用了常见的Beam Search(集束搜索),只在最后用了一个较为新颖的重排序算法来从集束搜索得到的TOP K个结果中选出最佳的那个。在排序时,他们用一个训练好的“反向模型”来由生成的回复倒推输入。选用使输入语句概率最大的那个输出作为最终的输出。由于采用了Beam Search,它的解码过程应该是比较耗时的。 Meena比较有意思,作者们表示由于模型的Perplexity(困惑度)够低,完全不需要使用集束搜索。而是采用非常简洁的采样+重排就可以得到好的句子。 困惑度p可以理解为,如果每个时间步都根据语言模型计算的概率分布随机挑词,那么平均情况下,挑多少个词才能挑到正确的那个。 –知乎用户TimsonShi 所谓采样,就是在每一步都根据输出层得到的概率分布,随机选一个输出token,直到得出一个完整的句子。重复采用过程N次,得能到N个句子,最后将这N句话根据归一化后的句子概率排序,选出最优的。需要注意的是他们的输出层并不是对Vocabulary的简单Softmax,而是像知识蒸馏里一样增加了采样温度,即 $$p_i=\frac{\exp(z_j/T)}{\sum_j{\exp(z_j/T)}}$$ 作者们发现T的大小会影响生成结果。T小的时候,倾向于使用常规词汇,而T大的时候倾向于使用与上下文相关的词汇。论文使用的T为0.88,N为20。他们的结果显示这种采样的效果相比于集束搜索确实有很大的改进。下面一个是集束搜索的结果,另一个是采样,采样很好地避免了集束搜索生成结果丰富性差的弊端。 能得到这么好的结果有一个重要前提,就是模型的困惑度也就是perplexity够低。Meena的困惑度有多低呢,它是10.2,相比较之下DialoGPT的困惑度大约是15。 评价指标与结果 对评价指标的选择,两篇论文有很多共同点。他们都已经从BLEU这种考量词级别重合度的离散客观指标进化到了直接跟对话质量相关的抽象指标。从这一点也可以感觉出这个领域已经进入了相对成熟的阶段。DialoGPT使用人工评价的方式,评价模型的relevance,informativeness和how human-like。而Meena则用综合sensibleness和specificity的称为SSA的指标对机器人进行评价。所谓SSA就是对sensibleness和specificity的简单平均,A对应就是average。 Sensibleness我认为基本对应relevance,是指机器人能否得出符合上下文的有意义回答。但有意义是不够的,正如文章所说,一个只会回答“I don’t know”这类万金油句子的机器人说的话也是符合上下文的,但其实并不好。 Specificity对应infomativeness,是另一个维度,它评估机器人是否能给出有具体信息的答案。例如当人问”你喜欢电影吗”的时候你可以回答“我不知道/这很难说”,但一个更令人满意的回答可能是”当然,我特别喜欢科幻电影”。 我们来看一下Meena论文里的结果,因为他们为了比较,在相同的评测体系下把DialoGPT也测了。在Meena论文的评价指标下Meena的水平相当高,人类的SSA大概是0.82,Meena是0.78,已经相当接近。其他的对手在Meena面前基本抬不起头来,DialoGPT 0.51,Cleverbot 0.44。他们还评估了小冰和Mitsuku这两个著名的模块化机器人(非端到端的神经对话模型),SSA分别是0.36和0.56。文中还特别提到小冰的Specificity很差,只有0.19,这和我的使用感受是一致的。小冰感觉总是在扯淡,很难说出有意义的东西来。 SSA体系虽好,但完全依赖人工评价。Meena论文还评估了perplexity和SSA的相关度。结果如上图所示,横坐标是困惑度,纵坐标是SSA,Perplexity和SSA呈现非常明显的负相关关系,相关度大概是0.94。这就是说在训练模型的时候只需要评估混乱度基本就能知道最终的效果。这个结论我觉得非常重要,它无疑给广大神经对话模型研究人员带来了一个福音,毕竟人工评价实在太麻烦也太贵了。这下好了,以后大家奔着降低perplexity去就好了。 对话样例 在这一小节再放几张对话小样,从图中可以看出,这些模型的表现都很好,回答不仅流畅还符合一些常识。当然,我们在前面的文章里也讲过,这种隐含知识不太好控制。但只要语料够优秀,效果看起来是非常棒的。 后记 DialoGPT去年年底就发布了,而且微软大方地提供了预训练好的模型,他们也成为了这个领域当时的SOTA,但没想到这么快就被谷歌超越了。今年初看到谷歌连续放出Meena和REALM的时候还是蛮激动的,一个在类人闲聊上获得突破,另一个大幅提高了外挂知识库的利用水平。Twitter上针对这两篇论文也充满了乐观的论调,甚至有人预测2021年我们就能看到AGI了。这两年的技术发展确实让我们对文本数据的利用水平有了质的飞越,虽然不知道AGI如何,中文房间我感觉很快就会造好了。 另外也感慨Quoc V. Le带领的团队最近势头真猛。谷歌海量算力给了他们无限的空间,已经有在CV(例如去年的EfficientNet已经红遍Kaggle社区了)、NLP等领域遍地开花之势。如果中文房间真的盖好了,门牌上应该写的是1600 Amphitheatre Parkway, Mountain View, CA, U.S.吧。 参考阅读 DialoGPT: http://arxiv....

March 2, 2020 · 1 min · Yuanhao