对 Transformer 中位置编码 Position Encoding 的理解

文章详细探讨了Transformer中使用位置编码的不同方法,包括Sinusoidal位置编码、RoPE(旋转位置编码)和ALiBi(注意力带有线性偏置的输入长度外推),强调了它们在捕捉序列顺序信息和可解释性方面的特点,以及在大语言模型中的可延长性需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

什么是位置编码 Position Encoding

一、将绝对位置编码加在 Transformer 的输入端 (Sinusoidal 位置编码或可学习位置编码)

二、将绝对位置编码乘在 q k v (RoPE 位置编码)

三、将相对位置编码加在注意力权重   (ALiBi 位置编码)


什么是位置编码 Position Encoding

Transformer 的本质是一种全局注意力机制,它将输入序列中任意两个点之间的距离变为 1,可以实现长时或长距离的注意力机制。但这样带来的问题是,输入序列丧失了顺序信息,而对 NLP 来说输入字符的顺序是十分重要的,所以就需要位置编码 Position Encoding 来帮助 Transformer 感知输入序列的顺序信息。关于 Transformer 的基本原理和计算过程可以参考。基于位置编码 Position Encoding 的加载位置,可以将位置编码 Postion Encoding 分为:

  1. 如上图位置 1,将绝对位置编码加在 Transformer 的输入端,典型代表是 Sinusoidal 位置编码和可学习位置编码;
  2. 如上图位置 2,将绝对位置编码乘在 q k v ,典型代表是 RoPE 位置编码;
  3. 如上图位置 3,将相对位置编码加在注意力权重 q^{T}k,典型代表是 ALiBi 位置编码。

绝对位置编码指的是可以施加在某向量上的位置编码向量或者位置编码矩阵,在位置编码向量或者位置编码矩阵中包含位置或序列信息。相对位置编码并不是具体的位置编码向量或者位置编码矩阵,而是直接把基于两个位置的相对距离计算的权重直接加在注意力系数上。

一般而言,对于绝对位置编码的要求是:

  • 位置编码中的数值必须是有界的,不能类似于 1 .. n 的形式,这样不利于模型训练
  • 不同位置编码之间可以通过线性变换进行转换,且线性变换矩阵仅仅与两位置之间的相对位置有关,这样便于模型的学习
  • 不同位置编码之间的内积需要具有衰减特性,以体现距离近的位置相关度更强,距离远的位置相关度更弱

相对位置编码不存在具体数值,所以对其要求仅仅是:

  • 不同位置之间的编码值仅仅与两位置之间的相对距离有关,且这个编码值需要具有衰减特性,以体现距离近的位置相关度更强,距离远的位置相关度更弱

位置编码的原理是什么,在 Transfomer 计算中如何起作用,这个很难说清楚,深度学习本就具有不可解释性,更多的是通过大量实验尝试出来的结果。但是从可解释性上来说,从1 到 2 到 3,可解释性逐步变强。

在大语言模型中,对位置编码 Position Encoding 有一个新的要求,就是可延长性。所谓可延长性指的是训练时模型只看到了 1 .. n 位置的位置编码,但测试时可能出现 n+1 或者更远位置的位置编码。想要让位置编码具有可延长性,就需要让模型仅仅学习位置编码的相对特征而不是绝对特征,即任意两个位置,只要距离相同,那对加权系数的影响应该相同,从 1 到 2 到 3,理论上位置编码的可延长性逐步增强。但当前在大语言模型中,方案 2 的应用更广泛。

但实际上,深度学习模型是不可解释的,是没有严格理论逻辑的,很多都是总结的规律而已,在实际应用中可能需要多次尝试,选取最佳方案。

一、将绝对位置编码加在 Transformer 的输入端 (Sinusoidal 位置编码或可学习位置编码)

对于绝对位置编码最容易想到的就是二进制编码,假设输入特征的维度是 3,那二进制编码如下图所示:

可以获得 8 个位置的绝对位置编码,但此种编码方式不满足绝对位置编码要求中的后两点,所以实际使用效果并不好。

当前最常用的绝对位置编码方式为 Sinusoidal 编码,它满足所有要求。位置 t 处的绝对位置编码为:

其中,\omega _{k}=\frac{1}{10000^{\frac{2k}{d}}} ,d 是特征长度,k 取值范围是 1 ~ d/2。可以看出来上面位置编码每两个为一组,对于长度为 d 的位置编码,总共包含 d/2 组 sin 与 cos 的组合,每一组组合中的角速度是相同的,从上往下角速度逐步变小,最后一组组合的角速度为 1/10000,足以保证有 2*3.1415*10000  个绝对位置编码而不重复。可以把这个位置编码想象成一个包含 d/2 个指针的时钟,一个 sin 与 cos 组合构成一根指针,总共 d/2 根指针,这些指针的旋转角速度,从上往下逐步减小,就像从秒针到时针变化一样,也像十进制编码一样从低位到高位变化速度逐步减慢,在最下面的指针完成一圈旋转之前,不会出现重复的位置编码。

