位置编码(Positional Encoding)

一句话概述

位置编码(Positional Encoding)是Transformer架构中解决"词序信息缺失"问题的关键组件。自注意力机制本身是"位置无关"的——它只关心词与词之间的语义相关性,不关心词在句子中的位置。如果不加位置信息,"我爱你"和"你爱我"在自注意力眼中是完全相同的。位置编码通过为每个位置生成一个唯一的向量(与词向量相加),将位置信息注入模型。原始Transformer使用正弦/余弦函数生成固定位置编码,具有优雅的数学性质——模型可以自然地外推到比训练时更长的序列。BERT使用可学习的位置嵌入(Learned Positional Embedding),让模型在训练中自己学习位置表示。无论哪种方式,位置编码都是让Transformer理解"词序"的不可或缺的组件。

💡 核心要点:①自注意力是位置无关的,必须通过位置编码注入位置信息 ②正弦位置编码使用sin/cos函数,不同频率对应不同维度,具有外推能力 ③可学习位置嵌入(如BERT)让模型学习位置表示,但无法外推到更长序列 ④位置编码与词向量相加后输入Transformer,使得模型同时感知"语义"和"位置"

教学与演示

一、为什么需要位置编码:自注意力的"位置盲区"

是什么(定义):位置编码(Positional Encoding, PE)是为序列中每个位置生成一个表示向量,将其与对应的词向量相加,从而使模型能够感知词在序列中的位置。没有位置编码,自注意力机制对输入序列的排列是不变的(permutation invariant)——将"猫追老鼠"重排为"老鼠追猫"后,自注意力计算出的结果是完全相同的(只是行顺序不同)。

大白话 自注意力是一个"脸盲"——它只关心"谁和谁有关",不关心"谁排在第几个"。就像你给一个脸盲的人看一张班级合影,他能认出"这个人和那个人关系好"(因为他们互动多),但说不出"张三站在第几排第几个"。位置编码就像是给每个人身上贴了一个"坐标标签"——"第1排第3个"、"第2排第5个"——这样脸盲的人也能知道每个人站在哪里了。

为什么(原理):自注意力的位置不变性可以从数学上证明。自注意力的核心操作是QK^T·V,其中Q=XW^Q, K=XW^K, V=XW^V。如果对输入X的行进行置换(即改变词序),Q、K、V的行也对应置换,但QK^T的结果(除了行列顺序)保持不变——因为点积操作不关心向量的排列顺序。这意味着自注意力完全无法区分"狗咬人"和"人咬狗",必须通过位置编码来补救。

import numpy as np

# 位置编码的必要性:演示自注意力的位置不变性
# 证明没有位置编码,"猫追老鼠"和"老鼠追猫"是相同的

class SelfAttentionWithoutPosition:
    def __init__(self, d_model=6, d_k=3):
        np.random.seed(42)
        self.W_Q = np.random.randn(d_model, d_k) * 0.5
        self.W_K = np.random.randn(d_model, d_k) * 0.5
        self.W_V = np.random.randn(d_model, d_k) * 0.5

    def forward(self, X):
        Q = X @ self.W_Q
        K = X @ self.W_K
        V = X @ self.W_V
        d_k = K.shape[1]
        scores = Q @ K.T / np.sqrt(d_k)
        scores = scores - np.max(scores, axis=1, keepdims=True)
        attn = np.exp(scores) / np.sum(np.exp(scores), axis=1, keepdims=True)
        return attn @ V, attn


sa = SelfAttentionWithoutPosition(d_model=6, d_k=3)

# 句子1:"猫 追 老鼠"(正常语序)
X1 = np.array([
    [0.9, 0.1, 0.2, 0.1, 0.3, 0.1],  # "猫"
    [0.2, 0.8, 0.3, 0.1, 0.5, 0.2],  # "追"
    [0.1, 0.2, 0.9, 0.1, 0.1, 0.3],  # "老鼠"
])

