【深度学习】位置编码

一、引言

Self-Attention并行的计算方式未考虑输入特征间的位置关系,这对NLP来说是不可接受的,毕竟一个句子中每个单词都有着明显的顺序关系。Transformer没有RNN、LSTM那样的顺序结构,所以Transformer在提出Self-Attention的同时提出了Positional Encoding。

如图所示,Transformer在Attention模块之前将位置编码加进了待输入特征中。

二、位置编码

首先需要明确NLP中数据的形式,一个批次包含多个句子,每个句子包含多个单词,每个单词被转为长度相同的token向量。由于每个句子中包含的单词数不同,所以会通过padding统一同一批次的embedding。假设一个批次padding后的embedding维度为 [ b a t c h _ s i z e , n u m _ t o k e n , d i m _ t o k e n ] [batch\_size,num\_token,dim\_token] [batch_size,num_token,dim_token]。

1. 直观的位置编码

1.1 索引型

将token的索引作为位置编码,第一个token编码为0,第二个token编码为1,以此类推。

其主要问题在于位置编码的值无界

1.2 [0,1]型

为保证值有界 ,可限制位置编码范围为 [ 0 , 1 ] [0,1] [0,1],第一个token编码为0,最后一个token编码为1,其余token等间隔取值。例如共3个token时,位置编码为 [ 0 , 0.5 , 1 ] [0,0.5,1] [0,0.5,1],共4个token时,位置编码为 [ 0 , 0.33 , 0.66 , 1 ] [0,0.33,0.66,1] [0,0.33,0.66,1]。

其主要问题在于两个句子的token个数不同时,两个相同位置间的相对距离不同。共3个token时,第三个与第一个token间距为1,但共4个token时,第三个与第一个token间距为0.66。

1.3 二进制型

为保证值有界句子长度不同时相对距离相同,可通过索引的二进制编码作为位置编码。下图为一个包含8个token,token向量长度为3的句子的位置编码。

如图,因为位置编码与embedding需要相加,所以通常位置编码的维度与embedding的维度相同。直白地说,有几个token就有几个位置编码,token向量的维度是多少位置编码向量的维度就是多少。

其主要问题在于二进制编码的位置向量处于离散空间,与输入相加后进入浮点世界,造成了空间上的浪费

不过,我们可以观察该类型位置编码的规律。纵向来看,每个维度的编码值变化频率不同 ,蓝色变化周期为4,绿色变化周期为2,红色变化周期为1。

1.4 周期型

为保证值有界句子长度不同时相对距离相同节约空间 ,周期型位置编码包含了类似二进制型位置编码的变化规律,并将离散的二进制转为连续的 sin ⁡ \sin sin或 cos ⁡ \cos cos。

以 sin ⁡ \sin sin为例,我们用 p o s pos pos表示embedding中token的索引,用 i i i表示token上元素的索引。于是第 p o s pos pos个token的位置编码可以表示如下:
P E ( p o s ) = [ sin ⁡ ( p o s 2 0 ) , sin ⁡ ( p o s 2 1 ) , ⋯   , sin ⁡ ( p o s 2 i ) , ⋯   , sin ⁡ ( p o s 2 d i m _ t o k e n − 1 ) ] PE_{(pos)}=[\sin(\frac{pos}{2^0}),\sin(\frac{pos}{2^1}),\cdots,\sin(\frac{pos}{2^i}),\cdots,\sin(\frac{pos}{2^{dim\_token-1}})] PE(pos)=[sin(20pos),sin(21pos),⋯,sin(2ipos),⋯,sin(2dim_token−1pos)]

其中, p o s = 0 , 1 , ⋯   , n u m _ t o k e n − 1 pos=0,1,\cdots,num\_token-1 pos=0,1,⋯,num_token−1, i = 0 , 1 , ⋯   , d i m _ t o k e n − 1 i=0,1,\cdots,dim\_token-1 i=0,1,⋯,dim_token−1。

可见,每个维度上 1 2 i \frac{1}{2^i} 2i1被用来控制变化规律,详情如下图。

但是,使用 1 2 i \frac{1}{2^i} 2i1来控制变化规律会使 P E ( p o s ) PE_{(pos)} PE(pos)很快形成一个闭环。

