Positional Encoding的原理和计算

前言

     最近一段时间在看研究生导师发的资料,因为导师是做自然语言处理和知识图谱方向的,所以之前学的CNN的知识暂时用不上啦。NLP和KG方面的知识我之前没怎么接触过,所以最近打算写一系列博文来记录一下学习过程,如果有说得不对的地方请大佬指正。

一、Positional Encoding的介绍

    先说说Positional Encoding(位置编码)的由来。我们都知道传统做NLP邻域使用的模型大多是RNN模型(比如说LSTM、Bi-LSTM),基于RNN模型的训练是一个迭代的过程,也就是把一句话中的每一个词按顺序输入模型中,当RNN处理完当前的这个词时才可以输入下一个词,这种方式虽然可以使RNN按顺序读取语义信息,但是由于是串行计算,当语句较长的时候计算效率较低。谷歌大脑在2017年发布的论文《Attention is all you need》中提出了一种新型的NLP处理模型——TransfomerTransfomer的效果非常好并且可以对语句进行并行计算,也就是同时把一句话中的所有词都输入进去同时计算,大大加快了计算效率。但是问题来了,并行计算好是好,但是我们怎么让模型知道一句话中每个字的顺序信息呢?这就要引出我们的Positional Encoding(位置编码)了。

二、Positional Encoding的计算

2.1 Transfomer的输入

Transformer的输入部分

    首先我会先讲一下Positional Encoding(位置编码)的计算过程,然后在根据我自己的理解来讲Positional Encoding(位置编码)的原理。首先给出Transformer的输入部分,如上图所示。X:[batch size,sequence length]指的是初始输入的多语句矩阵,多语句矩阵通过查表,得到词向量矩阵X_{embedding}:[batch size,sequence length,embedding dimension]batch size指的是句子数,sequence length指的是输入的句子中最长的句子的字数,embedding dimension指的是词向量的长度(通过查表得到)。XX_{embedding}的示意图如下图所示。

两个变量的示意图

2.2 计算

    如上图所示word embedding指的是词向量由每个词根据查表得到,pos embedding就是我们要求的Positional Encoding,也就是位置编码。可以看到word embedding和pos embedding逐点相加得到composition,即既包含语义信息又包含位置编码信息的最终矩阵。pos embedding是根据pos(当前字符在句子中的位置)计算得到的,具体公式为:

其中pos指当前字符在句子中的位置(如:“你好啊”,这句话里面“你”的pos= 0),d_{model}指的是word embedding的长度(比如说:查表得到“民主”这个词的word embedding为[1,2,3,4,5],则d_{model}= 5),i的取值范围是:i= 0,1,...,d_{model}-1。当i的值为偶数是使用上面那条公式,当i的值为奇数时使用下面那条公式。当pos=3,d_{model}= 128Positional Encoding(或者说是pos embedding)的计算结果为:

现在是不是好理解一点了,每一个字所计算出来的Positional Encoding并不是一个值而是一个向量,他的长度和这个字的word embedding的长度一致,从而方便他们两个逐点相加得到既包含word embedding又包含位置信息的最终向量。

三、Positional Encoding的原理

    这一节我们来讨论一下Positional Encoding的原理,写这一节的时候我参考了知乎的这个回答,非常感谢原作者!

  1. 不知道大家有没有想过为什么我们要用这么复杂的方法计算Positional Encoding,那让我们先来构思一个简单一点的Positional Encoding表达方法。首先,给定一个长为T的文本,最简单的位置编码就是计数,即使用0,1,...,T-1作为文本中每个字的位置编码(例如第2个字的Positional Encoding=[1,1,...,1])。但是这种编码有两个缺点:1. 如果一个句子的字数较多,则后面的字比第一个字的Positional Encoding大太多,和word embedding合并以后难免会出现特征在数值上的倾斜;2. 这种位置编码的数值比一般的word embedding的数值要大,对模型可能有一定的干扰。
  2. 为了避免上述的问题,我们开始考虑把归一化,最简单的就是直接除以T,即使用0,\frac{1}{T},...,\frac{T-1}{T}(例如第2个字的Positional Encoding=[1/T,1/T,...,1/T])。这样固然使得所有位置编码都落入[0,1]区间,但是问题也是显著的:不同长度文本的位置编码步长是不同的,在较短的文本中紧紧相邻的两个字的位置编码差异,会和长文本中相邻数个字的两个字的位置编码差异一致,如下图所示。

由于上面两种方法都行不通,于是谷歌的科学家们就想到了另外一种方法——使用三角函数PE=sin(pos)。优点是:1、可以使PE分布在[0,1]区间。2、不同语句相同位置的字符PE值一样(如:当pos=0时,PE=0)。但是缺点在于三角函数具有周期性,可能出现pos值不同但是PE值相同的情况。于是我们可以在原始PE的基础上再增加一个维度:PE=[sin(\frac{pos}{\alpha }),sin(\frac{pos}{\beta })],虽然还是可能出现pos值不同但是PE值相同的情况,但是整个PE的周期是不是明显变长了。那如果我们把PE的长度加长到和word embedding一样长呢?就像PE=[sin(\frac{pos}{10000^{0/d_{model}}}),sin(\frac{pos}{10000^{2/d_{model}}}),...,sin(\frac{pos}{10000^{2i/d_{model}}})](是不是有Positional Encoding计算公式内味了),PE的周期就可以看成是无限长的了,换句话说不论pos有多大都不会出现PE值相同的情况。然后谷歌的科学家们为了让PE值的周期更长,还交替使用sin/cos来计算PE的值,于是就得到了论文中的那个计算公式:

参考文献

  • 74
    点赞
  • 178
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
以下是一个简单的 Python 代码,用于实现 Transformer 中的 Positional Encoding: ```python import torch import math class PositionalEncoding(torch.nn.Module): def __init__(self, d_model, max_seq_len=200): super().__init__() self.d_model = d_model self.max_seq_len = max_seq_len # Compute the positional encodings once in log space pe = torch.zeros(max_seq_len, d_model) position = torch.arange(0, max_seq_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) self.register_buffer('pe', pe) def forward(self, x): # Add positional encoding to all inputs x = x * math.sqrt(self.d_model) seq_len = x.size(1) if seq_len > self.max_seq_len: # Truncate long sequences x = x[:, :self.max_seq_len, :] else: # Pad short sequences padding = torch.zeros(x.size(0), self.max_seq_len - seq_len, x.size(2), device=x.device) x = torch.cat((x, padding), dim=1) x = x + self.pe[:seq_len, :] return x ``` 这个代码中的 PositionalEncoding 类使用了 PyTorch 的 nn.Module 类,所以可以很容易地在 PyTorch 模型中使用。在构造函数中,我们首先计算了一个大小为 `(max_seq_len, d_model)` 的矩阵 `pe`,其中 `max_seq_len` 是序列的最大长度,`d_model` 是嵌入向量的维度。这个矩阵是通过计算一些正弦和余弦函数得到的,公式为: $$ PE_{pos, 2i} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) \\ PE_{pos, 2i+1} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$ 其中 $pos$ 是位置,$i$ 是维度。这个公式可以保证对于不同的位置和维度,得到的值是不同的,从而使得模型可以学习到位置信息。 在 `forward` 方法中,我们把输入张量 `x` 和 `pe` 相加,并返回结果。在相加之前,我们还对 `x` 进行了一些处理,包括将其乘以 $\sqrt{d_{model}}$(这个处理与 Transformer 中的 Multi-Head Attention 有关),以及根据 `max_seq_len` 对序列进行截断或填充。
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值