BERT

作者: 小黄不头秃 | 来源:发表于2022-10-13 00:55 被阅读0次

(一)BERT

BERT其实是美国一个启蒙类节目里的一个角色。那个节目叫做芝麻街,然后各种各样的芝麻街角色就变成了,我们的神经网络,还有Elmo等。


(1)NLP里的迁移学习

我们在计算机视觉那块,即ResNet那一部分介绍过,在图像识别领域做迁移学习效果非常好。于是这类工作做的就是类似图像领域中的迁移学习,使用预训练好的模型来抽取词,句子的特征,例如word2vec或者语言模型

不更新与训练好的模型。

需要构建新的网络来抓取新任务需要的信息。他可以替代embedding层。

(2) BERT

基于微调的NLP模型,这就是BERT想要做到的事情。
他希望在预训练中抽取了足够多的信息,新的任务只需要增加一个 简单的输出层就好。

BERT架构:

  • 只有编码器的Transformer。
  • 他有两个版本:基础版(110M)和巨大版(340M)。
  • 在大规模数据上训练 >3B 词。
(3)对输入的修改

我们在介绍transformer的时候讲的是,一个样本就是一个句子对(source - target)。但是在BERT里面他只有encoder,那么输入要怎么办呢?BERT的做法是将两个句子拼接起来,作为一个输入。

并且加入了额外的片段嵌入,使用<cls>作为输入、句子之间用<seq>来作为分隔。为了让神经网络更好的区分句子之间的间断,所以加入了segment embedding,来标识两个不同的句子。

位置编码可以学习,不再使用前面讲的位置编码。来表示词之间的先后关系。

(4)预训练任务1:带掩码的语言模型

之前介绍的Transformer的编码器的时候我们知道,transformer的编码器中的任何一个节点是能够获取整条序列的信息的(即,双向),而对于一个往下不停预测的语言模型来说,这是不行的,标准的语言模型要求单向。

BERT其实是一个双向的模型,我需要看到句子的全貌提取有效的信息,于是就提出了带掩码的语言模型每次随机(15%的概率)将一些次元换成<mask>。就是让神经网络去还原<mask>的内容。

但是在实际的引用过程中,微调的任务的输入是不存在<mask>标签的。于是BERT决定,将选中的词中80%选中词元编程<mask>,10%的概率换成下一个随机词元,10%的概率保持原有词元。

(2)预训练任务2:下一个句子的预测

让一个任务是说,猜词。这一个任务就是说预测一个句子对中,两个句子是不是相邻的关系。

所以在训练样本中共有两种句子对:

  • 50%概率选中相邻的句子对
  • 50%概率选中随机句子对

<cls>对应的输出放到一个全连接层来预测。每一个输入的词元都会有一个输出,BERT这里选择使用<cls>对应的输出作为是否是连续句子的向量。

(二)代码实现

import torch
from torch import nn
from d2l import torch as d2l
#@save
def get_tokens_and_segments(tokens_a, tokens_b=None):
    """获取输入序列的词元及其片段索引"""
    tokens = ['<cls>'] + tokens_a + ['<sep>']
    # 0和1分别标记片段A和B
    segments = [0] * (len(tokens_a) + 2)
    if tokens_b is not None:
        tokens += tokens_b + ['<sep>']
        segments += [1] * (len(tokens_b) + 1)
    return tokens, segments
#@save
class BERTEncoder(nn.Module):
    """BERT编码器"""
    def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input,
                 ffn_num_hiddens, num_heads, num_layers, dropout,
                 max_len=1000, key_size=768, query_size=768, value_size=768,
                 **kwargs):
        super(BERTEncoder, self).__init__(**kwargs)
        self.token_embedding = nn.Embedding(vocab_size, num_hiddens)
        self.segment_embedding = nn.Embedding(2, num_hiddens)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module(f"{i}", d2l.EncoderBlock(
                key_size, query_size, value_size, num_hiddens, norm_shape,
                ffn_num_input, ffn_num_hiddens, num_heads, dropout, True))
        # 在BERT中,位置嵌入是可学习的,因此我们创建一个足够长的位置嵌入参数
        self.pos_embedding = nn.Parameter(torch.randn(1, max_len,
                                                      num_hiddens))

    def forward(self, tokens, segments, valid_lens):
        # 在以下代码段中,X的形状保持不变:(批量大小,最大序列长度,num_hiddens)
        X = self.token_embedding(tokens) + self.segment_embedding(segments)
        X = X + self.pos_embedding.data[:, :X.shape[1], :]
        for blk in self.blks:
            X = blk(X, valid_lens)
        return X