Sinusoidal 位置编码的使用方式是直接加在经过 Input Embedding 后的特征向量上,很多人会有疑问,为何选择相加而不是 concat,感觉相加会存在信息损失。但如上图所示,当 Q 与 A 完全相等时,相加和 concat 的效果完全一样。当 Q 与 A 不相等时,如果 head 只有一个,那通过调整 p 的值也可以做到完全等效。当 Q 与 A 不相等时,且 head 有多个时,相加与 concat 相比只是损失了很小一部分自由度,但是却换来了计算速度和内存使用上的优势,且很多时候过多的自由度有可能会增加训练难度、增加过拟合风险和降低模型性能,所以这里一般使用相加而不是 concat。由于深度学习模型本质上其实就是线性特征提取加非线性激活函数,所以最终 q 还是求和得到的,即使相加与 concat 不是完全等效,但最终还是归于求和操作。

接下来分析一下绝对位置编码的原理,绝对位置编码是通过对 q 和 k 施加影响,进而影响 q^{T}k 这个注意力权重系数。所以如上图所示,当把绝对位置编码加在 Input Embedding 后的特征向量上,那对 q^{T}k 的影响很复杂,难以分析 p 对这个注意力权重系数造成了什么影响,所以可解释性很低。

参考中,通过实际计算证明 Sinusoidal 是存在衰减特性的。从参考中可以知道,不同位置的位置编码可以通过一个仅与两位置相对距离相关的旋转矩阵线性变换得到,且不同位置编码的内积满足衰减性,所以 Sinusoidal 位置编码满足所有 3 点要求。

但 Sinusoidal 位置编码的可延长性并不好,推测原因可能是如上公式所示,当 x1 x2 Q K 固定时,q^{T}k 的结果并不仅仅取决于 p1 和 p2 的相对位置,还取决于其绝对位置。

很多应用下,直接让绝对位置编码变为可学习的参数,虽然效果也很好,但这个的可解释性就更低了。

二、将绝对位置编码乘在 q k v (RoPE 位置编码)

RoPE (Rotary Position Embedding) 旋转式位置编码是通过将一个向量旋转某个角度,为其赋予位置信息。它本质上与 Sinusoidal 位置编码是一样的,都是通过旋转角度来赋予特征向量以位置信息。有时也称 RoPE 为相对位置编码。

还是以从上到下的指针分布举例,对于 Sinusoidal 位置编码,其生成位置编码向量的过程就是,首先让所有指针都从 0 位置开始旋转,每个编码的序列位置对应一个旋转时长,只是从上到下的指针旋转速度不一样,越往下速度越慢。对于 RoPE,它并不会真的生成一个位置编码向量,而是生成一个位置编码旋转矩阵,并作用于 q 和 k 上,q 和 k 也是从上往下,每两个值构成一个指针,长度为 d 的 q 总共包含 d/2 个指针,且 q 确定后,每个指针的指向也就确定好了,且一般肯定不是起于 0 位置,这里与 Sinusoidal 不同。然后基于编码的序列位置对 q 的所有指针旋转一个时间长度,从上到下的旋转角速度从大到小,以实现对 q 的位置编码。这个旋转过程就是由位置编码旋转矩阵实现的,位置编码旋转矩阵的形式为:

其中,i 表示维度分组索引,\theta _{i}=10000^{\frac{-2i}{d}} 表示的是旋转角速度,m 是编码的序列位置,也可以说是旋转时间长度。

接下来分析一下 RoPE 编码的原理,它是直接作用在 q 和 k 之上,从而影响 q^{T}k 这个注意力权重系数。所以如上图所示,当把旋转位置编码矩阵乘在 q 和 k 上时,对 q^{T}k 的影响也很复杂,在参考中存在对这个式子的拆解分析,相比 Sinusoidal 要简单点,所以可解释性稍微好点。

参考中,通过实际计算证明 RoPE 是存在衰减特性的。RoPE 的旋转位置矩阵显然可以通过线性变换进行变换。所以 RoPE 满足绝对位置编码的 3 个要求。

RoPE 位置编码的可延长性很好,推测原因可能是如上公式所示,当 x1 x2 固定时,q^{T}k 的结果仅仅取决于 x1 和 x2 的相对位置,模型最终计算结果仅仅与相对位置有关,故具有理论上的可延长性。

