type
status
date
slug
summary
tags
category
icon
password
Last edited time
Mar 16, 2024 07:01 AM
Transformer中的位置编码——出现原因,变化历程
📝 主旨内容
为什么要有位置编码
RNN每个token分别计算一次,计算的同时会知道上一个token的计算结果
手机坏了,好想要256g的苹果啊
Transform的输入是一整排的tokens同时进行计算
self-attention的运算是无向的(在无位置信息的情况下,交换序列内任意两个单词并不影响输出)
在self-attention模型中,输入是一整排的tokens,对于人来说,我们很容易知道tokens的位置信息,比如:
(1)绝对位置信息。a1是第一个token,a2是第二个token......
(2)相对位置信息。a2在a1的后面一位,a4在a2的后面两位......
(3)不同位置间的距离。a1和a3差两个位置,a1和a4差三个位置....
但是这些对于self-attention来说,是无法分辩的信息,因为self-attention的运算是无向的。因为,我们要想办法,把tokens的位置信息,喂给模型。
位置编码的用法
input = input_embedding + positional_encoding
为什么是add而不是concatenate
假设位置编码为one-hot形式,concatenate到输入向量上进行词嵌入:
结果等价于先对位置索引和输入序列分别进行词嵌入,再相加。此时的位置编码是一种位置嵌入(position embedding)
位置编码的演变
用整型值标记位置
一种自然而然的想法是,给第一个token标记1,给第二个token标记2...,以此类推。
这种方法产生了以下几个主要问题:
(1)模型可能遇见比训练时所用的序列更长的序列。不利于模型的泛化。
(2)模型的位置表示是无界的。随着序列长度的增加,位置值会越来越大。
用[0,1]范围标记位置
为了解决整型值带来的问题,可以考虑将位置值的范围限制在[0, 1]之内,其中,0表示第一个token,1表示最后一个token。比如有3个token,那么位置信息就表示成[0, 0.5, 1];若有四个token,位置信息就表示成[0, 0.33, 0.69, 1]。
但这样产生的问题是,当序列长度不同时,token间的相对距离是不一样的。例如在序列长度为3时,token间的相对距离为0.5;在序列长度为4时,token间的相对距离就变为0.33。
因此,我们需要这样一种位置表示方式,满足于:
(1)它能用来表示一个token在序列中的绝对位置
(2)在序列长度不同的情况下,不同序列中token的相对位置/距离也要保持一致
(3)可以用来表示模型在训练过程中从来没有看到过的句子长度。
用二进制向量标记位置
考虑到位置信息作用在input embedding上,因此比起用单一的值,更好的方案是用一个和input embedding维度一样的向量来表示位置。这时我们就很容易想到二进制编码。如下图,假设d_model = 3,那么我们的位置向量可以表示成:
这下所有的值都是有界的(位于0,1之间),且transformer中的d_model本来就足够大,基本可以把我们要的每一个位置都编码出来了。
但是这种编码方式也存在问题:这样编码出来的位置向量,处在一个离散的空间中,不同位置间的变化是不连续的。假设d_model = 2,我们有4个位置需要编码,这四个位置向量可以表示成[0,0],[0,1],[1,0],[1,1]。我们把它的位置向量空间做出来:
如果我们能把离散空间(黑色的线)转换到连续空间(蓝色的线),那么我们就能解决位置距离不连续的问题。同时,我们不仅能用位置向量表示整型,我们还可以用位置向量来表示浮点型。
用周期函数(sin)来表示位置
回想一下,现在我们需要一个有界又连续的函数,最简单的,正弦函数sin就可以满足这一点。我们可以考虑把位置向量当中的每一个元素都用一个sin函数来表示,则第t个token的位置向量可以表示为:
结合下图,来理解一下这样设计的含义。图中每一行表示一个,每一列表示中的第i个元素。旋钮用于调整精度,越往右边的旋钮,需要调整的精度越大,因此指针移动的步伐越小。每一排的旋钮都在上一排的基础上进行调整(函数中t的作用)。通过频率来控制sin函数的波长,频率不断减小,则波长不断变大,此时sin函数对t的变动越不敏感,以此来达到越向右的旋钮,指针移动步伐越小的目的。 这也类似于二进制编码,每一位上都是0和1的交互,越往低位走(越往左边走),交互的频率越慢。
由于sin是周期函数,因此从纵向来看,如果函数的频率偏大,引起波长偏短,则不同t下的位置向量可能出现重合的情况。比如在下图中(d_model = 3),图中的点表示每个token的位置向量,颜色越深,token的位置越往后,在频率偏大的情况下,位置相连点连成了一个闭环,靠前位置(黄色)和靠后位置(棕黑色)竟然靠得非常近:
为了避免这种情况,我们尽量将函数的波长拉长。一种简单的解决办法是同一把所有的频率都设成一个非常小的值。因此在transformer的论文中,采用了
这个频率(这里i其实不是表示第i个位置,但是大致意思差不多,下面会细说)
总结一下,到这里我们把位置向量表示为:
其中,
用sin和cos交替来表示位置
目前为止,我们的位置向量实现了如下功能:
(1)每个token的向量唯一(每个sin函数的频率足够小)
(2)位置向量的值是有界的,且位于连续空间中。模型在处理位置向量时更容易泛化,即更好处理长度和训练数据分布不一致的序列(sin函数本身的性质)
那现在我们对位置向量再提出一个要求,不同的位置向量是可以通过线性转换得到的。这样,我们不仅能表示一个token的绝对位置,还可以表示一个token的相对位置,即我们想要:
这里,T表示一个线性变换矩阵。观察这个目标式子,联想到在向量空间中一种常用的线形变换——旋转。在这里,我们将t想象为一个角度,那么就是其旋转的角度,则上面的式子可以进一步写成:
三角函数知识
有了这个构想,我们就可以把原来元素全都是sin函数的做一个替换,我们让位置两两一组,分别用sin和cos的函数对来表示它们,则现在我们有:
在这样的表示下,我们可以很容易用一个线性变换,把转变为:
Transformer位置编码定义
有了上面的演变过程后,现在我们就可以正式来看transformer中的位置编码方法了。
定义:
- t是这个token在序列中的实际位置(例如第一个token为1,第二个token为2...)
- 是这个token的位置向量,表示这个位置向量里的第i个元素
- 是这个token的维度(在论文中,是512)
则可以表示为:
这里:,
把512维的向量两两一组,每组都是一个sin和一个cos,这两个函数共享同一个频率,一共有256组,由于我们从0开始编号,所以最后一组编号是255。sin/cos函数的波长(由决定)则从 2 增长到 2∗10000
在上面公式的定义下,时间步p和时间步p+k的位置编码的内积,是与p无关,只与k有关的定值(不妨自行证明下试试)。也就是说,任意两个相距k个时间步的位置编码向量的内积都是相同的,这就相当于蕴含了两个时间步之间相对位置关系的信息。此外,每个时间步的位置编码又是唯一的,这两个很好的性质使得上面的公式作为位置编码是有理论保障的。下面是位置编码模块的代码实现。
Transformer位置编码可视化
下图是一串序列长度为50,位置编码维度为128的位置编码可视化结果:
可以发现,由于sin/cos函数的性质,位置向量的每一个值都位于[-1, 1]之间。同时,纵向来看,图的右半边几乎都是蓝色的,这是因为越往后的位置,频率越小,波长越长,所以不同的t对最终的结果影响不大。而越往左边走,颜色交替的频率越频繁。
在Transformer的论文中,比较了用positional encoding和learnable position embedding(让模型自己学位置参数)两种方法,得到的结论是两种方法对模型最终的衡量指标差别不大。不过在后面的BERT中,已经改成用learnable position embedding的方法了,也许是因为positional encoding在进attention层后一些优异性质消失的原因(猜想)。Positional encoding有一些想象+实验+论证的意味,而编码的方式也不只这一种,比如把sin和cos换个位置,依然可以用来编码。
learnable position embedding
可学习(Learnable)位置编码是指将位置编码当作可训练参数,比如输入序列(经过嵌入层后)的大小为,则随机初始化一个的矩阵作为位置编码,随训练过程更新。可学习位置编码的缺点是没有外推性,即如果预训练序列的最大长度为,则无法处理长度超过n的序列。此时可以将超n部分的位置编码随机初始化并微调。
正弦位置编码:
优点:
- 通用性:这些编码可以为任何序列长度制作,为更长的未见序列提供灵活性。
- 固定值:预定义的数学函数消除了优化的需要,可能减少了过拟合的风险。
- 可解释性:正弦性质中的波形模式可能会根据位置指导模型的注意力。
理想的使用场景:
- 变量序列长度:非常适合长度变化的序列或那些超过训练长度的序列。
- 复制原始Transformer设计:如果要复制原始Transformer的结果或其实现,正弦编码是首选。
可学习位置编码:
优点:
- 适应性:模型可以为特定任务学习出最佳表示,可能提高性能。
- 简单性:这种方法的概念简单性与学习token的embedding无缝对齐。
- 一致性:如果其他功能(如单词)使用了可学习的embedding,这种方法更加合适。
理想的使用场景:
- 固定或已知序列长度:当序列长度的范围是已知的,并且不超过训练的最大长度。
- 特定领域的任务:对于位置具有独特意义的任务,定制的位置表示可能会有所裨益。
- 实验:探索新架构或任务的好处。
ViT中的位置编码
positional embedding
和class token
由nn.Parameter()
定义,该函数会将送到其中的Tensor注册到Parameters列表,随模型一起训练更新🤗 总结归纳
位置编码在非Transform架构是否有用?
从经典的卷积架构类比
以全连接网络的计算推导
📎 参考文章
- 作者:ziuch
- 链接:https://ziuch.com/article/Positional-Encoding-in-Transform
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章