文本匹配一直是自然语言处理(NLP)领域一个基础且重要的方向,一般研究两段文本之间的关系。文本相似度计算、自然语言推理、问答系统、信息检索等,都可以看作针对不同数据和场景的文本匹配应用。
最近,我和小伙伴们参与了阿里天池““新冠疫情相似句对判定大赛”,比赛任务:根据真实场景下疫情相关的肺炎、支原体肺炎等患者提问句对,识别相似的患者问题,就是典型的文本相似匹配应用。 截止3月18日,我们团队在942支参赛队伍中排名第四。
借助比赛的机会,我重新系统梳理、总结了文本匹配的经验方法。本文将着重介绍文本匹配任务中的经典网络Siamse Network,它和近期预训练语言模型的组合使用,一些论文提及的调优技巧以及在此次比赛数据集上的效果检验等。
在正式开始介绍之前,我们先来看一个有趣的故事: 孪生网络的由来!
“Siamse”中的“Siam”是古时泰国的称呼,中文译作暹罗,所以“Siamese”就是指“暹罗”人或“泰国”人。而“Siamese”在英语中是“孪生”的意思,这又是为什么呢?请看下图
十九世纪,泰国出生了一对连体婴儿“恩”和“昌”,当时的医学技术无法使两人分离出来,于是两人顽强地生活了一生。1829年他们被英国商人发现,进入马戏团,在全世界各地演出,1839年他们访问美国北卡罗莱那州成为“玲玲马戏团” 的台柱,最后成为美国公民。1843年4月13日跟英国一对姐妹结婚,恩生了10个小孩,昌生了12个。1874年,两人因病均于63岁离开了人间。他们的肝至今仍保存在费城的马特博物馆内。从此之后“暹罗双胞胎”(Siamese twins)就成了连体人的代名词,也因为这对双胞胎全世界开始重视这项特殊疾病。
由于结构具有鲜明的对称性,就像两个孪生兄弟,所以下图这种神经网络结构被研究人员称作“Siamese Network”,即孪生网络。
其中最能体现“孪生”的地方,在于网络具有相同的编码器(sentence encoder),即将文本转换为高维向量的部分(词嵌入)。网络随后对两段文本的特征进行交互,最后完成分类/相似预测。“孪生网络”结构简单,训练稳定,是很多文本任务不错的baseline模型。
孪生网络的具体用途是衡量两个输入文本的相似程度。例如,现在我们有两个文本 text1 和 text2,首先将文本分别输入 sentence encoder 进行特征提取和编码,将输入映射到新的空间得到特征向量 u和v;最终通过u、v的拼接组合,经过下游网络(比如全连接网络mlp)和激活函数来计算文本1和2的相似性。
整个过程有2个值得关注的点:
(1)在训练和测试过程中, 模型的编码器(sentence encoder)部分是权重共享的 ,这也是“孪生”一词的体现之处。编码器的选择非常广泛,传统的CNN、RNN和Attention、Transformer都可以。
(2)得到特征u、v后,可以直接使用距离公式,如cosine距离、欧式距离等得到两个文本的相似度。不过更通用的做法是,基于u和v构建用于建模两者匹配关系的特征向量,然后用额外的模型(mlp等)来学习通用的文本关系函数映射;毕竟我们的场景不一定只是衡量相似性,可能还有问答、蕴含等复杂任务。
基于孪生网络,还有人提出了 Triplet network 三连体网络。顾名思义,输入由三部分组成,文本1,和1相似的文本2,和1不相似的文本3。训练的目标非常朴素,期望让相同类别间的距离尽可能的小,让不同类别间的距离尽可能的大,即减小类内距,增大类间距。
自从2018年底Bert等预训练语言模型横空出世,NLP届的游戏规则某种程度上已经被大大更改了。在计算资源允许的条件下,Bert成为很多问题的优先选择;甚至有的时候,拿Bert跑一跑baseline,发现问题已经被解决了十之八九。
但是Bert的缺点也很明显,11亿参数量(base版本)使得预测、推理速度明显比CNN等传统网络慢了不止一个量级,对资源要求更高,也不适合处理某些任务。例如,从10000条句子中找到最相似的一对句子,由于可能的组合众多,需要完成49,995,000次推理计算;在一块现代V00GPU上使用Bert计算,将消耗65小时。
考虑到孪生网络的简洁有效,有没有可能将它和Bert强强联合取其精华呢?
当然可以,这正是论文 《Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks》 的工作,首次提出了 Sentence-Bert模型(以下简称SBert) 。SBert在众多文本匹配工作中(包括语义相似性、推理等)都取得了最优结果。更让人惊讶的是,前文所述的从10000条句子找最相似pair的任务,SBert仅需5秒就能完成!
让我们简短回顾此前Bert是怎么处理文本匹配任务的。
常规做法是将匹配任务转换成二分类任务(相似/不相似)。输入的两个文本拼接成一个序列(中间用一个特殊的符号“SEP”分割),经过12层(base-model)或24层(large-model)的multi-head Transformer模块编码后,将输出层的字向量取平均或者取第一个token位置“CLS”的特征作为句向量,经softmax完成最终分类。
但是论文作者 Nils Reimers 在实验中指出,这样的做法产生的结果并不理想(至少在处理语义检索和聚类问题时是如此),甚至往往比Glove词向量取平均的效果还差。
为了让Bert更好地利用文本信息,作者们在论文中提出了如下的SBert模型结构。是不是非常眼熟?对,这不就是之前见过的孪生网络嘛!
SBert沿用了孪生网络的结构,文本的encoder部分用同一个Bert来处理。之后,作者分别实验了CLS-token和2种池化策略(Avg-Pooling、Mean-Pooling),对Bert输出的字向量进行进一步特征提取、压缩,得到u、v。最后的u、v整合,作者提供了3种策略:
(1)针对分类任务,对u、v拼接组合,最后接入一个mlp网络,使用softmax进行分类输出,损失函数使用交叉熵;
(2)直接计算、输出余弦相似度;训练损失函数采取了均方根误差;
(3)如果输入的是三元组,论文种也给出了相应的损失函数。
总的来说,SBert直接使用Bert的原始权重进行初始化,在具体数据集上微调,训练过程和传统Siamse Network差异不大。但是这种训练方式能让Bert更好的捕捉句子之间的关系,生成更优质的句向量。在评估测试阶段,SBert直接使用余弦相似度来比较两个句向量之间的相似度,极大提升了推理速度。
有实验为证!作者在7个文本匹配相关的任务上做了对比实验,结果在其中的5个任务上,SBert都有更优表现。
此外,作者还做了一些有趣的消融实验。使用NLI和STS为代表的匹配数据集,在进行分类目标函数训练时,作者们测试了不同的整合策略,结果显示“(u, v, |u-v|)”的组合效果最好,这里面最重要的组成部分是元素差: (|u - v|) 。句向量之间的差异度量了两个句子嵌入的维度间的距离,确保相似的对更近,而不同的对更远。
此外,在Pool方法中,平均池化的效果要比另两种方法更好。
完善的实验过程帮助我们避免了不少坑。文章最后,作者对SBert和传统的一些句嵌入方法做了对比,SBert的计算效率要更高一些。其中的smart-batching是论文中的一个小trick,先将输入的文本按长度排序,这样同一个mini-batch的文本长度更加统一,padding填充处理时能显著减少填充的token。
我们将SBert模型在本次比赛的数据集上做了测试。使用数据增强后,线下的训练集和验证集数量分别是13,500和1000条句子组合。预训练模型权重选择的是roberta_wwm_large,训练过程中加入了对抗训练,通过在embedding层额外增加一些噪声点提升模型的泛化能力。
最终SBert单模型在线下验证集上的准确率是957%。直接使用Bert微调的方式,准确率为953%。
总的来说,我们做这次比赛的目的是为了积累更多的经验,尽可能将学术界的前沿算法和工业界结合,从而更好的将相关技术在实际项目中落地。
本文总体介绍了文本匹配任务中常用的网络结构Siamse Network,以及在此基础上改进而来的Sentence-BERT模型。
Siamse Network 简洁的设计和平稳高效训练非常适合作为文本匹配任务的baseline模型,包括不限于问答对话、文本蕴含、文本相似等任务;如果孪生网络不能有效解决,可以再尝试其他更复杂的模型。SBert则充分利用了孪生网络的优点和预训练语言模型强大的特征抽取优势,在众多匹配任务上取得了最优实验结果。
抛开具体任务不谈,SBert 可以帮助我们生成更好的句向量,在一些任务上可能产生更优结果。在推理阶段,SBert直接计算余弦相似度的方式,大大缩短了预测时间;在语义检索、信息搜索等任务中预计会有不错表现。同时, 得益于生成的高质量句嵌入特征,SBert也非常适合做文本聚类、新FAQ发现等工作。
写这篇文章的起因是看ALBERT的时候,对其中参数因式分解,减少参数的方式不理解,后来通过原码来了解原理。后来想到虽然平时基于bert的nlp任务做的挺多的,但对原理还是一知半解的,所以在此记录。后续有时间的话,将常见的,看过的论文做个总结,不然容易忘记。(attention is all your need,bert,albert,roberta,sentence -bert,simcse,consert,simbert,nezha,ernie,spanbert,gpt,xlnet,tinybert,distillbert)
从图一可以明显看出,bert主要分为三块。embedding层,encoder层,以及pooler层,本章为embedding层的原码分析。
可以看出,输入的input,会先经过tokernizer,会补上cls,sep等特殊字符。然后embedding层会获取句子的token embeddings+segment embeddings+position embeddings作为最终的句子embedding。
1 token embedding:
token embedding有两种初始化方式。如果是训练预训练,随机出初始化一个30522768的lookup table(根据wordpiece算法,英文一共有30522个sub-word就可以代表所有词汇,每个sub-word 768纬)。如果是在预训练模型的基础上finetune,读取预训练模型训练好的lookup table。假设输入的句子经过tokernized长度为16。经过lookup table就是16768维的句子表示。
2 position embedding:
position embedding的lookup table 大小512768,说明bert最长处理长度为512的句子。长于512有几种截断获取的方式。position embedding的生成方式有两种:1 根据公式直接生成 2 根据反向传播计算梯度更新。其中,transformer使用公式直接生成,公式为:
其中,pos指的是这个word在这个句子中的位置;2i指的是embedding词向量的偶数维度,2i+1指的是embedding词向量的奇数维度。为什么这个公式能代表单词在句子中的位置信息呢?因为位置编码基于不同位置添加了正弦波,对于每个维度,波的频率和偏移都有不同。也就是说对于序列中不同位置的单词,对应不同的正余弦波,可以认为他们有相对关系。优点在于减少计算量了,只需要一次初始化不需要后续更新。
其中, bert使用的是根据反向传播计算梯度更新。
3 segment embedding:
bert输入可以为两句话。[cls][seq][seq]。每句话结尾以seq分割。从embedding的大小可以看出,lookup table由两个768组成,对应第一句和第二句。该参数也由训练得到。
4 LN以及dropout:
embeddings = dropout(layernorm(token embeddings+segment embeddings+position embeddings))。Normalization 有很多种,但是它们都有一个共同的目的,那就是把输入转化成均值为 0 方差为1的数据。我们在把数据送入激活函数之前进行normalization(归一化),因为我们不希望输入数据落在激活函数的饱和区,发生梯度消失的问题,使得我们的模型训练变得困难。这里不使用bn可以去除batch size对模型的影响。
下一篇为bert核心encoder模块的解析。
n-gram语言模型:根据前面n个词预测当前词,它的缺点是,一般只能取1-2,n越大计算成本越高,这就使得它关注的信息是非常局限的。
预训练语言模型:wordvec\glove\fasttext。wordvec是根据周围词预测当前词或当前词预测周围词,相比于n-gram,它关注了下文,但它仍然是关注局部信息。glove通过构建词频共现矩阵来训练词向量,将全局信息融入到词向量中。fasttext仍然是局部的,只是他分词是基于subword,对于oov词相对友好。三者共同的缺点是,无法解决一词多义问题。
高级语言模型:elmo\GPT,elmo采用1层静态向量+2层单向LSTM提取特征,并且能够解决一词多义,elmo是一个双向语言模型,但实际上是两个单向语言模型(方向相反)的拼接,这种融合特征的能力比 BERT 一体化融合特征方式弱。GPT采用Transformer的decoder单元提取特征,同样也可以解决一词多义问题,但GPT是单向的。所以,对上下文信息的融合,二者能力还不够。
bert是双向语言模型,句子没有shift_mask操作,所以是完整的上下文环境,证实了双向语言模型对文本特征表示的重要性。bert同时证实了预训练模型能够简化很多繁重任务的网络结构,在11个nlp任务上都有显著提升。
bert采用Transformer的encoder单元提取特征,encoder中包含几个重要的机制:self-attention、muti-head attention、position encoding。
bert分为bert_base和bert_large大小两个模型,bert_base采用了12个encoder单元,768维隐藏层,12个attention。bert_base采用了24个encoder单元,1024维隐藏层,16个attention。
input:单句或句对组合,有[cls]作为句子开头的标记,[sep]作为句子分隔和结束的标记。
token embedding:对于英文采用WordPiece embeddings,也就是一个单词会被拆成词根词缀的,比如图中的playing被拆成了play和ing两个token;对于中文,就是单子拆分。
segment embedding:相邻句子采用不同的标志分隔,形如111111111100000011111100000。
position embedding:在transformer中,单词之间是没有先后顺序的,而语言本身是有序的,所以采用采用正余弦函数来计算每个单词的先后顺序,这种方式有点勉强,算是折中方式。
前面讲到elmo也是双向语言模型,它是采用bi-LSTM来提取特征,如下:
比如一句话:‘北京是中国的首都’,在LSTM中从左往右,预测‘中国’的时候只能看到‘北京’,从右往左,预测‘中国’的时候只能看到‘首都’,然后将两个lstm的输出做拼接来达到上下文信息融合的目的。其实是没有完全做到双向,只是以结构的改变来接近双向语言模型。真正的双向是预测‘中国’的时候,需要同时看到‘北京’和‘首都’。由此,mask LM产生了。
mask LM的原理是将‘中国’遮盖住,同时用‘北京’和‘首都’来预测‘中国’。‘北京’和‘首都’联系起来语言模型很容易联想到就是‘中国’啦。这个思想和wordvec的CBOW模型如出一辙,就是用周围词预测当前词,只是这个思想放在厉害的transformer中,便能大显其能。
BERT的mask方式:在选择mask的15%的词当中,80%情况下使用mask掉这个词,10%情况下采用一个任意词替换,剩余10%情况下保持原词汇不变。这样mask的优点是什么?
1)被随机选择15%的词当中以10%的概率用任意词替换去预测正确的词,相当于文本纠错任务,为BERT模型赋予了一定的文本纠错能力;
2)被随机选择15%的词当中以10%的概率保持不变,缓解了finetune时候与预训练时候输入不匹配的问题(预训练时候输入句子当中有mask,而finetune时候输入是完整无缺的句子,即为输入不匹配问题)。
在Mask LM任务中,模型学到了词与词之间的关系,而NSP任务是要模型学到句子与句子之间的关系,比如问答、推理等。它将训练语料分为两类,一是将50%语料构建成正常语序的句子对,比如A-B句子对,B就是A的实际下一个句子,并做标记为isnext;二是将50%语料构建成非正常语序句子对,B是来自语料库的随机句子,并做标记为notnext。然后通过对句子对的关系做分类,预测B到底是不是A句子的下一个句子,使模型具有句子级别的识别能力。
微调的目的在于我们的任务与bert预训练任务是不一致的,但是bert是非常好的语言模型,他具备提取词法和句法的强大能力。将bert嵌入到我们的网络结构中,能够简化在语言模型方面的复杂结构。只需要将输入做成和bert适配的格式就行,而在bert后面接上全连接、CNN等简单模型进行训练,就能够使训练得到一个比较好的效果。
GPT 和 BERT 都采用Transformer,Transformer 是encoder-decoder 结构,GPT 的单向语言模型采用 decoder 部分,decoder 的部分见到的都是不完整的句子;BERT 的双向语言模型则采用 encoder 部分,采用了完整句子。他俩最主要的区别在于BERT是双向语言模型,更适合文本分类等任务,GPT是单向语言模型,更适合生成式任务。
1)低层网络捕捉了短语级别的结构信息
2)表层信息特征在底层网络(3,4),句法信息特征在中间层网络(6~9),语义信息特征在高层网络。(9~12)
3)主谓一致表现在中间层网络(8,9)
1)ROBERTA
•静态mask->动态mask:在bert中每一个epoch被mask的是相同的词,而ROBERTA在每一个epoch结束,重新随机15%的词,使不同的词被mask。
•去除句对NSP任务,输入连续多个句子:在bert中最长是512个token,输入单句或者句对不容易把512个token占满,ROBERTA输入更多句子占满512个坑位。
•训练使用更多数据 更大batch size 更长时间
2)ALBERT
•减少参数:词表 V 到隐层 H 的中间,插入一个小维度 E,即一个VxH的embedding变成两个VxE, ExH的两个fc。
•共享所有层的参数:Attention 和 FFN,在bert中每一层的Attention 和 FFN的参数是不一样的。
•SOP 替换 NSP:负样本换成了同一篇文章中的两个逆序的句子,bert中是A-->B和A-->随机,ALBERT中是A-->B,B-->A。
•BERT对MASK 15% 的词来预测。ALBERT 预测的是 n-gram 片段,包含更完整的语义信息。
•训练数据长度:90%取512,BERT90% 128
•对应BERT large:H:1024 ->4096 L:24->12 窄而深->宽而浅
Bert和Transformer都是深度学习领域的 pretrained language model(预训练语言模型),但它们在模型结构和应用上有以下几点主要区别:
1 模型结构:
Bert是基于Transformer编码器结构的模型,只有Encoder部分。而Transformer是由Encoder和Decoder组成的完整序列到序列结构的模型。
Bert的模型结构更简单,主要用于上下文语义理解任务,如文本分类、文本相似度计算等。Transformer可以应用于更复杂的任务,如机器翻译、摘要生成等需要生成语言序列的任务。
2 预训练语料:
Bert使用Wikipedia和BookCorpus进行预训练,语料广泛且无监督。Transformer通常使用有监督的平行语料,如WMT数据集进行预训练。
Bert的预训练更广泛,可以学习到更丰富的语义知识。而Transformer得到的知识更加专业和针对性。
3 应用领域:
Bert用于NLP下游任务更广泛,主要用于语言理解相关任务,如命名实体识别、情感分析、文本分类等。
Transformer应用于机器翻译、摘要生成、对话等生成模型更为广泛。
4 权重共享:
Bert使用相同的参数进行多层Transformer Encoder堆叠,权重共享,模型更加简洁。
Transformer的Encoder和Decoder具有不同的参数,权重不共享,模型相对更复杂。
总之,Bert和Transformer虽有Transformer Encoder的共同点,但实际上是两个不同的预训练语言模型,在模型结构、预训练语料、应用领域和权重共享等方面具有很大差异。根据不同的任务需求选择使用Bert或者Transformer可以获得更好的效果。 它们的创新也推动了NLP领域的蓬勃发展。
欢迎分享,转载请注明来源:浪漫分享网
评论列表(0条)