Transformer代码讲解(最最最最......详细)Transformer代码讲解(最最最最…详细)
整个代码主要分为两部分去讲解:
⼀、完整代码
⼆、部分代码剖析
1、主函数if __name__ == '__main__':
2、从整体⽹路结构来看,分为三个部分:编码层,解码层,输出层
3、Encoder 部分包含三个部分:词向量embedding,位置编码部分,注意⼒层及后续的前馈神经⽹络
4、PositionalEncoding 代码实现
5、get_attn_pad_mask
6、EncoderLayer :包含两个部分,多头注意⼒机制和前馈神经⽹络
7、MultiHeadAttention
8、ScaledDotProductAttentio
9、PoswiseFeedForwardNet
10、Decoder
11、DecoderLayer(nn.Module):
12、get_attn_subsequent_mask(seq)
正⽂:
⼀、完整代码
import numpy as np
import torch
as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import math
def make_batch(sentences):
input_batch =[[src_vocab[n]for n in sentences[0].split()]]
output_batch =[[tgt_vocab[n]for n in sentences[1].split()]]
target_batch =[[tgt_vocab[n]for n in sentences[2].split()]]
return torch.LongTensor(input_batch), torch.LongTensor(output_batch), torch.LongTensor(target_batch)
## 10
def get_attn_subsequent_mask(seq):
"""
seq: [batch_size, tgt_len]
"""
attn_shape =[seq.size(0), seq.size(1), seq.size(1)]
# attn_shape: [batch_size, tgt_len, tgt_len]
subsequence_mask = np.s(attn_shape), k=1)# ⽣成⼀个上三⾓矩阵
subsequence_mask = torch.from_numpy(subsequence_mask).byte()
return subsequence_mask  # [batch_size, tgt_len, tgt_len]
## 7. ScaledDotProductAttention
class ScaledDotProductAttention(nn.Module):
def__init__(self):
super(ScaledDotProductAttention, self).__init__()
def forward(self, Q, K, V, attn_mask):
## 输⼊进来的维度分别是 [batch_size x n_heads x len_q x d_k]  K: [batch_size x n_heads x len_k
x d_k]  V: [batch_size x n_heads x len_k x d_v] ##⾸先经过matmul函数得到的scores形状是 : [batch_size x n_heads x len_q x len_k]
scores = torch.matmul(Q, K.transpose(-1,-2))/ np.sqrt(d_k)
## 然后关键词地⽅来了,下⾯这个就是⽤到了我们之前重点讲的attn_mask,把被mask的地⽅置为⽆限⼩,softmax之后基本就是0,对q的单词不起作⽤        scores.masked_fill_(attn_mask,-1e9)# Fills elements of self tensor with value where mask is one.
attn = nn.Softmax(dim=-1)(scores)
context = torch.matmul(attn, V)
return context, attn
## 6. MultiHeadAttention
class MultiHeadAttention(nn.Module):
def__init__(self):
super(MultiHeadAttention, self).__init__()
## 输⼊进来的QKV是相等的,我们会使⽤映射linear做⼀个映射得到参数矩阵Wq, Wk,Wv
self.W_Q = nn.Linear(d_model, d_k * n_heads)# 这⼉是不是应该是d_q啊
self.W_K = nn.Linear(d_model, d_k * n_heads)
self.W_V = nn.Linear(d_model, d_v * n_heads)
self.linear = nn.Linear(n_heads * d_v, d_model)
self.layer_norm = nn.LayerNorm(d_model)
def forward(self, Q, K, V, attn_mask):
## 这个多头分为这⼏个步骤,⾸先映射分头,然后计算atten_scores,然后计算atten_value;
##输⼊进来的数据形状: Q: [batch_size x len_q x d_model], K: [batch_size x len_k x d_model], V: [batch_size x len_k x d_model]
residual, batch_size = Q, Q.size(0)
# (B, S, D) -proj-> (B, S, D) -split-> (B, S, H, W) -trans-> (B, H, S, W)
##下⾯这个就是先映射,后分头;⼀定要注意的是q和k分头之后维度是⼀致额,所以⼀看这⾥都是dk
q_s = self.W_Q(Q).view(batch_size,-1, n_heads, d_k).transpose(1,2)# q_s: [batch_size x n_heads x len_q x d_k]
k_s = self.W_K(K).view(batch_size,-1, n_heads, d_k).transpose(1,2)# k_s: [batch_size x n_heads x len_k x d_k]
v_s = self.W_V(V).view(batch_size,-1, n_heads, d_v).transpose(1,2)# v_s: [batch_size x n_heads x len_k x d_v]
## 输⼊进⾏的attn_mask形状是 batch_size x len_q x len_k,然后经过下⾯这个代码得到新的attn_mask : [batch_size x n_heads x len_q x len_k],就是把pad信息重复了n个头上
attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads,1,1)
##然后我们计算 ScaledDotProductAttention 这个函数,去7.看⼀下
## 得到的结果有两个:context: [batch_size x n_heads x len_q x d_v], attn: [batch_size x n_heads x len_q x len_k]
context, attn = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
context = anspose(1,2).contiguous().view(batch_size,-1, n_heads * d_v)# context: [batch_size x len_q x n_heads * d_v]
output = self.linear(context)
return self.layer_norm(output + residual), attn # output: [batch_size x len_q x d_model]
## 8. PoswiseFeedForwardNet
class PoswiseFeedForwardNet(nn.Module):
def__init__(self):
super(PoswiseFeedForwardNet, self).__init__()
self.layer_norm = nn.LayerNorm(d_model)
def forward(self, inputs):
residual = inputs # inputs : [batch_size, len_q, d_model]
output = nn.ReLU()(anspose(1,2)))
output = v2(output).transpose(1,2)
return self.layer_norm(output + residual)
## 4. get_attn_pad_mask
## ⽐如说,我现在的句⼦长度是5,在后⾯注意⼒机制的部分,我们在计算出来QK转置除以根号之后,softmax之前,我们得到的形状
## len_input * len_input  代表每个单词对其余包含⾃⼰的单词的影响⼒
## 所以这⾥我需要有⼀个同等⼤⼩形状的矩阵,告诉我哪个位置是PAD部分,之后在计算计算softmax之前会把这⾥置为⽆穷⼤;
## ⼀定需要注意的是这⾥得到的矩阵形状是batch_size x len_q x len_k,我们是对k中的pad符号进⾏标识,并没有对k中的做标识,因为没必要
## seq_q 和 seq_k 不⼀定⼀致(我⾃⼰的理解是原⽂是德⽂,翻译成英⽂,⽽原⽂的德语的单词个数和英语的单词个数不⼀样多,所以这⼉可能不⼀致),在交互注意⼒,q来⾃解码端,k来⾃编码端,所以告诉模型编码这边pad符号信息就可以,解码端的pad信息在交互注意⼒层是没有⽤到的;
def get_attn_pad_mask(seq_q, seq_k):
batch_size, len_q = seq_q.size()
batch_size, len_k = seq_k.size()
# eq(zero) is PAD token
pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)# batch_size x 1 x len_k, one is masking
return pad_pand(batch_size, len_q, len_k)# batch_size x len_q x len_k
## 3. PositionalEncoding 代码实现
class PositionalEncoding(nn.Module):
def__init__(self, d_model, dropout=0.1, max_len=5000):
super(PositionalEncoding, self).__init__()
## 位置编码的实现其实很简单,直接对照着公式去敲代码就可以,下⾯这个代码只是其中⼀种实现⽅式;
## 从理解来讲,需要注意的就是偶数和奇数在公式上有⼀个共同部分,我们使⽤log函数把次⽅拿下来,⽅便计算;
## pos代表的是单词在句⼦中的索引,这点需要注意;⽐如max_len是128个,那么索引就是从0,1,2,...,127
##假设我的demodel是512,2i那个符号中i从0取到了255,那么2i对应取值就是0,2,4 (510)
self.dropout = nn.Dropout(p=dropout)
self.dropout = nn.Dropout(p=dropout)
pe = s(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = p(torch.arange(0, d_model,2).float()*(-math.log(10000.0)/ d_model))
pe[:,0::2]= torch.sin(position * div_term)## 这⾥需要注意的是pe[:, 0::2]这个⽤法,就是从0开始到最后⾯,步长为2,其实代表的就是偶数位置
pe[:,1::2]= s(position * div_term)##这⾥需要注意的是pe[:, 1::2]这个⽤法,就是从1开始到最后⾯,步长为2,其实代表的就是奇数位置## 上⾯代码获取之后得到的pe:[max_len*d_model]
## 下⾯这个代码之后,我们得到的pe形状是:[max_len*1*d_model]
pe = pe.unsqueeze(0).transpose(0,1)
def forward(self, x):
"""
x: [seq_len, batch_size, d_model]
"""
x = x + self.pe[:x.size(0),:]
return self.dropout(x)
## 5. EncoderLayer :包含两个部分,多头注意⼒机制和前馈神经⽹络
class EncoderLayer(nn.Module):
def__init__(self):
super(EncoderLayer, self).__init__()
<_self_attn = MultiHeadAttention()
self.pos_ffn = PoswiseFeedForwardNet()
def forward(self, enc_inputs, enc_self_attn_mask):
## 下⾯这个就是做⾃注意⼒层,输⼊是enc_inputs,形状是[batch_size x seq_len_q x d_model] 需要注意的是最初始的QKV矩阵是等同于这个输⼊的,去看⼀下enc_self_attn函数 6.
enc_outputs, attn = _self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask)# enc_inputs to same Q,K,V
enc_outputs = self.pos_ffn(enc_outputs)# enc_outputs: [batch_size x len_q x d_model]
return enc_outputs, attn
## 2. Encoder 部分包含三个部分:词向量embedding,位置编码部分,注意⼒层及后续的前馈神经⽹络
class Encoder(nn.Module):
def__init__(self):
super(Encoder, self).__init__()
self.src_emb = nn.Embedding(src_vocab_size, d_model)## 这个其实就是去定义⽣成⼀个矩阵,⼤⼩是 src_vocab_size * d_model
self.pos_emb = PositionalEncoding(d_model)## 位置编码情况,这⾥是固定的正余弦函数,也可以使⽤类似词向量的nn.Embedding获得⼀个可以更新学习的位置编码
self.layers = nn.ModuleList([EncoderLayer()for _ in range(n_layers)])## 使⽤ModuleList对多个encoder进⾏堆叠,因为后续的encoder并没有使⽤词向量和位置编码,所以抽离出来;
def forward(self, enc_inputs):
## 这⾥我们的 enc_inputs 形状是: [batch_size x source_len]  # 提问:这⼉的source_len == max_lenmax_len:输⼊⼀段话所包含的词的最多有多少个。
## 下⾯这个代码通过src_emb,进⾏索引定位,enc_outputs输出形状是[batch_size, src_len, d_model]  # 提问:这⼉是因为这⼉只有⼀句话,所以才是s rc_len,当有多句话时,这⼉应该是max_len?
enc_outputs = self.src_emb(enc_inputs)
## 这⾥就是位置编码,把两者相加放⼊到了这个函数⾥⾯,从这⾥可以去看⼀下位置编码函数的实现;3.
enc_outputs = self.pos_emb(anspose(0,1)).transpose(0,1)
##get_attn_pad_mask是为了得到句⼦中pad的位置信息,给到模型后⾯,在计算⾃注意⼒和交互注意⼒的时候去掉pad符号的影响,去看⼀下这个函数 4.这句话表⽰什么意思?
enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)#  get_attn_pad_mask告诉后⾯的层那些位置是被pad填充的
enc_self_attns =[]
for layer in self.layers:
## 去看EncoderLayer 层函数 5.
enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask)
enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask)
enc_self_attns.append(enc_self_attn)
return enc_outputs, enc_self_attns
## 10.
class DecoderLayer(nn.Module):
def__init__(self):
super(DecoderLayer, self).__init__()
self.dec_self_attn = MultiHeadAttention()
self.dec_enc_attn = MultiHeadAttention()
self.pos_ffn = PoswiseFeedForwardNet()
def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):
dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs, dec_self_attn_mask)
dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, enc_outputs, dec_enc_attn_mask)
dec_outputs = self.pos_ffn(dec_outputs)
return dec_outputs, dec_self_attn, dec_enc_attn
## 9. Decoder
class Decoder(nn.Module):
def__init__(self):
super(Decoder, self).__init__()
<_emb = nn.Embedding(tgt_vocab_size, d_model)
self.pos_emb = PositionalEncoding(d_model)
self.layers = nn.ModuleList([DecoderLayer()for _ in range(n_layers)])
def forward(self, dec_inputs, enc_inputs, enc_outputs):# dec_inputs : [batch_size x target_len]
dec_outputs = _emb(dec_inputs)# [batch_size, tgt_len, d_model]splitwise
dec_outputs = self.pos_emb(anspose(0,1)).transpose(0,1)# [batch_size, tgt_len, d_model]
## get_attn_pad_mask ⾃注意⼒层的时候的pad 部分
dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)
## get_attn_subsequent_mask 这个做的是⾃注意层的mask部分,就是当前单词之后看不到,使⽤⼀个上三⾓为1的矩阵
dec_self_attn_subsequent_mask = get_attn_subsequent_mask(dec_inputs)
## 两个矩阵相加,⼤于0的为1,不⼤于0的为0,为1的在之后就会被fill到⽆限⼩
dec_self_attn_mask = ((dec_self_attn_pad_mask + dec_self_attn_subsequent_mask),0)
## 这个做的是交互注意⼒机制中的mask矩阵,enc的输⼊是k,我去看这个k⾥⾯哪些是pad符号,给到后⾯的模型;注意哦,我q肯定也是有pad符号,但是这⾥我不在意的,之前说了好多次了哈
dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)
dec_self_attns, dec_enc_attns =[],[]
for layer in self.layers:
dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)
dec_self_attns.append(dec_self_attn)
dec_enc_attns.append(dec_enc_attn)
return dec_outputs, dec_self_attns, dec_enc_attns
## 1. 从整体⽹路结构来看,分为三个部分:编码层,解码层,输出层
class Transformer(nn.Module):
def__init__(self):
super(Transformer, self).__init__()
self.decoder = Decoder()## 解码层
self.projection = nn.Linear(d_model, tgt_vocab_size, bias=False)## 输出层 d_model 是我们解码层每个token输出的维度⼤⼩,之后会做⼀个 tgt_voca b_size ⼤⼩的softmax  # d_model:在这⾥每⼀个词表的维度都被设计成了512,⽽现在这是在预测
# ⼀个德语单词被翻译成英语,它会对应为那个单词,所以这⾥输⼊就是⼀个单词在词表中的维度,这⾥的维度是512,在词表中⼀个单词的维度是512。如果⼀句话有n个单词,那么在翻译的整个过程中就会调⽤n次这个全连接函数。然后假设英语单词有100000个,那么这⼉的tgt_vocab_size就是1000000个# 到达这⼉,就好像是⼀个分类任务,看这个单词属于这100000个类中的哪⼀个类,最后全连接分类的结果然后再进⾏⼀个softmax就会得到这100000个单词每个单词的概率。那个那个单词的概率最⼤,那么我们就把这个德语单词翻译成那个单词。也就是我们这⼉的projection就是那个德语单词被翻译成英语单词的词。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。