transformer 是最新的处理 序列到序列 问题的架构,单纯由 self-attention 组成,其优良的可并行性以及可观的表现提升,让它在NLP领域大受欢迎,并引入到cv领域。

transformer vs CNN RNN

cnn 利用的是卷积,与 transformer 比较的话,卷积可理解为 局部注意力

rnn 也是处理序列问题的,把 rnn 的循环结构换成 self-attention 就变成 transformer 了;

下图是 transformer 的网络结构图

注意,在 encode 部分是 attention,即可以看到上下文,前文 和 后文,而 decode 部分是 mask attention,只能看到前文

encode 部分类似 完形填空,decode 部分知道前文预测后文,有生成的作用 

Position Encoding

为什么需要 Position Encoding

在任何语言中,词语的位置和顺序对句子的意思都至关重要;

在 RNN 中,以序列的模式逐个处理句子中的词语,这种网络结构天然保存了词语的顺序信息

但在 词嵌入 中,单词并不是以 词在 句子中的顺序进行 embedding 的,而是很随意的把所有词进行 embedding,而不考虑词的顺序;

transformer 的输入其实就是 词向量,而词向量已经丧失了词的位置和顺序关系,故需加入该信息,就是 Position Encoding;

一句话概括,Positional Encoding就是句子中词语相对位置的编码,让Transformer保留词语的位置信息

 

怎么做 Position Encoding

要加入 词语 在 句子 中的 位置信息,有几种思路:

1. 给每个词语添加一个线性增长的时间戳:即每个词一个下标,如 从0开始,第一个词0,第二个词1,依次...,第N个词N,

----但是 每个句子长度不一,如果句子很长,N会很大,Position Encoding 和 Word Embedding 合并后,模型可能更多关注 Position Encoding(大),这肯定是有问题的;

2. 归一化的时间戳:既然长句子N很大,那归一化处理,把 encoding 限制在 [0 1]之间呢?

----这会造成不同长度的句子,相邻词之间的步长不一致,使得 encoding 无法正确描述 词与词 之间的关系;

故理想 position encoding 应具备以下条件

对于每个位置(时间节点)的词语,都具备独一无二的编码

编码有一定的值域范围,避免比 word embedding 大很多,使得模型倾向于 position

需要体现一定的相对次序关系,并且在一定范围内的编码差异不应该依赖于文本长度,具有一定translation invariant平移不变性

 

论文公式

Pt 最终是一个 值域在 [-1,1] 之间的高维矩阵(向量)

 

 

Sinusoidal位置编码的每个分量都是正弦或余弦函数,所有每个分量的数值都具有周期性。如下图所示,每个分量都具有周期性,并且越靠后的分量,波长越长,频率越低。

这是一个非常重要的性质,基于RoPE的大模型的长度外推工作,与该性质有着千丝万缕的关联,后续我们会进行分享。

 

Sinusoidal位置编码还具有远程衰减的性质,具体表现为:对于两个相同的词向量,如果它们之间的距离越近,则他们的内积分数越高,反之则越低。如下图所示,我们随机初始化两个向量q和k,将q固定在位置0上,k的位置从0开始逐步变大,依次计算q和k之间的内积。我们发现随着q和k的相对距离的增加,它们之间的内积分数震荡衰减。

因为Sinusoidal位置编码中的正弦余弦函数具备周期性,并且具备远程衰减的特性,所以理论上也具备一定长度外推的能力

 

代码实现

只是实现了上面的公式,参考一下就行了

class PositionalEncoding(nn.Module):

    def __init__(self, d_model, max_len=5000):
        super(PositionalEncoding, self).__init__()       
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0).transpose(0, 1)
        #pe.requires_grad = False
        self.register_buffer('pe', pe)

    def forward(self, x):
        return x + self.pe[:x.size(0), :]

 

论文为什么这么做?

第二篇参考资料简单解释了一下,我觉得没有证明为什么,只是形象比喻了一下,仅供参考吧

               128维位置编码2D示意图

 

 

 

 

参考资料:

https://blog.csdn.net/qq_40744423/article/details/121930739  详解Transformer中的Positional Encoding

https://zhuanlan.zhihu.com/p/338592312  一文教你彻底理解Transformer中Positional Encoding

https://www.jianshu.com/p/251a0530bc0e  Transformer中的positional encoding