# 句子2:"老鼠 追 猫"(颠倒语序)
# 只是交换了第1行和第3行
X2 = np.array([
    [0.1, 0.2, 0.9, 0.1, 0.1, 0.3],  # "老鼠"
    [0.2, 0.8, 0.3, 0.1, 0.5, 0.2],  # "追"
    [0.9, 0.1, 0.2, 0.1, 0.3, 0.1],  # "猫"
])

output1, attn1 = sa.forward(X1)
output2, attn2 = sa.forward(X2)

print("=== 自注意力的位置不变性 ===\n")

print("句子1:'猫 追 老鼠'")
print("注意力权重矩阵:")
print(attn1)

print("\n句子2:'老鼠 追 猫'(词序颠倒)")
print("注意力权重矩阵:")
print(attn2)

print("\n观察:")
print("- 句子1的注意力矩阵[0,1](猫对追) = 句子2的注意力矩阵[2,1](猫对追)")
print("  值完全相同!因为词向量相同,只是位置不同")
print(f"  attn1[0,1] = {attn1[0,1]:.4f}")
print(f"  attn2[2,1] = {attn2[2,1]:.4f}")
print("\n结论:自注意力完全无法区分词序!必须加位置编码。")
位置编码的注入方式\(X_{\text{input}} = X_{\text{embedding}} + PE\)
大白话 位置编码就像"给每个词贴一个坐标标签"。词向量告诉模型"这是什么词"(语义),位置编码告诉模型"这个词在第几个位置"(位置)。把两者相加,就得到了一个同时知道"什么词"和"在哪里"的综合表示。就像快递单上既有"收货人姓名"(词向量)又有"收货地址"(位置编码),两者缺一不可。

什么用(应用):位置编码是所有Transformer模型(BERT、GPT、T5等)的必要组件。它使模型能够理解词序,从而正确理解句子含义。在机器翻译中,位置编码帮助模型区分"源语言词序"和"目标语言词序"(如英语的SVO和日语的SOV);在语言模型中,位置编码让模型知道下一个词应该生成在第几个位置;在文本分类中,位置编码帮助模型理解"开头"和"结尾"的语义差异。

哪些坑(缺点):位置编码是Transformer中一个活跃的研究问题。正弦编码虽然优雅,但外推能力有限——当推理序列长度远超训练长度时,模型性能会下降。可学习位置嵌入虽然灵活,但完全无法外推到更长序列。此外,位置编码的"加法"方式意味着位置信息和语义信息是线性混合的,这种混合方式是否最优仍有争议——近年来出现了旋转位置编码(RoPE)、ALiBi等新的位置编码方法,试图解决这些问题。

二、正弦位置编码:sin/cos的数学之美

是什么(定义):正弦位置编码(Sinusoidal Positional Encoding)是原始Transformer论文中使用的位置编码方法。对于位置pos和维度i,编码值为:PE(pos, 2i) = sin(pos / 10000^{2i/d_model}),PE(pos, 2i+1) = cos(pos / 10000^{2i/d_model})。偶数维度使用正弦,奇数维度使用余弦,不同维度使用不同的频率(从2π到10000·2π)。这种设计使得每个位置产生一个唯一的编码向量,且不同位置的编码之间存在线性关系。

大白话 正弦位置编码就像"给每个位置分配一个独特的波形"。想象一个乐队有512个乐器(对应512维),每个乐器演奏不同频率的音符。第1个位置时,所有乐器同时演奏一个特定和弦;第2个位置时,所有乐器演奏另一个和弦……每个位置的和弦都是独一无二的。更重要的是,从第1个位置的和弦可以"推导"出第5个位置的和弦——这就是正弦编码的线性关系属性。

为什么(原理):正弦位置编码的数学性质非常优雅。第一,它是有界的——sin和cos的值都在[-1,1]之间,不会像可学习嵌入那样产生任意大的值。第二,它允许模型外推——由于编码是连续函数,理论上可以为任意位置计算编码。第三(也是最重要的),存在线性关系PE(pos+k) = f(PE(pos))——这意味着模型可以通过学习一个线性变换来"推断"相对位置关系,这有助于模型理解"距离"的概念。

