BERT全网最强最完全最深度解析-大厂面试必备

1. BERT模型

BERT的全称是Bidirectional Encoder Representation from Transformers,即双向Transformer的Encoder,因为decoder是不能获要预测的信息的。
模型的主要创新点都在pre-train方法上,即用了Masked LM和Next Sentence Prediction两种方法分别捕捉词语和句子级别的representation

1.1 模型结构

由于模型的构成元素Transformer已经解析过,就不多说了,BERT模型的结构如下图最左:
在这里插入图片描述
在这里插入图片描述
BERT对比这两个算法的优点是 只有BERT的表征会基于所有层中的左右两侧语境。BERT能做到这一点得益于MLM训练策略 与 Transformer中多头自注意力机制 将任意位置的两个单词的距离转换成了1。

1.2 Embedding

在这里插入图片描述
其中:WordPiece是指将单词划分成一组有限的公共子词单元,能在单词的有效性和字符的灵活性之间取得一个折中的平衡。例如上图的示例中‘playing’被拆分成了‘play’和‘ing’。

在BERT中, 主要是以两种预训练的方式(两个自监督任务)来建立语言模型,一种是Masked LM,另一种是Next sentence prediction,通过这两种预训练方式来建立语言模型。

1.3 Pre-training Task 1#: Masked LM

第一步预训练的目标就是做语言模型,从上文模型结构中看到了这个模型的不同,即bidirectional
关于为什么要如此的bidirectional,作者在reddit上做了解释,
意思就是如果使用预训练模型处理其他任务,那人们想要的肯定不止某个词左边的信息,而是左右两边的信息。而考虑到这点的模型ELMo只是将left-to-right和right-to-left分别训练拼接起来。直觉上来讲我们其实想要一个deeply bidirectional的模型,但是普通的LM又无法做到,因为在训练时可能会“穿越”所以作者用了一个加mask的trick。

这里对双向的理解可以进一步详细描述一下:经过这样的操作之后, 序列里面的每一个字, 都含有这个字前面的信息和后面的信息, 这就是双向的理解, 在这里, 一句话中每一个字, 经过注意力机制和加权之后, 当前这个字等于用这句话中其他所有字重新表达了一遍, 每个字含有了这句话中所有成分的信息.

在训练的时候随机从输入语料上mask掉一些单词,然后通过的上下文预测该单词,在训练过程中作者随机mask 15%的token最终的损失函数只计算被mask掉那个token模型通过上下文的理解预测被遮盖或替换的部分。

Mask如何做也是有技巧的,由于训练有MASK,预测没有MASK,所以为了缓解训练和预测之间的gap,所以随机mask的时候10%的单词会被替代成其他单词,10%的单词不替换,剩下80%才被替换为[MASK]。
这样做的好处是什么?
个人觉得,除了 能够缓解gap,我们假设15%全部mask,那么模型首先是知道该句事先是有一个mask;而采取上述那样的mask方式后,在进行此次预测时,模型并不知道此次句子中会不会有mask,所以会更加刻意的去关注每一个词,这样可以在一定程度上既提高了模型的鲁棒性,又提升了训练效果。

要注意的是,Masked LM预训练阶段模型是不知道真正被mask的是哪个词,所以模型每个词都要关注。

因为序列长度太大(512包括标识符)会影响训练速度,所以90%的steps都用seq_len=128训练,余下的10%步数训练512长度的输入。
在这里插入图片描述

1.4 Pre-training Task 2#: Next Sentence Prediction