三、将相对位置编码加在注意力权重 q^{T}k  (ALiBi 位置编码)

ALiBi (Attention with Linear Biases Enables Input Length Extrapolation)是相对位置编码,它的原理很简单,它直接作用于注意力权重 q^{T}k 之上,AliBi 就是一个如下图所示的矩阵,可以直接与 q^{T}k  求和。观察这个矩阵可以发现,这是一个相对距离矩阵,只要相对位置越近则衰减越小,且衰减的幅度与绝对位置无关。所以,ALiBi 既满足相对位置编码的一个要求,也具有很好的可延长性。ALiBi 的可解释性也非常好。

<think>嗯,用户现在又给了另一段代码,让我解释为什么需要把Position列扩展成三个特征。之前我已经解释过独热编码的基本原理,但这次用户的代码里有一些不同的地方,比如列名是Position_null、Position_long、Position_short,而初始Position设置为0,然后进行了独热编码。可能需要看看用户是不是哪里弄错了,或者有什么特别的原因。 首先,用户可能对这段代码中的独热编码过程有疑问。原来的Position列初始值都是0,那么进行One-Hot编码后,按理说应该只会有一个类别,也就是0,但用户却生成了三个列,这似乎有问题。因为OneHotEncoder默认会根据fit时的数据来决定类别数量,如果所有样本的Position都是0,那么编码后应该只有一个列是1,其他列可能不存在。但用户的代码里却出现了三个列,这可能说明用户在数据预处理时有其他步骤,或者初始化的Position之后被修改过,比如在回测过程中,Position的值可能变为-1、0、1或者其他类别,导致编码器在之后处理时能识别到这三个类别。 或者,用户可能提前配置了OneHotEncoder的categories参数,强制指定了三个类别,比如null、long、short,对应某些特定的值。例如,可能在后续的操作中,Position会有这三个状态,而初始化时设为0可能只是初始状态,对应Position_null。这时候,即使初始时所有数据都是0,编码器仍然会生成三个特征列,以备后续数据中出现其他类别。 另外,用户提到的列名是Position_null、Position_long、Position_short,这可能对应三个不同的持仓状态,比如null表示无持仓,long表示多头,short表示空头。这时候,即使初始时都是null,但编码器仍然需要为其他可能的类别预留位置,因此生成三个特征列。这可能是因为在训练模型时,需要保持输入特征的一致性,即使某些类别在初始数据中没有出现,但在后续的实时数据或测试数据中可能会出现,因此需要提前定义好所有可能的类别。 不过,根据一般的OneHotEncoder行为,如果训练时数据中的某个特征只有单一类别(比如Position全是0),那么编码器只会生成一个列,而不是三个。除非用户在初始化encoder时明确指定了categories参数,比如categories=[[-1,0,1]],或者类似的操作,强制编码器生成三个列,即使当前数据中没有这些值。这可能是一个预防措施,确保在后续的数据处理中,即使出现其他类别,编码器也能正确转换,而不会出现维度不匹配的问题。 用户的代码中,初始Position设置为0,然后进行独热编码,得到三个列。这可能意味着在数据预处理阶段,Position可能被设定为三个可能的值,比如null(0)、long(1)、short(-1),或者其他类似的情况。但初始时所有数据都是0,也就是Position_null为1,其他为0。而后续在策略执行过程中,Position的值可能会改变,这时候对应的独热编码列也会更新。 或者,可能用户在这里的代码存在错误,因为如果Position列在fit时只有0这一个值,那么OneHotEncoder默认只会生成一个类别,对应的列,而不是三个。这时候生成的position_encoded_df会有问题,导致后续的代码出错。用户可能需要检查是否在fit之后,Position的值被修改为包含其他类别,或者是否在创建encoder时指定了categories参数,确保三个类别都被包含进去。 总结来说,用户可能想了解为什么需要将Position列扩展为三个特征,尤其是在初始值都是0的情况下。可能的解释包括:预定义所有可能的类别,确保输入维度的一致性,或者在后续数据中会出现其他类别。需要向用户解释清楚独热编码的工作原理,特别是在类别预定义和数据处理流程中的重要性,以及为何在初始阶段即使只有一个类别也需要生成三个特征列,可能是为了模型的输入维度固定,避免后续出现维度不匹配的问题。</think>您提供的代码片段展示了持仓状态(Position)从单一数值列到独热编码(One-Hot Encoding)的转换过程。以下分步骤解析其技术原理和必要性: --- ### **一、初始代码解析** #### 1. 创建初始持仓列 ```python self.market_data['Position'] = 0 # 所有样本初始持仓为0 ``` - **物理含义**:初始化持仓状态为"无持仓"(假设0表示空仓) #### 2. 应用独热编码 ```python encoder = OneHotEncoder(sparse=False) position_encoded = encoder.fit_transform(self.market_data[['Position']]) ``` - **输入**:单列数值型特征 `Position`(初始全为0) - **输出**:编码后的矩阵 `position_encoded`(示例:若存在0,1,-1三种状态,则输出维度为3) #### 3. 合并编码结果 ```python position_columns = ['Position_null', 'Position_long', 'Position_short'] position_encoded_df = pd.DataFrame(position_encoded, columns=position_columns) self.market_data = pd.concat([self.market_data, position_encoded_df], axis=1) ``` - **逻辑意义**:将离散的持仓状态映射为三个独立布尔特征 --- ### **二、为何需要扩展为三个特征?** #### 1. **分类特征的本质要求** - **原始问题**:持仓状态是典型的**离散型分类变量**(如:空仓/多单/空单),而非连续数值 - **直接数值编码的缺陷**: ```python # 错误示例:将分类变量视为连续值 Position = [-1, 0, 1] # 暗示存在数值关系(如-1 < 0 < 1) ``` - 模型可能误判持仓状态之间的"距离"(如认为0到1的差距与0到-1的差距相同) #### 2. **独热编码的数学优势** - **正交化表示**:每个状态对应一个独立维度 $$ \text{空仓} \rightarrow [1,0,0], \quad \text{多单} \rightarrow [0,1,0], \quad \text{空单} \rightarrow [0,0,1] $$ - **消除虚假数值关联**:模型不会错误学习不存在的序关系 #### 3. **模型输入的兼容性** - **神经网络特性**:每个输入特征对应独立的权重参数 - **独热编码效果**: - 允许模型单独学习每个持仓状态的影响 - 避免单列数值输入导致权重混淆(如将持仓方向与价格波动错误关联) --- ### **三、代码中可能存在的问题** #### 1. **初始值与编码结果的矛盾** - **现象**:初始时所有 `Position=0`,但编码生成了三列 - **原因**:`OneHotEncoder` 默认会根据训练数据自动推断类别数量 - 若训练时数据仅包含 `Position=0`,则实际只会生成 **1个有效列** - 代码中显示生成三列,可能表明: - 在完整数据流中,`Position` 后续会包含 `-1, 0, 1` 三种状态 - 或编码器通过 `categories` 参数预定义了所有可能值 #### 2. **命名与实际值的对应关系** - **代码中的列名**:`Position_null`, `Position_long`, `Position_short` - **潜在映射逻辑**(需结合业务定义): ```python Position_null = 1 # 无持仓(初始状态) Position_long = 1 # 持多单(假设原Position=1) Position_short = 1 # 持空单(假设原Position=-1) ``` #### 3. **推荐改进方案** - **显式定义类别**:避免自动推断导致后续维度不一致 ```python encoder = OneHotEncoder( categories=[[-1, 0, 1]], # 明确指定所有可能值 sparse=False ) ``` - **验证编码结果**:在初始阶段检查输出维度是否符合预期 --- ### **四、实际应用场景示例** 假设在交易策略中,持仓状态随时间变化: | 时间戳 | Position(原始值) | Position_null | Position_long | Position_short | |--------|---------------------|---------------|---------------|----------------| | t1 | 0 | 1 | 0 | 0 | | t2 | 1 | 0 | 1 | 0 | | t3 | -1 | 0 | 0 | 1 | - **模型输入**:每个时间步的特征向量包含独立持仓状态信息 - **训练优势**:模型可清晰区分不同持仓方向对市场影响的差异 --- ### **五、对比错误做法** #### 直接使用数值输入: ```python # 错误示例:将持仓方向作为连续值输入 inputs = torch.tensor([[0], [1], [-1]], dtype=torch.float32) ``` - **导致问题**: 1. 模型可能错误拟合 `Position=-1 → 0 → 1` 的虚假序关系 2. 无法捕捉持仓状态的非线性影响(如空单与多单的影响不对称) --- ### **结论** 将持仓状态扩展为三个独热编码特征,是为了: 1. **数学合理性**:消除离散变量的虚假数值关系 2. **模型兼容性**:确保每个类别独立影响预测结果 3. **工程稳健性**:预定义所有可能状态,避免后续数据出现新类别时维度不匹配 在您的代码中,即使初始时所有样本的 `Position=0`,提前生成三列编码是为了预留特征空间,确保后续持仓状态变化时数据结构保持一致。这是处理分类变量的标准做法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值