import numpy as np

# 正弦位置编码的完整实现
# 演示其数学性质和可视化

def sinusoidal_positional_encoding(seq_len, d_model):
    """
    生成正弦位置编码
    seq_len: 序列长度
    d_model: 编码维度
    """
    # 初始化位置编码矩阵
    PE = np.zeros((seq_len, d_model))

    # 每个位置的索引:0, 1, 2, ..., seq_len-1
    position = np.arange(seq_len)[:, np.newaxis]  # 形状: (seq_len, 1)

    # 每个维度的缩放因子:10000^(2i/d_model)
    # i 是维度索引,从0到d_model/2-1
    div_term = np.exp(
        np.arange(0, d_model, 2) * (-np.log(10000.0) / d_model)
    )  # 形状: (d_model/2,)

    # 偶数维度(0, 2, 4, ...):使用正弦
    PE[:, 0::2] = np.sin(position * div_term)
    # 奇数维度(1, 3, 5, ...):使用余弦
    PE[:, 1::2] = np.cos(position * div_term)

    return PE


# 生成位置编码并可视化
seq_len = 10  # 10个位置
d_model = 16  # 16维编码

PE = sinusoidal_positional_encoding(seq_len, d_model)

print("=== 正弦位置编码 ===\n")

print(f"位置编码矩阵形状: {PE.shape}({seq_len}个位置,{d_model}维)\n")

# 展示前几个位置的前几个维度
print("前5个位置的前8个维度:")
print("位置  |", end="")
for d in range(8):
    print(f"  dim{d} ", end="")
print()
for pos in range(5):
    print(f"  {pos}   |", end="")
    for d in range(8):
        print(f" {PE[pos, d]: .3f}", end="")
    print()

# 验证线性关系:PE(pos+k)可以通过PE(pos)的线性变换得到
print("\n\n=== 正弦编码的线性关系属性 ===")
print("验证:PE(pos+1)可以通过PE(pos)的线性变换得到\n")

# 对于任意两个位置pos和pos+k,存在一个矩阵M使得 PE(pos+k) ≈ M·PE(pos)
# 这是正弦编码最重要的数学性质
pos = 3
print(f"位置{pos}的编码(前6维):{PE[pos, :6]}")
print(f"位置{pos+1}的编码(前6维):{PE[pos+1, :6]}")

# 正弦的加法公式:sin(a+b) = sin(a)cos(b) + cos(a)sin(b)
# 这意味着PE(pos+k)可以通过PE(pos)和cos/sin的线性组合得到
print("\n由于sin/cos的加法公式,PE(pos+k)和PE(pos)之间存在线性关系")
print("这使得模型可以轻松学习相对位置信息!")

# 展示不同频率的波形
print("\n\n=== 不同维度的频率变化 ===")
print("维度0(高频,快速变化):")
for pos in range(10):
    print(f"  pos={pos}: {PE[pos, 0]:.4f}")
print("维度8(低频,缓慢变化):")
for pos in range(10):
    print(f"  pos={pos}: {PE[pos, 8]:.4f}")
print("\n高频维度区分相邻位置,低频维度区分远距离位置")
print("这种多频率设计使模型能同时感知局部和全局位置信息")
正弦位置编码的定义\(PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right), \quad PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right)\)
大白话 正弦位置编码就像"给每个位置一个独特的电话号码"。每个电话号码(位置编码)由多个"音调"(维度)组成——有的音调很高(高频维度,快速变化),有的音调很低(低频维度,缓慢变化)。高音调帮助你区分"第5个和第6个"这种相邻位置,低音调帮助你区分"开头和结尾"这种远距离位置。所有的音调组合在一起,就形成了独一无二的"位置签名"。

什么用(应用):正弦位置编码是原始Transformer和许多后续模型(如Transformer-XL)使用的位置编码方式。它的最大优势是外推能力——训练时使用512长度的序列,推理时可以直接处理1024长度的序列(虽然效果可能下降)。在需要处理变长序列的场景(如流式语音识别、在线翻译),正弦编码的这种灵活性非常有用。此外,正弦编码是确定性的(不需要学习),因此在某些低资源场景下比可学习嵌入更稳定。

