旋转位置RoPE编码详解

一. 旋转位置编码和正余弦位置编码比对

旋转位置编码(RoPE)和正余弦位置编码(Sinusoidal Position Encoding)是两种常用的位置编码方法,它们在处理序列数据时具有不同的数学形式和特性。以下是对两者优劣的详细说明及底层数学推理:

1. 正余弦位置编码(Sinusoidal Position Encoding)

1)数学形式
对于位置 \( \text{pos} \) 和维度 \( i \),正余弦编码定义为:
\[
\text{PE}(\text{pos}, 2i) = \sin\left(\frac{\text{pos}}{10000^{2i/d}}\right), \quad
\text{PE}(\text{pos}, 2i+1) = \cos\left(\frac{\text{pos}}{10000^{2i/d}}\right)
\]
其中 \( d \) 是模型的嵌入维度,\( i \in [0, d/2) \)。不同维度使用不同频率的正余弦函数,频率随维度增加呈指数下降。

2)优点
1. 显式相对位置编码:  
   对于固定偏移 \( k \),存在线性变换 \( T_k \) 使得 \( \text{PE}(\text{pos} + k) = T_k \cdot \text{PE}(\text{pos}) \)。例如,对于频率 \( \omega_i = 1/10000^{2i/d} \),有:
   \[
   \begin{aligned}
   \sin(\omega_i (\text{pos} + k)) &= \sin(\omega_i \text{pos}) \cos(\omega_i k) + \cos(\omega_i \text{pos}) \sin(\omega_i k), \\
   \cos(\omega_i (\text{pos} + k)) &= \cos(\omega_i \text{pos}) \cos(\omega_i k) - \sin(\omega_i \text{pos}) \sin(\omega_i k),
   \end{aligned}
   \]
   这等效于二维旋转矩阵 \( R(\omega_i k) \) 作用在 \( [\sin(\omega_i \text{pos}), \cos(\omega_i \text{pos})]^T \)。因此,相对位置信息可通过线性组合捕捉。

2. 确定性与可扩展性: 
   编码是确定性的,无需学习参数,且能处理任意长度序列。

3)缺点
1. 内容与位置耦合:  
   位置编码与词嵌入通过加法结合,导致注意力分数包含四部分:
   \[
   \mathbf{q}_i^T \mathbf{k}_j = (\mathbf{x}_i + \text{PE}(i)) \mathbf{W}_Q \mathbf{W}_K^T (\mathbf{x}_j + \text{PE}(j))^T,
   \]
   其中交叉项(如 \( \mathbf{x}_i \mathbf{W}_Q \mathbf{W}_K^T \text{PE}(j)^T \))需要模型学习复杂的位置-内容交互。

2. 绝对位置偏差:  
   编码本质上是绝对位置的,长序列泛化可能受限。

---

2. 旋转位置编码(Rotary Position Embedding, RoPE)

1)数学形式
对词嵌入 \( \mathbf{x} \in \mathbb{R}^d \),将其划分为 \( d/2 \) 个二维子空间,对每个子空间应用旋转矩阵 \( R_m \):
\[
R_m = \begin{bmatrix}
\cos m\theta_i & -\sin m\theta_i \\
\sin m\theta_i & \cos m\theta_i
\end{bmatrix}, \quad \theta_i = 10000^{-2i/d},
\]
其中 \( m \) 是位置,\( i \) 是子空间索引。查询和键向量通过旋转注入位置信息:
\[
\mathbf{q}_m = R_m \mathbf{x}_m \mathbf{W}_Q, \quad \mathbf{k}_n = R_n \mathbf{x}_n \mathbf{W}_K.
\]

2)优点
1. 显式相对位置编码:  
   注意力分数 \( \mathbf{q}_m^T \mathbf{k}_n \) 仅依赖相对位置 \( n - m \):
   \[
   \mathbf{q}_m^T \mathbf{k}_n = (\mathbf{x}_m \mathbf{W}_Q)^T R_{m}^T R_n (\mathbf{x}_n \mathbf{W}_K) = (\mathbf{x}_m \mathbf{W}_Q)^T R_{n - m} (\mathbf{x}_n \mathbf{W}_K),
   \]
   其中 \( R_{n - m} \) 是相对位置的旋转矩阵。内积直接编码相对位置,无需模型学习。

