位置编码和RoPE
前言
关于位置编码和RoPE
应用广泛,是很多大模型使用的一种位置编码方式,包括且不限于LLaMA、baichuan、ChatGLM等等
第一部分 transformer原始论文中的标准位置编码
RNN的结构包含了序列的时序信息,而Transformer却完全把时序信息给丢掉了,比如“他欠我100万”,和“我欠他100万”,两者的意思千差万别,故为了解决时序的问题,Transformer的作者用了一个绝妙的办法:位置编码(Positional Encoding)
1.1 标准位置编码的起源
即将每个位置编号,从而每个编号对应一个向量,最终通过结合位置向量和词向量,作为输入embedding,就给每个词都引入了一定的位置信息,这样Attention就可以分辨出不同位置的词了,具体怎么做呢?
- 如果简单粗暴的话,直接给每个向量分配一个数字,比如1到1000之间
- 也可以用one-hot编码表示位置
3. transformer论文中作者通过sin函数和cos函数交替来创建 positional encoding,其计算positional encoding的公式如下
其中,pos相当于是每个token在整个序列中的位置,相当于是0, 1, 2, 3...(看序列长度是多大,比如10,比如100),dmodel代表位置向量的维度(也是词embedding的维度,transformer论文中设置的512维)
不要小看transformer的这个位置编码,不少做NLP多年的人也不一定对其中的细节有多深入,而网上大部分文章谈到这个位置编码时基本都是千篇一律、泛泛而谈,很少有深入,故本文还是细致探讨下
1.2 标准位置编码的示例:多图多举例
考虑到一图胜千言 一例胜万语,举个例子,当我们要编码「我 爱 你」的位置向量,假定每个token都具备512维,如果位置下标从0开始时,则根据位置编码的计算公式可得『且为让每个读者阅读本文时一目了然,我计算了每个单词对应的位置编码示例(在此之前,这些示例在其他地方基本没有)』
当对上的单词「我」进行位置编码时,它本身的维度有512维
当对上的单词「爱」进行位置编码时,它本身的维度有512维
1.3 标准位置编码的coding实现
代码实现如下
“”“位置编码的实现,调用父类nn.Module的构造函数”“”
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout) # 初始化dropout层
# 计算位置编码并将其存储在pe张量中
pe = torch.zeros(max_len, d_model) # 创建一个max_len x d_model的全零张量
position = torch.arange(0, max_len).unsqueeze(1) # 生成0到max_len-1的整数序列,并添加一个维度
# 计算div_term,用于缩放不同位置的正弦和余弦函数
div_term = torch.exp(torch.arange(0, d_model, 2) *
-(math.log(10000.0) / d_model))
# 使用正弦和余弦函数生成位置编码,对于d_model的偶数索引,使用正弦函数;对于奇数索引,使用余弦函数。
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0) # 在第一个维度添加一个维度,以便进行批处理
self.register_buffer('pe', pe) # 将位置编码张量注册为缓冲区,以便在不同设备之间传输模型时保持其状态
# 定义前向传播函数
def forward(self, x):
# 将输入x与对应的位置编码相加
x = x + Variable(self.pe[:, :x.size(1)],
requires_grad=False)
# 应用dropout层并返回结果
return self.dropout(x)
这里面其实有很大的一个关键,但大部分资料甚至RoPE原始论文都不会给你特别强调出来,即为何要构造这么一个等式呢?
- 原因在于左边算是q和k向量的内积,而这恰好是transformer计算自注意力机制的核心一步,右边等式则意味着m与n的相对位置
- 如此一来,该等式便把“q和k的内积”与“它们的相对位置”给串起来了 也如阿荀所说,左边是含有各自绝对位置信息的q向量和k向量,而这个等式就是RoPE追求的目标,物理含义就是通过显式传入绝对位置信息实现与传入相对位置信息对等的情况
所以简单来说 RoPE 的 self-attention 操作的流程是
- 对于 token 序列中的每个词嵌入向量,首先计算其对应的 query 和 key 向量
- 然后对每个 token 位置都计算对应的旋转位置编码
- 接着对每个 token 位置的 query 和 key 向量的元素按照 两两一组 应用旋转变换
- 最后再计算 query 和 key 之间的内积得到 self-attention 的计算结果