哪些坑(缺点):正弦编码的外推能力有限——当推理序列长度数倍于训练长度时,模型性能会显著下降。这是因为模型从未见过"大位置"对应的编码模式。此外,正弦编码的"加法"方式意味着位置信息和语义信息是线性混合的,这种混合方式可能不是最优的。近年来,旋转位置编码(RoPE)通过将位置信息编码为向量旋转(而非加法),在多个基准上取得了更好的效果,已成为LLaMA、Qwen等主流模型的选择。

三、可学习位置嵌入与旋转位置编码

是什么(定义):除了正弦位置编码,还有两种主流的位置编码方式。可学习位置嵌入(Learned Positional Embedding)——将位置编码视为模型参数,在训练中通过反向传播学习,BERT和GPT系列都使用这种方式。旋转位置编码(Rotary Position Embedding, RoPE)——通过将Q和K向量按位置进行旋转,将相对位置信息直接编码到注意力计算中,成为LLaMA、Qwen等现代大模型的首选。

大白话 可学习位置嵌入就像"靠经验记住位置"。模型通过大量训练,自己学会了"第1个位置大概长这样"、"第512个位置大概长那样"。好处是灵活——模型可以自适应地学习适合任务的位置表示;坏处是死板——训练时只见过512个位置,遇到第513个位置就懵了。RoPE则像是在Q和K之间加了一个"角度差"——两个词之间的距离越远,它们的Q和K之间的"角度"就越大,注意力分数自然就降低。这种方式天然编码了相对位置关系。

为什么(原理):RoPE的核心思想是将绝对位置编码转化为相对位置编码。具体来说,它通过一个旋转矩阵R(pos)将Q和K向量按位置pos旋转一个角度,使得Q_pos^T·K_pos'只依赖于相对位置(pos-pos'),而不是绝对位置。RoPE的数学公式为:q_pos = R(pos)·q, k_pos = R(pos)·k,其中R(pos)是一个分块对角旋转矩阵。这样,注意力分数中自然包含了位置信息,且具有更好的外推能力。

import numpy as np

# 旋转位置编码(RoPE)的简化实现
# 演示RoPE如何将位置信息编码为向量旋转

