LLM学习笔记-位置编码篇

在Transformer模型中,位置编码(Positional Encoding)的引入是为了补充自注意力机制(Self-Attention)在捕捉序列位置信息方面的不足。自注意力机制是Transformer的核心,但它对输入序列的位置信息并不敏感。具体来说,Transformer模型对输入序列中的每个元素进行处理时是并行的,而不是像传统的循环神经网络(RNN)那样按顺序处理。因此,如果没有位置信息,Transformer模型无法区分序列中元素的先后顺序。
位置编码的主要作用如下

  • 提供位置信息:位置编码为每个序列元素提供其在序列中的位置,使得模型可以识别和区分序列中不同位置的元素。

  • 增强模型的表达能力:通过加入位置编码,模型不仅可以学习元素的内容信息,还可以学习元素的位置信息,从而更好地捕捉序列中的依赖关系和模式。

  • 与自注意力机制结合:位置编码与自注意力机制结合,使得模型能够同时关注到序列中元素的相对位置和全局上下文信息,这对于处理长距离依赖和全局信息非常重要。

为什么不直接用token的索引作为它的位置编码?

  • 因为序列长度是可变的,因此token的索引也随之变化,当句子非常长的时候索引也会变的非常大。此外直接用索引作为位置编码会损害模型的泛化能力,即缺乏处理没见过的长度的能力。
  • 用normalize把索引归一化到0-1之间会产生另一个问题。同一个归一化的值,比如0.1, 在不同长度的序列中表示的位置不同,这就会导致不同长度序列中,第m个token与第n个token的相对距离是变化的,这不利于绝对位置和相对位置的计算。

理想情况下,位置编码应满足以下条件

  • 为每个时间步骤(单词在句子中的位置)输出唯一的编码。
  • 任何两个时间步骤之间的距离在不同长度的句子之间应该是一致的。
  • 我们的模型应该方便地推广到更长的句子。它的值应该是有界的。
  • 它必须是确定性的。

基于以上原因我们采取其他方法进行位置编码。在本篇文章中,我会主要介绍Transformer结构模型中常见的位置编码,主要包括固定位置编码、可学习位置编码和旋转位置编码RoPE。

固定位置编码
可学习位置编码
旋转位置编码

固定位置编码

Transformer论文中用到的固定位置编码

其形式为
Description

pos是位置,i是维度索引,d_model是嵌入总维度。每个维度的位置编码都对应一个正弦波,波长从2π到10000 · 2π,对应的频率从高频到低频。你可以把这个位置编码想象成一个包含d_model/2对的sin和cos向量, 每一对sin和cos都对应一个频率。

这里我解释一下波长和频率的计算,后边的旋转矩阵会用到。

对于一个正弦函数, y = Asin(Bx + C) + D. 其波长为λ=2π/|B| 和周期计算一样,为从一个最高点到下一个最高点的距离。 频率与波长成反比关系。波长越长,变化越慢,即频率越低。回到我们的位置编码,从位置编码公式中我们不难得出,波长的计算和 Description 有关。

  • 对于较小的 i,分母较小,波长较短, 频率较高,当i=0时, 波长为2π, 频率为1/2π。这意味着正弦和余弦函数的变化较快。
  • 对于较大的 i,分母较大,波长较长, 频率较低,当i=d/2时, 波长为10000 · 2π,频率为1/(10000 · 2π)。这意味着正弦和余弦函数的变化较慢。

位置编码python实现

