Transformer的输入部分进行了解,主要是文本嵌入层的代码分析和位置编码。
1.文本嵌入层的代码分析
#定义Embeddings类来实现文本嵌入层,这里s说明代表两个一模一样的嵌入层,他们共享参数.
class Embeddings(nn.Module):
#"""类的初始化函数,有两个参数. d _model:指词嵌入的维度, vocab:指词表的大小. """
def __init__(self, d_model, vocab):
#接着就是使用super的方式指明继承nn.Module的初始化函数,我们自己实现的所有层都会这样去
super(Embeddings,self).__init__()
#调用nn中预定义层Embeddings,获得一个词嵌入对象self.lut
self.lut = nn.Embedding(vocab, d_model)
#最后将d_model传入类中
self.d_model = d_model
"""可以将其理解为该层的前向传播逻辑,所有层中都会有此函数当传给该类的实例化对象参数时,自动调用该类函数
参数x︰因为Embedding层是首层,所以代表输入给模型的文本通过词汇映射后的张量"""
def forward(self, x):
#将x传给self. lut并与根号下self.d_model相乘作为结果返回
return self.lut(x) * math.sqrt(self.d_model)
d_model = 512
vocab = 1000
x=Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
emb =Embeddings(d_model, vocab)
emb1 = emb(x)
print(emb1)
print(emb1.shape)
结果:
2.位置编码器
#定义位置编码器类,我们同样把它看做一个层,因此会继承nn.Module
class PositionalEncoding(nn.Module):
""""位置编码器类的初始化函数,共有三个参数,分别是
d_model:词嵌入维度,
dropout:置0比率,
max_len:每个句子的最大长度"""
def __init__(self,d_model, dropout, max_len=5000):
super(PositionalEncoding, self).__init__()
#实例化nn中预定义的Dropout层,并将dropout传入其中,获得对象self.dropout
self.dropout = nn.Dropout(p=dropout)
#初始化一个位置编码矩阵,它是一个0阵,矩阵的大小是max_len ,d_model.
pe= torch.zeros(max_len, d_model)
#初始化一个绝对位置矩阵,在我们这里,词汇的绝对位置就是用它的索引去表示.
#所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度
#又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个(max_len x 1) 的矩阵,
#由[0,1,2...max_len]-> [[0],[1]...[max_len]]
##目的是要把position的信息放到pe里面去
position = torch.arange(0, max_len).unsqueeze(1)
#定义一个变换矩阵使得position的[max_len,1]*变换矩阵得到pe[max_len,d_model]->变换矩阵格式[1,d_model]
#除以这个是为了加快收敛速度
#div_term格式是[0,1,2...d_model/2],分成了两个部分,步长为2
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(1000.0) / d_model))
pe[:,0::2] = torch.sin(position * div_term) #偶数位置
pe[:,1::2] = torch.sin(position * div_term) #奇数位置
#此时pe[max_len,d_model]
#embedding三维(可以是[batch_size,vocab,d_model]),vocab就是max_len
#将pe升起一个维度扩充成三维张量
pe=pe.unsqueeze(0)
#位置编码矩阵注册成模型的buffer,它不是模型中的参数,不会跟随优化器进行优化
#注册成buffer后我们就可以在模型的保存和加载时,将这个位置编码器和模型参数加载进来
self.register_buffer('pe',pe)
def forward(self, x):
# x代表文本序列的词嵌入向量
#pe编码过长将第二个维度也就是max_len的维度缩小成句子的长度
x = x + Variable(self.pe[:,:x.size(1)],requires_grad = False)
return self.dropout(x)
pos表示单词在句子中的绝对位置,pos=0,1,2…,例如:Jerry在"Tom chase Jerry"中的pos=2;d_model表示词向量的维度,在这里dmodel=512;2i和2i+1表示奇偶性,i表示词向量中的第几维,例如这里dmodel=512,故i=0,1,2…255。
为什么是将positional encoding与词向量相加,而不是拼接呢?拼接相加都可以,只是本身词向量的维度512维就已经蛮大了,再拼接一个512维的位置向量,变成1024维,这样训练起来会相对慢一些,影响效率
dropout是让某些神经元失效。举个例子:
总结: