当前位置:   article > 正文

自然语言基础2 BERT源码 & 大模型技术(attention/transformer/LLM) 如何处理各类NLP任务_bert模型和大模型

bert模型和大模型

自然语言基础2 BERT源码 & 大模型技术(attention/transformer/LLM) 如何处理各类NLP任务

1.简介

本文以huggingface BERT模型为例,概述常见Transformer的结构,以及数据处理的过程和相关概念,这里一定要牢记attention论文中的那张图,bert主要是其中encoder部分的实现(其实也可以通过参数变为decoder部分)

现在的transformer模型 (大模型LLM) 有很多了,包括GPTs, LLaMa, Moss等等,但作为初学者,4-5年前的GPT2和BERT相对会好上手一些,一方面可借此了解常见NLP任务,数据处理和模型基本结构等。

总的来说,这类模型都是基于attention, 至于是self-attention,cross-attention还是multi-head attention, 大体的layer结构是相似的,核心就是QKV的计算。

区别更多是nlp数据的数据,包括训练技巧,和具体任务的不同。

这里有几个重要概念:

  • tokenizer

tokenizer可以理解为对文本的编码,将文本编码为模型可以处理的数据。

NLP文本是“字符串”的形式保存句子(sentence),比如"A Titan RTX has 24GB of VRAM", 但模型计算需要的是浮点数, 因此需要将字符串中的sentence分词,分词是将句子划分为更小的单位,如单词或者特殊符号。

分词对照的是一个涵盖所有单词和符号的字典,也就是词汇表/语料库(vocab),每个单词对应不同的数字以区分。

在Bert中,其大小为vocab_size = 30522, 即有30522个tokens, vocab中的特殊标识符(special token)包括:

<[UNK]> unkown  

未知的令牌, 不在词汇表中的标记无法转换为 ID,而是设置为此标记。

<[SEP]> separator 

分隔符标记,在从多个序列构建序列时使用,例如 用于序列分类或 和 问答 (QA) 任务 。 它还用作用特殊标记构建的序列的最后一个标记。

<[PAD]> padding

用于填充的标记,例如在批处理不同长度的序列时。

<[CLS]> classifier

进行序列分类时使用的分类器标记(对整个序列进行分类而不是对每个标记进行分类)。它通常是序列的第一个标记。

<[MASK]> masking values

用于屏蔽值的标记。 这是使用掩码语言建模训练该模型时使用的标记。 这是模型将尝试预测的标记。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • input_ids

分词后,各个词对应的id, id值为 [0, 30522]

  • attention_mask

mask是在将序列批处理在一起时使用的可选参数。该参数向模型指示哪些标记应该被关注。

mask是一个二进制张量,指示填充索引的位置,以便模型不会关注它们。 对于 BertTokenizer,1 表示应注意的值,而 0 表示填充值。 该注意力掩码位于分词器(tokenizer)返回的字典中,键为“attention_mask”:

  • token_type_ids

某些模型的目的是进行序列分类或问题回答。 这些需要在相同的输入 ID 中编码两个不同的序列。 它们通常由特殊标记分隔,例如分类器[CLS]和分隔符标记[SEP]。 例如,BERT 模型构建其两个序列输入:

" [CLS] SEQUENCE_A [SEP] SEQUENCE_B [SEP] "

  • position_ids

模型使用 position_ids 来识别哪个令牌位于哪个位置。 与将每个标记的位置嵌入其中的 RNN 相反,Transformer 不知道每个标记的位置。 职位 ID 就是为此目的而创建的。

它是可选参数。 如果没有将 position_ids 传递给模型,将自动创建为绝对位置嵌入。绝对位置嵌入在 [0, config.max_position_embeddings - 1] 范围内选择。 某些模型使用其他类型的位置嵌入,例如正弦位置嵌入或相对位置嵌入。

  • embedding

这个是transformer模型输入attention(encoder)前的操作,需要将原有的尺寸映射到潜空间的尺寸(hidden_states),我们来看一下BertEmbeddings:


class BertEmbeddings(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
        self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
        self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size)

        self.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps)
        self.dropout = nn.Dropout(config.hidden_dropout_prob)

    def forward(self, input_ids=None, token_type_ids=None, position_ids=None, inputs_embeds=None):
        if input_ids is not None:
            input_shape = input_ids.size()
        else:
            input_shape = inputs_embeds.size()[:-1]

        seq_length = input_shape[1]
        device = input_ids.device if input_ids is not None else inputs_embeds.device
        if position_ids is None:
            position_ids = torch.arange(seq_length, dtype=torch.long, device=device)
            position_ids = position_ids.unsqueeze(0).expand(input_shape)
        if token_type_ids is None:
            token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=device)

        if inputs_embeds is None:
            inputs_embeds = self.word_embeddings(input_ids)
        position_embeddings = self.position_embeddings(position_ids)
        token_type_embeddings = self.token_type_embeddings(token_type_ids)

        embeddings = inputs_embeds + position_embeddings + token_type_embeddings
        embeddings = self.LayerNorm(embeddings)
        embeddings = self.dropout(embeddings)
        return embeddings
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

可以看到,BERT 将 word, position, type_token的size映射为hidden_size, 并将三者加到一起,成为输入transformer的embedding

2.模型结构

2.1 设置Config

BERT-Base, Uncased: 12-layer, 768-hidden, 12-heads, 110M parameters
BERT-Large, Uncased: 24-layer, 1024-hidden, 16-heads, 340M parameters

class transformers.BertConfig(vocab_size=30522, hidden_size=768, num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072, hidden_act='gelu', hidden_dropout_prob=0.1, attention_probs_dropout_prob=0.1, max_position_embeddings=512, type_vocab_size=2, initializer_range=0.02, layer_norm_eps=1e-12, pad_token_id=0, gradient_checkpointing=False, **kwargs)
  • 1
  • max_position_embeddings

位置编码, 该模型可能使用的最大序列长度。 通常将其设置为较大的值以防万一(例如 512 / 1024 / 2048)。

  • num_hidden_layers

bert默认是12层

2.2 一个 Layer

  • BertEmbeddings

# 输入:
#      input_ids (or inputs_embeds=), token_type_ids, position_ids

# 输出:

#     embeddings = inputs_embeds + position_embeddings + token_type_embeddings
#     embeddings = LayerNorm(embeddings)
#     embeddings = dropout(embeddings) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • BertSelfAttention

# 输入:
#      hidden_state # QKV都通过 hidden_state 输入 FC 得到

# 输出:
#     context_layer, attention_score
    # context_layer 是 attentionQKV的计算输出
    # attention_score是QK的矩阵乘积,是可选的 (output_attentions)

# Tips:
# 如果用在decoder里面,那么 Q 来自 hidden_state
# K,V来自可选参数 : encoder_hidden_states=None, encoder_attention_mask=None,
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • BertSelfOutput

attention之后的全连接层

# 输入:
#      hidden_states, input_tensor

# 输出:
#     hidden_states

# 代码很简单:
class BertSelfOutput(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.hidden_size, config.hidden_size)
        self.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps)
        self.dropout = nn.Dropout(config.hidden_dropout_prob)

    def forward(self, hidden_states, input_tensor):
        hidden_states = self.dense(hidden_states)
        hidden_states = self.dropout(hidden_states)
        hidden_states = self.LayerNorm(hidden_states + input_tensor)
        return hidden_states
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • BertAttention

BertSelfAttention + BertSelfOutput

  • BertIntermediate

它位于BERT模型的编码器部分,在每个编码器层的中间位置, 区别于BertSelfOutput是没用layerNorm

  • BertOutput

这个和 BertSelfOutput 完全一样

  • BertLayer

这个实现的是 attention论文中 encoder 或者 decoder完整的一层, encoder就是 (Multi-head attention + (Add & Norm)) + (FC + (Add & Norm))

  1. 这里的 add 是 残差的意思 即 y = f(x) + x

  2. 如果把Bert当作decoder的话,其需要在中间加一层, 输入encoder的输出 ( 即K , V 的 hidden_state ),同时decoder上一层的attention 输出的 hidder_state 作为Q, 该层一般叫做 cross_attention

  3. 这里面主要用到了分快运算技术,具体为 chunk_size_feed_forward()函数, 来自reformer

沿着指定的维度(chunk_dim)分割成大小为chunk_size的小块输入张量。然后,它独立地对每个块应用一个层的前向函数(forward fn),以节省内存。如果前向函数在分割维度(chunk_dim)上是独立的,那么这个函数将产生与不应用函数相同的结果。

具体为为 [batchsize , sequence_length, hidden_size] 的 tensor 分为sequence_length个[batchsize, hidden_size]来计算以提升效率节省内存

  • BertEncoder

融合了多个attention层的transofomer, 标准的attention论文中encoder的复现