def sinusoidal_pos_embedding(seq_length, d_model, base):sin_cos_val = np.power(base,  2 * (np.arange(d_model) // 2) / d_model)pos_embed = np.zeros((seq_length, d_model))for k in range(seq_length):pos_embed[k, 0::2] = np.sin(k/sin_cos_val)pos_embed[k, 1::2] = np.cos(k/sin_cos_val)return pos_embedP = sinusoidal_pos_embedding(seq_length=100, d_model=512, base=10000)
cax = plt.matshow(P)
plt.gcf().colorbar(cax)
plt.savefig('sinusoidal.png')

得到

从图中可以看出,当维度较低时,频率高变化快,随着维度增大,频率变低。相邻两个token的位置编码的差异主要在左边低维度部分,即高频部分。

相对位置编码

选用正弦编码的另一个原因是因为它可以方便计算相对位置编码,原文如下

We chose this function because we hypothesized it would allow the model to easily learn to attend by relative positions, since for any fixed offset k, PEpos+k can be represented as a linear function of PEpos

对于每一个频率为ωk的sine-cosine对,都有一个线性变换矩阵 Description满足以下等式
Description

证明

M为一个2✖️2矩阵,我们想要找到一组u1, v1, u2, v2使得
Description
利用三角函数知识sin(a+b) = sin(a)cos(b)+cos(a)sin(b), cos(a+b) = cos(a)cos(b)-sin(a)sin(b), 展开右边的式子可得
Description

Description
求解这个等式得到
Description
线性变换矩阵M为
Description
从M的表达式可以看出,变换矩阵M和t无关,感兴趣的同学会发现M矩阵和旋转矩阵有点相似。因此我们可以通过一对正弦-余弦,M,将位置编码pt+ϕ 表示为位置编码pt 的线性函数,其中任意偏移量为 ϕ。此属性使模型能够轻松学习通过相对位置进行关注。

为什么使用正弦位置编码

  1. sin、cos值在[-1, 1]之间,保证了位置编码矩阵的每个值都处于一个稳定范围
  2. 每个位置的位置编码都是唯一的,这有利于绝对位置编码
  3. 不同位置之间的位置编码也可以进行计算, 这有利于相对位置计算
  4. 正弦位置编码具备远程衰减性,即随着两个token间距离的增大,这两个token的位置编码的内积会变小。这是符合直觉的,一般来说距离越远的两个token,关联性也就越小

可学习位置编码

与固定的位置编码不同,学习的位置嵌入是通过训练模型来学习的。这种方法的基本思想是为每个位置创建一个可训练的嵌入向量,并在训练过程中更新这些向量。

实现方式

  • 初始化嵌入矩阵:为每个位置初始化一个嵌入向量。例如,如果序列长度是 seq_length,嵌入维度是 d_model,那么初始化的嵌入矩阵大小将是 [seq_length, d_model]。
  • 学习嵌入向量:在模型训练过程中,这些嵌入向量会像其他模型参数一样被优化。
  • 应用嵌入向量:在将嵌入向量输入到模型中时,将输入序列中的每个位置映射到其对应的嵌入向量。例如,如果输入序列是 [token1, token2, ..., tokenN],则位置嵌入是 [pos_embedding1, pos_embedding2, ..., pos_embeddingN]。

可学习位置编码的python实现

import torch
import torch.nn as nnclass LearnedPositionEmbedding:def __init__(self, seq_length, d_model):super(LearnedPositionEmbedding, self).__init__()self.position_embedding = nn.Embedding(seq_length, d_model)def forward(inputs_id):positions = torch.range(0, inputs_id.size(1),dtype=torch.long, device=inputs_id.device)positions = positions.unsqueeze(0).expand_as(inputs_id)position_embedding = self.position_embedding(positions)return position_embedding 

可学习位置编码的优缺点

优点

  • 灵活性:学习的位置嵌入可以根据任务和数据的不同而自动调整,而不是依赖于固定的数学函数。
  • 适应性:这种方法允许模型更好地适应特定任务中的位置模式。

缺点

  • 序列长度的限制:可学习位置嵌入通常需要预先定义一个最大序列长度(max length)。如果输入序列长度超过了这个预定义的最大长度,模型就无法处理这些超长序列,因为没有为这些超长序列的位置预留嵌入向量
  • 计算和存储开销:了支持较长的序列,可学习位置嵌入需要存储更多的嵌入向量,这会增加模型的参数数量,从而增加计算和存储的开销。对于特别长的序列,这个问题会更加突出。
  • 泛化能力有限:可学习位置嵌入在训练时只能学习到固定范围内位置的嵌入。如果在推理阶段遇到比训练时更长的序列,模型可能无法很好地泛化到这些新的位置。因此,可学习位置嵌入在处理未见过的长序列时可能表现不佳。

旋转位置编码

旋转位置编码提出的出发点在于用绝对位置表示相对位置。回想一下transformer结构中query, key, value(下文用q、k、v表示)的计算
Description
xm表示m位置处的词嵌入。q、k、v在计算时考虑到了token的绝对位置信息m、n。计算q、k的内积得到注意力权重,再与v 计算加权得到最终结果。一种常见的把位置信息添加到q、k、v中的方法是将位置编码与词嵌入直接相加
Description
其中位置编码可以通过训练的方式获得,也可以直接采用正余弦位置编码

目的

虽然q、k计算时用到了绝对位置编码,但是我们希望两者的内积结果能包含相对位置信息,即我们希望得到如下等式
Description

求解

首先我们先把场景限制在2维。利用向量在二维平面的几何性质和他的复数形式,我们可以得到表达式
Description
fq、fk可以进一步表达为矩阵相乘的方式
Description
其中右边第一项为旋转矩阵。

进一步的,我们把2维表达式推广到d维, d为偶数。我们把d维分成d/2个子空间,整理得到如下表达式
Description
其中,右边第一项即为旋转位置编码
Description
我们可以得到
Description

从以上表达式我们可以看出,通过将q, k乘上包含绝对位置信息的旋转位置编码,得到了包含相对位置信息的内积结果。由于旋转位置编码过于稀疏会导致算力浪费,在实际应用中我们并不采用上述形式,而是采用如下计算形式
Description

旋转位置编码的python实现

class sinusoidalPositionalEmbedding(nn.Embedding):def __init__(self, d_model, max_position_embedding, device=None):super().__init__()self.inv_freq = 1 / (10000**(torch.arange(0, d_model, 2).float().to(device) / d_model))self.max_seq_length = max_position_embeddingpos = torch.arange(0, self.max_seq _length, device=self.inv_freq.device, dtype=self.inv_freq.dtype)freq = torch.enisum('i,j->ij', pos, self.inv_freq) emb = torch.cat((freq, freq), -1)dtype = torch.get_defaultself.cos = embed.cos()[None, None, :, :].to(dtype)self.sin = embed.sin()[None, None, :, :].to(dtype)def forward(self, x, seq_length=None):if seq_length > self.max_seq_length:self.max_seq_length = seq_lengthpos = torch.arange(0, self.max_seq _length, device=x.device, dtype=self.inv_freq.dtype)freq = torch.enisum('i,j->ij', pos, self.inv_freq) emb = torch.cat((freq, freq), -1)self.cos = embed.cos()[None, None, :, :].to(x.dtype)self.sin = embed.sin()[None, None, :, :].to(x.dtype)return (self.cos[:, :, :seq_length, ...].to(x.dtype),self.sin[:, :, :seq_length, ...].to(x.dtype))def rotate_half(x):x1 = x[:, :x.shape[-1]//2]x2 = x[:, x.shape[-1]//2:]return torch.cat((-x2, x1), dim = -1)def apply_rotraty_pos_embedding(q, k, cos, sin,position_ids):# The first two dimensions of cos and sin are always 1, so we can `squeeze` them.cos = cos.squeeze(1).squeeze(0)  # [seq_len, dim]sin = sin.squeeze(1).squeeze(0)  # [seq_len, dim]cos = cos[position_ids].unsqueeze(1)  # [bs, 1, seq_len, dim]sin = sin[position_ids].unsqueeze(1)  # [bs, 1, seq_len, dim]q_embed = q * cos + rotate_half(q) * sink_embed = k * cos + rotate_half(k) * sin    return q_embed, k_embed

要注意的一点是,上述实现是参考llama3中的rope实现,而不是原始roformer中rope的实现。llama3中rope的实现参考以下矩阵计算,这也是一种正确的旋转矩阵编码。实际上只要我们的矩阵R满足下图中蓝色的等式,那它就是旋转矩阵的一种有效形式。但要注意在训练和推理时使用相同的形式。

为什么要用旋转位置编码

  • 更好的理解序列信息:通过利用绝对位置表示相对位置,更好的捕捉到了token间的信息,帮助模型更好的理解和处理不同长度的序列
  • 具备远程衰减性:根据《Attention Is All You Need》论文中提出的,RoPE也进行如下设置θi= Description 这种设置具备远程衰减性,即内积结果会随着相对位置的增加而减少。此属性与直觉相一致:相对距离较长的一对token应该具有较少的相关性
  • 兼容性强RoPE编码可以很容易地集成到现有的模型架构中,特别是基于Transformer的模型。其设计简单,不需要对模型架构做大的改动,从而方便了模型的升级和优化。

ref:
Attention Is All You Need
Fixed Positional Embeddings
A Gentle Introduction to Positional Encoding in Transformer Models, Part 1
Transformer Architecture: The Positional Encoding
ROFORMER: ENHANCED TRANSFORMER WITH ROTARY POSITION EMBEDDING

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/778872.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Profinet转Modbus网关运用在DCS系统中与变频器的通讯案例

在DCS(分散控制系统)系统中,Profinet转Modbus网关(XD-MDPN100)的应用是一个典型的工业自动化通讯案例,通过Profinet转Modbus网关(XD-MDPN100)将Profinet协议转换为Modbus协议,从而实现DCS系统与变频器之间的无缝通讯。一、案例背景 随着工业自动化程度的不断提高,DCS…

OpenAI 重大人事变动,联创加入死敌;阿里视频框架 Tora 操控物体运动轨迹丨 RTE 开发者日报

开发者朋友们大家好:这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文章 」、「有看点的 会议 」,但内容仅代表编辑…

以“小”见“大” 打开“折叠”的世界

「 玩出个性,玩出潮流。」他来了!它来了! 昨晚,在年轻人的聚集地——B站,易烊千玺携手nova Flip亮相新生之夜。 带着“新一代”和“潮流”两个标签,这一款小折叠,为华为的折叠屏手机带来更多的可能性,也将激活整个折叠屏手机市场。 待激活的小折叠 经过几年的低迷期之后…

传奇单机版:复古三职业+无需虚拟机一键安装

今天给大家带来一款单机游戏的架设:传奇单机版。沉默版本 三职业 数值不变态 ,没有花里胡哨的东西(比如切割,生肖,时装等功能),客户端为16周年客户端 。另外:本人承接各种游戏架设(单机+联网) 本人为了学习和研究软件内含的设计思想和原理,带了单机架设教程,不适用…

被怼了:acks=all消息也会丢失?

消息队列是面试中一定会被问到的技术模块,虽然它在面试题占比不及并发编程和数据库,但也属于面试中的关键性问题。所以今天我们就来看一道,MQ 中高频,但可能会打破你以往认知的一道面试题。所谓的关键问题指的是这道面试题会影响你整体面试结果。我们在面试消息队列(Messa…

GaussDB安装

环境准备 1、关闭防火墙 systemctl stop firewalld 2、关闭selinux 临时禁用:setenforce 0 永久关闭: vi /etc/selinux/configSELINUX=disabled reboot 3、修改系统环境字符集 echo $LANG export LANG=en_US.UTF-8 永久修改 vi /etc/profile 添加 export LANG=en_US.UTF-8 so…

lambda 中 map 和 flatMap 的区别

lambda 中 map 和 flatMap 的区别https://blog.csdn.net/weixin_52772307/article/details/128944511总结: 当我们需要将具有层级结构的数据展平时,也就是将多层数据转换为单层数据操作时,我们可以使用 flatMap 方法。如果我们只是简单的对流中的数据计算或者转换时,可以使…

python 音频处理(2)——提取PPG特征之whisper库的使用(2.1)

PPG特征 提取PPG特征 whisper库使用提取PPG特征之——whisper库的使用(2.1) 1 安装对应的包方法一(自用): 直接pip即可: pip install openai-whisper 成功后如下图所示方法二: 当时用了他这个方法环境直接崩了,已老实conda install -c conda-forge ffmpeg conda insta…

数字量输入模块:远程组态说明

XD系列插片式远程 I/O模块是兴达易控技术研发的分布式扩展模块。XD系列成套系统主要由耦合器、各种功能I/O模块、电源辅助模块以及终端模块组成。有多种通讯协议总线的耦合器,例如 PROFINET、EtherCAT、Ethernet/IP、Cclink IE以及modbus/TCP等。I/O 模块可分为多通道数字量输…

Rust_learn_1

变量与可变性 变量 声明变量使用let关键字,在默认情况下,变量是不可变的(Immutable)。为此解决该问题,声明变量时在前面加上 mut,就可以使变量可变常量 常量(constant),在绑定值之后也是不可变的,但是与不可变的变量有很多区别:不可以使用mut,常量永远是不变的声明常…

【python海龟画图】代码整理

春联点击查看代码 import turtle t = turtle t.showturtle() t.penup() t.goto(-150,150) t.pendown()t.color(black, red) t.begin_fill() for i in range(2):t.forward(50)t.right(90)t.forward(400)t.right(90) t.end_fill()t.penup() t.goto(100, 150) t.pendown()t.begin…