Transformer输入Embedding及位置编码详解

系列文章目录



前言

我们在本文主要介绍Transformer中的输入Embedding及位置编码。

一、输入Embedding

1. 原理介绍

Embedding是一种将离散的输入数据(如单词、字符等)转换为连续的向量表示的方法。由于计算机无法直接处理一个单词或者一个汉字,所以需要Embedding把一个token转化成计算机可以识别的向量。
在Transfomrer中,我们有一个词表{‘P’,:0, ‘我’:1,‘爱’:2,’吃‘:3,‘苹果’:4,‘香蕉’:5,‘喜欢’:6},那么假如我们想输入这么一句话:‘我喜欢吃香蕉’,那么可以变成这么一个序列:[1,6,3,5],接着我们使用Embedding操作将输入映射到高维空间,每一个token映射为512维,则输入是1x4维的向量,输出则是4x512维。

输入:1x4维 [1,6,3,5]
输出:4x512维
‘1’ [0.0325,0.0357,0.0783…-0.0267,0.0832] (长为512)
‘6’ [0.0586,0.0736,0.1023…0.0691, -0.0356]
‘3’ [0.0672,0.1023,0.1820…-0.0145,-0.0235]
‘5’ [0.1035,0.1350,0.2608…-0.0589,0.01068]

2 .代码实现

在pytorch中实现Embedding的方法也很简单,直接调用nn.Embedding就可以了。

import torch
import torch.nn as nn

# 第一个参数为词表的大小,在本文的例子中为7。
# 第二个参数为映射到高维度后词向量的维度,本文中为512。
emb = nn.Embedding(7,512)

x = [1,6,3,5]
#emb的输入为LongTensor类型,在这里进行类型转换。
x = torch.LongTensor(x)

emb_x =emb(x)

print('x',x)
print('emb',emb,emb.weight)
print('emb_x',emb_x)
print('emb.weight[1]',emb.weight[1])

在这里插入图片描述

通过输出可以看到,nn.Embedding函数也具有参数,其维度大小为:(词表大小x映射后词向量维度),本文中词表大小为7,我们想把每个词映射成512维,所以emb的参数大小维度是7x512。同时embedding的参数也是需要训练的,其也是模型参数的一部分。
深入理解:通过观察emb_x的输出和emb.weight[1]的参数,可以发现,数字1进行embedding编码后的词向量,其实就是emb.weight[1]的参数。也就是说,如果输入为[1,6,3,5],那么对于’1’的词向量编码实际上就是把1作为索引,然后取出emb参数的第一行作为‘1’最终的embedding输出;对于‘6’的词向量编码输出就是把6作为索引,取出emb参数的第六行作为‘6’最终的embedding输出。
注意:在pytorch中,nn.Embedding的输入是LongTensor类型的,大家在使用embedding的时候不要忘记转换数据类型

二、位置编码

1. 原理介绍

由于Transfomrer的输入是一整个句子,是并行计算的,缺少位置信息,输入为’我爱吃香蕉‘和’我吃香蕉爱‘这两个句子最后在计算注意力的时候结果是一样的。所以我们需要在输入的时候为句子加入位置信息。关于Transformer中的位置编码的推导网上有很多,为了节约时间,本文就不再介绍了,直接进入主题。
Transformer中的位置编码公式为:
     P E ( p o s , 2 i ) = s i n ( p o s 1000 0 2 i / d m o d e l ) ( 1 ) PE_{(pos,2i)} = sin(\frac{pos} {10000^{2i/d_{model}}})(1) PE(pos,2i)=sin(100002i/dmodelpos)1
     P E ( p o s , 2 i + 1 ) = c o s ( p o s 1000 0 2 i / d m o d e l ) ( 2 ) PE_{(pos,2i+1)} = cos(\frac{pos} {10000^{2i/d_{model}}})(2) PE(pos,2i+1)=cos(100002i/dmodelpos)2
其中, p o s pos pos指的是词在句子中的位置,例如’我爱吃香蕉’中,‘我’在句子中的位置是第一个,所以pos是1,'香蕉‘在词典中是一个词,其所在位置是4,所以’香蕉‘的pos是4; i i i指在embedding后的词向量中所在的位置,所在位置如果为偶数( 2 i 2i 2i),则该位置用 s i n sin sin,否则用 c o s cos cos d m o d e l d_{model} dmodel是进行embedding后词向量的维度。
接下来我们举一个例子来说明。

输入:[1,3,6]
Embedding输出: 3x4维
‘1’ [0.0325, 0.0357, 0.0783, -0.0267]
‘3’ [0.0586, 0.0736, 0.1023, 0.0691]
‘6’ [0.0672, 0.1023, 0.1820, -0.0145]

