跟传统的 RNN 序列模型不同,在 Transformer 编码结构中,并没有针对词汇位置信息的处理,因为纯粹的 Attention 模块是无法捕捉输入顺序的,因此需要在 Embedding 层后加入位置编码器,将词汇位置不同,可能会产生不同语义信息,加入到词嵌入张量中。

Transformer 采用的是正余弦的绝对位置编码,这种编码方式可以保证,不同位置在所有维度上不会被编码到完全一样的值,从而使每个位置都获得独一无二的编码。通俗理解,就是将位置信息编码成向量后,每个位置对应的向量都是不同的。

公式解读

对于一个位置来说,pos 是固定的,d_model 也是常量,变化的就是 2i 值。

x           0    1    2    3    4    5    6    7
sin         0         2         4         6    
cos              0         2         4         6    
(x//2)*2    0    0    2    2    4    4    6    6

代码示例

1、封装位置编码层

这个项目中,大家要建立层的意识。位置编码层,是接受上一层(文本嵌入层)的结果,加上位置编码之后,再输出给下一层。

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super().__init__()

    def forward(self, x):
        pass

2、计算位置编码值

self.pe = torch.zeros(max_len, d_model)
for pos in range(max_len):
    for j in range(d_model):
        angle = pos / math.pow(10000, (j//2)*2 / d_model)
        if j % 2 == 0:
            self.pe[pos][j] = math.sin(angle)
        else:
            self.pe[pos][j] = math.cos(angle)

3、词向量和位置编码相加

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

pe = PositionalEncoding(8)
output2 = pe(output1)
print(output2)

4、加入Dropout

def __init__(self, d_model, dropout=0.1, max_len=5000):
    super(PositionalEncoding, self).__init__()
    self.dropout = nn.Dropout(dropout)

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

这节课的标题叫做,位置编码层的直观代码实现,换句话说,就是还有不直观的实现方法。为了方便大家理解,在修改 pe-tensor 数值的时候,是一个一个修改,用到了两层 for 循环,时间复杂度很高。下节课,再给大家讲解一种,利用 tensor 特殊性质,不是那么直观,但是比较高效的生成方法。

本文链接:http://ichenhua.cn/edu/note/652

版权声明:本文为「陈华编程」原创课程讲义,请给与知识创作者起码的尊重,未经许可不得传播或转售!