6.9意境级讲解BERT更好的进行微调方法总结
Posted 炫云云
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了6.9意境级讲解BERT更好的进行微调方法总结相关的知识,希望对你有一定的参考价值。
而在本篇中,我们将讲解以 BERT 为代表的语言模型,进行介绍以及模型的微调。
过去,在NLP领域通常是一个任务一个模型,但今天已经逐渐迈向希望模型先了解普遍的语言,再去解各式各样的NLP任务
1 、预训练和微调简介
今天在NLP领域一个常见的方法是,根据大量无标注的文字资料来训练一个模型,希望这个模型能读懂文字,这个读懂我们接下来就会讲,这个训练过程就叫Pre-train预训练。接下来针对想要机器解的任务再收集少量的对应的有标注训练资料去Fine-tune微调刚刚预训练的模型,然后就可以让机器去解各式各样的任务。
例如,就上图而言,有三个任务要解,这三个任务只需要把原来的模型进行稍稍地修改并用有限的对应任务的训练资料来微调,期待模型可以得到很好的结果。这个其实也很接近人类学习语言的过程,比如要测试你的英文水平,如雅思托福,有各式各样的题型,但是你学习英文的方法并不是通过不断地做题来学到的。而是类似上图在阅读大量英文资料后,了解每一个词汇的作用,语法等,接下来再做一些考古题针对性学习就可以通过某个语言能力的鉴定。
这便是 NLP 领域所追求的目标。我们期待可以训练一个模型,它真的了解人类的语言。在需要解各式各样的任务的时候,只需要稍微微调一下,它就知道怎么做了。讲到Pre-train Model,最知名的就是BERT。
预训练语言模型的缩写大多是芝麻街的人物。这显然是起名艺术大师们的有意为之。他们甚至都可以抛弃用首字母缩写的原则去硬凑出芝麻街人名。
2、预训练 model
2.1 Word2vec & Glove
那在BERT之前,其实就已经有Pre-train Model了,它做的就是给输入的每一个token输出一个embedding vector表示,这个vector应该包含了这个token的语义,意思相近的token应该有相似的embedding,且embedding的某些维度应该是代表某些特定的语义。
像这样的预训练模型,在没有ELMO、BERT之前,就是像上图一样。输入一个token就输出一个embedding就结束了,那怎么实现呢?有很多种方法,举例来说,可以建一个表,这个表里面存的就是每个token都有一个向量对应。
但像过去这样的预训练模型,最大的问题就是,同一个token都有相同的embedding,像这样的模型是不考虑每个token的上下文的,同样的token无论上下文如何都有一样的embedding。这样的著名经典模型有Word2vec、Glove,不再细讲。
2.2 FastText
2.2.1 English
除了 Word2vec、Glove之外,还有什么样的技术呢?如果你今天考虑的是英文,那token就不能是词汇了,因为英文的词汇实在是太多了,且有未收录词的问题,就是说如果给模型一个它从没见过的词,它将无法做embedding。所以怎么办呢?
输入将改成字母的序列,输出就是这个词的向量。我们期待模型可以通过读取词汇的字首和字根,判断一个没有看过的词汇的意思。其中的代表模型就是鼎鼎大名的 FastText。
2.2.2 Chinese
那如果是中文呢?对于中文,还有更特别的解法,在中文里面,部首也是跟语义有关的,如水字旁就是与水有关,木字旁就与植物有关。因此,对于中文,每一个词汇就像是一个图画,那我们能不能把中文的字的Image,如上图”信“,丢到CNN里,期待CNN可以学会它看到人字旁看到言就知道人言为信,输出信对应的embedding。这个不见得能做到 人言为信 这么高端的理解。但至少还能学习到部首偏旁等。
2.3 预训练模型问题
至此,上述就是过去常用的预训练模型,但是像这样的模型,它不会考虑每个token的上下文的。如上图,”养只狗“的狗和”单身狗“的狗都是狗,它们的embedding也就是一样的,但是我们知道这两只狗的狗是有着不同意思的。
那过去还没有ELMO这一套之前,有人会想说,也许我们就给不同意思的狗就当作不同token来看,通过加下标表示不同的狗。但这样的想法又会有问题,这两个狗虽然有语义不同,但也是有一定相关的。
2.4 上下文有关 Embedding
所以后来就有了 Contextualized Word Embedding 与上下文有关的词嵌入,如ELMO、BERT等等就是Contextualized Word Embedding。
Contextualized Word Embedding与过去的 Word Embedding、Glove、FastText不同的地方就是,过去的这些模型都是吃一个token就要吐出一个embedding。而Contextualized 都是吃一整个句子,再给每一个token一个embedding,虽然这也是给每个token一个embedding,但它是在看过整个句子即上下文后才给出embedding。
2.4.1 架构
那像这样的与上下文有关的embedding应该长什么样子呢?其实你只需要找到模型的可以输入 token sequence,输出是每个token对应的vector就可以了,就像是seq2seq里面的Encoder这样的东西。
而这样的模型往往都非常的深,有很多层,网络的架构可以用LSTM(ELMO)、Self-attention layers(BERT)或者Tree-based model(文法树,今天并没有非常的流行)
可能是因为 LSTM 和 Transformer 在预训练过程中就已经学到了文法的信息。Tree-based 模型就显得不是那么必要了。Tree-based 模型没有流行起来的另一个原因是,很多研究者实验中发现,它做出来的效果并不比 LSTM 好。它只有在处理文法结构非常清楚的问题时,比如数学公式,才会明显好过 LSTM。而其他文本序列任务上,表现一般。
这样的融合了上下文信息的词向量,确实能做到不同语境意思不一样。上图是 BERT embedding 的可视化。上面五个句子指的都是苹果,而下面五个句子指的都是公司。我们把这十个句子中的"苹"的embedding都抽出来,两两之间去计算余弦相似度。图中颜色越亮表示相关性越高。结果发现,前五个句子的"苹"字之间,和后五个句子的"苹"字之间,语义会比较像。
2.4.2 Trend
Bigger
如上图,像这样的模型越来越大,而除了让模型越来越大的研究方向外,就是让模型越来越小。
Smaller
既然BERT非常大,另一个研究方向就是让BERT变得更小。如上图所示,其中最有名的便是ALBERT,且模型结构几乎与BERT一样,原来BERT12、24层都是不一样的参数,而ALBERT12、24层都是一样的参数,且模型效果甚至优于BERT。
那怎么让模型变小呢?
模型压缩:
- 网络剪枝
- 知识蒸馏
- 参数量化
- 权重共享 与因数分解
上述的四种方法都可以实现让模型变小,而这些都在机器学习课程中讲过,如果你想知道哪些模型对应哪些方法,可以参考:
模型结构改变
除了用一些神经网络压缩方法让模型结构变小,可不可以通过直接修改网络结构来减少参数呢?
近年来,在神经网络结构上也有一些突破,如上图的这些模型,这些模型设计的初衷都是为了让预训练模型可以读非常长的sequence,如一本书的长度。
举例来说,像BERT这样的模型一次只能读512个token,但Transformer-XL可以让模型读跨segment的sequence,跨段阅读。
而Reformer和Longformer都是想要减少self-attention带来的运算量, O ( n 2 ) O(n^2) O(n2),n是token sequence的长度。
具体怎么实现请参考:
5.1注意力机制 Attention is all you need
3、 怎么进行微调
3.1 问题
在有一个预训练模型之后,我们希望可以在预训练模型上面再叠一些具体的NLP任务的任务层,就可以用在具体特定的NLP任务上。那我们该怎么改造这个预训练模型,即怎么改造这个输入为token sequence,输出为对应token的vector的预训练模型,使得它可以使用在任意的NLP任务上?
NLP任务分类
对输入划分:
- one sentence
- multiple sentences
对输出划分:
- 一个类别
- 每个token对应一个类别
- 从输入复制
- 序列生成
修改预训练模型使得可以解决上面的问题,我们先看输入。
3.2 输入处理
首先,先看输入部分,对于输入部分,预训练模型本身不需要修改,对于一个句子没有什么特殊处理,但对于多个句子需要每个句子之间加“[SEP]”分隔符后再输入给预训练模型。
而什么时候模型需要同时输入多个句子呢?举例来说,如QA问题,要将问题和文章一起输入给模型、又如NLI自然语言推理问题,要将前提和假设一起输入给模型。
3.3 输出处理
输入的部分处理我们已经讲完了,而输出的部分一共有四个可能,接下来我们将把输出部分的四个可能一个一个看下去。
3.3.1 一个类别
- 输入:one sentence
- 输出:one class
如上图,对于模型输入一整个句子,输出一个类别的问题。
在原始BERT论文中解法如上图绿色框框,同样是读入所有的token sequence,并在开头加一个特殊符号“[CLS]”表示分类符号,预训练模型通过"[CLS]"得到和整个句子有关的embedding再输入给一个线性分类器里,输出类别。
另外一个做法是不加“[CLS]”特殊符号了,就是把所有token的embedding sequence都丢到一个可以处理sequence的模型中,如RNN、LSTM等,输出一个类别。
3.3.2 每个token对应一个类别
- 输入:one sentence
- 输出:class for each token
如上图,对于模型输入一整个句子,输出每个token的类别的任务。
我们只需要一个模型,它可以把整个句子中每个token经过预训练模型得到的embedding sequence当作输入,接下来对每一个embedding进行输出类别,它可以是LSTM。
3.3.3 从输入复制
- 输入:document
- 输出:two number
如上图,第三个任务是解类似QA问题,而且是Extraction-based Question Answering,如SQuAD,给模型读一篇文章,并提出一个问题,希望模型能正确的得到答案,且答案是原文中的一部分。
13.1Question Answering 问答系统意境级讲解
而对于模型而言,它只需要输出两个数字s和e表示起始索引和终止索引,其索引内的token sequence就是正确答案,那该怎么实现呢?在这里举一个BERT的例子。如下图。
解法:将问题 q i q_i qi和文章 d i d_i di中间加"[SEP]",开头加"[CLS]"输入给BERT,此时我们得到了每一个词汇对应位置的embedding,另外,再从头训练两个向量橙蓝,其维度和黄色embedding是一样的,然后将橙色的向量与文章每个词汇的embedding做dot-product点积运算(Attention技术)得到每一个文章内的词汇的scalar,再将其softmax后得到对应分数,找出其中分数最高的,如上图的 d 2 d_2 d2,此时s就等于2。同理,蓝色的向量与橙色的向量运算过程一样,得到e等于多少。至此,答案就是 “ d s d_s ds至 d e d_e de” ,即“ d 2 d_2 d2 d 3 d_3 d3”。
橙色的向量决定s,蓝色的向量决定e。
在训练的时候,橙蓝向量从头训练,BERT微调就好。
此时,会有这样的问题,假设s和e矛盾了,比如s=3,e=2,这不是没有答案了吗?对,此时模型就是输出此题无解,在SQuAD2.0中是有无解的问题的。
3.3.4 序列生成
最后一个任务就是怎么让预训练模型处理seq2seq问题呢,也就是怎么把预训练模型接入seq2seq模型里面呢?
输入:sentence
输出:sentence
最简单的做法就是把预训练模型当作seq2seq中的Encoder,具体任务模型就是Decoder。但这样的坏处就是这个具体任务模型没有被事先预训练到,一般而言具体任务的有标注数据会很少很少,我们期待这个具体任务的模型越小越好,希望这个具体任务模型大多数参数都是经过预训练的,但上述这种Encoder-Decoder方法显然不能使用预训练具体任务模型。
其实,对于seq2seq问题,还有另外一种使用预训练模型的方法。
现在 w 1 , w 2 w_1,w_2 w1,w2 是输入的sequence,接下来,输入一个特别的符号"[SEP]",它会输出一个embedding,将这个embedding再丢到具体训练任务模型中,然后输出产生想要输出的sequence的第一个token w 3 w_3 w3。接下来,再把这个token w 3 w_3 w3丢到预训练模型中,再产生embedding,把这个embedding同样输入到具体训练任务模型中生成第二个token w 4 w_4 w4,依次生成,直至具体训练任务输出"[EOS]"。
以上讲的都是怎么在预训练模型上加上什么东西,让它能够解决各式各样的具体问题。
接下来,假如我们有一些具体训练任务的有标注数据,我们该怎么微调预训练模型呢?
3.4 微调
3.4.1 具体方法
根据具体训练任务的有标注数据进行微调预训练模型有两种方法,
第一种做法是,预训练模型训练完后就固定住了,变成了一个Feature Extractor特征提取器,对输入的token sequence,输出一堆的embedding表示,再将这些embedding丢到具体任务模型中,我们只微调具体任务模型的参数。如图 3.4-1所示。
图
3.4
−
1
图 3.4-1
图3.4−1
第二种做法是,我们把预训练模型与具体任务模型接在一起,微调模型时既会微调预训练模型,也会微调具体训练任务模型。如果直接训练这种组合起来的巨大模型,往往会过拟合,但是预训练模型的大部分已经预训练过了,只有很小部分的参数是由具体训练任务改变。在文献上,第二种方法往往优于第一种方法。如图 3.4-2所示。
图
3.4
−
2
图 3.4-2
图3.4−2
3.4.2 Adaptor
但是如果我们现在采取的是第二种方法,Fine-tune整个巨大的模型,会遇到什么样的问题呢?你可能会遇到这样的问题,如图 3.4-3所示。
图
3.4
−
3
图 3.4-3
图3.4−3
例如我们有三个具体的NLP任务,如果我们仅仅使用同一个预训练模型+具体任务模型的方式,经过每一个具体任务数据Fine-tune整个模型后,同一个预训练模型因不同训练数据的微调会得到三种有区别的预训练模型,如上图的预训练Model就应该一个变蓝色、一个变绿色、一个变灰色的。那每一个任务都需要存一个新的这样的预训练模型,而预训练模型往往都是非常的巨大,这样是非常不切实际的。
图
3.4
−
4
图 3.4-4
图3.4−4
所以就有了Adapter1,我们能不能对于不同的训练任务只调预训练模型的一部分就好? 如图 3.4-4, 我们在预训练模型中加入一些layers,这些layers就叫Adaptor,它只是模型的一小部分参数而已。我们在根据不同训练任务微调模型时,只需调Adapter的部分即可,即预训练模型的参数是不变的。
因此,对于不同任务,我们只需要存不同的Adapter参数和共同的一个预训练模型参数即可,这样的参数量就要比每一个任务都存一个预训练模型的参数量远远小得多。微调时,只调整 Adapter的参数。
Adapter模型架构
图
3.4
−
5
图 3.4-5
图3.4−5
Adapter结构如图 3.4-5, 显示了我们的Adapter体系结构,以及它在Transformer中的应用 。Transformer的每一层包含两个主要的子层:注意层和前馈层。紧接着是一个层归一化, 将特征大小映射回输入的大小。我们在每个子层后面插入两个Adapter,然后将适配器的输出直接传递到以下标准化层.
为了限制参数的数量,我们提出了一个瓶颈体系结构。Adapter首先将原始的 d d d-维特征投射到一个更小的维度 m m m,应用非线性,然后投射回 d d d-维。每一层添加的参数总数,包括偏差,为 2 m d + d + m 2md+d+m 2md+d+m.通过设置 m ≪ d m \\ll d m≪d,我们限制了每个任务添加的参数的数量;实际上,我们使用的是原始模型参数的大约0.5-8%。瓶颈维度 m m m提供了一种简单的方法来权衡性能和参数效率。适配器模块本身在内部有一个残差连接。使用残差连接,如果投影层的参数初始化为接近零,则模块初始化为近似单位函数。
其实Adaptor方法有很多,怎样才能得到最好的Adaptor结构设计呢?这也是一个值得研究的问题。
这种方法的效果很不错。相比于要预训练整个模型,只训练 Adaptor 表现也差不多。
3.4.3 PALs
还有其他的方法减少微调的参数,比如将 Task-special Layer 移至 Transformer 两个 LN 之间。如图 3.4-6所示。
图
3.4
−
6
图 3.4-6
图3.4−6
我们可以与每个BERT层“并行”添加一个特定于任务的函数,如下所示:
h
l
+
1
=
L
N
(
h
l
+
S
A
(
h
l
)
+
T
S
(
h
l
)
)
\\mathbf{h}^{l+1}=\\mathrm{LN}\\left(\\mathbf{h}^{l}+\\mathrm{SA}\\left(\\mathbf{h}^{l}\\right)+\\mathrm{TS}\\left(\\mathbf{h}^{l}\\right)\\right)
hl+1=LN(hl+SA(hl)+TS(hl))
其中
l
l
l是层索引。这意味着如果
TS
(
⋅
)
\\operatorname{TS}(\\cdot)
TS(⋅)输出一个零向量,我们就可以恢复原来的BERT模型。Task-special Layer 的计算如下:
TS
(
h
)
=
V
D
g
(
V
E
h
)
\\operatorname{TS}(\\mathbf{h})=V^{D} g\\left(V^{E} \\mathbf{h}\\right)
TS(h)=VDg(VEh)
V
E
V^{E}
VE 是
d
s
×
d
m
d_{s} \\times d_{m}
ds×dm参数矩阵,
V
D
V^{D}
VD是
d
m
×
d
s
d_{m} \\times d_{s}
dm×ds 参数矩阵,其中
d
s
<
d
m
d_{s}<d_{m}
ds<dm。
3.4.4 K-Adapter
以上是关于6.9意境级讲解BERT更好的进行微调方法总结的主要内容,如果未能解决你的问题,请参考以下文章
图
3.4
−
7
图 3.4-7
图3.