深度学习入门--Transformer中的Positional Encoding详解

大家好,我是CuddleSabe,目前大四在读,深圳准入职算法工程师,研究主要方向为多模态(VQA、ImageCaptioning等),欢迎各位佬来讨论!
我最近在有序地计划整理CV入门实战系列及NLP入门实战系列。在这两个专栏中,我将会带领大家一步步进行经典网络算法的实现,欢迎各位读者(da lao)订阅

Transformer中的Positional Encoding详解

Positional Encoding

    • Transformer中的Positional Encoding详解
  • 为什么要有Postional Encoding
    • Encoding的选择
  • Postional Encoding公式推导变换
  • 实现代码
  • 编码可视化

为什么要有Postional Encoding

因为Self-Attention相对于传统的RNN在输入计算时没有输入先后顺序,而是采用并行化的思想来加快运算,这样Self-Attention在前一个token结果还没有出来的时候便可以同时处理下一个token。但是这样在并行化提升运算速度的同时也会带来一个问题,那便是丧失了序列的顺序性。因此为了不损失顺序性,在将序列输入之前还需要结合位置编码(Positional Encoding)。
深度学习入门--Transformer中的Positional Encoding详解_第1张图片

Encoding的选择

这里我们先考虑最简单的一种Encoding方式:用token的序号来进行编码。
即:北京欢迎您,我爱它。
P E i = i PE_{i}=i PEi=i
那么这种最简单的Encoding结果为(不考虑标点符号):

token Encoded
‘北’ 0
‘京’ 1
‘欢’ 2
‘迎’ 3
‘您’ 4
‘我’ 5
‘爱’ 6
‘它’ 7

可以看到,这种编码方式的最明显缺点是:
对于特长序列,编码长度会不可控。

那么再看一种编码方式
P E i = i l e n PE_{i}=\frac{i}{len} PEi=leni
那么这样编码结果为(不考虑标点符号):

token Encoded
‘北’ 0 .000
‘京’ 0.125
‘欢’ 0.250
‘迎’ 0.375
‘您’ 0.500
‘我’ 0.625
‘爱’ 0.750
‘它’ 0.875
这样编码结果限制为0到1,
但同时也会带来一个新的问题,那就是编码结果的尺度随着不同序列长度变化也在变化。
例如,10个单词和100个单词的句子经过编码后,相邻单词的位置编码差距不是同一个数量级,这种尺度不统一的话,便无法进行训练。

因此,一种良好的位置编码方式需要达到以下两点:
1.位置编码长度不会随着序列长度而变化,而是限制在一定范围
2.位置序列的变化尺度需要统一

数学基础好的同学可能已经猜到了,满足这两条的便是———三角函数!

公式如下,如果想要详细解释可查看这篇文章:
地址:Positional Encoding详细解释

公式:
P E p o s , 2 i = s i n ( p o s / 1000 0 2 i / d ) PE_{pos,2i}=sin(pos/10000^{2i/d}) PEpos,2i=sin(pos/100002i/d)
P E p o s , 2 i + 1 = c o s ( p o s / 1000 0 2 i / d ) PE_{pos,2i+1}=cos(pos/10000^{2i/d}) PEpos,2i+1=cos(pos/100002i/d)
这里d为Embedding时的d_model参数,也是Embedding时一个单词的特征数,详细请看我的另一篇文章:
PyTorch文字处理及Embedding

Postional Encoding公式推导变换

我们已知公式如下,那么我们如果使用PyTorch实现呢?
P E p o s , 2 i = s i n ( p o s / 1000 0 2 i / d ) PE_{pos,2i}=sin(pos/10000^{2i/d}) PEpos,2i=sin(pos/100002i/d)
P E p o s , 2 i + 1 = c o s ( p o s / 1000 0 2 i / d ) PE_{pos,2i+1}=cos(pos/10000^{2i/d}) PEpos,2i+1=cos(pos/100002i/d)
这里我们需要将公式进行变换
我们设
T 2 i = 1 / 1000 0 2 i / d T_{2i}=1/10000^{2i/d} T2i=1/100002i/d T 2 i + 1 = 1 / 1000 0 2 i / d T_{2i+1}=1/10000^{2i/d} T2i+1=1/100002i/d
那么进一步,两边取对数
l n T = − 2 i d l n 10000 lnT=-\frac{2i}{d}ln10000 lnT=d2iln10000
进而
T = e − 2 i d l n 10000 T=e^{-\frac{2i}{d}ln10000} T=ed2iln10000
至于这里为什么不直接使用初始公式进行计算而是推导到现在,
个人理解是因为可以避免10000的幂作为分母而带来的计算机的计算误差。

实现代码

class PositionalEncoding(nn.Module):
    "实现PE功能"
    def __init__(self, d_model, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
         
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0., max_len).unsqueeze(1)
        div_term = torch.exp(torch.arange(0., d_model, 2) *
                             -(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)           # [1, max_len, d_model]
        self.register_buffer('pe', pe)
         
    def forward(self, x):
        x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)   # 这里是Embedding与Positional Encoding相结合
        return self.dropout(x)

编码可视化

将pe画出,得到以下图像

深度学习入门--Transformer中的Positional Encoding详解_第2张图片

你可能感兴趣的:(深度学习入门系列,算法,深度学习,python,人工智能)