假设输入是1x3维,进行embedding,每个词向量的维度被映射成4维,输出维3x4维,d_model为4。所以我们根绝公式可以算出embedding后的输出每个位置的位置编码是多少。

位置编码表
pos\i0123
0 s i n ( 0 1000 0 0 / 4 ) sin(\frac{0} {10000^{0/4}}) sin(100000/40) c o s ( 0 1000 0 0 / 4 ) cos(\frac{0} {10000^{0/4}}) cos(100000/40) s i n ( 0 1000 0 2 / 4 ) sin(\frac{0} {10000^{2/4}}) sin(100002/40) c o s ( 0 1000 0 2 / 4 ) cos(\frac{0} {10000^{2/4}}) cos(100002/40)
1 s i n ( 1 1000 0 0 / 4 ) sin(\frac{1} {10000^{0/4}}) sin(100000/41) c o s ( 1 1000 0 0 / 4 ) cos(\frac{1} {10000^{0/4}}) cos(100000/41) s i n ( 1 1000 0 2 / 4 ) sin(\frac{1} {10000^{2/4}}) sin(100002/41) c o s ( 1 1000 0 2 / 4 ) cos(\frac{1} {10000^{2/4}}) cos(100002/41)
2 s i n ( 2 1000 0 0 / 4 ) sin(\frac{2} {10000^{0/4}}) sin(100000/42) c o s ( 2 1000 0 0 / 4 ) cos(\frac{2} {10000^{0/4}}) cos(100000/42) s i n ( 2 1000 0 2 / 4 ) sin(\frac{2} {10000^{2/4}}) sin(100002/42) c o s ( 2 1000 0 2 / 4 ) cos(\frac{2} {10000^{2/4}}) cos(100002/42)

最后,输入句子embedding后的输出(3x4)与算出的位置编码表(3x4)相加得到最终送进Transformer编码器中的输入。
注意:位置编码中的每个位置的编码是直接通过公式算出来的,不用进行训练,也不参与训练。位置编码表在Transformer中提前算出来存好,每次有输入的时候直接与存好的位置编码表相加即可。

2. 代码实现

import torch
import torch.nn as nn
import math

class PositionEncoding(nn.Module):
    def __init__(self,max_len,d_model,device): 
    #d_model为映射后词向量维度,max_len为输入句子的最大长度
        super(PositionEncoding,self).__init__()
        #self.PE指的是要算的位置编码表
        self.PE = torch.zeros(max_len,d_model,device = device)
        self.PE.requires_grad = False
        #pos指的是输入句子中的最大位置序列。[1,2,3...max_len]。
        pos = torch.arange(0,max_len,device=device).float().unsqueeze(1)
        _2i = torch.arange(0,d_model,2,device=device).float()
        #直接套用公式 
        self.PE[:,0::2] = torch.sin(pos / (10000**(_2i / d_model)))
        self.PE[:,1::2] = torch.cos(pos / (10000**(_2i / d_model)))
        
    def forward(self,x):
    	# x维度 [batch_size,seq_len]
        seq_len = x.size(1)
        return self.PE[:seq_len,:] 
        #只返回行数与句子长度相同的位置编码表即可

#进行实例化,输入句子的最大长度为6句子embedding后的词向量维度为4。
PE = PositionEncoding(6,4,'cuda')
#生成一个长度为3的输入,维度为1x3,即batchsize=1
X = torch.randn((1,3))
print(PE(X))
print(PE.PE)

在这里插入图片描述

从输出结果可以看出,我们在上文中手动计算的位置编码表与代码最终的计算输出相同。同时,我们也可以知道,位置编码表是在模型训练前就已经计算好的,只需根据输入句子的长度即可获得相应的位置编码表

三、Transformer输入:Embedding+位置编码


在前面两部分我们分别介绍了输入Embedding和位置编码,根据Transformer结构图我们可以知道,最终输入到Transformer中的便是输入Embedding与位置编码相加。
代码如下:

class TransformerEmbedding(nn.Module):
    def __init__(self, vocab_size, max_len, d_model, drop_prob, device):
     	# vocab_size为词表大小
     	# max_len为句子最大长度 (注意:词表大小与句子最大长度不相等)
     	# d_model为embedding词向量维度
     	# drop_prob为进行Dropout的概率
        super(TransformerEmbedding, self).__init__()
        self.tok_emb = nn.Embedding(vocab_size, d_model)
        self.pos_emb = PositionEncoding(max_len,d_model,device)
        self.drop_out = nn.Dropout(p=drop_prob)

    def forward(self, x):
        tok_emb = self.tok_emb(x)
        pos_emb = self.pos_emb(x)
        return self.drop_out(tok_emb + pos_emb)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值