日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Transformer模型Encoder和Decoder的Pytorch逐行實現

作者:MoxiMoses 更新時間: 2022-09-26 編程語言

文章目錄

  • 摘要
  • 一、Transformer
    • 1.1 為什么要使用attention
    • 1.2 Transformer的優點
  • 二、Transformer模型Encoder和Decoder原理講解與其Pytorch逐行實現
    • 2.1 word embedding
    • 2.2 單詞索引構成源句子和目標句子
    • 2.3 構建position embedding
    • 2.4 構造encoder的self-attention mask
    • 2.5 構造intra-attention的mask
    • 2.6 構成decoder self-attention的mask
    • 2.7 構建scaled self-attention
  • 總結


摘要

本周學習了Transformer模型,以及Encoder和Decoder原理講解與其Pytorch逐行實現,主要從6個細節難點去學習了Transformer的原理與實現,詳細地學習了Transformer的word embedding、position embedding和mask原理。


一、Transformer

文獻參考:https://arxiv.org/abs/1706.03762
文獻題目:Attention Is All You Need

1.1 為什么要使用attention

1)因為t時刻的計算依賴于t-1時刻的計算結果,這樣會導致模型的并行能力受到限制。
2)雖然在之前學習到的LSTM可以在一定程度上緩解長期依賴的問題,但是對于特別長期的依賴問題,LSTM是無法解決問題的,也就是說在encoder和decoder之間的語義向量無法保存過多的信息。

1.2 Transformer的優點

它采用了Attention機制,將序列中的任意兩個位置之間的距離是縮小為一個常量,并且它不類似于RNN的順序結構,因此具有較好的并行性,也符合現有的GPU框架。

在論文中,Transformer沒有使用到傳統的CNN和RNN,整個網絡由Attention機制組成。

二、Transformer模型Encoder和Decoder原理講解與其Pytorch逐行實現

如下圖所示,這6個方面是Transformer學習的難點。
在這里插入圖片描述

2.1 word embedding

關于word embedding,以序列建模為例,考慮source sentence 和 target sentence。
構建序列,序列的字符以其在詞表中的索引的形式表示。
src_seq:輸入的句子,第一個長度為2,第二個長度為4。
tgt_seq:目標的句子,第一個長度為4,第二個長度為3。

import numpy as np
import torch
import numpy
import torch.nn as nn
import torch.nn.functional as F


batch_size = 2

# 單詞表大小
max_num_src_words = 8
max_num_tgt_words = 8

# 序列的最大長度
max_src_seq_len = 5
max_tgt_seq_len = 5
model_dim = 8
max_position_len = 5

src_len = torch.Tensor([2, 4]).to(torch.int32)
tgt_len = torch.Tensor([4, 3]).to(torch.int32)
print(src_len)
print(tgt_len)

2.2 單詞索引構成源句子和目標句子

Step 1:用隨機數生成句子單詞索引,對句子做了padding,也就是對空白位置補0,以保證所有句子的長度一致,最后用torch,cat將兩個句子張量拼接在一起。

src_seq = torch.cat([torch.unsqueeze(F.pad(torch.randint(1, max_num_src_words, (L,)), (0, max_src_seq_len - L)), 0)
                     for L in src_len])
tgt_seq = torch.cat([torch.unsqueeze(F.pad(torch.randint(1, max_num_tgt_words, (L,)), (0, max_tgt_seq_len - L)), 0)
                     for L in tgt_len])
print(src_seq)
print(tgt_seq)

輸出源句子和目標句子

tensor([[6, 7, 0, 0, 0],
        [7, 1, 5, 7, 0]])
tensor([[1, 5, 6, 2, 0],
        [4, 7, 5, 0, 0]])

構造embedding
Step 2:構建word embedding,我們需要得到向量化的句子,也就是先構建詞表embedding,再利用索引得到向量化的句子。

src_embedding_table = nn.Embedding(max_num_src_words + 1, model_dim)
tgt_embedding_table = nn.Embedding(max_num_tgt_words + 1, model_dim)
src_embedding = src_embedding_table(src_seq)
tgt_embedding = tgt_embedding_table(tgt_seq)
print(src_embedding)
print(tgt_embedding)

輸出src_embedding和tgt_embedding