2. 模长不变性:  
   旋转是正交变换,保持向量模长,增强数值稳定性。

3. 长序列适应性:  
   相对位置编码天然适应长序列,测试时无需外推。

3)缺点
1. *实现复杂度:  
   需将向量分块旋转,实现较复杂。

2. 预设频率敏感性:  
   频率参数 \( \theta_i \) 需预设,可能影响不同任务表现。

3. 数学推导对比

1)正余弦编码的注意力分数
\[
\mathbf{q}_i^T \mathbf{k}_j = \underbrace{\mathbf{x}_i \mathbf{W}_Q \mathbf{W}_K^T \mathbf{x}_j^T}_{\text{内容项}} + \underbrace{\mathbf{x}_i \mathbf{W}_Q \mathbf{W}_K^T \text{PE}(j)^T + \text{PE}(i) \mathbf{W}_Q \mathbf{W}_K^T \mathbf{x}_j^T}_{\text{交叉项}} + \underbrace{\text{PE}(i) \mathbf{W}_Q \mathbf{W}_K^T \text{PE}(j)^T}_{\text{位置项}}.
\]
模型需同时学习内容、位置及其交互,增加了复杂性。

2)RoPE的注意力分数
\[
\mathbf{q}_m^T \mathbf{k}_n = (\mathbf{x}_m \mathbf{W}_Q)^T R_{n - m} (\mathbf{x}_n \mathbf{W}_K) = \sum_{i=0}^{d/2-1} (\mathbf{x}_m^{(i)} \mathbf{W}_Q^{(i)})^T R^{(i)}_{n - m} (\mathbf{x}_n^{(i)} \mathbf{W}_K^{(i)}),
\]
其中 \( R^{(i)}_{n - m} \) 是第 \( i \) 个子空间的旋转矩阵。相对位置 \( n - m \) 直接作用于内积,简化了位置建模。

3)总结

特性正余弦位置编码旋转位置编码
位置信息注入方式加法(词嵌入 + 位置编码)乘法(旋转矩阵作用于词嵌入)
相对位置编码需通过线性变换学习显式编码于内积中
长序列适应性可处理任意长度,但泛化能力有限更优(依赖相对位置,无需绝对位置外推)
计算复杂度低(预计算加法)中等(分块旋转操作)
数值稳定性可能受相加干扰优(旋转保持模长不变)
实现难度简单复杂

4)选择建议:  
- 正余弦编码适合计算资源有限或需要快速实现的场景。  
- 旋转编码适合对位置敏感的任务(如长文本生成、问答),尤其是需要显式相对位置建模的场景。

二. 旋转位置编码计算逻辑示例

以下以输入 **5个token**,每个token的词向量维度为 **512** 为例,详细说明旋转位置编码的计算步骤,包括权重矩阵 \( W^Q \)、\( W^K \) 的维度及旋转矩阵 \( R_m \) 的生成过程。

1. 输入与权重矩阵

(1) 输入矩阵
输入序列为 **5个token**,每个token的词向量维度为 **512**,输入矩阵表示为:
\[
X \in \mathbb{R}^{5 \times 512}
\]

(2) 权重矩阵
- **查询权重矩阵** \( W^Q \in \mathbb{R}^{512 \times 512} \)
- **键权重矩阵** \( W^K \in \mathbb{R}^{512 \times 512} \)
- **值权重矩阵** \( W^V \in \mathbb{R}^{512 \times 512} \)

通过线性变换生成初始的 \( Q \)、\( K \)、\( V \):
\[
Q = X W^Q, \quad K = X W^K, \quad V = X W^V \quad \Rightarrow \quad Q, K, V \in \mathbb{R}^{5 \times 512}
\]

2. 旋转位置编码的核心思想

- 目标:将位置信息注入 \( Q \) 和 \( K \) 中,使得它们的点积能隐式编码相对位置。
- 方法:将每个词向量的维度划分为 **256个二维子空间**,对每个子空间应用旋转矩阵 \( R_m^{(i)} \),旋转角度由位置 \( m \) 和频率 \( \theta_i \) 决定。

3. 旋转矩阵的生成

(1) 频率参数
对第 \( i \) 个二维子空间,频率参数定义为:
\[
\theta_i = 10000^{-2i/512}, \quad i \in [0, 255]
\]
例如:
- \( i = 0 \): \( \theta_0 = 10000^{0} = 1 \)
- \( i = 1 \): \( \theta_1 = 10000^{-2/512} \approx 0.9647 \)
- \( i = 255 \): \( \theta_{255} = 10000^{-510/512} \approx 1.58 \times 10^{-5} \)

(2) 旋转角度
对位置 \( m \),第 \( i \) 个子空间的旋转角度为:
\[
\text{angle}_i = m \cdot \theta_i
\]

(3) 旋转矩阵
生成二维旋转矩阵:
\[
R_m^{(i)} = \begin{bmatrix}
\cos(\text{angle}_i) & -\sin(\text{angle}_i) \\
\sin(\text{angle}_i) & \cos(\text{angle}_i)
\end{bmatrix}
\]

4. 计算旋转后的 Q 和 K

(1) 划分二维子空间
将每个token的 **512维向量** 划分为 **256个二维子空间**:
\[
\mathbf{q}_m = [\mathbf{q}_m^{(0)}, \mathbf{q}_m^{(1)}, \ldots, \mathbf{q}_m^{(255)}], \quad \mathbf{q}_m^{(i)} \in \mathbb{R}^2
\]
例如:
- \( \mathbf{q}_m^{(0)} = [q_{m,0}, q_{m,1}] \)
- \( \mathbf{q}_m^{(1)} = [q_{m,2}, q_{m,3}] \)
- ...
- \( \mathbf{q}_m^{(255)} = [q_{m,510}, q_{m,511}] \)
(2) 应用旋转矩阵
对每个子空间 \( i \) 和位置 \( m \),计算旋转后的向量:
\[
\begin{aligned}
q_{m,2i}^{\text{rot}} &= q_{m,2i} \cos(m\theta_i) - q_{m,2i+1} \sin(m\theta_i) \\
q_{m,2i+1}^{\text{rot}} &= q_{m,2i} \sin(m\theta_i) + q_{m,2i+1} \cos(m\theta_i)
\end{aligned}
\]

(3) 重构旋转后的 Q 和 K
将所有子空间旋转后的结果拼接回 **512维向量**,得到旋转后的 \( Q_{\text{rot}} \) 和 \( K_{\text{rot}} \)。

5. 注意力分数的计算

计算旋转后的注意力分数矩阵:
\[
\text{Attention Scores} = \frac{Q_{\text{rot}} \cdot K_{\text{rot}}^T}{\sqrt{512}}
\]
由于旋转矩阵的正交性,内积仅依赖相对位置 \( n - m \):
\[
\mathbf{q}_m^{\text{rot}} \cdot \mathbf{k}_n^{\text{rot}} = \sum_{i=0}^{255} \mathbf{q}_m^{(i)} \cdot R_{n-m}^{(i)} \cdot \mathbf{k}_n^{(i)}
\]

6. 具体数值示例

假设 **位置 \( m = 1 \)**,**子空间 \( i = 0 \)**:
- 频率:\( \theta_0 = 1 \)
- 旋转角度:\( \text{angle}_0 = 1 \times 1 = 1 \) 弧度
- 旋转矩阵:
\[
R_1^{(0)} = \begin{bmatrix}
\cos(1) & -\sin(1) \\
\sin(1) & \cos(1)
\end{bmatrix} \approx \begin{bmatrix}
0.5403 & -0.8415 \\
0.8415 & 0.5403
\end{bmatrix}
\]
- 原始子空间向量:\( \mathbf{q}_1^{(0)} = [2.0, 3.0] \)
- 旋转后向量:
\[
\begin{aligned}
q_{1,0}^{\text{rot}} &= 2.0 \times 0.5403 - 3.0 \times 0.8415 \approx -1.6245 \\
q_{1,1}^{\text{rot}} &= 2.0 \times 0.8415 + 3.0 \times 0.5403 \approx 3.0252
\end{aligned}
\]

7. 总结

步骤操作
输入维度5个token,每个512维
权重矩阵WQ,WK∈R512×512WQ,WK∈R512×512
子空间划分将512维划分为256个二维子空间
旋转矩阵生成基于位置 mm 和频率 θi=10000−2i/512θi​=10000−2i/512
旋转后Q/K每个二维子空间独立旋转,拼接后保持512维
注意力分数内积隐式编码相对位置 n−mn−m

通过旋转位置编码,模型无需显式学习位置偏差,直接通过几何变换捕捉相对位置关系,提升长序列建模能力。

三. 基于pytorch的完整代码实现

基于PyTorch的正余弦位置编码与旋转位置编码(RoPE)实现详解

1. 正余弦位置编码(Sinusoidal Position Encoding)

1)数学公式:
对于位置 \( \text{pos} \) 和维度 \( i \),编码定义为:
\[
\text{PE}(\text{pos}, 2i) = \sin\left(\frac{\text{pos}}{10000^{2i/d}}\right), \quad
\text{PE}(\text{pos}, 2i+1) = \cos\left(\frac{\text{pos}}{10000^{2i/d}}\right)
\]
其中 \( d \) 为嵌入维度。

2)PyTorch实现步骤:

1. 生成位置索引与维度索引:

import torch

def sinusoidal_position_encoding(max_len, d_model):
    position = torch.arange(max_len).unsqueeze(1)  # (max_len, 1)
    div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)
    pe = torch.zeros(max_len, d_model)
    pe[:, 0::2] = torch.sin(position * div_term)  # 偶数维度
    pe[:, 1::2] = torch.cos(position * div_term)  # 奇数维度
    return pe  # (max_len, d_model)

2. 与输入嵌入结合:

class TransformerEmbedding(nn.Module):
    def __init__(self, vocab_size, d_model, max_len):
        super().__init__()
        self.token_embed = nn.Embedding(vocab_size, d_model)
        self.pe = nn.Parameter(sinusoidal_position_encoding(max_len, d_model), requires_grad=False)
    
    def forward(self, x):
        # x: (batch_size, seq_len)
        embeddings = self.token_embed(x)  # (batch_size, seq_len, d_model)
        seq_len = x.size(1)
        embeddings += self.pe[:seq_len, :]  # 添加位置编码
        return embeddings

3. 关键细节:
- 预计算与缓存:位置编码矩阵预先计算并注册为不训练的模型参数(`requires_grad=False`)。
- 动态长度处理:通过切片 `pe[:seq_len]` 适应不同序列长度。
- 广播机制:PyTorch自动将 `pe` 广播到批次维度。

2. 旋转位置编码(RoPE)

1)数学公式:
对词向量 \( \mathbf{x} \in \mathbb{R}^d \),划分为 \( d/2 \) 个二维子空间,每个子空间应用旋转矩阵:
\[
R_m^{(i)} = \begin{bmatrix}
\cos(m\theta_i) & -\sin(m\theta_i) \\
\sin(m\theta_i) & \cos(m\theta_i)
\end{bmatrix}, \quad \theta_i = 10000^{-2i/d}
\]

2)PyTorch实现步骤:

1. 生成频率张量:

def get_freqs(d_model: int):
    theta = 1.0 / (10000 ** (torch.arange(0, d_model, 2) / d_model))
    return theta  # (d_model // 2,)

2. 生成旋转矩阵:

def rotate_half(x):
    # 将输入分为前后两半并旋转
    x1, x2 = x.chunk(2, dim=-1)
    return torch.cat((-x2, x1), dim=-1)

def apply_rotary_pos_emb(q, k, freqs):
    # q, k: (batch_size, seq_len, n_heads, head_dim)
    # freqs: (1, seq_len, 1, head_dim // 2)
    theta = freqs.unsqueeze(0).unsqueeze(2)  # (1, seq_len, 1, head_dim//2)
    theta = theta.to(q.device)
    
    # 构造复数形式
    q_complex = torch.view_as_complex(q.float().reshape(*q.shape[:-1], -1, 2))
    k_complex = torch.view_as_complex(k.float().reshape(*k.shape[:-1], -1, 2))
    
    # 应用旋转
    rotated_q = q_complex * torch.polar(torch.ones_like(theta), theta)
    rotated_k = k_complex * torch.polar(torch.ones_like(theta), theta)
    
    # 转换回实数形式
    rotated_q = torch.view_as_real(rotated_q).flatten(-2)
    rotated_k = torch.view_as_real(rotated_k).flatten(-2)
    return rotated_q.type_as(q), rotated_k.type_as(k)

