系列文章目录
前言
我们在本文主要介绍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\i 0 1 2 3 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)