tensor([[[ 4.0562e-01, -4.7774e-01,  1.1197e+00,  2.5271e-01, -2.8610e-01,
          -1.7786e+00, -3.4801e-01,  5.9147e-02],
         [ 4.0562e-01, -4.7774e-01,  1.1197e+00,  2.5271e-01, -2.8610e-01,
          -1.7786e+00, -3.4801e-01,  5.9147e-02],
         [-1.1680e-01,  2.6256e-01,  9.0318e-01,  2.5852e-01, -6.7806e-01,
          -3.2029e-01,  5.9314e-01,  1.4338e-01],
         [-1.1680e-01,  2.6256e-01,  9.0318e-01,  2.5852e-01, -6.7806e-01,
          -3.2029e-01,  5.9314e-01,  1.4338e-01],
         [-1.1680e-01,  2.6256e-01,  9.0318e-01,  2.5852e-01, -6.7806e-01,
          -3.2029e-01,  5.9314e-01,  1.4338e-01]],

        [[ 2.0992e+00,  4.3112e-01,  1.1805e-03,  1.9759e-01, -7.2922e-01,
          -2.1830e+00, -1.0120e-02, -4.0442e-01],
         [ 1.7823e-01, -4.7776e-01, -1.1815e+00, -1.3451e+00, -1.1886e+00,
           1.7289e+00, -3.9730e-01,  6.2238e-01],
         [-1.0617e+00, -2.1093e-01, -1.1825e+00, -6.9534e-01, -4.2309e-01,
           3.7529e-01, -1.8998e-01,  1.1653e-02],
         [-1.0617e+00, -2.1093e-01, -1.1825e+00, -6.9534e-01, -4.2309e-01,
           3.7529e-01, -1.8998e-01,  1.1653e-02],
         [-1.1680e-01,  2.6256e-01,  9.0318e-01,  2.5852e-01, -6.7806e-01,
          -3.2029e-01,  5.9314e-01,  1.4338e-01]]],
       grad_fn=<EmbeddingBackward0>)
tensor([[[-0.6926,  0.3298,  0.8671, -3.5071,  1.3463, -0.5505, -0.5561,
          -0.7192],
         [-1.7076, -0.5672, -0.1689,  0.8521,  1.2127,  0.6972,  0.6094,
           0.5606],
         [-0.6211, -0.0036,  1.6586, -0.0787,  0.2682, -1.5178, -1.5031,
          -1.7362],
         [-1.3177,  1.1583,  0.4895,  0.9380,  0.8419, -0.0501, -1.0925,
          -0.7124],
         [ 1.5036, -0.3206, -0.2090, -0.2941, -0.0675, -0.1443, -0.5557,
           0.5938]],

        [[-1.7076, -0.5672, -0.1689,  0.8521,  1.2127,  0.6972,  0.6094,
           0.5606],
         [-0.5977, -0.7804, -1.0782,  1.7742,  2.0498,  0.4659, -1.1477,
           1.5279],
         [-0.6211, -0.0036,  1.6586, -0.0787,  0.2682, -1.5178, -1.5031,
          -1.7362],
         [ 1.5036, -0.3206, -0.2090, -0.2941, -0.0675, -0.1443, -0.5557,
           0.5938],
         [ 1.5036, -0.3206, -0.2090, -0.2941, -0.0675, -0.1443, -0.5557,
           0.5938]]], grad_fn=<EmbeddingBackward0>)

2.3 構建position embedding

如下圖所示,我們根據論文中的公式去構建position embedding。
在這里插入圖片描述
Step 3:pe_embedding_table[:, 0::2]得到偶數列編碼,pe_embedding_table[:, 1::2]得到奇數列編碼。
構建位置索引,再通過nn.embedding就可以直接得到position embedding。

pos_mat = torch.arange(max_position_len).reshape((-1, 1))
i_mat = torch.pow(10000, torch.arange(0, 8, 2).reshape(1, -1) / model_dim)
pe_embedding_table = torch.zeros(max_position_len, model_dim)
# 偶數列
pe_embedding_table[:, 0::2] = torch.sin(pos_mat / i_mat)
# 奇數列
pe_embedding_table[:, 1::2] = torch.cos(pos_mat / i_mat)
# print(pos_mat)
# print(i_mat)
# print(pe_embedding_table)

pe_embedding = nn.Embedding(max_position_len, model_dim)
pe_embedding.weight = nn.Parameter(pe_embedding_table, requires_grad=False)

src_pos = torch.cat([torch.unsqueeze(torch.arange(max(src_len)), 0) for _ in src_len]).to(torch.int32)
tgt_pos = torch.cat([torch.unsqueeze(torch.arange(max(tgt_len)), 0) for _ in tgt_len]).to(torch.int32)

src_pe_embedding = pe_embedding(src_pos)
tgt_pe_embedding = pe_embedding(tgt_pos)
print(src_pe_embedding)
print(tgt_pe_embedding)

輸出src_pe_embedding和tgt_pe_embedding

tensor([[[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,
           1.0000e+00,  0.0000e+00,  1.0000e+00],
         [ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,
           9.9995e-01,  1.0000e-03,  1.0000e+00],
         [ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,
           9.9980e-01,  2.0000e-03,  1.0000e+00],
         [ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,
           9.9955e-01,  3.0000e-03,  1.0000e+00]],

        [[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,
           1.0000e+00,  0.0000e+00,  1.0000e+00],
         [ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,
           9.9995e-01,  1.0000e-03,  1.0000e+00],
         [ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,
           9.9980e-01,  2.0000e-03,  1.0000e+00],
         [ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,
           9.9955e-01,  3.0000e-03,  1.0000e+00]]])
tensor([[[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,
           1.0000e+00,  0.0000e+00,  1.0000e+00],
         [ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,
           9.9995e-01,  1.0000e-03,  1.0000e+00],
         [ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,
           9.9980e-01,  2.0000e-03,  1.0000e+00],
         [ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,
           9.9955e-01,  3.0000e-03,  1.0000e+00]],

        [[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,
           1.0000e+00,  0.0000e+00,  1.0000e+00],
         [ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,
           9.9995e-01,  1.0000e-03,  1.0000e+00],
         [ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,
           9.9980e-01,  2.0000e-03,  1.0000e+00],
         [ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,
           9.9955e-01,  3.0000e-03,  1.0000e+00]]])

2.4 構造encoder的self-attention mask

Step 4:因為句子的長度不一,于是我們會用padding去補0,之后便可得到有效位置矩陣。現在我們需要去得到有效矩陣,于是我們通過torch.bmm將有效位置矩陣與有效位置矩陣的轉置矩陣的1,2維進行相乘(0維是batch維,不參與運算),便可得到有效矩陣。

valid_encoder_pos = torch.unsqueeze(torch.cat([torch.unsqueeze(F.pad(torch.ones(L), (0, max(src_len) - L)), 0)
                                               for L in src_len]), 2)
valid_encoder_pos_matrix = torch.bmm(valid_encoder_pos, valid_encoder_pos.transpose(1, 2))
print(valid_encoder_pos_matrix)

輸出有效矩陣