如图,当 d i m _ t o k e n = 3 dim\token=3 dim_token=3时,以 0.1 0.1 0.1的间隔在 [ 0 , 20 ] [0,20] [0,20]取 p o s pos pos,得到200个 P E ( p o s ) PE{(pos)} PE(pos),前100个点为蓝色,后100个点为橙色,可以清晰看到它们的重叠部分。这表明即便 p o s pos pos不同, P E ( p o s ) PE_{(pos)} PE(pos)也有很多点的值是相同的,但我们希望位置编码像地址一样是独一无二的,所以我们使用 1 1000 0 i / d i m _ t o k e n \frac{1}{10000^{i/dim\token}} 10000i/dim_token1替换 1 2 i \frac{1}{2^i} 2i1来控制变化规律。此时, P E ( p o s ) PE{(pos)} PE(pos)如下图,不再有重叠。

于是有:
P E ( p o s ) = [ sin ⁡ ( w 0 p o s ) , sin ⁡ ( w 1 p o s ) , ⋯   , sin ⁡ ( w i p o s ) , ⋯   , sin ⁡ ( w d i m _ t o k e n − 1 p o s ) ] PE_{(pos)}=[\sin(w_0pos),\sin(w_1pos),\cdots,\sin(w_ipos),\cdots,\sin(w_{dim\_token-1}pos)] PE(pos)=[sin(w0pos),sin(w1pos),⋯,sin(wipos),⋯,sin(wdim_token−1pos)]

其中, w i = 1 1000 0 i / d i m _ t o k e n w_i=\frac{1}{10000^{i/dim\_token}} wi=10000i/dim_token1。

但它仍有一个问题,不同位置编码无法相互线性转换

2. Sinusoidal位置编码

为保证值有界句子长度不同时相对距离相同节约空间不同位置编码可相互线性转换 ,Sinusoidal型位置编码交替使用 sin ⁡ \sin sin和 cos ⁡ \cos cos,于是第 p o s pos pos个token的位置编码可表示如下:
P E ( p o s ) = [ sin ⁡ ( w 0 p o s ) , cos ⁡ ( w 0 p o s ) , ⋯   , sin ⁡ ( w i p o s ) , cos ⁡ ( w i p o s ) , ⋯   , sin ⁡ ( w d i m _ t o k e n 2 − 1 p o s ) , cos ⁡ ( w d i m _ t o k e n 2 − 1 p o s ) ] PE_{(pos)}=[\sin(w_0pos),\cos(w_0pos),\cdots,\sin(w_ipos),\cos(w_ipos),\cdots,\sin(w_{\frac{dim\token}{2}-1}pos),\cos(w{\frac{dim\_token}{2}-1}pos)] PE(pos)=[sin(w0pos),cos(w0pos),⋯,sin(wipos),cos(wipos),⋯,sin(w2dim_token−1pos),cos(w2dim_token−1pos)]

其中, p o s = 0 , 1 , ⋯   , n u m _ t o k e n − 1 pos=0,1,\cdots,num\_token-1 pos=0,1,⋯,num_token−1, i = 0 , 1 , ⋯   , d i m _ t o k e n 2 − 1 i=0,1,\cdots,\frac{dim\_token}{2}-1 i=0,1,⋯,2dim_token−1, w i = 1 1000 0 i / d i m _ t o k e n w_i=\frac{1}{10000^{i/dim\_token}} wi=10000i/dim_token1。