vocab_size, num_hiddens, ffn_num_hiddens, num_heads = 10000, 768, 1024, 4
norm_shape, ffn_num_input, num_layers, dropout = [768], 768, 2, 0.2
encoder = BERTEncoder(vocab_size, num_hiddens, norm_shape, ffn_num_input,
                      ffn_num_hiddens, num_heads, num_layers, dropout)
tokens = torch.randint(0, vocab_size, (2, 8))
segments = torch.tensor([[0, 0, 0, 0, 1, 1, 1, 1], [0, 0, 0, 1, 1, 1, 1, 1]])
encoded_X = encoder(tokens, segments, None)
encoded_X.shape
#@save
class MaskLM(nn.Module):
    """BERT的掩蔽语言模型任务"""
    def __init__(self, vocab_size, num_hiddens, num_inputs=768, **kwargs):
        super(MaskLM, self).__init__(**kwargs)
        self.mlp = nn.Sequential(nn.Linear(num_inputs, num_hiddens),
                                 nn.ReLU(),
                                 nn.LayerNorm(num_hiddens),
                                 nn.Linear(num_hiddens, vocab_size))

    def forward(self, X, pred_positions):
        num_pred_positions = pred_positions.shape[1]
        pred_positions = pred_positions.reshape(-1)
        batch_size = X.shape[0]
        batch_idx = torch.arange(0, batch_size)
        # 假设batch_size=2,num_pred_positions=3
        # 那么batch_idx是np.array([0,0,0,1,1,1])
        batch_idx = torch.repeat_interleave(batch_idx, num_pred_positions)
        masked_X = X[batch_idx, pred_positions]
        masked_X = masked_X.reshape((batch_size, num_pred_positions, -1))
        mlm_Y_hat = self.mlp(masked_X)
        return mlm_Y_hat
mlm = MaskLM(vocab_size, num_hiddens)
mlm_positions = torch.tensor([[1, 5, 2], [6, 1, 5]])
mlm_Y_hat = mlm(encoded_X, mlm_positions)
mlm_Y_hat.shape
mlm_Y = torch.tensor([[7, 8, 9], [10, 20, 30]])
loss = nn.CrossEntropyLoss(reduction='none')
mlm_l = loss(mlm_Y_hat.reshape((-1, vocab_size)), mlm_Y.reshape(-1))
mlm_l.shape
#@save
class NextSentencePred(nn.Module):
    """BERT的下一句预测任务"""
    def __init__(self, num_inputs, **kwargs):
        super(NextSentencePred, self).__init__(**kwargs)
        self.output = nn.Linear(num_inputs, 2)

    def forward(self, X):
        # X的形状:(batchsize,num_hiddens)
        return self.output(X)
encoded_X = torch.flatten(encoded_X, start_dim=1)
# NSP的输入形状:(batchsize,num_hiddens)
nsp = NextSentencePred(encoded_X.shape[-1])
nsp_Y_hat = nsp(encoded_X)
nsp_Y_hat.shape
nsp_y = torch.tensor([0, 1])
nsp_l = loss(nsp_Y_hat, nsp_y)
nsp_l.shape
#@save
class BERTModel(nn.Module):
    """BERT模型"""
    def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input,
                 ffn_num_hiddens, num_heads, num_layers, dropout,
                 max_len=1000, key_size=768, query_size=768, value_size=768,
                 hid_in_features=768, mlm_in_features=768,
                 nsp_in_features=768):
        super(BERTModel, self).__init__()
        self.encoder = BERTEncoder(vocab_size, num_hiddens, norm_shape,
                    ffn_num_input, ffn_num_hiddens, num_heads, num_layers,
                    dropout, max_len=max_len, key_size=key_size,
                    query_size=query_size, value_size=value_size)
        self.hidden = nn.Sequential(nn.Linear(hid_in_features, num_hiddens),
                                    nn.Tanh())
        self.mlm = MaskLM(vocab_size, num_hiddens, mlm_in_features)
        self.nsp = NextSentencePred(nsp_in_features)

    def forward(self, tokens, segments, valid_lens=None,
                pred_positions=None):
        encoded_X = self.encoder(tokens, segments, valid_lens)
        if pred_positions is not None:
            mlm_Y_hat = self.mlm(encoded_X, pred_positions)
        else:
            mlm_Y_hat = None
        # 用于下一句预测的多层感知机分类器的隐藏层,0是“<cls>”标记的索引
        nsp_Y_hat = self.nsp(self.hidden(encoded_X[:, 0, :]))
        return encoded_X, mlm_Y_hat, nsp_Y_hat

相关文章

网友评论

      本文标题:BERT

      本文链接:https://www.haomeiwen.com/subject/oianortx.html