tensor([[[1., 1., 0., 0.],
         [1., 1., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],

        [[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]]])

我們用1減去有效矩陣,并將其賦成bool型便可得到無效矩陣。

invalid_encoder_pos_matrix = 1 - valid_encoder_pos_matrix
mask_encoder_self_attention = invalid_encoder_pos_matrix.to(torch.bool)
print(invalid_encoder_pos_matrix)
print(mask_encoder_self_attention)

輸出無效矩陣

tensor([[[False, False,  True,  True],
         [False, False,  True,  True],
         [ True,  True,  True,  True],
         [ True,  True,  True,  True]],

        [[False, False, False, False],
         [False, False, False, False],
         [False, False, False, False],
         [False, False, False, False]]])

通過softmax得到mask矩陣。

score = torch.randn(batch_size, max(src_len), max(src_len))
masked_score = score.masked_fill(mask_encoder_self_attention, -1e9)
prob = F.softmax(masked_score, -1)
print(src_len)
print(score)
print(masked_score)
print(prob)

查看輸出結果

tensor([2, 4], dtype=torch.int32)
tensor([[[-0.2043, -0.3917, -1.0137, -1.1826],
         [-0.0821,  0.7781, -1.1928,  0.2586],
         [-0.2179,  0.8631, -0.9092,  0.0432],
         [ 0.3567,  1.0154, -1.0005,  0.2797]],

        [[-0.9328, -0.6068,  1.0793,  1.0886],
         [-0.8354,  0.6814,  1.3121,  1.2134],
         [ 2.0781, -0.4271, -2.4216,  0.0345],
         [-0.9104, -1.5460,  0.7630,  0.5593]]])
tensor([[[-2.0433e-01, -3.9166e-01, -1.0000e+09, -1.0000e+09],
         [-8.2061e-02,  7.7813e-01, -1.0000e+09, -1.0000e+09],
         [-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09],
         [-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09]],

        [[-9.3281e-01, -6.0677e-01,  1.0793e+00,  1.0886e+00],
         [-8.3541e-01,  6.8140e-01,  1.3121e+00,  1.2134e+00],
         [ 2.0781e+00, -4.2715e-01, -2.4216e+00,  3.4491e-02],
         [-9.1038e-01, -1.5460e+00,  7.6298e-01,  5.5926e-01]]])
tensor([[[0.5467, 0.4533, 0.0000, 0.0000],
         [0.2973, 0.7027, 0.0000, 0.0000],
         [0.2500, 0.2500, 0.2500, 0.2500],
         [0.2500, 0.2500, 0.2500, 0.2500]],

        [[0.0574, 0.0796, 0.4295, 0.4335],
         [0.0457, 0.2083, 0.3914, 0.3546],
         [0.8181, 0.0668, 0.0091, 0.1060],
         [0.0892, 0.0473, 0.4756, 0.3879]]])

2.5 構造intra-attention的mask

這里的步驟和Step 4差不多,但是要注意Q @ K ^ T,即decoder Q與encoder K ^ T的相關有效性。

valid_decoder_pos = torch.unsqueeze(torch.cat([torch.unsqueeze(F.pad(torch.ones(L), (0, max(tgt_len) - L)), 0)
                                               for L in tgt_len]), 2)
valid_cross_pos_matrix = torch.bmm(valid_decoder_pos, valid_encoder_pos.transpose(1, 2))
invalid_cross_pos_matrix = 1 - valid_cross_pos_matrix
mask_cross_attention = invalid_cross_pos_matrix.to(torch.bool)
print(valid_cross_pos_matrix)
print(invalid_cross_pos_matrix)
print(mask_cross_attention)

查看輸出結果

tensor([[[1., 1., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 0., 0.]],

        [[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [0., 0., 0., 0.]]])
tensor([[[0., 0., 1., 1.],
         [0., 0., 1., 1.],
         [0., 0., 1., 1.],
         [0., 0., 1., 1.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [1., 1., 1., 1.]]])
tensor([[[False, False,  True,  True],
         [False, False,  True,  True],
         [False, False,  True,  True],
         [False, False,  True,  True]],

        [[False, False, False, False],
         [False, False, False, False],
         [False, False, False, False],
         [ True,  True,  True,  True]]])

2.6 構成decoder self-attention的mask

這里的步驟和Step 4差不多,但是這里用到torch.tril生成三角矩陣,并且對行和列都進行了補0。

valid_decoder_tri_matrix = torch.cat([torch.unsqueeze(F.pad(torch.tril(torch.ones((L, L))),
                                                            (0, max(tgt_len) - L, 0, max(tgt_len) - L)), 0)
                                      for L in tgt_len])
invalid_decoder_tri_matrix = 1 - valid_decoder_tri_matrix
invalid_decoder_tri_matrix = invalid_decoder_tri_matrix.to(torch.bool)
print(valid_decoder_tri_matrix)
print(invalid_decoder_tri_matrix)

查看三角矩陣

tensor([[[1., 0., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 1., 0.],
         [1., 1., 1., 1.]],

        [[1., 0., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 1., 0.],
         [0., 0., 0., 0.]]])
tensor([[[False,  True,  True,  True],
         [False, False,  True,  True],
         [False, False, False,  True],
         [False, False, False, False]],

        [[False,  True,  True,  True],
         [False, False,  True,  True],
         [False, False, False,  True],
         [ True,  True,  True,  True]]])

通過無效矩陣做softmax,得到mask。

score = torch.randn(batch_size, max(tgt_len), max(tgt_len))
masked_score = score.masked_fill(invalid_decoder_tri_matrix, -1e9)
prob = F.softmax(masked_score, -1)
print(tgt_len)
print(prob)

輸出得到的mask

tensor([4, 3], dtype=torch.int32)
tensor([[[1.0000, 0.0000, 0.0000, 0.0000],
         [0.3802, 0.6198, 0.0000, 0.0000],
         [0.6869, 0.2416, 0.0716, 0.0000],
         [0.1204, 0.1475, 0.1840, 0.5481]],

        [[1.0000, 0.0000, 0.0000, 0.0000],
         [0.5099, 0.4901, 0.0000, 0.0000],
         [0.1156, 0.8410, 0.0434, 0.0000],
         [0.2500, 0.2500, 0.2500, 0.2500]]])

最后一行是因為補了一行的0,然后概率相等,各自都是0.2500。

2.7 構建scaled self-attention

def scaled_dot_product_attention(Q, K, V, attn_mask):
    # shape of Q,K,V : (batch_size*num_head. seq_len, model_dim/num_head)
    score = torch.bmm(Q, K.transpose(-2, -1)/torch.sqrt(model_dim))
    masked_score = score.masked_fill(attn_mask, -1e9)
    prob = F.softmax(masked_score, -1)
    context = torch.bmm(prob, V)
    return context

總結

在本周的學習中,主要學習了Transformer模型,是一種避免循環的模型結構,完全依賴于注意力機制對輸入輸出的全局依賴關系進行建模。Transformer模型的優勢是突破了 RNN 模型不能并行計算的限制;相比CNN來說,計算兩個位置之間的關聯所需的操作次數不隨距離增長;自注意力可以產生更具可解釋性的模型。本周對論文只是進行簡單地閱讀,在下周會對論文進行詳細地閱讀并理解,并對Transformer模型進行詳細地解釋說明。

原文鏈接:https://blog.csdn.net/peaunt1/article/details/127040300

欄目分類
最近更新