Transformer是一个Seq2Seq架构的模型,所以它也由Encoder与Decoder这2部分组成。但它并不是使用RNN神经网络来捕获远距离信息的,它主要采用一种自注意力机制的深度学习模型,这一机制可以按输入数据各部分重要性的不同而分配不同的权重。
image.png
image.png
论文
代码实例分享于https://github.com/SamLynnEvans/Transformer Transformer做翻译任务

  1. class Transformer(nn.Module):
  2. def __init__(self, src_vocab, trg_vocab, d_model, N, heads, dropout):
  3. super().__init__()
  4. self.encoder = Encoder(src_vocab, d_model, N, heads, dropout)
  5. self.decoder = Decoder(trg_vocab, d_model, N, heads, dropout)
  6. self.out = nn.Linear(d_model, trg_vocab)
  7. def forward(self, src, trg, src_mask, trg_mask):
  8. e_outputs = self.encoder(src, src_mask)
  9. d_output = self.decoder(trg, e_outputs, src_mask, trg_mask)
  10. output = self.out(d_output)
  11. return output

模型层结构

1.embedding layer(嵌入层)

d_model=512

  1. class Embedder(nn.Module):
  2. def __init__(self, vocab_size, d_model):
  3. super().__init__()
  4. self.d_model = d_model
  5. self.embed = nn.Embedding(vocab_size, d_model)
  6. def forward(self, x):
  7. return self.embed(x)

2.positional encoding(位置编码)

单词embedding其实是embedding + positional encoding,positional encoding和embedding的维度相同,其中pos是指当前词在句子中的位置,i是指向量中每个值的index。例如将K、V按行进行打乱,那么Attention之后的结果是一样的。但是序列信息非常重要,代表着全局的结构,因此必须将序列的token相对或者绝对position信息利用起来。
在偶数位置,使用正弦编码,在奇数位置,使用余弦编码:
image.png

  1. class PositionalEncoder(nn.Module):
  2. def __init__(self, d_model, max_seq_len = 200, dropout = 0.1):
  3. super().__init__()
  4. self.d_model = d_model
  5. self.dropout = nn.Dropout(dropout)
  6. # create constant 'pe' matrix with values dependant on pos and i
  7. pe = torch.zeros(max_seq_len, d_model)
  8. for pos in range(max_seq_len):
  9. for i in range(0, d_model, 2):
  10. pe[pos, i] = math.sin(pos / (10000 ** ((2 * i)/d_model)))
  11. pe[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1))/d_model)))
  12. pe = pe.unsqueeze(0)
  13. self.register_buffer('pe', pe)
  14. def forward(self, x):
  15. # make embeddings relatively larger
  16. # 这一步非必须,也可以注释掉,目的是使得embedding的值大一点,
  17. x = x * math.sqrt(self.d_model)
  18. #add constant to embedding
  19. seq_len = x.size(1)
  20. pe = Variable(self.pe[:,:seq_len], requires_grad=False)
  21. if x.is_cuda:
  22. pe.cuda()
  23. x = x + pe
  24. return self.dropout(x)

3.Scaled dot-product attention(缩放的点乘注意力机制

输入的embedding和三个随机初始化的矩阵相乘(这个过程也可描述为经过一次线性变化)映射为维度相同的Q,K, V(映射后的维度是dq,dk,dv),然后使用self-attention(点积attention)公式计算,这三个矩阵在BP过程中会一直更新。
image.png
其中,d_k = query.size(-1)=64,d_k·h=d_model=512原因:对于很大的Transformer - 图5,点积结果增大,将softmax函数推向具有极小梯度的区域,为抵消这种影响,在Transformer中点积缩小Transformer - 图6倍。

  1. def attention(q, k, v, d_k, mask=None, dropout=None):
  2. scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k)
  3. if mask is not None:
  4. mask = mask.unsqueeze(1)
  5. scores = scores.masked_fill(mask == 0, -1e9)
  6. scores = F.softmax(scores, dim=-1)
  7. if dropout is not None:
  8. scores = dropout(scores)
  9. output = torch.matmul(scores, v)
  10. return output

4.Multi-head attention(多头注意力)

multi-head attention是初始化多组Q、K、V的矩阵,这里的多头一般是指要进行h=8次,头之间参数不共享,即每次QKV进行线性变化的W都不一样,然后进行h=8次缩放点积注意力机制,tranformer最后得到的结果是8个矩阵,拼接后再映射到输出维度。结合代码。
image.png

  1. class MultiHeadAttention(nn.Module):
  2. def __init__(self, heads, d_model, dropout = 0.1):
  3. super().__init__()
  4. self.d_model = d_model
  5. self.d_k = d_model // heads
  6. self.h = heads
  7. self.q_linear = nn.Linear(d_model, d_model)
  8. self.v_linear = nn.Linear(d_model, d_model)
  9. self.k_linear = nn.Linear(d_model, d_model)
  10. self.dropout = nn.Dropout(dropout)
  11. self.out = nn.Linear(d_model, d_model)
  12. def forward(self, q, k, v, mask=None):
  13. bs = q.size(0)
  14. # perform linear operation and split into N heads
  15. k = self.k_linear(k).view(bs, -1, self.h, self.d_k)
  16. q = self.q_linear(q).view(bs, -1, self.h, self.d_k)
  17. v = self.v_linear(v).view(bs, -1, self.h, self.d_k)
  18. # transpose to get dimensions bs * N * sl * d_model
  19. k = k.transpose(1,2)
  20. q = q.transpose(1,2)
  21. v = v.transpose(1,2)
  22. # calculate attention using function we will define next
  23. scores = attention(q, k, v, self.d_k, mask, self.dropout)
  24. # concatenate heads and put through final linear layer
  25. concat = scores.transpose(1,2).contiguous().view(bs, -1, self.d_model)
  26. output = self.out(concat)
  27. return output

5.Padding mask

padding mask 在所有的 scaled dot-product attention 里面都需要用到,因为文本的长度不同,所以我们会在处理时对其进行padding。但是注意padding是将这些位置设置为非常大的负数,这样经过softmax后的概率接近0。这么做的目的是在进行attention机制时,注意力不应该放在padding的部分。

6.残差连接

残差模块X + self-attention(X),即X经过self-attention后的结果再加上输入X,目的是为了反向传播时不会梯度消失,防止网络退化。
image.png

7.Layer Normalization

Normalization有很多种,但是它们都有一个共同的目的,那就是把输入转化成均值为0方差为1的数据。BN是在batch上计算均值和方差,而LN则是对针对同一层的所有神经元做归一化;
我们再回想下BatchNormalization,其实它是在每个神经元上对batch_size个数据做归一化,每个神经元的均值和方差均不相同。而LayerNormalization则是对所有神经元做一个归一化,这就跟batch_size无关了。哪怕batch_size为1,这里的均值和方差只和神经元的个数有关系。变长的样本,BN无法处理,BN的缺点(batch_size 和样本变长特征影响)
image.png

  1. class Norm(nn.Module):
  2. def __init__(self, d_model, eps = 1e-6):
  3. super().__init__()
  4. self.size = d_model
  5. # create two learnable parameters to calibrate normalisation
  6. self.alpha = nn.Parameter(torch.ones(self.size))
  7. self.bias = nn.Parameter(torch.zeros(self.size))
  8. self.eps = eps
  9. def forward(self, x):
  10. norm = self.alpha * (x - x.mean(dim=-1, keepdim=True)) \
  11. / (x.std(dim=-1, keepdim=True) + self.eps) + self.bias
  12. return norm

8.Position-wise Feed-Forward network

Feed Forward 层比较简单,是一个两层的全连接层,第一层的激活函数为 Relu,第二层不使用激活函数。为什么需要前馈。

  1. class FeedForward(nn.Module):
  2. def __init__(self, d_model, d_ff=2048, dropout = 0.1):
  3. super().__init__()
  4. # We set d_ff as a default to 2048
  5. self.linear_1 = nn.Linear(d_model, d_ff)
  6. self.dropout = nn.Dropout(dropout)
  7. self.linear_2 = nn.Linear(d_ff, d_model)
  8. def forward(self, x):
  9. x = self.dropout(F.relu(self.linear_1(x)))
  10. x = self.linear_2(x)
  11. return x

Encoder结构

Encoder由N = 6 个完全相同的层堆叠而成。每一层都有两个子层。第一个子层是一个multi-head self-attention机制,第二个子层是一个简单的、位置完全连接的前馈网络。我们对每个子层再采用一个残差连接,接着进行层标准化。也就是说,每个子层的输出是LayerNorm(x + Sublayer(x)),其中Sublayer(x) 是由子层本身实现的函数。为了方便这些残差连接,模型中的所有子层以及嵌入层产生的输出维度都为dmodel = 512。

  1. class Encoder(nn.Module):
  2. def __init__(self, vocab_size, d_model, N, heads, dropout):
  3. super().__init__()
  4. self.N = N
  5. self.embed = Embedder(vocab_size, d_model)
  6. self.pe = PositionalEncoder(d_model, dropout=dropout)
  7. self.layers = get_clones(EncoderLayer(d_model, heads, dropout), N)
  8. self.norm = Norm(d_model)
  9. def forward(self, src, mask):
  10. x = self.embed(src)
  11. x = self.pe(x)
  12. for i in range(self.N):
  13. x = self.layers[i](x, mask)
  14. return self.norm(x)
  15. def get_clones(module, N):
  16. return nn.ModuleList([copy.deepcopy(module) for i in range(N)])

encoder 结构 emb+positionemb , (归一化),多头注意力,残差+x,接归一化,输入到前馈网络,残差。

论文中是先残差再归一化,但是在 http://nlp.seas.harvard.edu/2018/04/03/attention.html#encoder中的SublayerConnection是先归一化,后残差的。论文中应该是先Add再Nrom

  1. class EncoderLayer(nn.Module):
  2. def __init__(self, d_model, heads, dropout=0.1):
  3. super().__init__()
  4. self.norm_1 = Norm(d_model)
  5. self.norm_2 = Norm(d_model)
  6. self.attn = MultiHeadAttention(heads, d_model, dropout=dropout)
  7. self.ff = FeedForward(d_model, dropout=dropout)
  8. self.dropout_1 = nn.Dropout(dropout)
  9. self.dropout_2 = nn.Dropout(dropout)
  10. def forward(self, x, mask):
  11. x2 = self.norm_1(x)
  12. x = x + self.dropout_1(self.attn(x2,x2,x2,mask))
  13. x2 = self.norm_2(x)
  14. x = x + self.dropout_2(self.ff(x2))
  15. return x

Decoder结构

解码器同样由N = 6 个完全相同的层堆叠而成。 除了每个编码器层中的两个子层之外,解码器还插入第三个子层,该层对Encoder堆栈的输出执行multi-head attention。 与编码器类似,我们在每个子层再采用残差连接,然后进行层标准化。我们还修改解码器堆栈中的self-attention子层,以防止位置关注到后面的位置。这种掩码结合将输出嵌入偏移一个位置,确保对当前位置的预测 i只能依赖小于当前位置的已知输出。

  1. class Decoder(nn.Module):
  2. def __init__(self, vocab_size, d_model, N, heads, dropout):
  3. super().__init__()
  4. self.N = N
  5. self.embed = Embedder(vocab_size, d_model)
  6. self.pe = PositionalEncoder(d_model, dropout=dropout)
  7. self.layers = get_clones(DecoderLayer(d_model, heads, dropout), N)
  8. self.norm = Norm(d_model)
  9. def forward(self, trg, e_outputs, src_mask, trg_mask):
  10. x = self.embed(trg)
  11. x = self.pe(x)
  12. for i in range(self.N):
  13. x = self.layers[i](x, e_outputs, src_mask, trg_mask)
  14. return self.norm(x)
  1. # build a decoder layer with two multi-head attention layers and
  2. # one feed-forward layer
  3. class DecoderLayer(nn.Module):
  4. def __init__(self, d_model, heads, dropout=0.1):
  5. super().__init__()
  6. self.norm_1 = Norm(d_model)
  7. self.norm_2 = Norm(d_model)
  8. self.norm_3 = Norm(d_model)
  9. self.dropout_1 = nn.Dropout(dropout)
  10. self.dropout_2 = nn.Dropout(dropout)
  11. self.dropout_3 = nn.Dropout(dropout)
  12. self.attn_1 = MultiHeadAttention(heads, d_model, dropout=dropout)
  13. self.attn_2 = MultiHeadAttention(heads, d_model, dropout=dropout)
  14. self.ff = FeedForward(d_model, dropout=dropout)
  15. def forward(self, x, e_outputs, src_mask, trg_mask):
  16. x2 = self.norm_1(x)
  17. x = x + self.dropout_1(self.attn_1(x2, x2, x2, trg_mask))
  18. x2 = self.norm_2(x)
  19. x = x + self.dropout_2(self.attn_2(x2, e_outputs, e_outputs, \
  20. src_mask))
  21. x2 = self.norm_3(x)
  22. x = x + self.dropout_3(self.ff(x2))
  23. return x

1.sequence mask

sequence mask 是为了使得 decoder 不能看见未来的信息。也就是对于一个序列,在 time_step 为 t 的时刻,我们的解码输出应该只能依赖于 t 时刻之前的输出,而不能依赖 t 之后的输出。因此我们需要想一个办法,把 t 之后的信息给隐藏起来。
那么具体怎么做呢?也很简单:产生一个上三角矩阵,上三角的值全为0。把这个矩阵作用在每一个序列上,就可以达到我们的目的。
对于 decoder 的 self-attention,里面使用到的 scaled dot-product attention,同时需要padding mask 和 sequence mask 作为 attn_mask,具体实现就是两个mask相加作为attn_mask。
其他情况,attn_mask 一律等于 padding mask。

训练和解码

训练

  1. def train_model(model, opt):
  2. print("training model...")
  3. model.train()
  4. start = time.time()
  5. if opt.checkpoint > 0:
  6. cptime = time.time()
  7. for epoch in range(opt.epochs):
  8. total_loss = 0
  9. if opt.floyd is False:
  10. print(" %dm: epoch %d [%s] %d%% loss = %s" % \
  11. ((time.time() - start) // 60, epoch + 1, "".join(' ' * 20), 0, '...'), end='\r')
  12. if opt.checkpoint > 0:
  13. torch.save(model.state_dict(), 'weights/model_weights')
  14. for i, batch in enumerate(opt.train):
  15. src = batch.src.transpose(0, 1)
  16. trg = batch.trg.transpose(0, 1)
  17. trg_input = trg[:, :-1]
  18. src_mask, trg_mask = create_masks(src, trg_input, opt)
  19. preds = model(src, trg_input, src_mask, trg_mask)
  20. ys = trg[:, 1:].contiguous().view(-1)
  21. opt.optimizer.zero_grad()
  22. print(src.shape) #torch.Size([1, 28])
  23. print(src_mask.shape) #torch.Size([1, 1, 28])
  24. print(src_mask)
  25. print(trg.shape) #torch.Size([1, 34])
  26. print(trg_mask.shape) #torch.Size([1, 33, 33])
  27. print(trg_mask)
  28. print(preds.view(-1, preds.size(-1)).shape, ys.shape) #torch.Size([33, 663]) torch.Size([33])
  29. loss = F.cross_entropy(preds.view(-1, preds.size(-1)), ys, ignore_index=opt.trg_pad)
  30. loss.backward()
  31. opt.optimizer.step()
  32. if opt.SGDR == True:
  33. opt.sched.step()
  34. total_loss += loss.item()
  35. if (i + 1) % opt.printevery == 0:
  36. p = int(100 * (i + 1) / opt.train_len)
  37. avg_loss = total_loss / opt.printevery
  38. if opt.floyd is False:
  39. print(" %dm: epoch %d [%s%s] %d%% loss = %.3f" % \
  40. ((time.time() - start) // 60, epoch + 1, "".join('#' * (p // 5)),
  41. "".join(' ' * (20 - (p // 5))), p, avg_loss), end='\r')
  42. else:
  43. print(" %dm: epoch %d [%s%s] %d%% loss = %.3f" % \
  44. ((time.time() - start) // 60, epoch + 1, "".join('#' * (p // 5)),
  45. "".join(' ' * (20 - (p // 5))), p, avg_loss))
  46. total_loss = 0
  47. if opt.checkpoint > 0 and ((time.time() - cptime) // 60) // opt.checkpoint >= 1:
  48. torch.save(model.state_dict(), 'weights/model_weights')
  49. cptime = time.time()
  50. print("%dm: epoch %d [%s%s] %d%% loss = %.3f\nepoch %d complete, loss = %.03f" % \
  51. ((time.time() - start) // 60, epoch + 1, "".join('#' * (100 // 5)), "".join(' ' * (20 - (100 // 5))), 100,
  52. avg_loss, epoch + 1, avg_loss))
  1. src.shape:torch.Size([1, 26])
  2. src_mask.shape:torch.Size([1, 1, 26])
  3. src_mask:tensor([[[True, True, True, True, True, True, True, True, True, True, True,
  4. True, True, True, True, True, True, True, True, True, True, True,
  5. True, True, True, True]]])
  6. tgr.shape:torch.Size([1, 31])
  7. tgr_mask.shape:torch.Size([1, 30, 30])
  8. tgr_mask:tensor([[[ True, False, False, False, False, False, False, False, False, False,
  9. False, False, False, False, False, False, False, False, False, False,
  10. False, False, False, False, False, False, False, False, False, False],
  11. [ True, True, False, False, False, False, False, False, False, False,
  12. False, False, False, False, False, False, False, False, False, False,
  13. False, False, False, False, False, False, False, False, False, False],
  14. [ True, True, True, False, False, False, False, False, False, False,
  15. False, False, False, False, False, False, False, False, False, False,
  16. False, False, False, False, False, False, False, False, False, False],
  17. ...
  18. [ True, True, True, True, True, True, True, True, True, True,
  19. True, True, True, True, True, True, True, True, True, True,
  20. True, True, True, True, True, True, True, True, True, False],
  21. [ True, True, True, True, True, True, True, True, True, True,
  22. True, True, True, True, True, True, True, True, True, True,
  23. True, True, True, True, True, True, True, True, True, True]]])
  24. torch.Size([30, 663]) torch.Size([30])

训练解码

  1. #输入src_seq
  2. src_seq: i see.
  3. sentence: ['i', 'see', '.']
  4. indexed: [8, 192, 2]
  5. beam_search 找到最优路径
  • 输入:encoder的输出 & 对应i-1位置decoder的输出。
  • 输出:对应i位置的输出词的概率分布

所以中间的attention不是self-attention,它的K,V来自encoder,Q来自上一位置decoder的输出。

  • 解码:这里要注意一下,训练和预测是不一样的。在训练时,解码是一次全部decode出来,用上一步的ground truth来预测(mask矩阵也会改动,让解码时看不到未来的token);而预测时,因为没有ground truth了,需要一个个预测。
  • 解码的区别主要是:一个是attention的输入,一个是decoder 的mask。
  • 直到输出一个特殊符号,表示已经完成了。对于Decoder,和Encoder一样,我们在每个Decoder的输入做词嵌入并添加上表示每个字位置的位置编码

    预测解码

  • 输入:encoder的输出 & 对应i-1位置decoder的输出。

  • 输出:对应i位置的输出词的概率分布
  • 首先tgr的第一个元素是,直到预测结束,每次模型输入的tgr都是将上一步的tgr 的预测结果作为输入。 如第12行所示:

    1. def beam_search(src, model, SRC, TRG, opt):
    2. # outputs是三个句子,三个句子的第一个元素是<sos>,同时预测了第二个元素,也就是目标序列的第一个词
    3. outputs, e_outputs, log_scores = init_vars(src, model, SRC, TRG, opt)
    4. # outputs:【3,80】
    5. # e_outputs:【3,3,663】
    6. # log_scores:[1, 3]
    7. print("outputs", outputs.shape) # [3,80]
    8. eos_tok = TRG.vocab.stoi['<eos>']
    9. src_mask = (src != SRC.vocab.stoi['<pad>']).unsqueeze(-2)
    10. ind = None
    11. for i in range(2, opt.max_len): # 从第3个索引,也就是第二个词开始预测,
    12. trg_mask = nopeak_mask(i, opt)
    13. out = model.out(model.decoder(outputs[:, :i],
    14. e_outputs, src_mask, trg_mask)) # []
    15. out = F.softmax(out, dim=-1)
    16. print("out", out.shape) # [k, seqlength,vocab_size] #[3, 2, 663]
    17. outputs, log_scores = k_best_outputs(outputs, out, log_scores, i, opt.k)
    18. print("outputs", outputs.shape) # torch.Size([3, 80])
    19. print("log_scores",log_scores.shape)
    20. ones = (outputs == eos_tok).nonzero() # Occurrences of end symbols for all input sentences.
    21. print("ones", ones)
    22. if opt.device == 0:
    23. sentence_lengths = torch.zeros(len(outputs), dtype=torch.long).cuda()
    24. else:
    25. sentence_lengths = torch.zeros(len(outputs), dtype=torch.long)
    26. for vec in ones:
    27. i = vec[0]
    28. if sentence_lengths[i] == 0: # First end symbol has not been found yet
    29. sentence_lengths[i] = vec[1] # Position of first end symbol
    30. num_finished_sentences = len([s for s in sentence_lengths if s > 0])
    31. # <eos>的个数等于topk 的个数,证明三个都预测完了
    32. if (outputs == eos_tok).cpu().numpy().argmax(axis=1).nonzero()[0].shape[0] == opt.k:
    33. alpha = 0.7
    34. div = 1 / (torch.tensor(((outputs == eos_tok).cpu().numpy().argmax(axis=1))).type_as(log_scores) ** alpha)
    35. _, ind = torch.max(log_scores * div, 1)
    36. ind = ind.data[0]
    37. break
    38. if ind is None:
    39. print(eos_tok)
    40. print(outputs)
    41. print((outputs[0] == eos_tok).nonzero())
    42. length = (outputs[0] == eos_tok).nonzero()[0]
    43. return ' '.join([TRG.vocab.itos[tok] for tok in outputs[0][1:length]])
    44. else:
    45. print("res outputs", outputs)
    46. print("res nonzero", (outputs[ind] == eos_tok).nonzero())
    47. length = (outputs[ind] == eos_tok).nonzero()[0]
    48. return ' '.join([TRG.vocab.itos[tok] for tok in outputs[ind][1:length]])

    问题

    1.transform 的encoder和decoder的区别?

    :::info 1)结构方面:encoder有两个子层,分别是多头的的自注意力网络,还有一个前馈网络,每层之间采用残差连接,然后再进行LayerNorm。decoder每个block除了编码器层中的两个子层之外,解码器还插入第三个子层,该层对Encoder堆栈的输出执行multi-head attention。
    2)输入方面:decoder的时候,encoder-decoder中的attention,query 来自decoder的多层注意力输出,key和value来自encoder的输出。encoder的时候,key ,query, value 是一样的。
    3)mask方面:encoder是因为文本长度不一样,padding是将超出实际长度的位置设置为非常大的负数,这样经过softmax后的概率接近0。decoder 对目标序列的self-attention,以防止位置关注到后面的位置。这种mask结合将输出嵌入偏移一个位置,确保对当前位置的预测 只能依赖小于i当前位置的已知输出。 :::

    2.transform 编码器和解码器中attention的区别?

    :::info 在“Encoder-Decoder Attention”层,query来自上面的decoder的多头注意力层的输出,key和value来自encoder的输出。这允许Decoder中的每个位置能关注到输入序列中的所有位置。这模仿Seq2Seq模型中典型的Encoder-Decoder的attention机制。

:::

3.Transformer为什么需要进行Multi-head Attention?

:::info 将计算结果映射到不同的子空间,使模型关注到不同方面的信息,最后再综合起来。 :::

4.为什么需要前馈?

:::info 在编码器的self-attention和前馈神经网络后分别都有,目的是
(1)防止梯度消失。
(2)解决深度学习网络的退化问题。(退化问题是指,当模型对不同的输入均给出同样的输出) :::

5.Transformer的并行化提现在哪里?

:::info (1)并行计算的能力。RNN系列的模型,并行计算能力很差,因为 T 时刻的计算依赖 T-1 时刻的隐层计算结果,而 T-1 时刻的计算依赖 T-2 时刻的隐层计算结果,如此下去就形成了序列依赖关系。
(2)特征提取能力更好。 :::

6.Transformer和RNN,CNN的不同?

:::info (1)它是直接进行的长距离依赖特征的获取的,不像RNN需要通过隐层节点序列往后传,也不像CNN(局部编码)需要通过增加网络深度来捕获远距离特征。
(2)RNN系列的模型,并行计算能力很差,Transformer并性能力
(3)复杂度,当句子长度和词嵌入维度差不多的时候,复杂度差不多, :::

  1. 每一层的计算复杂度
  2. 能够被并行的计算,用需要的最少的顺序操作的数量来衡量
  3. 网络中long-range dependencies的path length,在处理序列信息的任务中很重要的在于学习long-range dependencies。影响学习长距离依赖的关键点在于前向/后向信息需要传播的步长,输入和输出序列中路径越短,那么就越容易学习long-range dependencies。因此我们比较三种网络中任何输入和输出之间的最长path length

image.png

7.self-attention时间复杂度

image.png

8.全连接网络和自注意力区别

image.png :::info (1)全连接模型节点之间是可学习的权重,注意力模型节点之间动态生成的权重。
(2)全连接网络虽然是一种非常直接的建模远距离依赖的模型, 但是无法处理变长的输入序列。不同的输入长度,其连接权重的大小也是不同的。由于自注意力模型的权重是动态生成的,因此可以处理变长的信息序列。 :::

9.什么是transformer

:::info Transformer是一个Seq2Seq架构的模型,所以它也由Encoder与Decoder这2部分组成。但它并不是使用RNN神经网络来捕获远距离信息的,它主要采用一种自注意力机制的深度学习模型,这一机制可以按输入数据各部分重要性的不同而分配不同的权重。 :::

10.transformer 的结构

:::info 分别描述encoder 和decoder的结构 :::

11.transformer attention的时候为啥要除以根号D

:::info 至于attention后的权重为啥要除以 Transformer - 图13,作者在论文中的解释是点积后的结果大小是跟维度成正比的,所以经过softmax以后,梯度就会变很小,除以 Transformer - 图14 后可以让attention的权重分布方差为1,而不是 Transformer - 图15
具体细节(简而言之就是softmax如果某个输入太大的话就会使得权重太接近于1,梯度很小)
transformer中的attention为什么scaled?1733 赞同 · 53 评论回答 :::

参考

https://www.yuque.com/kaiba-20hbu/aev2fm/yexiqv
https://www.yuque.com/kaiba-20hbu/aev2fm/yexiqv#so66x
哈弗团队 pytorch版本代码 http://nlp.seas.harvard.edu/2018/04/03/attention.html