该形式下 P E ( p o s ) PE_{(pos)} PE(pos)可以线性变换,可由下式证明:
P E ( p o s + Δ p o s ) = ( sin ⁡ ( w 0 ( p o s + Δ p o s ) ) cos ⁡ ( w 0 ( p o s + Δ p o s ) ) ⋯ sin ⁡ ( w d i m _ t o k e n 2 − 1 ( p o s + Δ p o s ) ) cos ⁡ ( w d i m _ t o k e n 2 − 1 ( p o s + Δ p o s ) ) ) = ( [ cos ⁡ ( w 0 Δ p o s ) sin ⁡ ( w 0 Δ p o s ) − sin ⁡ ( w 0 Δ p o s ) cos ⁡ ( w 0 Δ p o s ) ] ⋯ 0 ⋯ ⋯ ⋯ 0 ⋯ [ cos ⁡ ( w d i m _ t o k e n 2 − 1 Δ p o s ) sin ⁡ ( w d i m _ t o k e n 2 − 1 Δ p o s ) − sin ⁡ ( w d i m _ t o k e n 2 − 1 Δ p o s ) cos ⁡ ( w d i m _ t o k e n 2 − 1 Δ p o s ) ] ) ( sin ⁡ ( w 0 p o s ) cos ⁡ ( w 0 p o s ) ⋯ sin ⁡ ( w d i m _ t o k e n 2 − 1 p o s ) cos ⁡ ( w d i m _ t o k e n 2 − 1 p o s ) ) = T Δ p o s ∗ P E ( p o s ) \begin{split} PE_{(pos+\Delta pos)} &= \left(\begin{array}{c} \sin(w_0(pos+\Delta pos))\\ \cos(w_0(pos+\Delta pos))\\ \cdots\\ \sin(w_{\frac{dim\token}{2}-1}(pos+\Delta pos))\\ \cos(w{\frac{dim\token}{2}-1}(pos+\Delta pos)) \end{array}\right)\\ &= \left(\begin{array}{c} \left[\begin{array}{c} \cos(w_0\Delta pos)&\sin(w_0\Delta pos)\\ -\sin(w_0\Delta pos)&\cos(w_0\Delta pos) \end{array}\right]&\cdots&0\\ \cdots&\cdots&\cdots\\ 0&\cdots&\left[\begin{array}{c} \cos(w{\frac{dim\token}{2}-1}\Delta pos)&\sin(w{\frac{dim\token}{2}-1}\Delta pos)\\ -\sin(w{\frac{dim\token}{2}-1}\Delta pos)&\cos(w{\frac{dim\token}{2}-1}\Delta pos) \end{array}\right]\\ \end{array}\right) \left(\begin{array}{c} \sin(w_0pos)\\ \cos(w_0pos)\\ \cdots\\ \sin(w{\frac{dim\token}{2}-1}pos)\\ \cos(w{\frac{dim\token}{2}-1}pos) \end{array}\right)\\ &= T{\Delta pos}*PE_{(pos)} \end{split} PE(pos+Δpos)= sin(w0(pos+Δpos))cos(w0(pos+Δpos))⋯sin(w2dim_token−1(pos+Δpos))cos(w2dim_token−1(pos+Δpos)) = [cos(w0Δpos)−sin(w0Δpos)sin(w0Δpos)cos(w0Δpos)]⋯0⋯⋯⋯0⋯[cos(w2dim_token−1Δpos)−sin(w2dim_token−1Δpos)sin(w2dim_token−1Δpos)cos(w2dim_token−1Δpos)] sin(w0pos)cos(w0pos)⋯sin(w2dim_token−1pos)cos(w2dim_token−1pos) =TΔpos∗PE(pos)

实际上,是用到如下和角公式中的第1项和第3项:

三、应用

1. Transformer中的位置编码

1.1 简介

上述Sinusoidal位置编码是在Transformer中针对NLP问题提出的。一个 n u m _ t o k e n = 50 , d i m _ t o k e n = 128 num\_token=50,dim\_token=128 num_token=50,dim_token=128的句子的位置编码如下图:

纵向来看,不同列的变化频率不同,从左到右频率依次下降。即使后58个维度无变化,为保证位置编码与embedding维度相同,仍然保留完整的128个维度。

位置编码在Transformer模型中的作用主要体现在以下几个方面:

(1) 捕捉词序信息 :由于位置编码与词的位置相关,因此它们可以帮助模型理解输入序列中词的顺序。这对于依赖关系分析、句法分析等任务非常重要。

(2) 防止重复使用相同输入 :由于位置编码是随机的,相同的输入序列会产生不同的位置编码。这有助于防止模型重复使用相同的输入来生成输出,从而提高模型的多样性和泛化能力。

(3) 增加模型的鲁棒性:位置编码的随机性可以帮助模型更好地处理噪声和异常值,从而提高其鲁棒性。

在实际应用中,位置编码通常在自注意力机制之前添加到输入序列中。这样,自注意力机制可以同时考虑词的语义信息和位置信息,从而更好地捕捉输入序列中的依赖关系。

1.2 实现

python 复制代码
import torch.nn as nn
import torch


class PositionalEncoding(nn.Module):
    def __init__(self, dim_token, max_num_token=5000):
        super(PositionalEncoding, self).__init__()

        self.encoding = torch.zeros(max_num_token, dim_token)

        pos = torch.arange(0, max_num_token).unsqueeze(dim=1)  # 不是每次实时计算,而是预估一个pos上限
        _2i = torch.arange(0, dim_token, step=2)  # 共计算dim_token/2-1次,每次计算两个值sin和cos

        self.encoding[:, 0::2] = torch.sin(pos / (10000 ** (_2i / dim_token)))
        self.encoding[:, 1::2] = torch.cos(pos / (10000 ** (_2i / dim_token)))

    def forward(self, x):
        batch_size, num_token, dim_token = x.size()
        return self.encoding[:num_token, :]  # num_token是padding前单词的数量


if __name__ == '__main__':
    x = torch.randn((2, 3, 6))  # [batch_size,num_token,dim_token]
    pe = PositionalEncoding(6)  # dim_token必须是偶数
    y = pe(x)

