让研究人员绞尽脑汁的Transformer位置编码

本文汇总了一些位置编码的工作,大体分为绝对式(训练式、三角式-Google原paper、递归式-用个RNN、相乘式)、相对式(Self-Attention with Relative Position Representations保证位置只和i-j的相对位置有关、XLNet式且从XLNet式起相对位置加在qk上为主、T5式仅仅是在Attention矩阵的基础上加一个可训练的偏置项同时对位置分桶、Deberta式和T5在qk展开项上有变化)、非套路式(CNN式和复数式)三种,从中我们可以看到各种神奇的操作。最后,笔者分享了RoPE

再展开写一下相对位置编码的思路,主要理解下面公式就比较容易:
qi=(xi+pi)WQkj=(xj+pj)WKvj=(xj+pj)WVai,j=softmax(qikjT)oi=∑jai,jvj q_i=(x_i+p_i)W_Q \\ k_j=(x_j+p_j)W_K \\ v_j=(x_j+p_j)W_V \\ a_{i,j} = softmax(q_ik_j^T) \\ o_i=\sum_j{a_{i,j}v_j} qi=(xi+pi)WQkj=(xj+pj)WKvj=(xj+pj)WVai,j=softmax(qikjT)oi=jai,jvj
上面相当于把self attention的公式进行了展开,如果我们进一步把qikjq_ik_jqikj给展开,很明显结果中存在着4项,将不同位置进行替换、加成可训练参数就是XLNet、T5式、Deberta式的区别