因为涉及到QA和NLI(自然语言推断)之类的任务,增加了第二个预训练任务,目的是让模型理解两个句子之间的联系训练的输入是句子A和B,B有一半的几率是A的下一句,输入这两个句子,模型预测B是不是A的下一句。预训练的时候可以达到97-98%的准确度。

  1. 首先我们拿到属于上下文的一对句子, 也就是两个句子, 之后我们要在这两段连续的句子里面加一些特殊 t o k e n token token:
    [ c l s ] [cls] [cls]上一句话, [ s e p ] [sep] [sep]下一句话. [ s e p ] [sep] [sep]
    也就是在句子开头加一个 [ c l s ] [cls] [cls], 在两句话之中和句末加 [ s e p ] [sep] [sep]
  2. 我们假设两句话是 [ c l s ] [cls] [cls] my dog is cute [ s e p ] [sep] [sep] he likes playing [ s e p ] [sep] [sep] [ c l s ] [cls] [cls]我的狗很可爱 [ s e p ] [sep] [sep]他喜欢玩耍 [ s e p ] [sep] [sep]), 除此之外, 我们还要准备同样格式的两句话, 但他们不属于上下文关系的情况;
    [ c l s ] [cls] [cls]我的狗很可爱 [ s e p ] [sep] [sep]企鹅不擅长飞行 [ s e p ] [sep] [sep], 可见这属于上下句不属于上下文关系的情况;
    在实际的训练中, 我们让上面两种情况出现的比例为 1 : 1 1:1 1:1, 也就是一半的时间输出的文本属于上下文关系, 一半时间不是.
  3. 我们进行完上述步骤之后, 还要随机初始化一个可训练的 s e g m e n t   e m b e d d i n g s segment \ embeddings segment embeddings, 见上图中, 作用就是用 e m b e d d i n g s embeddings embeddings的信息让模型分开上下句, 我们一把给上句全 0 0 0 t o k e n token token, 下句啊全 1 1 1 t o k e n token token, 让模型得以判断上下句的起止位置, 例如:
    [ c l s ] [cls] [cls]我的狗很可爱 [ s e p ] [sep] [sep]企鹅不擅长飞行 [ s e p ] [sep] [sep]
    0   0    0    0    0    0    0    0     1    1    1    1    1    1    1    1 0 \quad \ 0 \ \ 0 \ \ 0 \ \ 0 \ \ 0 \ \ 0 \ \ 0 \ \ \ 1 \ \ 1 \ \ 1 \ \ 1 \ \ 1 \ \ 1 \ \ 1 \ \ 1 0 0  0  0  0  0  0  0   1  1  1  1  1  1  1  1
    上面 0 0 0 1 1 1就是 s e g m e n t   e m b e d d i n g s segment \ embeddings segment embeddings.
  4. 注意力机制就是, 让每句话中的每一个字对应的那一条向量里, 都融入这句话所有字的信息, 那么我们在最终隐藏层的计算结果里, 只要取出 [ c l s ] t o k e n [cls]token [cls]token所对应的一条向量, 里面就含有整个句子的信息, 因为我们期望这个句子里面所有信息都会往 [ c l s ] t o k e n [cls]token [cls]token所对应的一条向量里汇总:
    模型最终输出的隐藏层的计算结果的维度是:
    我们 X h i d d e n : [ b a t c h _ s i z e ,   s e q _ l e n ,   e m b e d d i n g _ d i m ] X_{hidden}: [batch\_size, \ seq\_len, \ embedding\_dim] Xhidden:[batch_size, seq_len, embedding_dim]
    我们要取出 [ c l s ] t o k e n [cls]token [cls]token所对应的一条向量, [ c l s ] [cls] [cls]对应着   s e q _ l e n \ seq\_len  seq_len维度的第 0 0 0条:
    c l s _ v e c t o r = X h i d d e n [ : ,   0 ,   : ] cls\_vector = X_{hidden}[:, \ 0, \ :] cls_vector=Xhidden[:, 0, :]
    c l s _ v e c t o r ∈ R b a t c h _ s i z e ,   e m b e d d i n g _ d i m cls\_vector \in \mathbb{R}^{batch\_size, \ embedding\_dim} cls_vectorRbatch_size, embedding_dim
    之后我们再初始化一个权重, 完成从 e m b e d d i n g _ d i m embedding\_dim embedding_dim维度到 1 1 1的映射, 也就是逻辑回归, 之后用 s i g m o i d sigmoid sigmoid函数激活, 就得到了而分类问题的推断.
    我们用 y ^ \hat{y} y^来表示模型的输出的推断, 他的值介于 ( 0 ,   1 ) (0, \ 1) (0, 1)之间:
    y ^ = s i g m o i d ( L i n e a r ( c l s _ v e c t o r ) ) y ^ ∈ ( 0 ,   1 ) \hat{y} = sigmoid(Linear(cls\_vector)) \quad \hat{y} \in (0, \ 1) y^=sigmoid(Linear(cls_vector))y^(0, 1)

1.5 Fine-tunning

在这里插入图片描述
在这里插入图片描述
如上图所示,
在这里插入图片描述
其他预测任务需要进行一些调整,如图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 优缺点

在这里插入图片描述
还有一个缺点,MASK标记间是假设独立同分布的,未考虑MASK间的联系!

为了解决2.2缺点mask问题1,作者才想到15%mask中:80%mask,10%随机替换,10%不变。
问题2一直没有很好的解决,但是付出的代价换来的性能提升是值得的。

3. 总结

BERT是两阶段模型,第⼀阶段双向语⾔模型预训练,这里注意要用双向⽽不是单向,第⼆阶段采用具体任务Fine-tuning或者做特征集成;特征抽取要用Transformer作为特征提取器⽽不是 RNN或者CNN;双向语⾔模型可以采取MASK的⽅法去做。

