预训练模型,NLP的版本答案!
1. 碎碎念
大概是两年前,跟百度的nlp组,参与合作过Ernie在对话系统上的应用。
问题其实很多,模型训练慢,一个月迭代一次很正常(现在做业务,两周就要有一轮迭代),显卡内存动不动就给爆了。
最后在业务上,效果提升也不明显。
一方面是个人技术菜,没利用好。另一方面,线上的lstm模型,已经喂了几百万/千万,各种方式清洗过的质量不错的样本(大力出奇迹),基线其实很高了。
并且infer非常慢,对话系统对实时性要求又高,只能离线在别的地方用(当时蒸馏这个做法还不流行)。
但我依然震惊了,因为它只用了人工标注的几万高准样本。而我们之前是花了几个月的时间,各种洗样本。
人家用比你少的多的样本,一个月顶你几个月,这还不够屌么。
所以,我当时写的nlp预训练模型笔记中,称赞bert为集大成者。觉得在预训练这块,像他这样突的突破性进展,短期内是不会有了。(GPT当时做的其实挺不错的,但开源速度太慢了!)https://zhuanlan.zhihu.com/p/91158598
两年过去了,bert的各种魔改依然漫天飞。有很多人做了很棒的尝试,但现在看来,大框架还是没有突破。
之所以想重新写一篇,关于nlp预训练的综述,是因为这篇综述——pre-trainned models: past, present and future。
整体框架,各个方向的概括,来龙去脉讲的很清楚,有种看历史书的感觉,也学了不少新知识。
一共45页,引用就13页,引用的论文的作者一共几千人,就是这几千人一步步推动着预训练走到了现在。
2. 预训练在nlp带来的变化
在还用LR,GBDT做文本分类,CRF做序列标注的年代。
样本的量级并没有那么重要,因为参数的限制,导致几十万跟几百万的样本对模型带来的提升并不明显。当时提升效果的秘诀是,人工看看模型特征权重,针对性的把样本调整下,特征改改。
但这种工作,相信我,做起来是真的,真的很无聊。
Rnn,LSTM出现后,当时我们组有个大佬,hi的签名是——大力出奇迹。
虽然当时ResNet这种技术还没出现,神经网络无法做深,基本上就几层。但已有的参数量,已经足够支撑百万/千万级的样本学习了。
并且根据当年的经验,百万/千万级别的样本,准确率不用很高,最后效果也能很不错。
很自然,我们当时的工作从调特征的,变成洗样本的。当年洗样本花样老多了,核心思路就是靠各种外部知识,知识词典(当时图谱还不流行),搜索query等。(当然也很无聊)
还有什么用active learning挖掘高质量样本去标注,但在当时的场景下,效率贼低。
因为当时nn的特点,几万高准人工标注样本,大概率是比不上人工清洗的几百万准确率一般的样本。
但BERT出来后世界就变了,几万高准人工标注样本,大概率能跟几百万准确率一般的样本持平,如果样本分布合理,甚至能更好。
所以有了BERT后,nlp的工作流程就变成这样的了。
没样本,就靠文法规则快速撸一个基线。最好是跟pm,外包,后端搞一个规则框架。Pm负责监督外包归纳文法规则,后端负责规则框架的实现,算法负责整体框架的设计。
这样pm得到了效果提升,后端实现了一个技术框架,你等样本标够了入场做模型就好,没有人受伤的世界达成了。
如果是大公司,那就可以找别的部门/中台,蹭现成的接口呗。当然由于是通用接口,效果大概率不会特别好,至于提针对性优化,那就慢慢等吧。
于此同时,找外包定规则,标样本,等标注了几万还不错的样本后。上albert做个还不错的基线,耗时要求高就蒸馏一把,耗时要求低那就上线。
正常情况下,一年拿上百万标样本是很合理的,有钱就标个千万的,反正样本多多益善。至于没钱怎么办?业务部门,百万都拿不出来,那还是先别做算法了。。
这时,搞搞对抗样本,active learning挖掘高质量样本,也就更make sense了。
3. BERT/GPT出现的先觉条件
就跟相对论的出现离不开黎曼几何等技术积淀。BERT/GPT的出现,也是基于很多技术沉淀。
3.1 transfer learning
高中毕业的人学相对论,大概率看不懂,但大受震撼。小学毕业的人去学,脑子聪明点,估计第二天就能想到N种方式来推翻相对论。知识的积淀能够帮助你更快的入门新领域,少走弯路。
transfer learning就是做这个的,分成两种,feature transfer和parameter transfer。
「Feature transfer」——万物皆可embedding,把embedding当作一种pre-encode的知识,而新的模型基于这个embedding的基础上训练。2005年这个idea就提出来了,Word2vec就是其经典实现。
「Parameter transfer」——基于一个intuitive的假设,新的目标task和已经学习的task可以共享parameter,如果两个task差别并不大。先把知识学到共享参数中,再拿目标领域的样本,fine-tuning这些共享参数。这个idea也挺早,2004年就提出来了。
Elmo,BERT是基于这两种方法来实现。
有趣的是,parameter transfer15年就成为CV的主流了。而nlp的大规模应用,要等到18/19年。
个人猜测,核心在于样本获取方式的难易度。之前做对话系统的时候,nlp是可以靠引入priori知识来清洗数据。得到准确率还可以的百万/千万级别的样本。这个级别的样本,是能达到浅层神经网络的参数天花板的。在nlp预训练模型无法做深做大的情况下,收益并不明显。
但这篇论文的说法是——文本很难像图片那样构建一个ImageNet这样大规模的数据集合,因为标注文本是要比标注图片难的多。本人主要经验都在对话的nlu上,open-ended or Non-open-ended的nlg经验很少。这个只能说保留意见吧。
3.2 ResNet,越深越好
如何把网络做深,从2012年就开始有人尝试,直到2016年ResNet的出现,给出了一个系统性的解决方案。
这里我单独拉一小节,一个是的确很重要,一个是要承认我当年的无知。
ResNet刚出来的时候,我很不屑一顾,觉得技术实现也不复杂。并且做深了有啥用呢?也就是秀技,没什么实际意义。
但基于此,再结合self-supervised learning,和谷歌做搜索时爬取的万亿级别的无标注文本数据。
BERT就出来了
3.3 self-supervised learning
谷歌搞BERT真的是一个理所应当的事情(当然我老东家百度为啥没搞出来,这我就不知道了),它做搜索爬取的网页数据,大到它当年专门搞了一个hadoop来管理。
这万亿文本肯定是无标注的,同时也包含了很多知识。从这些无标注的数据中抽取知识,就是self-supervised learning。(unsupervised learning自然就是聚类算法,community discovery,anomaly detect)
nlp上最早最出名的self-supervised learning自然就是word2vec,但它学的是word embedding,无法解决word polysemy的问题。
所以,当时的一个思路就是基于RNN/LSTM来做预训练模型,也就是Elmo。但RNN/LSTM身为序列模型,必须要序列训练,无法并行训练加速。
而互联网累积的unlabel数据实在是太多了,几亿算是开胃菜。所以transformer这种非序列模型的好处就体现出来了。当然,transformer也还是慢,因为数据实在太多。所以这篇综述,专门拿了一章来讨论如何提升efficiency。
3.4 Transformer
太经典了,Transformer的论文建议来回看个十遍。
而GPT/BERT之所以选它作为基础模块,是因为当时它们面临两个问题。
第一,几十亿,甚至上百亿的文本带的知识是很多的,需要一个超大的模型,有足够的参数来容纳这些知识。
第二,这个模型框架的训练速度还不能太慢。
Transformer由于借鉴了ResNet的一些操作,保证了参数增加,效果也能跟随提升(当然现在大家发现有点过参数化)。同时相比于序列模型RNN/LSTM,能支持并行训练。比较好的解决了这两个问题,但BERT/GPT使用Transformer的方式略有不同。
「3.4.1 GPT」
GPT是第一个把Transformer和self-supervised pre-training做结合的,但奈何它开源太慢了。
GPT是generative pre-traing,它是语言模型的套路。也就是我知道上文了,然后maximizing the log-likelihood。所以,它的transformer必然是单向的,把下文mask掉,用上文预测下一个词即可。这种框架,天然适合生成式的下游任务。
「3.4.2 BERT」
BERT是随机mask词,MLM(masked language modelinng),类似完形填空,给了上下文,预测空着的词是什么,好处是上下文的信息都学习到了。
除此之外,还加了一个task——NSP(next sentence prediction)。但根据RoBERTa这篇论文, NSP是relatively useless for the training of bert。
4 GPT/BERT后的优化
两个方向,unified sequence modeling和cognitive-inspired architectures。
4.1 unified sequence modeling
nlp可以分成两种任务,自然语言理解(nlu)和自然语言生成(nlg)。
「GPT毫无疑问是good at nlg」。但对于nlu,intuitively,双向肯定是要比单向好的,因为双向的话你了解的背景知识就更多了么,但改改还是可以在nlu的下游任务用的,效果也不差。毕竟——'what I cannot create, I do not understand'
「BERT在nlu是good at」,但在nlg问题就大了,因为它是双向的,天然和nlg冲突。
一个解决方案就是,nlg的时候用GPT,nlu的时候用BERT,于是皆大欢喜。
但我们有没有办法搞一个unified的预训练模型呢?
「XLNET」——针对BERT的在nlg上的问题,XLNet 在预训练permutate token的顺序,把尾部一定量的词mask掉,然后再用Autoregressive(上一时刻的输出作为下一时刻的输入)这种方式预测被mask的词。
「UniLM」——把单向,双向,seq2seq的目标联合建模。
「GLM」——给定变长mask span,不告诉模型 MASK token 的数量,让模型去生成 mask 掉的 token,第一个在nlg和nlu都达到最优的预训练模型。
「Encoder-Decoder」——在GLM之前,bert的encoder框架,和GPT的decoder框架都无法解决一个问题,fill black with variable lengths。所以,参考翻译搞一个encoder-decoder预训练模型,如MASS, T5, BART。但本身预训练模型就很大了,这直接double。以及encoder-decoder在nlu上的表现一直都不是很好,问题比较多。
4.2 cognitive-inspired architectures
这个方向就比较有意思了,transformer这种框架,是否足够好到模拟人类的认知系统了么?答案是否定的。
所以,有人从认知科学的角度,设计了新的框架。主要是从maintainable working memory和sustainable long-term memory两个角度。而working memory和long-term memory有什么区别,这个建议看认知科学相关的内容来了解。
「maintainable working memory」——transformer这种定长窗口的记忆方式,跟人类的认知记忆差别是蛮大的,LSTM这种可维护式的才比较接近人类认知方式。所以,就有人想着去设计一个可维护的working memory。Transformer-XL通过segment-level recurrence 和相对位置编码,CogQA通过维护一个多跳认知图的方式来实现了一个可维护的working memory。
「sustainable long-term memory」——2019年的一篇论文做了一个实验,发现transformer的feed-forward网络起到了记忆网络的效果。但这个记忆能力始终是有限的,而人类是维护了一个持续多年的long-term memory的。所以,把知识提前编码,要用的时候做替换,有人分别从语料,实体的角度做了一些尝试。以上的方法在开放领域的问答都取得了不错的效果。
4.3 其他工作
一个方向是对mask策略的优化,SpanBERT、开头我说的百度ERNIE、NEZHA、WWM这些都是。另一个方向是把masked-prediction改成更难的,如ELECTRA把MLM替换成了token detection。
当然,以上的这些尝试,至少从我的观察来看,还没有得到工业界的普遍认可,大家还是各种魔改bert到处用。
5. 多种数据源
三个方向,多语言,多模态,知识增强的预训练模型。每个方向都可以单独展开写一篇综述。多语言,多模态我接触很少,这里只能简单写一些个人理解。
5.1 多语言
基于多语言的预训练模型,跟单语言的区别在于,学习任务的设计,对平行语料的利用,以及生成式预训练模型。
「mBERT」——跟bert一模一样的框架,基于100多种语言的样本,用MMLM(multilingual masked language modeling)训练了一个预训练模型。从分析来看,mBERT的确具有一定的cross-lingual能力。
「XLM」——MMLM没法利用平行语料,而在翻译任务中,平行语料是非常重要的。intuitively,平行语料也能更有助于cross-lingual知识的学习。所以,提出了一个TLM task,将两个语义匹配的句子合并为一个,并在这两个部分中随机mask。
「Unicoder」——用了两种新的task。CLWR(cross-linngual word recovery),通过attention机制,使用target语言的embedding来表示sorce语言的embedding,它的目标是recover source语言embedding,这个task使得模型能够学到不同语言word-level的对其知识。CLPC(corss-linngual paraphrase classification),把平行语料当作正样本,非平行语料负样本,然后做分类,这个task就学到了sentence-level的对其知识。
「ALM」——从平行语料中,自动切换序列,然后使用 MLM来学习,让模型基于别的语言的context来做预测。
「mBART」——借鉴了DAE(denoising autoencoding)这种经典的生成式task,方式是添加了语言symbol在encoder输入的结尾和decoder输入的开头。
「XNLG」——DAE虽然训练的时候是用多语言,但encoder输入和decoder输出往往都是同一种语言。所以,这个预训练模型提出了XAE(cross-lingual autoencoding),encoder输入和decoder的输入在XAE就不是同一种语言了
5.2 多模态
小孩子学习的时候,如果文字搭配一些图片,往往能学习的更快,如果能搭配讲解视频,肯定就更快了。
同理,我们把文字,声音,图片,视频一起学习,期望达到更好的效果,就是多模态。
这块可挖的坑是真的多,不同模态的组合,就要针对性设计新的task,故事再好好讲讲,一篇论文就出来了。
但个人感觉吧,语料,计算量,模型框架的不成熟,这个方向短期内做出突破性进展其实挺难的。。但这肯定是未来,因为我们的目标就是让模型越来越像人。
这块个人能力有限,了解太少,就不献丑了,大家有兴趣还是自己看论文吧。
5.3 知识增强型预训练模型
当年还在用lr做文本分类的时候,有一个特征贼好用,叫词典特征。
举个例子,播放xxx,如果xxx是个冷门歌曲,训练样本基本上没见到。那么模型就会困惑这是个电影分类,还是音乐分类呢?
这个时候通过词典特征,告诉模型xxx是首歌,就能解决这个问题。当然,这两年现在大家都不叫词典了,叫知识图谱。
「ERNIE」——举例子,苹果手机评测,bert可能把'手'给随机mask了,但把'苹果手机'mask肯定更合理。但这也有个大坑,就是你需要先把实体识别出来,但实体识别是有准确率的,识别不准的反而是noise。
「Comet」——针对ERNIE的这个问题,直接将结构化知识转化为序列化文本,让模型自己学习对其。
以上都是对结构化知识来进行学习,事实上还有一些用非结构化的知识来一起学习。但是!非结构化数据往往特别脏,如果数据预处理准确率只有80,那么收益甚至可能是负向。
以及发paper的数据往往相对干净一些,业务场景谨慎调研使用。
6. 提升计算速度
三部分,
system-level optimization efficient learning algorithms model compression strategies。
6.1 system-level optimization
太偏工程,有兴趣的同学建议自己看论文,不献丑了。
6.2 efficient learning algorithms
「训练方法上的优化」
方法一,ELECTRA,上面说过了,但不太通用
方法二,超大batch,这个是真通用,某个同事用过效果还挺好。参考这篇论文——Large Batch Optimization for Deep Learning: Training BERT in 76 minutes。
方法三,基于transofmer的一些特性,如发现不同层可以share相似的self-attention patterns。所以,可以先训练浅层模型,然后复制以构建深层模型。以及在训练期间删除一些层。
「模型结构上的优化」
魔改transformer,来降低它的复杂度。但这些方法大部分是有实现成本的,尝试前建议谨慎调研。
「个人建议,机器管够的话,直接上超大batch,分布式,氪金使我变强。」
6.3 模型压缩
「参数共享」——albert现在非常流行,它就是通过factorized embedding parameterization和参数共享,显著的降低了模型的参数,却有着跟bert相同甚至更好的效果。但这也说明预训练模型有over-parameterized的问题。
「模型剪枝」——预训练模型会不会有一些useless的部分呢?有人尝试过在训练的时候有选择的drop transformer layer。以及有人研究发现transformer的多头有点冗余,只要一小部分就能有不错的效果。
「知识蒸馏」——参考我之前写的笔记,去年写的应该还没有很老。https://zhuanlan.zhihu.com/p/106810758
「模型量化」——预训练模型经常是16bits或者32bits,最近的实验显示,改成8 bits只对效果有little impact。
7. 解释和理论分析
这一块其实蛮有意思的,四个部分。预训练模型学了什么,预训练模型的鲁棒性,structural sparsity/modularity,以及预训练模型的理论分析。
7.1 预训练模型学到了什么
分成两种知识,语言知识和世界知识。
「语言知识——四种方式来分析」
「Representation Probing」, 固定预训练模型的参数,训练一个新的线性层基于预训练的隐层。这是最流行的做法,因为做法很通用。通过分析发现,tokens,chunks,pairwise relations这些知识都学到了 「Representation Analysis」,对表示层进行统计分析,例如distances或者similarities。 「Attention analysis」,对attention层进行统计分析,跟Representation Analysis类似。 「Generation Analysis」,使用语言模型来直接评估不同句子和词的概率分布。有人通过预训练模型来recover syntactic tree,发现效果跟人工设计的schema很接近。
世界知识——知识盲区,先不献丑了,有兴趣的同学看论文。
7.2 Structural Sparsity of PTMs
其实前面几章也提到了,transformer有过参数化的问题。
根据分析,在翻译,摘要抽取,nlu上,多头就有点redundant了。以及low levels of pruning也不会影响下游task的效果。
7.3 预训练模型的理论分析
为何预训练有效果?这里做了两个假设
更好的优化,相比于随机初始化,预训练的网络更接近全局最优解。 更好的正则化,在预训练模型的test error更好的时候,预训练的training error 不一定比随机模型好,这代表更强的泛化能力。
最后实验结论偏向于第二种假设。
这个笔记写的是真的累。。内容太多了,论文看了好几天,也写了好几天,快五千字了。有兴趣的同学还是建议自己去看看论文。接下来准备写点没那么累的笔记,如召回的离线评估跟在线不一致的问题(偏吐槽),或者热点挖掘。