位置编码--RoPE
ref: https://medium.com/ai-insights-cobet/rotary-positional-embeddings-a-detailed-look-and-comprehensive-understanding-4ff66a874d83
- 背景
在标准的Transformer模型中,位置编码(Positional Encoding,PE)通常通过固定的正弦和余弦函数生成,旨在为模型提供序列中各个元素的位置。这个方法的一个潜在问题是,它对于长序列的泛化能力较差,尤其是在处理长文本或其他复杂的序列数据时,传统的位置编码可能无法很好地捕捉到长距离的依赖关系。
1.1 前置概念:
Transformer 中位置嵌入的必要性
为了理解 RoPE 的重要性,我们首先回顾一下为什么位置嵌入至关重要。Transformer 模型根据其固有设计不考虑输入标记的顺序。
例如,“狗追猪”和“猪追狗”这样的短语虽然含义不同,但由于被视为一组无序的标记,因此无法区分。为了保留序列信息,从而保留含义,位置嵌入被集成到模型中。
绝对位置嵌入的工作原理
在句子的上下文中,假设我们有一个表示单词的嵌入。为了编码其位置,我们使用另一个维度相同的向量,其中每个向量唯一地表示句子中的一个位置。例如,为句子中的第二个单词指定一个特定的向量。因此,每个句子位置都有其独特的向量。然后,Transformer 层的输入由单词嵌入与其对应的位置嵌入相加而成。
主要有两种方法可以生成这些嵌入:
从数据中学习:在这里,位置向量是在训练过程中学习的,就像其他模型参数一样。我们为每个位置学习一个唯一的向量,例如从 1 到 512。然而,这引入了一个限制——最大序列长度是有上限的。如果模型只学习到位置 512,它就无法表示比这更长的序列。
正弦函数:此方法涉及使用正弦函数为每个位置构建唯一的嵌入。虽然这种构造的复杂细节很复杂,但它本质上为序列中的每个位置提供了唯一的位置嵌入。实证研究表明,从数据中学习并使用正弦函数在现实世界模型中提供了相当的性能。
绝对位置嵌入的局限性
尽管绝对位置嵌入被广泛使用,但它也存在缺点:
有限序列长度:如上所述,如果模型学习位置向量到某个点,它就无法固有地表示超出该限制的位置。
位置嵌入的独立性:每个位置嵌入都独立于其他嵌入。这意味着在模型看来,位置 1 和 2 之间的差异与位置 2 和 500 之间的差异相同。然而,直观地看,位置 1 和 2 应该比位置 500 更紧密相关,因为位置 500 的距离要远得多。这种相对定位的缺乏可能会妨碍模型理解语言结构细微差别的能力。
1.2 理解相对位置嵌入
相对位置嵌入不关注标记在句子中的绝对位置,而是关注标记对之间的距离。此方法不会直接将位置向量添加到词向量中。相反,它改变了注意力机制以纳入相对位置信息。
案例研究:T5 模型
利用相对位置嵌入的一个突出模型是 T5(文本到文本传输转换器)。T5 引入了一种处理位置信息的细致入微的方法:
ref:https://0809zheng.github.io/2022/07/01/posencode.html
位置偏移的偏差: T5 使用偏差(浮点数)来表示每个可能的位置偏移。例如,偏差 B1 可能表示相隔一个位置的任意两个标记之间的相对距离,而不管它们在句子中的绝对位置如何。
自注意力层中的集成:此相对位置偏差矩阵被添加到自注意力层中的查询和键矩阵的乘积中。这确保了相同相对距离的标记始终由相同的偏差表示,无论它们在序列中的位置如何。
可扩展性:此方法的一个显著优势是可扩展性。它可以扩展到任意长的序列,这比绝对位置嵌入有明显的优势。旋转位置编码(Rotary Position Embedding,简称RoPE)是一种用于序列建模中的位置编码方法,特别是在Transformer模型中,广泛应用于处理自然语言处理(NLP)任务。RoPE通过引入旋转矩阵来对序列中的每个位置进行编码,从而使得模型能够有效捕捉序列中的位置信息,尤其适用于长序列的建模。
尽管相对位置嵌入具有理论上的吸引力,但它也带来了一定的实际挑战。
性能问题:将 T5 的相对嵌入与其他类型的相对嵌入进行比较的基准测试表明,它们可能会更慢,特别是对于较长的序列。这主要是由于自注意力层中的额外计算步骤,其中位置矩阵被添加到查询键矩阵中。
键值缓存使用中的复杂性:由于每个附加标记都会改变其他每个标记的嵌入,因此这会使 Transformer 中键值缓存的有效使用变得复杂。对于那些不熟悉的人来说,键值缓存对于提高 Transformer 模型的效率和速度至关重要。
- 什么是旋转位置嵌入(RoPE)?
RoPE 代表了一种编码位置信息的新方法。传统方法,无论是绝对方法还是相对方法,都有其局限性。绝对位置嵌入为每个位置分配一个唯一的向量,虽然这种方法很简单,但扩展性不佳,无法有效捕捉相对位置。另一方面,相对嵌入关注标记之间的距离,这增强了模型对标记关系的理解,但使模型架构复杂化。
RoPE 巧妙地结合了两者的优势。它以一种让模型能够理解标记的绝对位置及其相对距离的方式对位置信息进行编码。这是通过旋转机制实现的,其中序列中的每个位置都由嵌入空间中的旋转表示。RoPE 的优雅之处在于它的简单性和效率,使模型能够更好地掌握语言语法和语义的细微差别。
RoPE的核心思想
RoPE通过引入旋转矩阵的思想,将位置编码与元素的表示融合。具体来说,RoPE会将每个位置编码看作是一个旋转变换,它不仅能够表示位置的位置信息,还能通过旋转变换捕捉到序列中元素之间的相对关系。这种旋转编码不仅适应了固定长度的序列,也可以有效地扩展到更长的序列。
RoPE的编码方法不再是简单的加法,而是通过对每个位置进行旋转,使得位置的关系通过旋转角度来编码。例如,通过将每个位置的编码看作是一个复数,并通过旋转操作来变化这些复数,从而实现位置编码的变化。
旋转位置嵌入的机制
RoPE 引入了一个新概念。它不是添加位置向量,而是对词向量进行旋转。想象一下“dog”的二维词向量。为了编码它在句子中的位置,RoPE 会旋转这个向量。旋转角度 (θ) 与单词在句子中的位置成正比。例如,对于第一个位置,向量旋转 θ,对于第二个位置,旋转 2θ,依此类推。这种方法有几个好处:
向量的稳定性:在句子末尾添加标记不会影响开头的单词向量,从而有助于高效缓存。
保持相对位置:如果两个词(例如“pig”和“dog”)在不同上下文中保持相同的相对距离,则它们的向量会旋转相同的量。这确保了这些向量之间的角度以及点积保持不变
RoPE 的技术实现涉及旋转矩阵。在 2D 情况下,论文中的方程包含一个旋转矩阵,该矩阵将向量旋转 Mθ 角度,其中 M 是句子中的绝对位置。此旋转应用于 Transformer 的自注意力机制中的查询Q和键向量K。
ref:https://0809zheng.github.io/2022/07/01/posencode.html
即:
对于更高维度,向量被分割成 2D 块,每对独立旋转。这可以看作是空间中旋转的 n 维螺旋。
import torch
import torch.nn as nn
device= "cuda" if torch.cuda.is_available() else "cpu"
class RotaryPositionalEmbedding (nn.Module):
def __init__ ( self, d_model, max_seq_len,device ):
super (RotaryPositionalEmbedding, self).__init__()
# 创建一个旋转矩阵。
self.rotation_matrix = torch.zeros(d_model, d_model, device=torch.device( "cuda" ))
for i in range (d_model):
for j in range (d_model):
i_tensor = torch.tensor(i, dtype=torch.float32)
j_tensor = torch.tensor(j, dtype=torch.float32)
self.rotation_matrix[i, j] = torch.cos(i_tensor * j_tensor * 0.01 )
# 创建一个位置嵌入矩阵。
self.positional_embedding = torch.zeros(max_seq_len, d_model, device=torch.device( "cuda" ))
for i in range (max_seq_len):
for j in range (d_model):
i_tensor = torch.tensor(i, dtype=torch.float32)
j_tensor = torch.tensor(j, dtype=torch.float32)
self.positional_embedding[i, j] = torch.cos(i_tensor * j_tensor * 0.01 )
def forward ( self, x ):
"""
参数:
x:形状为 (batch_size, seq_len, d_model) 的张量。
返回:
形状为 (batch_size, seq_len, d_model) 的张量。
"""
# 将位置嵌入添加到输入张量。
x=x.to(device)
x += self.positional_embedding.to(device)
# 将旋转矩阵应用于输入张量。
x = torch.matmul(x, self.rotation_matrix)
return x
rope=RotaryPositionalEmbedding ( d_model=2, max_seq_len =32,device=device)
x=torch.randn(4, 32, 2)
res_x=rope.forward(x) # 4,32,2
print()
补充:在NLP(自然语言处理)中,张量的形状 (batch_size, seq_len, d_model) 通常用于表示输入数据的结构,其中:
batch_size:表示一个批次中样本的数量。也就是说,batch_size 是每次处理的句子或文本的数量。比如,若 batch_size=32,则每次模型处理32个样本。
seq_len:表示序列的长度,通常是句子的长度或输入序列中的单词数量。在NLP任务中,句子可以被视为由若干个单词或标记组成,seq_len 即为该序列中的单词数。
d_model:表示模型中每个单词或标记的向量表示的维度。每个单词被嵌入为一个固定维度的向量,d_model 就是这个嵌入空间的维度,也可以理解为特征空间的维度。比如在Transformer模型中,d_model 就是输入到模型中的每个标记(token)对应的向量维度。
d_model 的值为 1 和 2 的区别
假设你有以下两种情况:
d_model = 1:表示每个单词或标记只用一个数值来表示。模型处理的就是一个标量值,每个单词只有一个特征。例如,输入的每个标记只是一个实数值,这种情况极为简单,通常不适用于复杂的NLP任务。
d_model = 2:表示每个单词或标记用一个二维向量来表示。每个单词就有两个特征,这比单个数值更有表达能力,能够在二维空间中进行一些基本的表示,比如区分不同的类别或属性。
d_model 的值越大,表示模型能用更高维度的空间来表达单词的语义信息,通常会有更强的表示能力。这在实际中通常是一个非常大的数值,比如 512 或 1024,以便能够捕捉更加复杂和细腻的语言特征。
要将每个token(标记)表示为一个向量,通常会使用**词嵌入(Word Embedding)**技术。词嵌入的目标是将每个token映射到一个低维连续空间,使得语义上相似的词在这个空间中也接近。通常的做法包括使用预训练的词嵌入模型(如Word2Vec、GloVe、FastText等),或者在训练过程中通过模型本身来学习这些嵌入向量(例如在Transformer模型中)。
在Transformer模型(如BERT、GPT等)中,通常会有一个嵌入层,它将输入token的ID映射为一个d_model维度的向量。
Token Embedding:将token ID映射为d_model维度的向量。
Position Embedding:为每个token的位置添加位置编码(Position Encoding)。
Segment Embedding:有时还会为不同的句子添加段落编码(例如BERT中的[CLS]和[SEP]标记)。
例如,BERT的嵌入计算过程如下:
输入token的ID首先通过嵌入层映射到d_model维度的向量。
然后,位置编码(Position Encoding)会被加到每个token的嵌入向量中