基于 Transformer 的模型 因其解析和解释复杂文本的能力而闻名。它们依赖于理解单词的顺序和上下文,而传统的位置编码方法在这些任务中已经显示出其局限性。为了解决这一差距,由 旋转位置嵌入 (RoPE) 提供支持的 ROFORMER 模型重新定义了我们的位置编码方法。
传统位置编码
Transformer 将文本视为一系列标记,并允许序列的并行处理以提高效率。然而,这种优势也带来了挑战:模型对代币顺序固有的不可知论。 位置编码就是答案,为每个令牌提供一个唯一的签名,表示其序列位置。
绝对位置嵌入
最初,像 BERT 这样的模型使用绝对位置嵌入,为序列中的每个位置分配一个固定向量。这种方法虽然简单,但本质上缺乏适应序列长度变化或强调标记之间相对距离的能力,而这对于理解许多语言结构至关重要。
相对位置嵌入
为了捕捉语言的动态本质,引入了相对位置嵌入,重点关注标记之间的距离而不是它们的绝对位置。尽管它们在概念上具有优势,但这些嵌入引入了计算复杂性,并且无法无缝集成到 Transformers 的自注意力机制中,从而限制了它们的功效。
ROFORMER 和旋转位置嵌入
认识到现有位置编码策略的局限性,ROFORMER 引入了旋转位置嵌入 (RoPE),这种方法结合了绝对和相对位置信息的优点,而没有各自的缺点。
旋转位置嵌入
RoPE 使用旋转矩阵对位置信息进行编码,使模型不仅能够了解令牌的位置,还能了解它与序列中每个其他令牌的关系。
Credit: ArXiv
它通过几何透镜进行操作,将标记位置视为多维空间中的点,通过旋转来标记它们的顺序关系。这种旋转允许模型在其自注意力机制中保留和利用绝对和相对位置线索。
实施 RoPE
实现 RoPE 涉及将每个标记的位置编码为旋转矩阵,并将该矩阵应用到 Transformer 的自注意力机制中。这个过程允许对位置信息进行灵活、动态的解释,适应不同的序列长度并捕获令牌相互关系的本质,而无需大量的计算开销。
首先,您需要一个函数来生成旋转嵌入,然后将这些嵌入集成到您的模型中。下面的示例假设您熟悉在 Keras 中创建自定义层。
步骤 1:定义旋转嵌入函数
该函数在给定最大序列长度和嵌入维数的情况下生成旋转嵌入。
from tensorflow.keras.layers import Layer
import numpy as np
def get_rotary_embedding(dim, max_seq_len):
inv_freq = 1.0 / (10000 ** (tf.range(0, dim, 2, dtype=tf.float32) / dim))
t = tf.range(max_seq_len, dtype=tf.float32)
freqs = tf.einsum('i,j->ij', t, inv_freq)
emb = tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
return emb
inv_freq = 1.0 / (10000 ** (tf.range(0, dim, 2, dtype=tf.float32) / dim))
该行根据位置索引计算指数缩放频率的倒数。这些频率用于生成旋转嵌入的正弦模式,这有助于对序列中的相对位置信息进行编码。这种机制在理解元素的顺序和相对位置至关重要的任务中特别有用,例如在自然语言处理或时间序列分析中。
详细信息:
-
tf.range(0, dim, 2, dtype=tf.float32)
创建从 0 到dim
(不包括)的值范围,步进 2。dtype=tf.float32
参数指定该张量的元素是 32 位浮点数。例如,如果“dim”为 8,则将生成“[0, 2, 4, 6]”。 -
然后将“tf.range”生成的张量除以嵌入的维数(“dim”)。此操作将这些索引缩小到 0 到 1 之间的范围(如果“dim”为偶数,则不包括在内;如果“dim”为奇数,则稍微包含在内,因为范围步骤会跳过所有其他值)。继续使用
dim
= 8 的示例,除以 8 得到[0.0, 0.25, 0.5, 0.75]
。 -
10000 ** (...)
运算将之前缩放的张量中每个元素的 10,000 次幂进行计算。 10,000 的基数有些随意,但选择它是为了确保频率在较大范围内变化,这有助于模型更有效地区分不同的位置。对于“[0.0, 0.25, 0.5, 0.75]”,它将对每个进行幂运算,从而导致较高元素的值更大。 -
最后,通过取上一步的值的倒数 (1/x) 来获得逆频率。对于较高的索引,逆频率较小,这意味着序列中较远的元素将具有较小的频率,从而影响它们的位置如何编码到模型中。这允许嵌入以可以通过模型的注意力机制推断相对位置的方式进行缩放。
该行:
freqs = tf.einsum('i,j->ij', t, inv_freq)
使用 TensorFlow 的“tf.einsum”函数,该工具允许使用爱因斯坦求和符号简洁高效地表达张量运算。
此操作有效地计算“t”和“inv_freq”向量的外积,得到一个矩阵,其中每个元素“(i, j)”是“t”的第“i”个元素与“ inv_freq
的第 j 个元素。该矩阵(“freqs”)表示用于生成旋转嵌入的正弦模式的频率。
步骤 2:用于旋转嵌入的自定义 Keras 层
现在,让我们创建一个自定义 Keras 层,将旋转嵌入应用于输入张量。该层假设输入张量的形状为“(batch_size,sequence_length,embedding_dim)”。
class RotaryEmbeddingLayer(Layer):
def __init__(self, dim, max_seq_len, **kwargs):
super().__init__(**kwargs)
self.dim = dim
self.max_seq_len = max_seq_len
self.rotary_embeddings = get_rotary_embedding(dim, max_seq_len)
def call(self, inputs):
seq_len = tf.shape(inputs)[1]
embeddings = self.rotary_embeddings[:seq_len]
cos_emb = embeddings[:, None, :self.dim // 2]
sin_emb = embeddings[:, None, self.dim // 2:]
# Decompose inputs into sine and cosine components
inputs_cos = inputs[..., :self.dim // 2]
inputs_sin = inputs[..., self.dim // 2:]
# Apply rotary embeddings
rotated_cos = inputs_cos * cos_emb - inputs_sin * sin_emb
rotated_sin = inputs_sin * cos_emb + inputs_cos * sin_emb
return tf.concat([rotated_cos, rotated_sin], axis=-1)
def get_config(self):
config = super().get_config()
config.update({
"dim": self.dim,
"max_seq_len": self.max_seq_len
})
return config
embeddings = self.rotary_embeddings[:seq_len]
行根据当前输入序列长度选择预先计算的旋转嵌入的适当子集。由于序列的长度可能因批次而异,因此这种切片操作可确保仅使用与实际序列长度相对应的嵌入。
变量“embeddings”现在保存形状为“(seq_len, embedding_dim)”的张量,其中“seq_len”是当前批次中序列的长度,“embedding_dim”是嵌入的维度。该张量包含序列中每个位置的旋转位置嵌入,直到“seq_len”。
emb = tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
将位置频率的正弦和余弦变换组合成单个张量:
-tf.cos(freqs)
和 tf.sin(freqs)
分别对 freqs
张量应用余弦和正弦变换。 “freqs”张量包含输入序列中每个位置和嵌入空间每个维度的频率值,根据序列位置和嵌入维度的逆频率计算。正弦和余弦函数按元素应用,产生两个与“freqs”形状相同的张量。这些转换有助于以捕获位置关系的循环性质的方式对位置进行编码,从而促进模型理解相对位置的能力。
-tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
沿着最后一个轴(用 axis=-1
表示)连接余弦和正弦变换张量。并排连接这些张量可以有效地使“freqs”张量的维数加倍,前半部分表示每个位置的余弦变换值,后半部分表示正弦变换值。级联确保每个位置编码都包含正弦和余弦信息,从而允许保留有关位置信号的幅度和相位的信息。
- 连接的张量“emb”现在保存输入位置的完整旋转嵌入。 “emb”的形状在其前两个维度(对应于序列位置和嵌入维度)中将与“freqs”相同,但其最后一个维度将是其两倍大,同时考虑正弦和余弦值。这些嵌入用于通过以旋转等变方式添加位置信息来调制输入嵌入。
-cos_emb = 嵌入[:, None, :self.dim // 2]
:
-
第一个冒号
:
表示“选择该维度中的所有元素”,在本例中,它指的是序列中的所有位置。 -
None
用于添加额外的维度,使张量成为 3 维的。这样做通常是为了确保与需要特定维数输入的某些操作的兼容性。例如,当与另一个 3 维张量执行逐元素乘法时,形状必须根据广播规则对齐。 -
:self.dim // 2
,选择最后一个轴的前半个维度。由于“embedding_dimension”加倍以包含正弦和余弦值,因此除以 2 可以有效地仅选择嵌入的余弦分量。
步骤 3:与 Keras 模型集成
定义“RotaryEmbeddingLayer”后,您可以将其集成到 Keras 模型中。该层应先应用于您的嵌入,然后再将其馈送到注意层或任何后续模型层。
以下是如何将旋转嵌入集成到模型中的简化示例:
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Embedding, Dense
max_seq_len = 512
embedding_dim = 64
inp = Input(shape=(max_seq_len,))
x = Embedding(input_dim=10000, output_dim=embedding_dim)(inp)
x = RotaryEmbeddingLayer(dim=embedding_dim, max_seq_len=max_seq_len)(x)
# Add your model's layers here, e.g., Transformer blocks
x = Dense(1, activation='sigmoid')(x)
model = Model(inputs=inp, outputs=x)
model.summary()