赞
踩
本文以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可以理解为对文本的编码,将文本编码为模型可以处理的数据。
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
用于屏蔽值的标记。 这是使用掩码语言建模训练该模型时使用的标记。 这是模型将尝试预测的标记。
分词后,各个词对应的id, id值为 [0, 30522]
mask是在将序列批处理在一起时使用的可选参数。该参数向模型指示哪些标记应该被关注。
mask是一个二进制张量,指示填充索引的位置,以便模型不会关注它们。 对于 BertTokenizer,1 表示应注意的值,而 0 表示填充值。 该注意力掩码位于分词器(tokenizer)返回的字典中,键为“attention_mask”:
某些模型的目的是进行序列分类或问题回答。 这些需要在相同的输入 ID 中编码两个不同的序列。 它们通常由特殊标记分隔,例如分类器[CLS]和分隔符标记[SEP]。 例如,BERT 模型构建其两个序列输入:
" [CLS] SEQUENCE_A [SEP] SEQUENCE_B [SEP] "
模型使用 position_ids 来识别哪个令牌位于哪个位置。 与将每个标记的位置嵌入其中的 RNN 相反,Transformer 不知道每个标记的位置。 职位 ID 就是为此目的而创建的。
它是可选参数。 如果没有将 position_ids 传递给模型,将自动创建为绝对位置嵌入。绝对位置嵌入在 [0, config.max_position_embeddings - 1] 范围内选择。 某些模型使用其他类型的位置嵌入,例如正弦位置嵌入或相对位置嵌入。
这个是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
可以看到,BERT 将 word, position, type_token的size映射为hidden_size, 并将三者加到一起,成为输入transformer的embedding
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)
位置编码, 该模型可能使用的最大序列长度。 通常将其设置为较大的值以防万一(例如 512 / 1024 / 2048)。
bert默认是12层
# 输入:
# input_ids (or inputs_embeds=), token_type_ids, position_ids
# 输出:
# embeddings = inputs_embeds + position_embeddings + token_type_embeddings
# embeddings = LayerNorm(embeddings)
# embeddings = dropout(embeddings)
# 输入:
# 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,
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
BertSelfAttention + BertSelfOutput
它位于BERT模型的编码器部分,在每个编码器层的中间位置, 区别于BertSelfOutput是没用layerNorm
这个和 BertSelfOutput 完全一样
这个实现的是 attention论文中 encoder 或者 decoder完整的一层, encoder就是 (Multi-head attention + (Add & Norm)) + (FC + (Add & Norm))
这里的 add 是 残差的意思 即 y = f(x) + x
如果把Bert当作decoder的话,其需要在中间加一层, 输入encoder的输出 ( 即K , V 的 hidden_state ),同时decoder上一层的attention 输出的 hidder_state 作为Q, 该层一般叫做 cross_attention
这里面主要用到了分快运算技术,具体为 chunk_size_feed_forward()函数, 来自reformer
沿着指定的维度(chunk_dim)分割成大小为chunk_size的小块输入张量。然后,它独立地对每个块应用一个层的前向函数(forward fn),以节省内存。如果前向函数在分割维度(chunk_dim)上是独立的,那么这个函数将产生与不应用函数相同的结果。
具体为为 [batchsize , sequence_length, hidden_size] 的 tensor 分为sequence_length个[batchsize, hidden_size]来计算以提升效率节省内存
融合了多个attention层的transofomer, 标准的attention论文中encoder的复现
训练技巧是 Gradient checkpointing:
技术在训练时节省内存,它将计算图中的某些节点标记为检查点,
并在前向传播期间保存这些节点的部分计算结果。
在反向传播过程中,只需要重新计算与检查点相关的节点,从而减少了内存消耗。
使用梯度检查点时,内存消耗和计算成本之间存在一个权衡。
尽管需要更多的计算来重新计算检查点之后的节点,但它显著减少了内存需求,
使得可以在有限的内存资源下训练更大的模型,
在计算资源有限的情况下实现更大规模的训练。
除了第一个是bert encoder 预训练模型外,其他都是基于预训练模型加的head,这个head一般就是个fc映射。
针对不同任务,不同的head类主要是数据格式的处理以适配输入和损失函数的计算
这个是最基本的bert预训练模型,即训练大量数据得到PreTraining bert, 将其输出接上不不同的 head 用于各项下游nlp任务
这是一个标准头的实现,封装了Bertencoder, encoder输出后 接额外的FC: BertLMPredictionHead 作为特定任务输出
BertLMHeadModel的任务是基于输入文本的上下文,预测下一个词或一系列词的概率分布。它通过线性层和softmax激活函数对Bert模型的输出进行转换,以生成语言模型的预测结果。 接收经过Bert模型编码器处理后的特征表示作为输入。它可以被视为Bert模型的一部分,负责生成语言模型的预测结果。
BertForMaskedLM用于处理遮蔽语言建模(Masked Language Modeling)任务。
在遮蔽语言建模任务中,输入文本中的某些词会被遮蔽(即用特殊的"[MASK]"符号替代),
然后模型需要预测这些被遮蔽的词。BertForMaskedLM针对这种任务进行了优化。
具体而言,BertForMaskedLM是在BERT模型的基础上添加了一个用于遮蔽语言建模的头部模型。
该头部模型包含一个线性层和softmax激活函数,用于根据Bert模型的输出进行预测。
在训练过程中,BertForMaskedLM会接收一段包含遮蔽词的输入文本,并根据Bert模型的输出预测这些遮蔽词的概率分布。
训练过程中,通过最小化预测结果与真实标签的差异来优化模型参数。
在 inference 阶段,BertForMaskedLM可以用于预测遮蔽词的概率分布,
从而得到对输入文本中遮蔽位置的推测。
BertForMaskedLM在BERT模型的输出基础上进行预测,
从而对输入文本中遮蔽词的位置进行推测。
BertForNextSentencePrediction
BertForSequenceClassification
BertForMultipleChoice
BertForTokenClassification
BertForQuestionAnswering
BertForQuestionAnswering是基于BERT模型的特定任务模型,用于处理问答(Question Answering)任务。
在问答任务中,给定一个问题和一个上下文文本(通常是一篇文章或段落),
模型需要从上下文中找到与问题相关的答案。
BertForQuestionAnswering针对这种任务进行了优化。
具体而言,BertForQuestionAnswering是在BERT模型的基础上添加了一个用于问答任务的头部模型。
该头部模型包含一个线性层和softmax激活函数,用于对BERT模型的输出进行答案的开始位置和结束位置的预测。
在训练过程中,BertForQuestionAnswering接收一个问题和上下文文本作为输入,
并根据Bert模型的输出进行答案位置的预测。
训练过程中,通过最小化预测结果与真实答案位置的差异来优化模型参数。
在应用阶段,BertForQuestionAnswering可以接收一个问题和上下文文本,
然后预测最可能的答案的开始位置和结束位置。
总之,BertForQuestionAnswering是基于BERT模型的特定任务模型,
用于处理问答任务。它通过添加一个头部模型,
在BERT模型的输出基础上进行预测,从而找到问题在上下文中最可能的答案位置。
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/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。