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 不一定比随机模型好,这代表更强的泛化能力。
最后实验结论偏向于第二种假设。
这个笔记写的是真的累。。内容太多了,论文看了好几天,也写了好几天,快五千字了。有兴趣的同学还是建议自己去看看论文。接下来准备写点没那么累的笔记,如召回的离线评估跟在线不一致的问题(偏吐槽),或者热点挖掘。