ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

人工智能-位置编码

人工智能-位置编码 1 绝对位置编码1.1 公式绝对位置编码Absolute Positional Encoding是在每个输入序列的元素上加上位置信息以表示该元素在序列中的具体位置。这个位置向量通常通过固定的函数生成与输入数据无关。通常使用的是正弦和余弦函数这样生成的编码具有很强的周期性能够捕捉序列中的相对位置信息。位置编码就行给一堆人贴上一个位置序号以后就能识别出来位置信息。看了很多文章要么只讲逻辑要么只贴代码即使同时贴了代码和逻辑的也对代码不做解读。这里就记录下来给大家分享一下让AI不再那么神秘有兴趣都朋友多关注加强交流分享。绝对位置编码的公式如下1 d-model是嵌入维度2 其中 i 为维度索引取值范围为 0 ≤ i d_model/23 PE(pos,2i) 表示词位置2i表示词位置嵌入的偶数位置4 PE(pos,2i1) 表示词位置2i表示词位置嵌入的偶数位置5 pos 其实对应每次词的行一行的偶数列用sin,奇数列用cos计算1.2 原理一、为什么能标识先后顺序每个位置pos的向量是由多组不同频率的(sin(pos), cos(pos))二维正交基拼接而成每一组都对应一个唯一的旋转角度。位置每往后移动1步所有组的向量都会按各自的固定角速度同步旋转一个固定角度位置越靠后累计旋转的总角度就越大。不同位置的向量旋转状态完全不同模型可以直接通过向量的角度差异判断两个token谁在前、谁在后不会出现顺序混淆。二、为什么能直接计算相对距离利用三角函数的和差化积公式位置pos的向量旋转k步后就能精准得到位置posk的向量变换过程只和间隔步数k有关和绝对位置pos完全无关。两个不同位置的向量做内积运算最终得到的结果只由它们的相对距离差决定绝对位置信息会被自动抵消。比如pos2和pos5的向量内积和pos10和pos13的向量内积结果完全相等模型不需要额外训练就能直接通过内积数值得到两个token之间相隔多少步。1.3 缺陷一、单维度三角函数的周期性本质正余弦函数的周期固定为2π360°当两个位置的相位差刚好接近2π的整数倍时该维度上的sin/cos输出值几乎完全相等。比如相位差为2π0.1和相位差为0.1的两个点单维度的函数输出值几乎一致单维度相似度接近1完全无法区分长距离和短距离。二、多频率维度的组合失效Transformer位置编码使用的频率序列是1/10000^(2i/d_model)低频维度的周期极长高频维度的周期极短。当序列长度足够大时大量高频维度的相位差会同时落在2π整数倍附近多个维度的输出值同步趋同最终导致两个距离极远的位置整体向量的余弦相似度异常升高。三、位置编码的设计固有缺陷原始正余弦位置编码没有对长距离的相位差做约束随着pos不断增大不同位置的向量空间分布会逐渐出现周期性重叠。远距离位置的向量在高维空间中会意外靠近模型无法通过向量内积准确区分长距离的间隔最终导致长序列场景下的位置感知能力失效。所以延伸了其他的编码方案。RoPERotary Position Embedding旋转位置编码是目前大模型的标配位置编码方案核心逻辑是‌通过向量旋转操作把绝对位置信息注入到Q、K向量中最终让注意力得分天然只依赖token之间的相对位置‌完美解决了原始正余弦位置编码的长距离相似度异常、外推能力差的问题下面上一个教科书上的代码class PositionalEncoding(torch.nn.Module): def __init__(self, d_model 312, dropout 0.05, max_len80): :param d_model: pe编码维度一般与word embedding相同方便相加 :param dropout: dorp out :param max_len: 语料库中最长句子的长度即word embedding中的L super(PositionalEncoding, self).__init__() # 定义drop out self.dropout torch.nn.Dropout(pdropout) # 计算pe编码 pe torch.zeros(max_len, d_model) # 建立空表每行代表一个词的位置每列代表一个编码位 position torch.arange(0, max_len).unsqueeze(1) # 建个arrange表示词的位置以便公式计算size(max_len,1) #计算公式中10000**2i/d_model) #torch.arange(0, d_model, 2) 2i div_term torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) # 计算偶数维度的pe值 pe[:, 1::2] torch.cos(position * div_term) # 计算奇数维度的pe值 pe pe.unsqueeze(0) # size(1, L, d_model)为了后续与word_embedding相加,意为batch维度下的操作相同 self.register_buffer(pe, pe) # pe值是不参加训练的 def forward(self, x): # 输入的最终编码 word_embedding positional_embedding x x self.pe[:, :x.size(1)].clone().detach().requires_grad_(False) print(x shape {}.format(x.shape)) return self.dropout(x) # size [batch, L, d_model]div_term这个地方代码估计不容易看懂反正我这种数学思维不太敏捷的看了几眼都没有明白现在的教科书没有发现一本能写的简单透彻的不是这个东西有多难而是讲的人没讲好一笔带过甚至都不提一下代码一贴就完事。很多人不是说数学不好而是太久了记不到。因为代码计算逻辑和公式看起来长得并不一样这里给一下推导过程。首先给简单的公式 10 e^ln10。这个其实很简单 设in10 b,那么 10 e^b就得出了结论成立PE(pos,2i)公式就变成了于是除了多了一个负号div_term计算公式就是右边的样子公式中的负号本质是‌对指数运算的符号约定‌。若不加负号exp(i⋅(log(10000.0)/d_model)) 会导致频率随维度随i增大而‌指数增长‌频率越高周期越短相邻维度的正弦/余弦函数差异过大无法有效区分位置信息。加上负号后exp(−i⋅(log(10000.0)/d_model)) 使频率随维度i增大而‌指数衰减‌频率越低周期越长相邻维度的正弦/余弦函数差异适中能平衡“区分位置”与“维度间差异”的需求公式中的 10000是‌“波长缩放因子”‌其对数 log(10000) 决定了频率衰减的“速度”10000 越大频率衰减越平缓相邻维度的正弦/余弦函数更相似10000 越小频率衰减越陡峭相邻维度的正弦/余弦函数差异越大在Transformer的实验中10000 是‌经验值‌能平衡不同维度的频率差异使位置编码在序列中有效传递位置信息讲一下代码中的主要逻辑以一个句子 “我很牛逼”为例词嵌入可以参考我其他文章分词后成为 [我,很,牛逼], 句子长度就是3因为可能有很多句子那么我们可以设置一个最大长度比如80,不足部分就使用0代表空填充这样我很牛逼就变成了[我,很,牛逼00....0] 然后这个句子列表的词是使用312长度来编码一个词就变成了 80 x 312 [ [],[],[]......[]]二维数组。代码中的pe就初始成 80 x 312 与输入句子成形状这样才能相加.div_term就是每个2i的乘积因子数组当i0时pos0 ,PE(0,0)-sin,PE(0,1)-cos,即为第一行数据312列中第一列和第二列。所以pe[:, 0::2]pe[:, 1::2] 刚好把pe矩阵所有行的位置参数都算出来了。最后将pe与输入矩阵相加就将位置信息标记到了输入矩阵上。2 相对位置编码
返回列表