在2022年又出了一篇TRAIN SHORT, TEST LONG: ATTENTION WITH LINEAR BIASES ENABLES INPUT LENGTH EXTRAPOLATION,提出了Alibi,在百川最新的大模型(https://mp.weixin.qq.com/s/UOm4riBrLmulOPJO0h_pew)中用了这种相对位置编码:

  • 可学习的参数:这种比较常见,BRET 中就是这么做的,但这种方式弊端很明显,因为位置信息是学习出来的,所以如果训练集里面没有见过覆盖某个长度,推理的效果就无法得到保证。
  • 正弦位置编码:这是早期 transformer 使用的位置编码,论文中有尝试做实验,这种编码会随着训练/预测时的文本长度差异增大,(超过 50 个token 后)性能显著下降。
  • 旋转编码:论文中提到这种方式是比较不错的,只不过因其在每一层都要做一次向量旋转,从而降低训练和推理的速度。
    在这里插入图片描述
    在这里插入图片描述

ALiBi 的实现思路很直觉,模型在接收输入时直接去掉 Position Embedding 向量,
而是在 Attention 中计算 query·Key 的值后面加入一个偏置常量(非训练变量),来达到注入位置信息的效果。
而这个常量是一个 事先计算好 的数值,并且每个头(head)的值都有所不同。

sin cos位置编码实现

import torch

def getPositionEncoding(seq_len, d, n=10000):
    P = torch.zeros(seq_len, d)
    for k in range(seq_len):
        for i in torch.arange(d//2):
            denominator = n ^ (2*i//d)
            P[k, 2*i] = torch.sin(k/denominator)
            P[k, 2*i+1] = torch.cos(k/denominator)
    return P

P = getPositionEncoding(seq_len=3, d=4)
print(P)

'''
tensor([[0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00],
        [1.0000e-04, 1.0000e+00, 1.0000e-04, 1.0000e+00],
        [2.0000e-04, 1.0000e+00, 2.0000e-04, 1.0000e+00]])
'''

RoPE代码实现

import torch
from typing import Tuple

def precompute_freqs_cis(dim: int, seq_len: int, theta: float = 10000.0):
    # 计算词向量元素两两分组之后,每组元素对应的旋转角度
    freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))

    # 生成 token 序列索引 t = [0, 1,..., seq_len-1]
    t = torch.arange(seq_len, device=freqs.device)
    # freqs.shape = [seq_len, dim // 2] 
    freqs = torch.outer(t, freqs).float()
    # torch.polar的文档, https://pytorch.org/docs/stable/generated/torch.polar.html
    # torch.polar输入参数是abs和angle,abs所有值都一样,abs和angle的shape都一样
    # torch.polar输入参数是abs和angle,则freqs_cis = abs*(cos(angle) + sin(angle)i)
    freqs_cis = torch.polar(torch.ones_like(freqs), freqs)
    return freqs_cis

def apply_rotary_emb(
    xq: torch.Tensor,
    xk: torch.Tensor,
    freqs_cis: torch.Tensor,
) -> Tuple[torch.Tensor, torch.Tensor]:
    # xq.shape = [batch_size, seq_len, dim]
    # xq_.shape = [batch_size, seq_len, dim // 2, 2]
    xq_ = xq.float().reshape(*xq.shape[:-1], -1, 2)
    xk_ = xk.float().reshape(*xk.shape[:-1], -1, 2)
    
    # 转为复数域,  xq_.shape = [batch_size, seq_len, dim // 2]
    xq_ = torch.view_as_complex(xq_)
    xk_ = torch.view_as_complex(xk_)
    # 应用旋转操作,然后将结果转回实数域
    # xq_out.shape = [batch_size, seq_len, dim]
    xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(2) #从dim=2维度开始拍平
    xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(2)

    return xq_out.type_as(xq), xk_out.type_as(xk)

if __name__ == '__main__':
    seq_len,dim=3,4
    freqs_cis = precompute_freqs_cis(dim=dim, seq_len=seq_len, theta=10000.0)
    xq = torch.rand(1, seq_len, dim)
    xk = torch.rand(1, seq_len, dim)
    res = apply_rotary_emb(xq, xk, freqs_cis)
    # res的shape是1, seq_len, dim
    
'''
class Attention(nn.Module):
    def __init__(self, args: ModelArgs):
        super().__init__()

        self.wq = Linear(...)
        self.wk = Linear(...)
        self.wv = Linear(...)
        
        self.freqs_cis = precompute_freqs_cis(dim, max_seq_len * 2)

    def forward(self, x: torch.Tensor):
        bsz, seqlen, _ = x.shape
        xq, xk, xv = self.wq(x), self.wk(x), self.wv(x)

        xq = xq.view(batch_size, seq_len, dim)
        xk = xk.view(batch_size, seq_len, dim)
        xv = xv.view(batch_size, seq_len, dim)

        # attention 操作之前,应用旋转位置编码
        xq, xk = apply_rotary_emb(xq, xk, freqs_cis=freqs_cis)
        
        # scores.shape = (bs, seqlen, seqlen)
        scores = torch.matmul(xq, xk.transpose(1, 2)) / math.sqrt(dim)
        scores = F.softmax(scores.float(), dim=-1)
        output = torch.matmul(scores, xv)  # (batch_size, seq_len, dim)
  # ......
'''

以下转载自https://kexue.fm/archives/8130#CNN%E5%BC%8F
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

### Transformer 模型中的位置编码Transformer 模型中,由于自注意力机制本身不包含序列顺序的信息,因此引入了位置编码来为输入嵌入提供相对或绝对的位置信息。位置编码使得模型能够利用词语之间的顺序关系,从而更好地捕捉上下文依赖。 #### 绝对位置编码 早期版本的 Transformer 使用正弦和余弦函数作为绝对位置编码方案[^1]: ```python import math import torch def get_position_encoding(seq_len, d_model): position_enc = np.array([ [pos / np.power(10000, 2 * (i // 2) / d_model) for i in range(d_model)] for pos in range(seq_len)]) position_enc[:, 0::2] = np.sin(position_enc[:, 0::2]) # apply sin to even indices in array; 2i position_enc[:, 1::2] = np.cos(position_enc[:, 1::2]) # apply cos to odd indices in array; 2i+1 return torch.from_numpy(position_enc).float() ``` 这种编码方式不仅简单有效,而且可以扩展到任意长度的序列上。然而,随着研究的发展,研究人员发现了一些潜在的问题并提出了改进方法。 #### 改进与最新研究方向 为了提高模型性能,一些研究表明可以通过学习得到更优的位置表示形式。例如,在某些任务中采用可训练参数的形式替代固定的正弦波形可能带来更好的效果[^3]。此外,还有工作探索如何结合相对位置偏移量来进行建模,这有助于增强长距离依赖性的捕获能力而不增加计算复杂度。 另一项重要进展涉及多尺度或多粒度的位置感知机制。这类方法试图通过不同层次的时间间隔或者空间区域内的交互作用来丰富位置信息表达,进而提升下游任务的表现。具体实现包括但不限于分层结构设计、局部窗口内操作等策略[^2]。 #### 实验验证与实际应用 实验结果显示,这些新型位置编码技术能够在多种NLP任务中取得显著优于原始Transformer的结果。特别是在处理较长文本片段时,改进后的模型往往能展现出更强的理解能力和更高的准确性。不过值得注意的是,不同的应用场景可能会偏好特定类型的解决方案;因此选择最合适的技术仍需基于具体需求考虑。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值