2. DETR中的位置编码

2.1 简介

DETR将Transformer用在了CV的目标检测任务中,目标检测要求预测目标框,因此位置信息也很重要,所以也需要引入位置编码。DETR引入的位置编码也是Sinusoidal形式的。

不过,图像的维度与句子的维度不同。句子维度为 [ b a t c h _ s i z e , n u m _ t o k e n , d i m _ t o k e n ] [batch\_size,num\_token,dim\_token] [batch_size,num_token,dim_token],Transformer中位置编码与句子维度相同,一个位置编码向量表示一个句子(位置编码向量长度 = d i m _ t o k e n =dim\_token =dim_token)。图像维度为 [ b a t c h _ s i z e , n u m _ c h a n n e l , h e i g h t , w i d t h ] [batch\_size,num\_channel,height,width] [batch_size,num_channel,height,width],DETR中一个位置编码向量表示一个像素(位置编码向量长度 = n u m _ c h a n n e l =num\_channel =num_channel,一半的向量表示横坐标,另一半表示纵坐标)。此外,DETR还考虑了padding问题,仅针对非padding区域计算位置编码。

不仅如此,DETR中位置编码仅在Attention的 Q Q Q和 K K K中,而Transformer在 Q Q Q、 K K K、 V V V上都有。

2.2 实现

python 复制代码
import torch.nn as nn
import torch


class PositionEmbeddingSine(nn.Module):
    def __init__(self, num_channel=64, temperature=10000):
        super().__init__()
        self.num_channel = num_channel
        self.temperature = temperature

    def forward(self, mask):
        assert mask is not None
        not_mask = ~mask  # mask中True表示padding区域,False表示非padding区域
        pos_y = not_mask.cumsum(1)  # 如果是padding区域,pos不增加
        pos_x = not_mask.cumsum(2)  # 横、纵坐标均计算pos

        i = torch.arange(self.num_channel)
        wi = self.temperature ** (2 * (i // 2) / self.num_channel)  # 2i = i // 2

        pos_x = pos_x[:, :, :, None] / wi  # 所有像素都有num_channel/2个横坐标
        pos_y = pos_y[:, :, :, None] / wi  # 所有像素都有num_channel/2个纵坐标

        pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)  # 原本dim只到3,在第4个维度上stack然后flatten能使sin和cos交替出现
        pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)

        pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)  # 前半部分为纵坐标,后半部分为横坐标
        return pos


if __name__ == '__main__':
    x = torch.randn((8, 4, 5, 6))  # [batch_size,num_channel,height,width]
    mask = torch.zeros((8, 5, 6))  # 同一图像上mask在每个通道上都一样
    mask = mask.bool()  # 默认没有padding
    pes = PositionEmbeddingSine(2)  # num_channel必须是偶数,这里输入的是num_channel/2,一半用于横坐标,另一半用于纵坐标
    y = pes(mask)

致谢:

本博客仅做记录使用,无任何商业用途,参考内容如下:
四种Position Embedding的原理与PyTorch手写逐行实现(Transformer/ViT/Swin-T/MAE)
【Transformer系列】深入浅出理解Positional Encoding位置编码
Transformer学习笔记一:Positional Encoding(位置编码)
DE⫶TR: End-to-End Object Detection with Transformers

相关推荐
lijianhua_97127 小时前
国内某顶级大学内部用的ai自动生成论文的提示词
人工智能
EDPJ7 小时前
当图像与文本 “各说各话” —— CLIP 中的模态鸿沟与对象偏向
深度学习·计算机视觉
蔡俊锋7 小时前
用AI实现乐高式大型可插拔系统的技术方案
人工智能·ai工程·ai原子能力·ai乐高工程
自然语7 小时前
人工智能之数字生命 认知架构白皮书 第7章
人工智能·架构
大熊背7 小时前
利用ISP离线模式进行分块LSC校正的方法
人工智能·算法·机器学习
eastyuxiao8 小时前
如何在不同的机器上运行多个OpenClaw实例?
人工智能·git·架构·github·php
诸葛务农8 小时前
AGI 主要技术路径及核心技术:归一融合及未来之路5
大数据·人工智能
光影少年8 小时前
AI Agent智能体开发
人工智能·aigc·ai编程
ai生成式引擎优化技术8 小时前
TSPR-WEB-LLM-HIC (TWLH四元结构)AI生成式引擎(GEO)技术白皮书
人工智能
帐篷Li8 小时前
9Router:开源AI路由网关的架构设计与技术实现深度解析
人工智能