3. 整合到注意力层:

class RotaryAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.head_dim = d_model // n_heads
        self.freqs = get_freqs(self.head_dim)
        
        self.wq = nn.Linear(d_model, d_model)
        self.wk = nn.Linear(d_model, d_model)
        self.wv = nn.Linear(d_model, d_model)
    
    def forward(self, x, freqs):
        # x: (batch_size, seq_len, d_model)
        B, L, _ = x.shape
        q = self.wq(x).view(B, L, self.n_heads, self.head_dim)
        k = self.wk(x).view(B, L, self.n_heads, self.head_dim)
        v = self.wv(x).view(B, L, self.n_heads, self.head_dim)
        
        # 应用RoPE
        q_rot, k_rot = apply_rotary_pos_emb(q, k, freqs)
        
        # 计算注意力分数
        scores = torch.einsum('bqhd,bkhd->bhqk', q_rot, k_rot) / (self.head_dim ** 0.5)
        attn = torch.softmax(scores, dim=-1)
        out = torch.einsum('bhqk,bkhd->bqhd', attn, v).reshape(B, L, -1)
        return out

4. 关键细节:
- 复数操作优化:使用 `torch.view_as_complex` 和 `torch.polar` 简化旋转操作。
- 维度处理:将多头注意力头维度(`head_dim`)分为二维子空间,适应旋转操作。
- 频率张量缓存:预先计算频率张量 `freqs`,避免重复计算。

3. 完整模型集成

1)RoPE集成到Transformer:

class RotaryTransformer(nn.Module):
    def __init__(self, vocab_size, d_model, n_heads, max_len):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.freqs = get_freqs(d_model // n_heads)
        self.attention = RotaryAttention(d_model, n_heads)
        # 其他层(FFN、LayerNorm等)省略
    
    def forward(self, x):
        x = self.embedding(x)
        freqs = self.freqs[:x.size(1)]  # 动态截取频率
        x = self.attention(x, freqs)
        return x

2)动态位置适应:

# 在训练时根据实际序列长度调整
def get_freqs_for_seq(seq_len, freqs_base):
    return freqs_base[:seq_len]

4. 验证与测试

1)正余弦编码验证:

max_len = 50
d_model = 512
pe = sinusoidal_position_encoding(max_len, d_model)
assert pe.shape == (max_len, d_model)

2)RoPE旋转验证:

