共计 2723 个字符,预计需要花费 7 分钟才能阅读完成。
注意力机制的掩码容许咱们发送不同长度的批次数据一次性的发送到 transformer 中。在代码中是通过将所有序列填充到雷同的长度,而后应用“attention_mask”张量来辨认哪些令牌是填充的来做到这一点,本文将具体介绍这个掩码的原理和机制。
咱们先介绍下如果不应用掩码,是如何运行的。这里用 GPT- 2 每次应用一个序列来执行推理,因为每次只有一个序列,所以速度很慢:
from transformers import GPT2LMHeadModel, GPT2Tokenizer
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
gpt2 = GPT2LMHeadModel.from_pretrained('gpt2')
context = tokenizer('It will rain in the', return_tensors='pt')
prediction = gpt2.generate(**context, max_length=10)
tokenizer.decode(prediction[0])
# prints 'It will rain in the morning, and the rain'
在显存容许的状况下,应用批处理输出的速度更快,因为咱们在一次推理的过程能够同时解决多个序列。对许多样本执行推理要快得多,但也略微简单一些,上面是应用 transformer 库进行推理的代码:
tokenizer.padding_side = "left"
tokenizer.pad_token = tokenizer.eos_token
sentences = ["It will rain in the",
"I want to eat a big bowl of",
"My dog is"]
inputs = tokenizer(sentences, return_tensors="pt", padding=True)
output_sequences = gpt2.generate(**inputs)
for seq in output_sequences:
print(tokenizer.decode(seq))
transformer 库帮咱们解决了很多细节,咱们当初具体的介绍它外面到底做了什么。
咱们将令牌输出到语言模型中,如 GPT- 2 和 BERT,作为张量进行推理。张量就像一个 python 列表,但有一些额定的特色和限度。比如说,对于一个 2 + 维的张量,该维中的所有向量必须是雷同的长度。例如,
from torch import tensor
tensor([[1,2], [3,4]]) # ok
tensor([[1,2], [3]]) # error!
当咱们对输出进行标记时,它将被转换为序列的张量,每个整数对应于模型词表中的一个项。以下是 GPT- 2 中的标记化示例:
如果咱们想在输出中蕴含第二个序列:
因为这两个序列有不同的长度,所以不能把它们组合成一个张量。这时就须要用虚构标记填充较短的序列,以便每个序列具备雷同的长度。因为咱们想让模型持续向序列的右侧增加,咱们将填充较短序列的左侧。
这就是注意力掩码的一个利用。注意力掩码通知模型哪些令牌是填充的,在填充令牌的地位搁置 0,在理论令牌的地位搁置 1。当初咱们了解了这一点,让咱们逐行查看代码。
tokenizer.padding_side = "left"
这一行通知标记器从右边开始填充 (默认是左边),因为最左边标记的 logits 将用于预测将来的标记。
tokenizer.pad_token = tokenizer.eos_token
这一行指定将应用哪个令牌进行填充。抉择哪一个并不重要,这里咱们抉择的是“序列完结”标记。
sentences = ["It will rain in the",
"I want to eat a big bowl of",
"My dog is"]
下面这三个序列在标记时都有不同的长度,咱们应用上面的办法填充:
inputs = tokenizer(sentences, return_tensors="pt", padding=True)
在进行表打算和增加填充后,失去了以下的后果:
{'input_ids': tensor([[50256, 50256, 50256, 1026, 481, 6290, 287, 262],
[40, 765, 284, 4483, 257, 1263, 9396, 286],
[50256, 50256, 50256, 50256, 50256, 3666, 3290, 318]
]),
'attention_mask': tensor([[0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 1, 1, 1]
])}
能够看到,第一个和第三个序列在开始时进行了填充,并且 attention_mask 参数标记了这个填充的地位。
当初让咱们将这个输出传递给模型来生成新的文本:
output_sequences = gpt2.generate(**inputs)
如果你不相熟函数调用的 **kwargs 语法,它是将输出字典作为命名参数传入,应用键作为参数名,并应用值作为相应的实参值。
咱们只须要循环遍历每个生成的序列并以人类可读的模式打印出后果,应用 decode() 函数将令牌 id 转换为字符串。
for seq in output_sequences:
print(tokenizer.decode(seq))
在注意力掩码中,咱们的输出是 0 和 1,然而在最终的计算时,会将在将有效地位的注意力权重设置为一个很小的值,通常为负无穷(-inf),以便在计算注意力分数时将其克制为靠近零的概率。
这时因为,在计算注意力权重时,须要进行 Softmax 的计算:
Softmax 函数的性质:注意力机制通常应用 Softmax 函数将注意力分数转化为注意力权重,Softmax 函数对输出值进行指数运算,而后进行归一化。当输出值十分小或负无穷时,通过指数运算后会靠近零。因而,将掩码设置为负无穷能够确保在 Softmax 函数计算时,对应地位的注意力权重趋近于零。
排除有效地位的影响:通过将有效地位的注意力权重设置为负无穷,能够无效地将这些地位的权重压低。在计算注意力权重时,负无穷的权重会使对应地位的注意力权重接近于零,从而模型会疏忽有效地位的影响。这样能够确保模型更好地关注无效的信息,进步模型的准确性和泛化能力。
然而负无穷并不是惟一的抉择。有时也能够抉择应用一个很大的正数,以达到类似的成果。具体的抉择能够依据具体的工作和模型的需要来确定。
https://avoid.overfit.cn/post/0538d928a1c14940b3861437ea2fcffa
作者:Prudhviraju Srivatsavaya