BERT的本质上也可以理解是通过在海量的语料的基础上运行自监督学习方法为单词学习一个好的特征表示。在以后特定的NLP任务中,我们可以直接使用BERT的特征表示作为该任务的词嵌入特征。所以BERT提供的是一个供其它任务迁移学习的模型,该模型可以根据任务微调或者固定之后作为特征提取器。

我们还可以利用Bert去生成句向量或词向量。每一层transformer的输出值,理论上来说都可以作为句/词向量,但是到底应该取哪一层呢,根据hanxiao大神的实验数据,最佳结果是取倒数第二层,最后一层的值太接近于目标,前面几层的值可能语义还未充分的学习到

4. 补充

在这里插入图片描述

1、不考虑多头的原因,self-attention中词向量不乘QKV参数矩阵,会有什么问题?

如果不乘QKV参数矩阵,那这个词对应的q,k,v就是完全一样的。在相同量级的情况下,q与k点积的值会是最大的(可以从“两数和相同的情况下,两数相等对应的积最大”类比过来)。那在softmax后的加权平均中,该词本身所占的比重将会是最大的,使得其他词的比重很少,无法有效利用上下文信息来增强当前词的语义表示。而乘以QKV参数矩阵,会使得每个词的q,k,v都不一样,能很大程度上减轻上述的影响。当然,QKV参数矩阵也使得多头可以,类似于CNN中的多核,使捕捉更丰富的特征/信息成为可能。

2、为什么BERT选择mask掉15%这个比例的词,可以是其他的比例吗?

和Transformer位置编码的三角函数的设定也是一样,都是经验设定。

3、使用BERT预训练模型为什么最多只能输入512个词,最多只能两个句子合成一句?

这是Google BERT预训练模型初始设置的原因,前者对应Position Embeddings,后者对应Segment Embeddings。

在BERT config中
“max_position_embeddings”: 512

因此,在直接使用Google 的BERT预训练模型时,输入最多512个词(还要除掉[CLS]和[SEP]),最多两个句子合成一句。这之外的词和句子会没有对应的embedding。
当然,如果有足够的硬件资源自己重新训练BERT,可以更改 BERT config,设置更大max_position_embeddings 值去满足自己的需求。

4、为什么BERT在第一句前会加一个[CLS]标志?

BERT 在第一句前会加一个[CLS]标志,最后一层该位对应向量可以作为整句话的语义表示,从而用于下游的分类任务等。
为什么选它呢,因为与文本中已有的其它词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个词的语义信息,从而更好的表示整句话的语义。

这里补充一下bert的输出,有两种:
一种是get_pooled_out(),就是上述[CLS]的表示,输出shape是[batch size,hidden size]。
一种是get_sequence_out(),获取的是整个句子每一个token的向量表示,输出shape是[batch_size, seq_length, hidden_size],这里也包括[CLS],因此在做token级别的任务时要注意它。

5、Self-Attention 的时间复杂度是怎么计算的?

在这里插入图片描述

6、Transformer在哪里做了权重共享,为什么可以做权重共享?

Transformer在两个地方进行了权重共享:
(1)Encoder和Decoder初始输入的Embedding处;
(2)预测时的全连接。

7、BERT非线性的来源在哪里?

前馈层的Relu激活函数和self-attention(self-attention是非线性的)

8、BERT的三个Embedding直接相加会对语义有影响吗?

这三个Embedding相加,是非常符合自然直觉的,就如同不同频率的三个信号进行相加,这个当然是完全可以的。

9、在BERT应用中,如何解决长文本问题?

举例: 在阅读理解问题中,article 常常长达1000+, 而Bert 对于这个量级的表示并不支持。

  1. 用Sliding Window(划窗),主要见于诸阅读理解任务(如Stanford的SQuAD)。Sliding Window即把文档分成有重叠的若干段,然后每一段都当作独立的文档送入BERT进行处理。最后再对于这些独立文档得到的结果进行整合
  2. 还可以分段来执行,各段可以求平均、求max、或者加个attention融合。

10、预训练语言模型ELMo、GPT、Bert双向单向的思考

首先预训练语言模型,因为后续要进行微调,所以其应用场景很多时候需要用到上下文的信息,那么如果直接去使用上下文信息,就会造成语言模型的泄密问题,先来看最早的ELMo,为了避免泄密,但又使用上下文,相当于前向和反向加一起,但这其实不是深层次的双向;然后又出现GPT,那么GPT从整体来看,其实还是单向的语言模型,对吧,但是实际上使用的transformer的decoder做特征抽取,将句子中上文的各个词的位置变成了1,所以GPT可以说是单向,但也不完全是单向,说双向,也不完全是双向;然后便是Bert了,Bert直接用的双向,这样大家就疑问了,不是泄密了吗,但是Bert的双向的预训练语言模型的任务的trick在于,其用上下文去预测mask,这就曲线救国的解决了,想双向又不敢双向的问题。所以Bert效果特别好,主要是这两个预训练任务的提出太神了。但是这样其实也造成了Bert不支持生成任务,像GPT、GPT-2生成任务非常牛逼,但是Bert不行,GPT、GPT-2语言模型的设定,自然可以很好的进行生成任务,而Bert则不行。但是Bert的双向效果之好,的确众人皆知。