# 检查旋转后的内积是否仅依赖相对位置
q = torch.randn(1, 2, 1, 64)  # 位置1和2的查询
k = torch.randn(1, 2, 1, 64)  # 位置1和2的键
freqs = get_freqs(64 // 2)
q_rot, k_rot = apply_rotary_pos_emb(q, k, freqs)
score_same = (q_rot[0,1] @ k_rot[0,1].T).item()  # 位置1与1
score_diff = (q_rot[0,1] @ k_rot[0,0].T).item()  # 位置1与0
assert score_same != score_diff  # 确保位置敏感

5. 总结

编码类型实现关键点
正余弦编码预计算位置编码矩阵,动态切片适配序列长度,通过广播机制高效相加。
旋转编码(RoPE)利用复数操作实现向量旋转,分块处理二维子空间,确保注意力分数反映相对位置。

性能优化建议:
- 缓存频率张量:避免每次前向传播重复计算。
- 混合精度训练:使用 `torch.cuda.amp` 加速RoPE中的复数运算。
- 自定义内核:对旋转操作编写CUDA内核以进一步提升效率。

<think>好的,用户现在问的是位置编码公式,特别是用中文回答。之前他问了Softmax函数的计算,现在转向位置编码,可能是对Transformer模型的学习感兴趣。我需要先回忆一下位置编码的相关知识,尤其是Transformer中的位置编码方法。 首先,用户可能已经了解Transformer模型中的自注意力机制,但自注意力本身不考虑序列顺序,所以需要位置编码来注入位置信息。位置编码分为绝对位置编码和相对位置编码,而Transformer原论文用的是正弦余弦的绝对位置编码。 接下来需要解释公式。原论文中的位置编码使用正弦和余弦函数的组合,频率不同,这样可以模型学习到相对位置信息。公式是PE(pos, 2i) = sin(pos / 10000^(2i/d_model)),PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))。需要详细拆解每个变量的意义,比如pos是位置,i是维度索引,d_model是嵌入维度。 然后要考虑用户的需求。他们可能想知道为什么用这样的公式,以及如何计算。可能需要解释这种编码方式允许模型学习到相对位置,因为正弦函数对相对位置有线性变换的性质。比如,PE(pos + k)可以表示为PE(pos)和某个线性变换的组合,这样模型更容易捕捉到相对位置信息。 此外,可能还需要提到其他位置编码方式,比如可学习的位置嵌入(如BERT使用的),或者相对位置编码(如Transformer-XL中的),但用户可能主要关注原论文的公式,所以重点在正弦余弦编码。 用户可能还关心代码实现,但之前Softmax的例子中有代码,所以这里是否需要给出示例?或者根据用户之前的提问风格,可能希望有步骤说明,比如如何计算某个具体位置编码向量,比如pos=1,d_model=512,那么i从0到255,分别计算sin和cos的值。 另外,需要解释为什么这样设计:正弦和余弦函数的交替使用,使得位置编码可以扩展到任意长度的序列,而无需重新训练。同时,不同频率的正余弦函数可以让模型捕获不同粒度的位置信息,高频(i小的维度)变化快,低频(i大的维度)变化慢。 可能还需要提到位置编码如何与词向量结合,通常是相加而不是拼接,这样可以在不增加维度的情况下注入位置信息。同时,需要说明在Transformer中位置编码是在输入层加到词嵌入上的。 最后,总结位置编码的作用和设计思想,以及在实际应用中的变体。可能还要提及数值稳定性问题,比如归一化处理,或者是否存在其他方法,但原论文中的方法已经很稳定,所以可能不需要深入。 需要检查是否有遗漏点,比如为什么用指数部分10000,是否有其他基数的选择,比如有些工作用不同的基数来调整波长。或者是否有研究指出这种编码方式的有效性。 总之,结构应该是:定义公式,分解变量,具体计算步骤,设计原理(为什么用正余弦,为什么不同频率),与词嵌入的结合方式,变体(如可学习的),代码示例(可选),总结作用和应用。</think>--- ### **位置编码公式详解(以 Transformer 为例)** 位置编码(Positional Encoding)是 Transformer 模型的核心组件之一,用于为序列中的每个位置添加**顺序信息**,弥补自注意力机制无法感知输入顺序的缺陷。以下是其公式、设计原理和实际计算的完整解析: --- ### **1. 原始位置编码公式** 在 Transformer 论文中,位置编码通过**正弦和余弦函数交替组合**生成。对于位置 $pos$ 和维度索引 $i$,其公式为: $$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \\ PE_{(pos, 2i+1)} = \cos\left(\frac{10000^{2i/d_{\text{model}}}}\right) $$ 其中: - $pos$:输入序列中的位置(从 $0$ 开始计数) - $d_{\text{model}}$:词向量的维度(如 512) - $i$:当前计算的位置编码的维度索引($0 \leq i < d_{\text{model}}/2$) --- ### **2. 公式拆解与设计思想** #### **(1) 波长控制** - **分母中的指数项**:$\frac{1}{10000^{2i/d_{\text{model}}}}$ 本质是 $\lambda_i = 10000^{2i/d_{\text{model}}}$,表示波长(Wave Length)。 - **波长随维度变化**:维度索引 $i$ 越大,波长 $\lambda_i$ 越长,对应频率越低(变化越缓慢)。 #### **(2) 交替使用正弦和余弦** - **奇偶维度交替**:偶数维度($2i$)用正弦,奇数维度($2i+1$)用余弦。 - **数学性质**:允许模型通过线性变换捕捉**相对位置关系**。例如,存在线性组合 $PE_{pos+k} = PE_{pos} \cdot M_k$($M_k$ 为变换矩阵),便于模型学习位置偏移。 --- ### **3. 具体计算步骤(以 $pos=1$, $d_{\text{model}}=4$ 为例)** 假设词向量维度为 4,位置 $pos=1$,计算其位置编码: 1. **确定维度索引范围**:$i$ 取 $0$ 和 $1$(因为 $d_{\text{model}}/2 = 2$) 2. **计算每个维度的值**: - **当 $i=0$**: $$ PE_{(1,0)} = \sin\left(\frac{1}{10000^{0/4}}\right) = \sin(1) \approx 0.8415 \\ PE_{(1,1)} = \cos\left(\frac{1}{10000^{0/4}}\right) = \cos(1) \approx 0.5403 $$ - **当 $i=1$**: $$ PE_{(1,2)} = \sin\left(\frac{1}{10000^{2/4}}\right) = \sin\left(\frac{1}{100}\right) \approx 0.0100 \\ PE_{(1,3)} = \cos\left(\frac{1}{10000^{2/4}}\right) = \cos\left(\frac{1}{100}\right) \approx 0.9999 $$ 3. **最终位置编码向量**: $$ PE_{1} = [0.8415, 0.5403, 0.0100, 0.9999] $$ --- ### **4. 位置编码的可视化** 将位置编码矩阵绘制为热力图,可观察到: - **纵向(位置方向)**:相邻位置编码值平滑变化。 - **横向(维度方向)**:低维度(左侧)高频振荡,高维度(右侧)低频变化。 ![Positional Encoding Heatmap](https://jalammar.github.io/images/t/transformer_positional_encoding_heatmap.png) --- ### **5. 为什么选择正弦/余弦函数?** #### **(1) 相对位置建模** - **线性关系**:对于固定偏移 $k$,存在线性变换 $M_k$,使得 $PE_{pos+k} = PE_{pos} \cdot M_k$。模型可通过学习 $M_k$ 捕捉相对位置。 - **方向感知**:正弦函数对正向和反向偏移敏感,增强序列方向性。 #### **(2) 长度外推性** - 正弦/余弦函数定义域为全体实数,模型可处理**比训练时更长的序列**(尽管实际性能可能下降)。 #### **(3) 与词嵌入的兼容性** - **简单相加**:位置编码与词嵌入**直接相加**($X = \text{Embedding} + PE$),而非拼接,避免增加维度。 --- ### **6. 代码实现** ```python import numpy as np def positional_encoding(pos, d_model): pe = np.zeros(d_model) for i in range(d_model // 2): denominator = 10000 ** (2 * i / d_model) pe[2*i] = np.sin(pos / denominator) pe[2*i+1] = np.cos(pos / denominator) return pe # 示例:生成位置 1 的编码(d_model=4) print(positional_encoding(1, 4)) # 输出: [0.8415, 0.5403, 0.0100, 0.9999] ``` --- ### **7. 位置编码的变体** | **类型** | **方法** | **特点** | |------------------|-------------------------------------------------------------------------|--------------------------------------| | 可学习的位置嵌入 | 随机初始化一个矩阵,随训练更新(如 BERT) | 灵活但可能过拟合短序列 | | 相对位置编码 | 编码相对距离而非绝对位置(如 Transformer-XL) | 更擅长处理长序列 | | RoPE (Rotary) | 通过旋转矩阵将位置信息融入注意力计算(如 LLaMA) | 保持相对位置的模长不变,数学性质更优 | --- ### **8. 总结** | **关键点** | **说明** | |--------------------|--------------------------------------------------------------------------| | 核心目标 | 为自注意力机制补充序列顺序信息 | | 波长设计 | 不同维度对应不同频率,捕获多粒度位置特征 | | 相加而非拼接 | 保持输入维度不变,与词嵌入直接融合 | | 长度外推性 | 理论上支持无限长序列,但实际需结合模型结构优化 | | 与注意力机制协同 | 通过 Query-Key 的点积操作,隐式学习位置关系 | 位置编码是 Transformer 突破 RNN 序列建模瓶颈的关键设计之一,其数学形式简洁却深刻,平衡了表达能力与计算效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值