def rope_rotate(x, position, dim):
    """
    对向量x应用RoPE旋转
    x: 输入向量,形状 (d,)
    position: 位置索引
    dim: 向量维度
    """
    # 为每个维度对计算旋转角度
    # theta_i = 10000^(-2i/dim),i = 0, 1, ..., dim/2-1
    theta = 1.0 / (10000 ** (np.arange(0, dim, 2) / dim))

    # 旋转角度 = position * theta
    angles = position * theta

    # 对每一对维度应用2D旋转
    x_rotated = x.copy()
    for i in range(0, dim, 2):
        cos_a = np.cos(angles[i // 2])  # 旋转角度的余弦
        sin_a = np.sin(angles[i // 2])  # 旋转角度的正弦
        # 2D旋转公式:
        # x'_i     = x_i * cos(θ) - x_{i+1} * sin(θ)
        # x'_{i+1} = x_i * sin(θ) + x_{i+1} * cos(θ)
        x_rotated[i] = x[i] * cos_a - x[i + 1] * sin_a
        x_rotated[i + 1] = x[i] * sin_a + x[i + 1] * cos_a

    return x_rotated


def compute_rope_attention(Q_raw, K_raw, seq_len, d_k):
    """
    使用RoPE计算注意力
    关键:RoPE修改了Q和K,使得Q_pos^T K_pos' 只依赖于相对位置
    """
    Q_rope = np.zeros_like(Q_raw)
    K_rope = np.zeros_like(K_raw)

    # 对每个位置应用旋转
    for pos in range(seq_len):
        Q_rope[pos] = rope_rotate(Q_raw[pos], pos, d_k)
        K_rope[pos] = rope_rotate(K_raw[pos], pos, d_k)

    # 计算注意力分数
    scores = Q_rope @ K_rope.T / np.sqrt(d_k)
    return scores, Q_rope, K_rope


# 演示
np.random.seed(42)
seq_len = 5
d_k = 8

# 所有位置使用相同的Q和K(模拟"没有任何位置信息"的情况)
Q_raw = np.random.randn(seq_len, d_k) * 0.5
K_raw = np.random.randn(seq_len, d_k) * 0.5

# 不使用RoPE的注意力分数(位置无关)
scores_no_rope = Q_raw @ K_raw.T

# 使用RoPE的注意力分数(位置相关)
scores_rope, Q_rope, K_rope = compute_rope_attention(Q_raw, K_raw, seq_len, d_k)

print("=== 旋转位置编码(RoPE)演示 ===\n")

print("不使用RoPE的注意力分数矩阵(位置无关):")
print(np.round(scores_no_rope, 3))

print("\n使用RoPE的注意力分数矩阵(位置相关):")
print(np.round(scores_rope, 3))

print("\n观察:")
print("- 不使用RoPE时,分数矩阵没有明显的位置模式")
print("- 使用RoPE后,相邻位置之间的分数通常更高(因为有位置衰减)")
print("- RoPE让注意力分数自然包含了相对位置信息!")

# 验证RoPE的相对位置性质
print("\n=== 验证RoPE的相对位置性质 ===")
print("RoPE的核心性质:Q_pos^T K_{pos+k} 只依赖于k(相对位置),而不是pos(绝对位置)")
pos1, pos2 = 1, 4
print(f"位置{pos1}的Q和位置{pos2}的K的点积: {np.dot(Q_rope[pos1], K_rope[pos2]):.4f}")
print(f"这反映了位置{pos1}和{pos2}之间的相对距离{pos2-pos1}")
RoPE的旋转矩阵\(R(pos) = \begin{bmatrix} \cos(pos\theta_0) & -\sin(pos\theta_0) & 0 & 0 & \cdots \\ \sin(pos\theta_0) & \cos(pos\theta_0) & 0 & 0 & \cdots \\ 0 & 0 & \cos(pos\theta_1) & -\sin(pos\theta_1) & \cdots \\ 0 & 0 & \sin(pos\theta_1) & \cos(pos\theta_1) & \cdots \\ \vdots & \vdots & \vdots & \vdots & \ddots \end{bmatrix}\)
大白话 RoPE就像"给每个词装了一个指南针"。没有RoPE时,两个词之间的注意力只看"内容相似度";有了RoPE后,每个词的"指南针"指针会根据它的位置转动一个角度。两个词的"指南针"如果指向相近的方向(相对位置小),它们就更可能互相注意;如果方向差很多(相对位置大),注意力自然降低。指南针的转动角度是连续可算的,所以即使遇到了训练时没见过的大位置,也能合理计算。

什么用(应用):RoPE已成为现代大语言模型的主流位置编码方式。LLaMA、LLaMA 2、Qwen、ChatGLM、Mistral等都使用RoPE。RoPE的主要优势包括:更好的长序列外推能力(通过NTK-aware scaling等方法可以扩展到训练长度的数倍)、将位置信息直接编码到注意力计算中(而非加法),以及更好的理论性质。在大模型时代,RoPE几乎已经成为位置编码的"默认选择"。

哪些坑(缺点):RoPE的计算开销略高于正弦编码(因为需要对Q和K逐位置旋转),但现代GPU可以高效处理。RoPE的外推仍然有限——当推理序列长度远超训练长度时,需要通过插值或缩放技术来调整旋转频率。此外,RoPE将位置信息编码到旋转中,使得位置编码不再与词向量"分离",这虽然在大多数场景下是优势,但在某些需要灵活调整位置信息的任务中可能不够灵活。

概念关系图谱

概念核心含义与AI的关系关联概念
位置编码(PE)为序列中每个位置生成唯一表示向量使Transformer感知词序,理解句子结构词嵌入、自注意力
正弦位置编码使用sin/cos函数生成固定位置编码原始Transformer方案,具有外推能力傅里叶特征、频率
可学习位置嵌入将位置编码作为模型参数学习BERT/GPT使用,灵活但无法外推嵌入层、参数学习
RoPE(旋转位置编码)通过向量旋转将相对位置编码进注意力LLaMA等现代大模型的首选旋转矩阵、相对位置
相对位置编码直接编码位置之间的相对距离对长序列更自然相对距离、偏置矩阵
外推(Extrapolation)在比训练时更长的序列上推理长文本处理的关键能力序列长度、泛化

重点答疑

Q1: 正弦编码为什么使用sin和cos组合,而不是只用sin?

只用sin会造成一个问题:PE(pos, i) = sin(pos·ω_i)中,对于不同的pos,sin值可能相同(因为sin是周期函数)。例如sin(0)=sin(π)=0。但如果同时使用sin和cos作为一对维度,位置(pos, i)和(pos, i+1)的编码值(sin(pos·ω), cos(pos·ω))是唯一的——因为(sin(θ), cos(θ))在单位圆上唯一确定一个角度θ。这就是为什么偶数维度用sin、奇数维度用cos——它们成对工作,确保每个位置编码的唯一性。

Q2: 位置编码为什么用"加法"而不是"拼接"?

这是一个历史设计选择,但有其合理性。如果使用拼接,输入维度会翻倍(d_model + d_model = 2d_model),后续所有层的参数量都会翻倍,计算量大幅增加。加法保持了维度不变,且在实践中被证明是有效的。从信息论角度,加法可以理解为"位置信息调制了语义信息"——词向量在位置编码的"指引"下被微调,而不是完全分离。

Q3: RoPE和正弦编码的核心区别是什么?

正弦编码是"绝对位置编码"——PE(pos)是一个固定的向量,与输入内容无关,通过加法注入位置信息。RoPE是"相对位置编码"——它不生成独立的PE向量,而是通过旋转Q和K向量,使注意力分数自然包含相对位置关系。RoPE的核心优势在于:它的位置信息直接编码在注意力计算中(Q^T K),而不是在输入中,这使得位置信息与语义信息有更自然的交互。

章节单词汇总

英文音标术语/释义
Positional Encoding/pəˈzɪʃənəl ɪnˈkoʊdɪŋ/位置编码,为序列位置生成唯一向量的方法
Sinusoidal/ˌsaɪnəˈsɔɪdəl/正弦的,使用sin/cos函数生成位置编码
RoPE (Rotary Position Embedding)/roʊp/旋转位置编码,通过向量旋转编码相对位置
Absolute Position/ˈæbsəluːt pəˈzɪʃən/绝对位置,词在序列中的具体索引
Relative Position/ˈrelətɪv pəˈzɪʃən/相对位置,两个词之间的位置差
Extrapolation/ɪkˌstræpəˈleɪʃən/外推,在训练范围外的序列长度上推理
Frequency/ˈfriːkwənsi/频率,正弦编码中不同维度的变化速度
Permutation Invariant/ˌpɜːrmjuˈteɪʃən ɪnˈveriənt/排列不变性,自注意力不感知词序的性质
Embedding/ɪmˈbedɪŋ/嵌入,将离散符号映射到连续向量空间
Unit Circle/ˈjuːnɪt ˈsɜːrkəl/单位圆,sin/cos编码的几何解释

面试练习

Q1 [单选] 为什么Transformer需要位置编码?

  • A. 为了增加模型的参数量
  • B. 因为自注意力机制是位置无关的,不感知词序
  • C. 为了加速训练
  • D. 为了防止过拟合
解答:自注意力机制对输入序列的排列是不变的(permutation invariant),无法区分"我爱你"和"你爱我"。位置编码注入位置信息,使模型能感知词序。

Q2 [单选] 原始Transformer使用的位置编码方式是什么?

  • A. 可学习位置嵌入
  • B. 相对位置编码
  • C. 正弦/余弦位置编码
  • D. RoPE
解答:原始Transformer论文使用正弦/余弦位置编码,偶数维度用sin,奇数维度用cos,不同维度使用不同频率。

Q3 [单选] 正弦位置编码中,低频维度(频率低)主要用于区分什么?

  • A. 相邻位置
  • B. 远距离位置
  • C. 所有位置
  • D. 首尾位置
解答:低频维度的波长很长,在一个序列范围内变化很小,因此主要用于区分远距离位置(如开头vs结尾)。高频维度波长短,快速变化,用于区分相邻位置。

Q4 [多选] 关于可学习位置嵌入,以下哪些说法是正确的?

  • A. 位置编码作为模型参数,通过训练学习
  • B. BERT和GPT使用可学习位置嵌入
  • C. 可以自然地外推到比训练时更长的序列
  • D. 最大序列长度在训练时确定,无法动态扩展
  • E. 可学习位置嵌入不需要任何训练
解答:BERT和GPT使用可学习位置嵌入,通过训练学习。但它有一个致命缺陷:最大序列长度在初始化时固定(如512),无法外推到更长的序列(第513个位置没有对应的嵌入向量)。

Q5 [单选] RoPE(旋转位置编码)的核心思想是什么?

  • A. 将位置编码与词向量拼接
  • B. 通过旋转Q和K向量,将相对位置信息编码到注意力计算中
  • C. 使用更大的位置编码矩阵
  • D. 完全移除位置编码
解答:RoPE通过旋转矩阵R(pos)旋转Q和K向量,使得Q_pos^T K_{pos'}只依赖于相对位置(pos-pos')。这使位置信息直接编码到注意力分数中,具有更好的外推能力。

Q6 [单选] 正弦位置编码公式中,10000这个常数的含义是什么?

  • A. 序列的最大长度
  • B. 频率基数,决定了不同维度波长的范围
  • C. 词表大小
  • D. 学习率
解答:10000是频率基数(base),不同维度的波长为2π·10000^{2i/d_model}到10000·2π。这个值的选择使得最低频率的波长约为10000×2π,远大于典型序列长度,确保编码的唯一性。

Q7 [多选] 以下哪些是现代大模型使用的位置编码方式?

  • A. RoPE(LLaMA、Qwen)
  • B. 可学习位置嵌入(GPT系列)
  • C. 正弦位置编码(所有现代模型)
  • D. ALiBi(部分模型使用线性偏置代替位置编码)
  • E. 不使用任何位置编码
解答:LLaMA和Qwen使用RoPE,GPT系列使用可学习位置嵌入,ALiBi通过线性衰减偏置来编码位置信息。正弦编码主要用于原始Transformer,现代大模型很少使用。所有Transformer都需要某种形式的位置编码。

Q8 [单选] 位置编码通常与词向量如何结合?

  • A. 拼接(Concatenation)
  • B. 加法(Addition)
  • C. 乘法(Multiplication)
  • D. 替换(Replacement)
解答:位置编码与词向量通过逐元素加法结合:X_input = X_embedding + PE。这保持了维度不变,且实践中证明有效。

Q9 [多选] 关于正弦位置编码的数学性质,以下哪些是正确的?

  • A. PE(pos+k)可以通过PE(pos)的线性变换得到
  • B. 编码值在[-1, 1]之间有界
  • C. 不同位置的编码向量是唯一的
  • D. 所有维度的频率相同
  • E. 可以计算任意位置的编码(可外推)
解答:正弦编码具有线性变换性质(因sin/cos加法公式),值在[-1,1]有界,不同位置编码唯一,可外推。不同维度使用不同频率——从高频(短波长)到低频(长波长)。

Q10 [单选] 在Transformer中,位置编码是在哪个阶段加入的?

  • A. 在注意力计算之后
  • B. 在前馈网络之后
  • C. 在词嵌入之后、输入第一个Transformer层之前
  • D. 在输出层之前
解答:位置编码在词嵌入之后、输入第一个Transformer层之前与词向量相加。这样后续所有层都能感知位置信息。有些变体(如Transformer-XL)在每一层都重新注入位置信息。