11、Bert的损失函数

预训练源码中是交叉熵损失函数。
微调根据下游任务决定,像我常用的分类任务,也是用的交叉熵。

12、FullTokenizer做了哪些事情

BasicTokenizer:分词、预处理等。
WordpieceTokenizer:前者的结果再细粒度的切分为WordPiece,解决OOV,中文不处理。

13、BERT的Position Embedding有什么缺点?相较于Transformer中的Position Encoding来说

  1. 可解释性不强;
  2. Transformer中的Position Encoding由于是公式计算,理论上可以扩展到无限长度的文本,但是BERT的Position Embedding由于是训练得到的,所以后续使用时会受到512长度的限制。

14、BERT中NSP任务低效的原因,相较于ALBERT的SOP任务

对于NSP任务来说,正样本=正常顺序的2个相邻句子,负样本=随机2个句子,
对于SOP任务来说,正样本=正常顺序的2个相邻句子,负样本=调换顺序的2个相邻句子。

NSP任务原本是希望去学习句间关系的,但该任务过于简单,模型可以直接从主题语义的角度去进行二分类,所以其学习是低效的。
而SOP任务,才是让模型更加关注句子间的关系。

  • 5
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
BERT是一种预训练的语言模型,它在自然语言处理领域中表现出色。在这里,我将简要介绍如何使用PyTorch实现BERT模型。 首先,我们需要导入必要的库: ```python import torch import torch.nn as nn from transformers import BertModel ``` 然后,我们定义BERT模型的类: ```python class BERT(nn.Module): def __init__(self, bert_path): super(BERT, self).__init__() self.bert = BertModel.from_pretrained(bert_path) self.dropout = nn.Dropout(0.1) self.fc = nn.Linear(768, 1) def forward(self, input_ids, attention_mask): output = self.bert(input_ids=input_ids, attention_mask=attention_mask) output = output.last_hidden_state output = self.dropout(output) output = self.fc(output) output = torch.sigmoid(output) return output ``` 在这个类中,我们首先使用`BertModel.from_pretrained()`方法加载预训练的BERT模型。然后,我们添加了一个dropout层和一个全连接层。最后,我们使用sigmoid函数将输出值转换为0到1之间的概率。 接下来,我们定义训练和测试函数: ```python def train(model, train_dataloader, optimizer, criterion, device): model.train() running_loss = 0.0 for inputs, labels in train_dataloader: inputs = inputs.to(device) labels = labels.to(device) optimizer.zero_grad() outputs = model(inputs['input_ids'], inputs['attention_mask']) loss = criterion(outputs.squeeze(-1), labels.float()) loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) epoch_loss = running_loss / len(train_dataloader.dataset) return epoch_loss def test(model, test_dataloader, criterion, device): model.eval() running_loss = 0.0 with torch.no_grad(): for inputs, labels in test_dataloader: inputs = inputs.to(device) labels = labels.to(device) outputs = model(inputs['input_ids'], inputs['attention_mask']) loss = criterion(outputs.squeeze(-1), labels.float()) running_loss += loss.item() * inputs.size(0) epoch_loss = running_loss / len(test_dataloader.dataset) return epoch_loss ``` 在训练函数中,我们首先将模型设置为训练模式,并迭代数据集中的每个批次,将输入和标签移动到GPU上,然后执行前向传播、计算损失、反向传播和优化器步骤。在测试函数中,我们将模型设置为评估模式,并在数据集上进行迭代,计算测试损失。 最后,我们可以实例化模型并开始训练: ```python if __name__ == '__main__': bert_path = 'bert-base-uncased' train_dataset = ... test_dataset = ... train_dataloader = ... test_dataloader = ... device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = BERT(bert_path).to(device) optimizer = torch.optim.Adam(model.parameters(), lr=1e-5) criterion = nn.BCELoss() for epoch in range(num_epochs): train_loss = train(model, train_dataloader, optimizer, criterion, device) test_loss = test(model, test_dataloader, criterion, device) print(f'Epoch {epoch+1}/{num_epochs}, Train loss: {train_loss:.4f}, Test loss: {test_loss:.4f}') ``` 在这里,我们首先定义数据集和数据加载器,然后实例化模型并将其移动到GPU上(如果可用)。然后,我们定义优化器和损失函数,并开始训练模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xu_Wave

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值