训练技巧是 Gradient checkpointing:

技术在训练时节省内存,它将计算图中的某些节点标记为检查点,
并在前向传播期间保存这些节点的部分计算结果。
在反向传播过程中,只需要重新计算与检查点相关的节点,从而减少了内存消耗。 
使用梯度检查点时,内存消耗和计算成本之间存在一个权衡。
尽管需要更多的计算来重新计算检查点之后的节点,但它显著减少了内存需求,
使得可以在有限的内存资源下训练更大的模型,
在计算资源有限的情况下实现更大规模的训练。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.训练 及 各类任务

除了第一个是bert encoder 预训练模型外,其他都是基于预训练模型加的head,这个head一般就是个fc映射。

针对不同任务,不同的head类主要是数据格式的处理以适配输入和损失函数的计算

  • BertForPreTraining

这个是最基本的bert预训练模型,即训练大量数据得到PreTraining bert, 将其输出接上不不同的 head 用于各项下游nlp任务

  1. 损失用的是交叉熵 CrossEntropyLoss, 即bert的输出作为 prediction_scores 和 labels 比较
  • BertLMHeadModel

这是一个标准头的实现,封装了Bertencoder, encoder输出后 接额外的FC: BertLMPredictionHead 作为特定任务输出

BertLMHeadModel的任务是基于输入文本的上下文,预测下一个词或一系列词的概率分布。它通过线性层和softmax激活函数对Bert模型的输出进行转换,以生成语言模型的预测结果。 接收经过Bert模型编码器处理后的特征表示作为输入。它可以被视为Bert模型的一部分,负责生成语言模型的预测结果。

  • BertForMaskedLM

BertForMaskedLM用于处理遮蔽语言建模(Masked Language Modeling)任务。

在遮蔽语言建模任务中,输入文本中的某些词会被遮蔽(即用特殊的"[MASK]"符号替代),
然后模型需要预测这些被遮蔽的词。BertForMaskedLM针对这种任务进行了优化。

具体而言,BertForMaskedLM是在BERT模型的基础上添加了一个用于遮蔽语言建模的头部模型。
该头部模型包含一个线性层和softmax激活函数,用于根据Bert模型的输出进行预测。

在训练过程中,BertForMaskedLM会接收一段包含遮蔽词的输入文本,并根据Bert模型的输出预测这些遮蔽词的概率分布。
训练过程中,通过最小化预测结果与真实标签的差异来优化模型参数。

在 inference 阶段,BertForMaskedLM可以用于预测遮蔽词的概率分布,
从而得到对输入文本中遮蔽位置的推测。

BertForMaskedLM在BERT模型的输出基础上进行预测,
从而对输入文本中遮蔽词的位置进行推测。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • BertForNextSentencePrediction

  • BertForSequenceClassification

  • BertForMultipleChoice

  • BertForTokenClassification

  • BertForQuestionAnswering


BertForQuestionAnswering是基于BERT模型的特定任务模型,用于处理问答(Question Answering)任务。

在问答任务中,给定一个问题和一个上下文文本(通常是一篇文章或段落),
模型需要从上下文中找到与问题相关的答案。
BertForQuestionAnswering针对这种任务进行了优化。

具体而言,BertForQuestionAnswering是在BERT模型的基础上添加了一个用于问答任务的头部模型。
该头部模型包含一个线性层和softmax激活函数,用于对BERT模型的输出进行答案的开始位置和结束位置的预测。

在训练过程中,BertForQuestionAnswering接收一个问题和上下文文本作为输入,
并根据Bert模型的输出进行答案位置的预测。
训练过程中,通过最小化预测结果与真实答案位置的差异来优化模型参数。

在应用阶段,BertForQuestionAnswering可以接收一个问题和上下文文本,
然后预测最可能的答案的开始位置和结束位置。

总之,BertForQuestionAnswering是基于BERT模型的特定任务模型,
用于处理问答任务。它通过添加一个头部模型,
在BERT模型的输出基础上进行预测,从而找到问题在上下文中最可能的答案位置。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4. Reference

  • https://huggingface.co/docs/transformers/model_doc/bert

  • https://huggingface.co/transformers/v3.0.2/main_classes/model.html

  • https://huggingface.co/transformers/v3.2.0/model_doc/bert.html

  • https://zhuanlan.zhihu.com/p/359682043

  • https://blog.ceshine.net/post/bert-gradient-checkpoint/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/180716
推荐阅读
相关标签
  

闽ICP备14008679号