深度學習之Transformer網路

故y發表於2022-12-27

【博主使用的python版本:3.6.8】


本次沒有額外的資料下載

Packages

import tensorflow as tf
import pandas as pd
import time
import numpy as np
import matplotlib.pyplot as plt

from tensorflow.keras.layers import Embedding, MultiHeadAttention, Dense, Input, Dropout, LayerNormalization
from transformers import DistilBertTokenizerFast #, TFDistilBertModel
from transformers import TFDistilBertForTokenClassification
from tqdm import tqdm_notebook as tqdm

1 - 位置編碼

在順序到序列任務中,資料的相對順序對其含義非常重要。當你訓練順序神經網路(如RNN)時,你按順序將輸入輸入到網路中。有關資料順序的資訊會自動輸入到模型中。但是,在訓練轉換器網路時,會一次性將資料全部輸入到模型中。雖然這大大減少了訓練時間,但沒有關於資料順序的資訊。這就是位置編碼有用的地方 - 您可以專門編碼輸入的位置,並使用以下正弦和餘弦公式將它們傳遞到網路中:

  • d是詞嵌入和位置編碼的維度
  • pos是單詞的位置。
  • i指位置編碼的每個不同維度。

正弦和餘弦方程的值足夠小(介於 -1 和 1 之間),因此當您將位置編碼新增到單詞嵌入時,單詞嵌入不會明顯失真。位置編碼和單詞嵌入的總和最終是輸入到模型中的內容。結合使用這兩個方程有助於變壓器網路關注輸入資料的相對位置。請注意,雖然在講座中,Andrew 使用垂直向量,但在此作業中,所有向量都是水平的。所有矩陣乘法都應相應調整。

1.1 - 正弦角和餘弦角

透過計算正弦和餘弦方程的內項,獲取用於計算位置編碼的可能角度:

 

練習 1 - get_angles

實現函式 get_angles() 來計算正弦和餘弦位置編碼的可能角度

def get_angles(pos, i, d):
    """
    獲取位置編碼的角度
    
    Arguments:
        pos -- 包含位置的列向量[[0], [1], ...,[N-1]]
        i --   包含維度跨度的行向量 [[0, 1, 2, ..., M-1]]
        d(integer) -- 編碼大小
    
    Returns:
        angles -- (pos, d) 陣列
    """
    
    angles = pos/ (np.power(10000, (2 * (i//2)) / np.float32(d)))
    
    
    return angles

我們測試一下:

def get_angles_test(target):
    position = 4
    d_model = 16
    pos_m = np.arange(position)[:, np.newaxis]
    dims = np.arange(d_model)[np.newaxis, :]

    result = target(pos_m, dims, d_model)

    assert type(result) == np.ndarray, "你必須返回一系列陣列集合"
    assert result.shape == (position, d_model), f"防止錯誤我們希望: ({position}, {d_model})"
    assert np.sum(result[0, :]) == 0
    assert np.isclose(np.sum(result[:, 0]), position * (position - 1) / 2)
    even_cols =  result[:, 0::2]
    odd_cols = result[:,  1::2]
    assert np.all(even_cols == odd_cols), "奇數列和偶數列的子矩陣必須相等"
    limit = (position - 1) / np.power(10000,14.0/16.0)
    assert np.isclose(result[position - 1, d_model -1], limit ), f"組後的值必須是 {limit}"

    print("\033[92mAll tests passed")

get_angles_test(get_angles)

# 例如
position = 4
d_model = 8
pos_m = np.arange(position)[:, np.newaxis]
dims = np.arange(d_model)[np.newaxis, :]
get_angles(pos_m, dims, d_model)
All tests passed
Out[9]:
array([[0.e+00, 0.e+00, 0.e+00, 0.e+00, 0.e+00, 0.e+00, 0.e+00, 0.e+00],
       [1.e+00, 1.e+00, 1.e-01, 1.e-01, 1.e-02, 1.e-02, 1.e-03, 1.e-03],
       [2.e+00, 2.e+00, 2.e-01, 2.e-01, 2.e-02, 2.e-02, 2.e-03, 2.e-03],
       [3.e+00, 3.e+00, 3.e-01, 3.e-01, 3.e-02, 3.e-02, 3.e-03, 3.e-03]])

1.2 - 正弦和餘弦位置編碼

現在,您可以使用計算的角度來計算正弦和餘弦位置編碼。

 

練習 2 - 位置編碼

實現函式 positional_encoding() 來計算正弦和餘弦位置編碼

  • np.newaxis 有用,具體取決於您選擇的實現。就是將矩陣升維
def positional_encoding(positions, d):
    """
    預先計算包含所有位置編碼的矩陣
    
    Arguments:
        positions (int) -- 要編碼的最大位置數
        d (int) --編碼大小 
    
    Returns:
        pos_encoding -- (1, position, d_model)具有位置編碼的矩陣
    """
    # 初始化所有角度angle_rads矩陣
    angle_rads = get_angles(np.arange(positions)[:, np.newaxis],
                            np.arange(d)[ np.newaxis,:],
                            d)
  
    # -> angle_rads has dim (positions,d)
    # 將 sin 應用於陣列中的偶數索引;2i
    angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])
  
    # a將 cos 應用於陣列中的偶數索引;2i; 2i+1
    angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])
    # END CODE HERE
    
    pos_encoding = angle_rads[np.newaxis, ...]
    
    return tf.cast(pos_encoding, dtype=tf.float32)

我們來測試一下:

def positional_encoding_test(target):
    position = 8
    d_model = 16

    pos_encoding = target(position, d_model)
    sin_part = pos_encoding[:, :, 0::2]
    cos_part = pos_encoding[:, :, 1::2]

    assert tf.is_tensor(pos_encoding), "輸出不是一個張量"
    assert pos_encoding.shape == (1, position, d_model), f"防止錯誤,我們希望: (1, {position}, {d_model})"

    ones = sin_part ** 2  +  cos_part ** 2
    assert np.allclose(ones, np.ones((1, position, d_model // 2))), "平方和一定等於1 = sin(a)**2 + cos(a)**2"
    
    angs = np.arctan(sin_part / cos_part)
    angs[angs < 0] += np.pi
    angs[sin_part.numpy() < 0] += np.pi
    angs = angs % (2 * np.pi)
    
    pos_m = np.arange(position)[:, np.newaxis]
    dims = np.arange(d_model)[np.newaxis, :]

    trueAngs = get_angles(pos_m, dims, d_model)[:, 0::2] % (2 * np.pi)
    
    assert np.allclose(angs[0], trueAngs), "您是否分別將 sin 和 cos 應用於偶數和奇數部分?"
 
    print("\033[92mAll tests passed")

    
positional_encoding_test(positional_encoding)
All tests passed
計算位置編碼的工作很好!現在,您可以視覺化它們。
pos_encoding = positional_encoding(50, 512)

print (pos_encoding.shape)

plt.pcolormesh(pos_encoding[0], cmap='RdBu')
plt.xlabel('d')
plt.xlim((0, 512))
plt.ylabel('Position')
plt.colorbar()
plt.show()
(1, 50, 512)

 

 每一行代表一個位置編碼 - 請注意,沒有一行是相同的!您已為每個單詞建立了唯一的位置編碼。

2 - 掩碼

構建transformer網路時,有兩種型別的掩碼很有用:填充掩碼和前瞻掩碼。兩者都有助於softmax計算為輸入句子中的單詞提供適當的權重。

2.1 - 填充掩碼

通常,輸入序列會超過網路可以處理的序列的最大長度。假設模型的最大長度為 5,則按以下序列饋送:

[["Do", "you", "know", "when", "Jane", "is", "going", "to", "visit", "Africa"], 
 ["Jane", "visits", "Africa", "in", "September" ],
 ["Exciting", "!"]
]
可能會被向量化為:
[[ 71, 121, 4, 56, 99, 2344, 345, 1284, 15],
 [ 56, 1285, 15, 181, 545],
 [ 87, 600]
]
將序列傳遞到轉換器模型中時,它們必須具有統一的長度。您可以透過用零填充序列並截斷超過模型最大長度的句子來實現此目的:
[[ 71, 121, 4, 56, 99],
 [ 2344, 345, 1284, 15, 0],
 [ 56, 1285, 15, 181, 545],
 [ 87, 600, 0, 0, 0],
]
長度超過最大長度 5 的序列將被截斷,零將被新增到截斷的序列中以實現一致的長度。同樣,對於短於最大長度的序列,它們也將新增零以進行填充。
但是,這些零會影響softmax計算 - 這是填充掩碼派上用場的時候!透過將填充掩碼乘以 -1e9 並將其新增到序列中,
您可以透過將零設定為接近負無窮大來遮蔽零。我們將為您實現這一點,以便您可以獲得構建transformer網路的樂趣!? 只需確保完成程式碼,以便在構建模型時正確實現填充。
遮蔽後,您的輸入應從 [87, 600, 0, 0, 0] 變為 [87, 600, -1e9, -1e9, -1e9],這樣當您採用 softmax 時,零不會影響分數。
def create_padding_mask(seq):
    """
   為填充單元格建立矩陣掩碼
    
    Arguments:
        seq -- (n, m) 矩陣
    
    Returns:
        mask -- (n, 1, 1, m)二元張量
    """
    #tf.math.equal(a,b) 表示a,b是否相等
    #tf.cast(a,tf.float32) 是將a轉化為tf.float32型別
    seq = tf.cast(tf.math.equal(seq, 0), tf.float32)
  
    # 新增額外尺寸以新增填充
    # to the attention logits.
    return seq[:, tf.newaxis, tf.newaxis, :] 

我們測試一下:

x = tf.constant([[7., 6., 0., 0., 1.], [1., 2., 3., 0., 0.], [0., 0., 0., 4., 5.]])
print(create_padding_mask(x))
tf.Tensor(
[[[[0. 0. 1. 1. 0.]]]


 [[[0. 0. 0. 1. 1.]]]


 [[[1. 1. 1. 0. 0.]]]], shape=(3, 1, 1, 5), dtype=float32)
如果我們將這個掩碼乘以 -1e9 並將其新增到樣本輸入序列中,則零基本上設定為負無窮大。請注意採用原始序列和掩碼序列的softmax時的差異:
print(tf.keras.activations.softmax(x))
print(tf.keras.activations.softmax(x + create_padding_mask(x) * -1.0e9))
tf.Tensor(
[[7.2876632e-01 2.6809818e-01 6.6454883e-04 6.6454883e-04 1.8064311e-03]
 [8.4437370e-02 2.2952460e-01 6.2391245e-01 3.1062772e-02 3.1062772e-02]
 [4.8541022e-03 4.8541022e-03 4.8541022e-03 2.6502502e-01 7.2041267e-01]], shape=(3, 5), dtype=float32)
tf.Tensor(
[[[[7.2973621e-01 2.6845497e-01 0.0000000e+00 0.0000000e+00
    1.8088353e-03]
   [2.4472848e-01 6.6524088e-01 0.0000000e+00 0.0000000e+00
    9.0030566e-02]
   [6.6483547e-03 6.6483547e-03 0.0000000e+00 0.0000000e+00
    9.8670328e-01]]]


 [[[7.3057157e-01 2.6876229e-01 6.6619500e-04 0.0000000e+00
    0.0000000e+00]
   [9.0030566e-02 2.4472848e-01 6.6524088e-01 0.0000000e+00
    0.0000000e+00]
   [3.3333334e-01 3.3333334e-01 3.3333334e-01 0.0000000e+00
    0.0000000e+00]]]


 [[[0.0000000e+00 0.0000000e+00 0.0000000e+00 2.6894143e-01
    7.3105854e-01]
   [0.0000000e+00 0.0000000e+00 0.0000000e+00 5.0000000e-01
    5.0000000e-01]
   [0.0000000e+00 0.0000000e+00 0.0000000e+00 2.6894143e-01
    7.3105854e-01]]]], shape=(3, 1, 3, 5), dtype=float32)

2.2 - 前瞻掩碼

前瞻面具遵循類似的直覺。在訓練中,您將可以訪問訓練示例的完整正確輸出。前瞻掩碼可幫助模型假裝它正確預測了部分輸出,並檢視它是否可以在不向前看的情況下正確預測下一個輸出。

例如,如果預期的正確輸出是 [1, 2, 3],並且您希望檢視給定模型是否正確預測了第一個值,它是否可以預測第二個值,則可以遮蔽第二個和第三個值。因此,您將輸入遮蔽序列 [1, -1e9, -1e9],看看它是否可以生成 [1, 2, -1e9]。

僅僅因為你這麼努力,我們也會為你??實現這個掩碼。同樣,請仔細檢視程式碼,以便以後可以有效地實現它。

def create_look_ahead_mask(size):
    """
    返回一個填充有 1 的上三角矩陣
    
    Arguments:
        size -- 矩陣大小
    
    Returns:
        mask -- (size, size) 張量
    """
    #tf.linalg.band_part 以對角線為中心,取它的副對角線部分,其他部分用0填充
    mask = tf.linalg.band_part(tf.ones((size, size)), -1, 0)
    return mask

我們來測試一下:

x = tf.random.uniform((1, 3))
temp = create_look_ahead_mask(x.shape[1])
<tf.Tensor: shape=(3, 3), dtype=float32, numpy=
array([[1., 0., 0.],
       [1., 1., 0.],
       [1., 1., 1.]], dtype=float32)>

3 - 自注意力

正如變形金剛論文的作者所說,“注意力就是你所需要的一切”。

 

 

 使用與傳統卷積網路配對的自我注意允許平行化,從而加快訓練速度。您將實現縮放的點積注意力,它將查詢、鍵、值和掩碼作為輸入,以返回序列中單詞的豐富的、基於注意力的向量表示。這種型別的自我注意可以在數學上表示為:

  • Q是查詢矩陣
  • K是鍵的矩陣
  • V是值的矩陣
  • M是您選擇應用的可選蒙版
  • dk是按鍵的尺寸,用於縮小所有內容,以便 softmax 不會爆炸

練習 3 - scaled_dot_product_attention

 實現函式 'scaled_dot_product_attention()' 來建立基於注意力的表示

def scaled_dot_product_attention(q, k, v, mask):
    """
    計算注意力權重。
      Q、K、V 必須具有匹配的前導尺寸。
      k, v 必須具有匹配的倒數第二個維度,即:seq_len_k = seq_len_v。
      面具根據其型別有不同的形狀(填充或向前看)
      但它必須是可廣播的新增。

    Arguments:
        q -- query shape == (..., seq_len_q, depth)
        k -- key shape == (..., seq_len_k, depth)
        v -- value shape == (..., seq_len_v, depth_v)
        掩碼:形狀可廣播的浮點張量
              自(..., seq_len_q, seq_len_k). Defaults to None.

    Returns:
        output -- attention_weights
    """
    # START CODE HERE
    
    # Q*K' 內積
    matmul_qk = tf.matmul(q, k, transpose_b=True)

    #  matmul_qk 的規模
    dk = tf.cast(tf.shape(k)[-1], tf.float32)
    scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)

    # 將掩碼新增到縮放張量中。
    if mask is not None:
        scaled_attention_logits += (mask * -1e9)

    # softmax 在最後一個軸 (seq_len_k) 上歸一化,以便分數
    # 相加等於1
    attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1) 
    # 注意力權重 * V
    output = tf.matmul(attention_weights, v)   # (..., seq_len_q, depth_v)
    
    # END CODE HERE

    return output, attention_weights

我們來測試一下:

def scaled_dot_product_attention_test(target):
    q = np.array([[1, 0, 1, 1], [0, 1, 1, 1], [1, 0, 0, 1]]).astype(np.float32)
    k = np.array([[1, 1, 0, 1], [1, 0, 1, 1 ], [0, 1, 1, 0], [0, 0, 0, 1]]).astype(np.float32)
    v = np.array([[0, 0], [1, 0], [1, 0], [1, 1]]).astype(np.float32)

    attention, weights = target(q, k, v, None)
    assert tf.is_tensor(weights), "Weights must be a tensor"
    assert tuple(tf.shape(weights).numpy()) == (q.shape[0], k.shape[1]), f"Wrong shape. We expected ({q.shape[0]}, {k.shape[1]})"
    assert np.allclose(weights, [[0.2589478,  0.42693272, 0.15705977, 0.15705977],
                                   [0.2772748,  0.2772748,  0.2772748,  0.16817567],
                                   [0.33620113, 0.33620113, 0.12368149, 0.2039163 ]])

    assert tf.is_tensor(attention), "Output must be a tensor"
    assert tuple(tf.shape(attention).numpy()) == (q.shape[0], v.shape[1]), f"Wrong shape. We expected ({q.shape[0]}, {v.shape[1]})"
    assert np.allclose(attention, [[0.74105227, 0.15705977],
                                   [0.7227253,  0.16817567],
                                   [0.6637989,  0.2039163 ]])

    mask = np.array([[0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]])
    attention, weights = target(q, k, v, mask)

    assert np.allclose(weights, [[0.30719590187072754, 0.5064803957939148, 0.0, 0.18632373213768005],
                                 [0.3836517333984375, 0.3836517333984375, 0.0, 0.2326965481042862],
                                 [0.3836517333984375, 0.3836517333984375, 0.0, 0.2326965481042862]]), "Wrong masked weights"
    assert np.allclose(attention, [[0.6928040981292725, 0.18632373213768005],
                                   [0.6163482666015625, 0.2326965481042862], 
                                   [0.6163482666015625, 0.2326965481042862]]), "Wrong masked attention"
    
    print("\033[92mAll tests passed")
    
scaled_dot_product_attention_test(scaled_dot_product_attention)

出色的工作!您現在可以實現自我關注。有了它,您就可以開始構建編碼器塊了!

4 - 編碼快

轉換器編碼器層將自我注意和卷積神經網路風格的處理配對,以提高訓練速度,並將 K 和 V 矩陣傳遞給解碼器,稍後將在作業中構建解碼器。在作業的這一部分中,您將透過配對多頭注意力和前饋神經網路來實現編碼器(圖 2a)。

  • 多頭注意力可以認為是多次計算自我注意力以檢測不同的特徵。
  • 前饋神經網路包含兩個密集層,我們將實現為函式全連線

您的輸入句子首先透過多頭注意力層,編碼器在對特定單詞進行編碼時會檢視輸入句子中的其他單詞。然後將多頭注意力層的輸出饋送到前饋神經網路。完全相同的前饋網路獨立應用於每個位置。

  • 對於MultiHeadAttention層,您將使用Keras實現。如果您對如何將查詢矩陣 Q、鍵矩陣 K 和值矩陣 V 拆分為不同的頭感到好奇,可以檢視實現。
  • 您還將使用具有兩個密集層的順序 API 來構建前饋神經網路層。
def FullyConnected(embedding_dim, fully_connected_dim):
    return tf.keras.Sequential([
        tf.keras.layers.Dense(fully_connected_dim, activation='relu'),  # (batch_size, seq_len, dff)
        tf.keras.layers.Dense(embedding_dim)  # (batch_size, seq_len, d_model)
    ])

4.1-編碼層

現在,您可以在編碼器層中將多頭注意力和前饋神經網路配對在一起!您還將使用殘差連線和層歸一化來幫助加快訓練速度(圖 2a)。

練習4 - EncoderLayer

使用 call() 方法實現 EncoderLayer()

在本練習中,您將使用 call() 方法實現一個編碼器塊(圖 2)。該函式應執行以下步驟:

  1. 您將 Q、V、K 矩陣和布林掩碼傳遞給多頭注意力層。請記住,要計算自注意Q,V和K應該是相同的。
  2. 接下來,您將多頭注意力層的輸出傳遞給輟學層。不要忘記使用訓練引數來設定模型的模式。
  3. 現在,透過新增原始輸入 x 和 dropout 圖層的輸出來新增跳過連線。
  4. 新增跳過連線後,透過第一層規範化傳遞輸出。
  5. 最後,重複步驟 1-4,但使用前饋神經網路而不是多頭注意力層。

其他提示:

  • __init__ 方法建立將由呼叫方法訪問的所有層。無論想在哪裡使用在 __init__ 方法中定義的層,都必須使用語法 self。[插入圖層名稱]。
  • 您會發現MultiHeadAttention的文件很有幫助。請注意,如果查詢、鍵和值相同,則此函式執行自我注意。
class EncoderLayer(tf.keras.layers.Layer):
    """
    編碼器層由多頭自注意力機構組成,
    然後是一個簡單的、按位置的全連線前饋網路。
    這個拱門包括圍繞兩者的殘餘連線
    子層,然後是層歸一化。
    """
    def __init__(self, embedding_dim, num_heads, fully_connected_dim, dropout_rate=0.1, layernorm_eps=1e-6):
        super(EncoderLayer, self).__init__()

        self.mha = MultiHeadAttention(num_heads=num_heads,
                                      key_dim=embedding_dim)

        self.ffn = FullyConnected(embedding_dim=embedding_dim,
                                  fully_connected_dim=fully_connected_dim)

        self.layernorm1 = LayerNormalization(epsilon=layernorm_eps)
        self.layernorm2 = LayerNormalization(epsilon=layernorm_eps)

        self.dropout1 = Dropout(dropout_rate)
        self.dropout2 = Dropout(dropout_rate)
    
    def call(self, x, training, mask):
        """
        編碼器層的正向傳遞
        
        Arguments:
           x -- 形狀張量(batch_size、input_seq_len、embedding_dim)
            訓練 -- 布林值,設定為 true 以啟用
                        失活層的訓練模式
            掩碼 -- 布林掩碼,以確保填充不是
                    被視為輸入的一部分
        Returns:
            out2 -- 形狀張量(batch_size、input_seq_len、embedding_dim)
        """
        # START CODE HERE
        # 計算自注意力使用 mha(~1 line)
        #-> 要計算自我注意Q,V和K應該相同(x)
        self_attn_output = self.mha(x, x, x, mask) # Self attention (batch_size, input_seq_len, embedding_dim)
        
        # 將失活層應用於自我注意輸出(~1 line)
        self_attn_output = self.dropout1(self_attn_output, training=training)
        
        # 對輸入和注意力輸出的總和應用層歸一化,以獲得
        # 多頭注意力層輸出 (~1 line)
        mult_attn_out = self.layernorm1(x + self_attn_output)  # (batch_size, input_seq_len, embedding_dim)

        # 透過FFN傳遞多頭注意力層的輸出(~1 line)
        ffn_output = self.ffn(mult_attn_out)  # (batch_size, input_seq_len, embedding_dim)
        
        # 將失活層應用於 FFN 輸出 (~1 line)
        ffn_output = self.dropout2(ffn_output, training=training)
        
        # 對多頭注意力和 FFN 輸出的輸出之和應用層歸一化,以獲得
        # 編碼器層輸出(~1 行)
        encoder_layer_out = self.layernorm2(ffn_output + mult_attn_out)  # (batch_size, input_seq_len, embedding_dim)
        # END CODE HERE
        
        return encoder_layer_out

測試一下吧:

def EncoderLayer_test(target):
    q = np.array([[[1, 0, 1, 1], [0, 1, 1, 1], [1, 0, 0, 1]]]).astype(np.float32)
    encoder_layer1 = EncoderLayer(4, 2, 8)
    tf.random.set_seed(10)
    encoded = encoder_layer1(q, True, np.array([[1, 0, 1]]))
    
    assert tf.is_tensor(encoded), "Wrong type. Output must be a tensor"
    assert tuple(tf.shape(encoded).numpy()) == (1, q.shape[1], q.shape[2]), f"Wrong shape. We expected ((1, {q.shape[1]}, {q.shape[2]}))"

    assert np.allclose(encoded.numpy(), 
                       [[-0.5214877 , -1.001476  , -0.12321664,  1.6461804 ],
                       [-1.3114998 ,  1.2167752 , -0.5830886 ,  0.6778133 ],
                       [ 0.25485858,  0.3776546 , -1.6564771 ,  1.023964  ]],), "Wrong values"
    
    print("\033[92mAll tests passed")
    

EncoderLayer_test(EncoderLayer)
All tests passed

4.2 - 全編碼器

幹得真棒!您現在已經成功實現了位置編碼、自我注意和編碼器層 - 拍拍自己的背。現在,您已準備好構建完整的變壓器編碼器(圖 2b),您將在其中嵌入輸入並新增計算的位置編碼。然後,您將編碼的嵌入饋送到編碼器層堆疊。

練習 5 - Encoder

使用 call() 方法完成 Encoder() 函式,以嵌入輸入、新增位置編碼並實現多個編碼器層

在本練習中,您將使用嵌入層、位置編碼和多個編碼器層初始化編碼器。您的 call() 方法將執行以下步驟:

  1. 透過嵌入層傳遞輸入。
  2. 透過將嵌入乘以嵌入維度的平方根來縮放嵌入。請記住在計算平方根之前將嵌入維度轉換為資料型別 tf.float32。
  3. 將位置編碼:self.pos_encoding [:, :seq_len, :] 新增到嵌入中。
  4. 將編碼嵌入傳遞到一個 dropout 層,記住使用訓練引數來設定模型訓練模式。
  5. 使用 for 迴圈將 dropout 層的輸出傳遞到編碼層堆疊。
class Encoder(tf.keras.layers.Layer):
    """
    整個編碼器首先將輸入傳遞到嵌入層
    並使用位置編碼將輸出傳遞到堆疊
    編碼器層
        
    """   
    def __init__(self, num_layers, embedding_dim, num_heads, fully_connected_dim, input_vocab_size,
               maximum_position_encoding, dropout_rate=0.1, layernorm_eps=1e-6):
        super(Encoder, self).__init__()

        self.embedding_dim = embedding_dim
        self.num_layers = num_layers

        self.embedding = Embedding(input_vocab_size, self.embedding_dim)
        self.pos_encoding = positional_encoding(maximum_position_encoding, 
                                                self.embedding_dim)


        self.enc_layers = [EncoderLayer(embedding_dim=self.embedding_dim,
                                        num_heads=num_heads,
                                        fully_connected_dim=fully_connected_dim,
                                        dropout_rate=dropout_rate,
                                        layernorm_eps=layernorm_eps) 
                           for _ in range(self.num_layers)]

        self.dropout = Dropout(dropout_rate)
        
    def call(self, x, training, mask):
        """
       編碼器的正向傳遞
        
        Arguments:
           x -- 形狀張量 (batch_size, input_seq_len)
            訓練 -- 布林值,設定為 true 以啟用
                        輟學層的訓練模式
            掩碼 -- 布林掩碼,以確保填充不是
                    被視為輸入的一部分
        Returns:
            out2 -- 形狀張量(batch_size、input_seq_len、embedding_dim)
        """

        seq_len = tf.shape(x)[1]
        
        # START CODE HERE
        # 透過嵌入層傳遞輸入
        x = self.embedding(x)  # (batch_size, input_seq_len, embedding_dim)
        # 透過將嵌入乘以嵌入維度的平方根來縮放嵌入
        x *= tf.math.sqrt(tf.cast(self.embedding_dim,tf.float32))
        # 將位置編碼新增到嵌入
        x += self.pos_encoding[:, :seq_len, :]
        # 透過失活層傳遞編碼嵌入
        x = self.dropout(x, training=training)
        # 透過編碼層堆疊傳遞輸出
        for i in range(self.num_layers):
            x = self.enc_layers[i](x,training, mask)
        # END CODE HERE

        return x  # (batch_size, input_seq_len, embedding_dim)

測試一下吧:

def Encoder_test(target):
    tf.random.set_seed(10)
    
    embedding_dim=4
    
    encoderq = target(num_layers=2,
                      embedding_dim=embedding_dim,
                      num_heads=2,
                      fully_connected_dim=8,
                      input_vocab_size=32,
                      maximum_position_encoding=5)
    
    x = np.array([[2, 1, 3], [1, 2, 0]])
    
    encoderq_output = encoderq(x, True, None)
    
    assert tf.is_tensor(encoderq_output), "Wrong type. Output must be a tensor"
    assert tuple(tf.shape(encoderq_output).numpy()) == (x.shape[0], x.shape[1], embedding_dim), f"Wrong shape. We expected ({eshape[0]}, {eshape[1]}, {embedding_dim})"
    assert np.allclose(encoderq_output.numpy(), 
                       [[[-0.40172306,  0.11519244, -1.2322885,   1.5188192 ],
                         [ 0.4017268,   0.33922842, -1.6836855,   0.9427304 ],
                         [ 0.4685002,  -1.6252842,   0.09368491,  1.063099  ]],
                        [[-0.3489219,   0.31335592, -1.3568854,   1.3924513 ],
                         [-0.08761203, -0.1680029,  -1.2742313,   1.5298463 ],
                         [ 0.2627198,  -1.6140151,   0.2212624 ,  1.130033  ]]]), "Wrong values"
    
    print("\033[92mAll tests passed")
    
Encoder_test(Encoder)
All tests passed

5 - 譯碼器

解碼器層採用編碼器生成的 K 和 V 矩陣,並使用輸出中的 Q 矩陣計算第二個多頭注意力層(圖 3a)。

5.1 - 譯碼器層

同樣,您將多頭注意力與前饋神經網路配對,但這次您將實現兩個多頭注意力層。您還將使用殘差連線和層歸一化來幫助加快訓練速度(圖 3a)。

練習 6 - DecoderLayer

使用 call() 方法實現解碼器層()

  • 塊 1 是一個多頭注意力層,具有殘差連線、輟學層和前瞻掩碼。
  • 模組 2 將考慮編碼器的輸出,因此多頭注意層將從編碼器接收 K 和 V,從模組 1 接收 Q。然後,您將應用輟學層、層歸一化和殘差連線,就像您之前所做的那樣。
  • 最後,Block 3 是一個具有 dropout 和歸一化層以及殘差連線的前饋神經網路。
  • 前兩個塊與 EncoderLayer 非常相似,只是在計算自我注意時會返回attention_scores
class DecoderLayer(tf.keras.layers.Layer):
    """
   解碼器層由兩個多頭注意力塊組成,
    一個接受新的輸入並使用自我注意,另一個
    一個將其與編碼器的輸出相結合,然後是
    完全連線的塊。
    """
    def __init__(self, embedding_dim, num_heads, fully_connected_dim, dropout_rate=0.1, layernorm_eps=1e-6):
        super(DecoderLayer, self).__init__()

        self.mha1 = MultiHeadAttention(num_heads=num_heads,
                                      key_dim=embedding_dim)

        self.mha2 = MultiHeadAttention(num_heads=num_heads,
                                      key_dim=embedding_dim)

        self.ffn = FullyConnected(embedding_dim=embedding_dim,
                                  fully_connected_dim=fully_connected_dim)

        self.layernorm1 = LayerNormalization(epsilon=layernorm_eps)
        self.layernorm2 = LayerNormalization(epsilon=layernorm_eps)
        self.layernorm3 = LayerNormalization(epsilon=layernorm_eps)

        self.dropout1 = Dropout(dropout_rate)
        self.dropout2 = Dropout(dropout_rate)
        self.dropout3 = Dropout(dropout_rate)
    
    def call(self, x, enc_output, training, look_ahead_mask, padding_mask):
        """
        解碼器層的正向傳遞
        
        引數:
            x -- 形狀張量(batch_size、target_seq_len、embedding_dim)
            enc_output -- 形狀張量(batch_size、input_seq_len、embedding_dim)
            訓練 -- 布林值,設定為 true 以啟用
                        輟學層的訓練模式
            look_ahead_mask -- target_input的布林掩碼
            padding_mask -- 第二個多頭注意力層的布林掩碼
        返回:
            out3 -- 形狀張量 (batch_size, target_seq_len, embedding_dim)
            attn_weights_block1 -- 形狀張量(batch_size、num_heads、target_seq_len、input_seq_len)
            attn_weights_block2 -- 形狀張量(batch_size、num_heads、target_seq_len、input_seq_len)
        """
        
        # START CODE HERE
        # enc_output.shape == (batch_size, input_seq_len, embedding_dim)
        
        # BLOCK 1
        # 計算自我注意和返回注意力分數為 attn_weights_block1 (~1 行)
        attn1, attn_weights_block1 = self.mha1(x, x, x,look_ahead_mask, return_attention_scores=True)  # (batch_size, target_seq_len, d_model)
        
        # 在注意力輸出上應用失活層(~1 行)
        attn1 = self.dropout1(attn1, training = training)
        
        # 對注意力輸出和輸入的總和應用層歸一化(~1 行)
        out1 = self.layernorm1(attn1 + x)

        # BLOCK 2
        # 使用來自第一個塊的 Q 和來自編碼器輸出的 K 和 V 計算自我注意。
        # 多頭注意力的呼叫接受輸入(查詢、值、鍵、attention_mask、return_attention_scores、訓練)
        # 將注意力分數作為attn_weights_block2返回(~1 行)
        attn2, attn_weights_block2 = self.mha2( out1,enc_output, enc_output, padding_mask, return_attention_scores=True)  # (batch_size, target_seq_len, d_model)
        
        # 在注意力輸出上應用失活層(~1 行)
        attn2 = self.dropout2(attn2, training=training)
        
        # 對注意力輸出和第一個塊的輸出之和應用層歸一化(~1 行)
        out2 = self.layernorm2(attn2 + out1)  # (batch_size, target_seq_len, embedding_dim)
        
        #BLOCK 3
        # 透過 FFN 傳遞第二個塊的輸出
        ffn_output = self.ffn(out2) # (batch_size, target_seq_len, embedding_dim)
        
        # 將輟學圖層應用於 FFN 輸出
        ffn_output = self.dropout3(ffn_output, training=training)
        
        # 將層歸一化應用於 FFN 輸出和第二個塊的輸出之和
        out3 =  self.layernorm3(ffn_output + out2) # (batch_size, target_seq_len, embedding_dim)
        # END CODE HERE

        return out3, attn_weights_block1, attn_weights_block2

測試一下:

def DecoderLayer_test(target):
    
    num_heads=8
    tf.random.set_seed(10)
    
    decoderLayerq = target(
        embedding_dim=4, 
        num_heads=num_heads,
        fully_connected_dim=32, 
        dropout_rate=0.1, 
        layernorm_eps=1e-6)
    
    encoderq_output = tf.constant([[[-0.40172306,  0.11519244, -1.2322885,   1.5188192 ],
                                   [ 0.4017268,   0.33922842, -1.6836855,   0.9427304 ],
                                   [ 0.4685002,  -1.6252842,   0.09368491,  1.063099  ]]])
    
    q = np.array([[[1, 0, 1, 1], [0, 1, 1, 1], [1, 0, 0, 1]]]).astype(np.float32)
    
    look_ahead_mask = tf.constant([[1., 0., 0.],
                       [1., 1., 0.],
                       [1., 1., 1.]])
    
    padding_mask = None
    out, attn_w_b1, attn_w_b2 = decoderLayerq(q, encoderq_output, True, look_ahead_mask, padding_mask)
    
    assert tf.is_tensor(attn_w_b1), "Wrong type for attn_w_b1. Output must be a tensor"
    assert tf.is_tensor(attn_w_b2), "Wrong type for attn_w_b2. Output must be a tensor"
    assert tf.is_tensor(out), "Wrong type for out. Output must be a tensor"
    
    shape1 = (q.shape[0], num_heads, q.shape[1], q.shape[1])
    assert tuple(tf.shape(attn_w_b1).numpy()) == shape1, f"Wrong shape. We expected {shape1}"
    assert tuple(tf.shape(attn_w_b2).numpy()) == shape1, f"Wrong shape. We expected {shape1}"
    assert tuple(tf.shape(out).numpy()) == q.shape, f"Wrong shape. We expected {q.shape}"

    assert np.allclose(attn_w_b1[0, 0, 1], [0.5271505,  0.47284946, 0.], atol=1e-2), "Wrong values in attn_w_b1. Check the call to self.mha1"
    assert np.allclose(attn_w_b2[0, 0, 1], [0.33365652, 0.32598493, 0.34035856]),  "Wrong values in attn_w_b2. Check the call to self.mha2"
    assert np.allclose(out[0, 0], [0.04726627, -1.6235218, 1.0327158, 0.54353976]), "Wrong values in out"
    

    # Now let's try a example with padding mask
    padding_mask = np.array([[0, 0, 1]])
    out, attn_w_b1, attn_w_b2 = decoderLayerq(q, encoderq_output, True, look_ahead_mask, padding_mask)

    assert np.allclose(out[0, 0], [-0.34323323, -1.4689083, 1.1092525, 0.7028891]), "Wrong values in out when we mask the last word. Are you passing the padding_mask to the inner functions?"

    print("\033[92mAll tests passed")
    
DecoderLayer_test(DecoderLayer)
All tests passed

5.2 - 全譯碼器

你快到了!是時候使用解碼器層構建完整的轉換器解碼器了(圖 3b)。您將嵌入輸出並新增位置編碼。然後,您將編碼的嵌入饋送到解碼器層堆疊。

練習7 - Decoder

mplement Decoder() 使用 call() 方法嵌入輸出、新增位置編碼和實現多個解碼器層

在本練習中,您將使用嵌入層、位置編碼和多個解碼器層初始化解碼器。您的 call() 方法將執行以下步驟:

  1. 透過嵌入層傳遞生成的輸出。
  2. 透過將嵌入乘以嵌入維度的平方根來縮放嵌入。請記住在計算平方根之前將嵌入維度轉換為資料型別 tf.float32。
  3. 將位置編碼:self.pos_encoding [:, :seq_len, :] 新增到嵌入中。
  4. 將編碼嵌入傳遞到一個 dropout 層,記住使用訓練引數來設定模型訓練模式。
  5. 使用 for 迴圈透過解碼層堆疊傳遞 dropout 層的輸出。
class Decoder(tf.keras.layers.Layer):
    """
   整個編碼器首先將目標輸入傳遞到嵌入層
    並使用位置編碼將輸出傳遞到堆疊
    解碼器層
        
    """ 
    def __init__(self, num_layers, embedding_dim, num_heads, fully_connected_dim, target_vocab_size,
               maximum_position_encoding, dropout_rate=0.1, layernorm_eps=1e-6):
        super(Decoder, self).__init__()

        self.embedding_dim = embedding_dim
        self.num_layers = num_layers

        self.embedding = Embedding(target_vocab_size, self.embedding_dim)
        self.pos_encoding = positional_encoding(maximum_position_encoding, self.embedding_dim)

        self.dec_layers = [DecoderLayer(embedding_dim=self.embedding_dim,
                                        num_heads=num_heads,
                                        fully_connected_dim=fully_connected_dim,
                                        dropout_rate=dropout_rate,
                                        layernorm_eps=layernorm_eps) 
                           for _ in range(self.num_layers)]
        self.dropout = Dropout(dropout_rate)
    
    def call(self, x, enc_output, training, 
           look_ahead_mask, padding_mask):
        """
       解碼器的正向傳遞
        
引數:
            x -- 形狀張量(batch_size、target_seq_len、embedding_dim)
            enc_output -- 形狀張量(batch_size、input_seq_len、embedding_dim)
            訓練 -- 布林值,設定為 true 以啟用
                        輟學層的訓練模式
            look_ahead_mask -- target_input的布林掩碼
            padding_mask -- 第二個多頭注意力層的布林掩碼
        返回:
            x -- 形狀張量(batch_size、target_seq_len、embedding_dim)
            attention_weights - 包含所有注意力權重的張量字典
                                每個形狀 形狀的張量(batch_size、num_heads、target_seq_len、input_seq_len)
        """

        seq_len = tf.shape(x)[1]
        attention_weights = {}
        
        # START CODE HERE
        # 建立單詞嵌入
        x = self.embedding(x)  # (batch_size, target_seq_len, embedding_dim)
        
        # 透過乘以維度的平方根來縮放嵌入
        x *= tf.math.sqrt(tf.cast(self.embedding_dim, tf.float32))
        
        # 計算位置編碼並新增到單詞嵌入
        x += self.pos_encoding[:, :seq_len, :]
        
        # 將失活圖層應用於 X
        x = self.dropout(x, training=training)

        # 使用 for 迴圈透過解碼器層堆疊傳遞 x 並更新attention_weights(總共 ~4 行)
        for i in range(self.num_layers):
            # pASS X和編碼器透過一堆解碼器層輸出,節省注意力權重
            #塊 1 和塊 2 的 # (~1 行)
            x, block1, block2 = self.dec_layers[i](x, enc_output, training, look_ahead_mask, padding_mask)

            #update attention_weights 字典,具有塊 1 和塊 2 的注意權重
            attention_weights['decoder_layer{}_block1_self_att'.format(i+1)] = block1
            attention_weights['decoder_layer{}_block2_decenc_att'.format(i+1)] = block2
        # END CODE HERE
        
        # x.shape == (batch_size, target_seq_len, embedding_dim)
        return x, attention_weights

測試一下:

def Decoder_test(target):
    
    tf.random.set_seed(10)
        
    num_layers=7
    embedding_dim=4 
    num_heads=3
    fully_connected_dim=8
    target_vocab_size=33
    maximum_position_encoding=6
    
    x = np.array([[3, 2, 1], [2, 1, 0]])

    
    encoderq_output = tf.constant([[[-0.40172306,  0.11519244, -1.2322885,   1.5188192 ],
                         [ 0.4017268,   0.33922842, -1.6836855,   0.9427304 ],
                         [ 0.4685002,  -1.6252842,   0.09368491,  1.063099  ]],
                        [[-0.3489219,   0.31335592, -1.3568854,   1.3924513 ],
                         [-0.08761203, -0.1680029,  -1.2742313,   1.5298463 ],
                         [ 0.2627198,  -1.6140151,   0.2212624 ,  1.130033  ]]])
    
    look_ahead_mask = tf.constant([[1., 0., 0.],
                       [1., 1., 0.],
                       [1., 1., 1.]])
    
    decoderk = Decoder(num_layers,
                    embedding_dim, 
                    num_heads, 
                    fully_connected_dim,
                    target_vocab_size,
                    maximum_position_encoding)
    outd, att_weights = decoderk(x, encoderq_output, False, look_ahead_mask, None)
    
    assert tf.is_tensor(outd), "Wrong type for outd. It must be a dict"
    assert np.allclose(tf.shape(outd), tf.shape(encoderq_output)), f"Wrong shape. We expected { tf.shape(encoderq_output)}"
    print(outd[1, 1])
    assert np.allclose(outd[1, 1], [-0.2715261, -0.5606001, -0.861783, 1.69390933]), "Wrong values in outd"
    
    keys = list(att_weights.keys())
    assert type(att_weights) == dict, "Wrong type for att_weights[0]. Output must be a tensor"
    assert len(keys) == 2 * num_layers, f"Wrong length for attention weights. It must be 2 x num_layers = {2*num_layers}"
    assert tf.is_tensor(att_weights[keys[0]]), f"Wrong type for att_weights[{keys[0]}]. Output must be a tensor"
    shape1 = (x.shape[0], num_heads, x.shape[1], x.shape[1])
    assert tuple(tf.shape(att_weights[keys[1]]).numpy()) == shape1, f"Wrong shape. We expected {shape1}" 
    assert np.allclose(att_weights[keys[0]][0, 0, 1], [0.52145624, 0.47854376, 0.]), f"Wrong values in att_weights[{keys[0]}]"
    
    print("\033[92mAll tests passed")
    
Decoder_test(Decoder)
tf.Tensor([-0.2715261 -0.5606004 -0.8617829  1.6939092], shape=(4,), dtype=float32)
All tests passed

6 - Transformer

唷!這是相當艱鉅的任務,現在你已經完成了深度學習專業化的最後一次練習。祝賀!你已經完成了所有艱苦的工作,現在是時候把它們放在一起了。

透過轉換器體系結構的資料流如下所示:

  1. 首先,輸入透過編碼器,該編碼器只是您實現的重複編碼器層:
  • 輸入的嵌入和位置編碼
  • 多頭關注您的輸入
  • 前饋神經網路以幫助檢測特徵
  1. 然後,預測的輸出透過解碼器,解碼器由你實現的解碼器層組成:
  • 輸出的嵌入和位置編碼
  • 對生成的輸出進行多頭關注
  • 多頭注意力,Q來自第一個多頭注意力層,K和V來自編碼器
  • 前饋神經網路,幫助檢測特徵
  1. 最後,在第 N 個解碼器層之後,應用兩個密集層和一個 softmax 來生成序列中下一個輸出的預測。

練習8 - Transformer

使用 call() 方法實現 Transformer()

  1. 使用適當的掩碼將輸入傳遞到編碼器。
  2. 使用適當的掩碼透過解碼器傳遞編碼器輸出和目標。
  3. 應用線性變換和軟最大值來獲得預測。
class Transformer(tf.keras.Model):
    """
    帶編碼器和解碼器的完整transformer
    """
    def __init__(self, num_layers, embedding_dim, num_heads, fully_connected_dim, input_vocab_size, 
               target_vocab_size, max_positional_encoding_input,
               max_positional_encoding_target, dropout_rate=0.1, layernorm_eps=1e-6):
        super(Transformer, self).__init__()

        self.encoder = Encoder(num_layers=num_layers,
                               embedding_dim=embedding_dim,
                               num_heads=num_heads,
                               fully_connected_dim=fully_connected_dim,
                               input_vocab_size=input_vocab_size,
                               maximum_position_encoding=max_positional_encoding_input,
                               dropout_rate=dropout_rate,
                               layernorm_eps=layernorm_eps)

        self.decoder = Decoder(num_layers=num_layers, 
                               embedding_dim=embedding_dim,
                               num_heads=num_heads,
                               fully_connected_dim=fully_connected_dim,
                               target_vocab_size=target_vocab_size, 
                               maximum_position_encoding=max_positional_encoding_target,
                               dropout_rate=dropout_rate,
                               layernorm_eps=layernorm_eps)

        self.final_layer = Dense(target_vocab_size, activation='softmax')
    
    def call(self, inp, tar, training, enc_padding_mask, look_ahead_mask, dec_padding_mask):
        """
        整個變壓器的正向傳遞
        引數:
            inp -- 形狀張量(batch_size、input_seq_len、fully_connected_dim)
            tar -- 形狀張量(batch_size、target_seq_len、fully_connected_dim)
            訓練 -- 布林值,設定為 true 以啟用
                        輟學層的訓練模式
            enc_padding_mask -- 布林掩碼,以確保填充不是
                    被視為輸入的一部分
            look_ahead_mask -- target_input的布林掩碼
            padding_mask -- 第二個多頭注意力層的布林掩碼
        返回:
            final_output -- 描述我
            attention_weights - 包含解碼器所有注意力權重的張量字典
                                每個形狀 形狀的張量(batch_size、num_heads、target_seq_len、input_seq_len)
        
        """
        # START CODE HERE
        # 使用適當的引數呼叫 self.encoder 以獲取編碼器輸出
        enc_output = self.encoder(inp,training,enc_padding_mask) # (batch_size, inp_seq_len, fully_connected_dim)
        
        # 使用適當的引數呼叫 self.decoder 以獲取解碼器輸出
        # dec_output.shape == (batch_size, tar_seq_len, fully_connected_dim)
        dec_output, attention_weights = self.decoder(tar, enc_output, training, look_ahead_mask, dec_padding_mask)
        
        # 透過線性層和softmax(~2行)傳遞解碼器輸出
        final_output = self.final_layer(dec_output)  # (batch_size, tar_seq_len, target_vocab_size)
        # START CODE HERE

        return final_output, attention_weights

我們測試一下:

def Transformer_test(target):
    
    tf.random.set_seed(10)


    num_layers = 6
    embedding_dim = 4
    num_heads = 4
    fully_connected_dim = 8
    input_vocab_size = 30
    target_vocab_size = 35
    max_positional_encoding_input = 5
    max_positional_encoding_target = 6

    trans = Transformer(num_layers, 
                        embedding_dim, 
                        num_heads, 
                        fully_connected_dim, 
                        input_vocab_size, 
                        target_vocab_size, 
                        max_positional_encoding_input,
                        max_positional_encoding_target)
    # 0 is the padding value
    sentence_lang_a = np.array([[2, 1, 4, 3, 0]])
    sentence_lang_b = np.array([[3, 2, 1, 0, 0]])

    enc_padding_mask = np.array([[0, 0, 0, 0, 1]])
    dec_padding_mask = np.array([[0, 0, 0, 1, 1]])

    look_ahead_mask = create_look_ahead_mask(sentence_lang_a.shape[1])

    translation, weights = trans(
        sentence_lang_a,
        sentence_lang_b,
        True,
        enc_padding_mask,
        look_ahead_mask,
        dec_padding_mask
    )
    
    
    assert tf.is_tensor(translation), "Wrong type for translation. Output must be a tensor"
    shape1 = (sentence_lang_a.shape[0], max_positional_encoding_input, target_vocab_size)
    assert tuple(tf.shape(translation).numpy()) == shape1, f"Wrong shape. We expected {shape1}"
        
    print(translation[0, 0, 0:8])
    assert np.allclose(translation[0, 0, 0:8],
                       [[0.02616475, 0.02074359, 0.01675757, 
                         0.025527, 0.04473696, 0.02171909, 
                         0.01542725, 0.03658631]]), "Wrong values in outd"
    
    keys = list(weights.keys())
    assert type(weights) == dict, "Wrong type for weights. It must be a dict"
    assert len(keys) == 2 * num_layers, f"Wrong length for attention weights. It must be 2 x num_layers = {2*num_layers}"
    assert tf.is_tensor(weights[keys[0]]), f"Wrong type for att_weights[{keys[0]}]. Output must be a tensor"

    shape1 = (sentence_lang_a.shape[0], num_heads, sentence_lang_a.shape[1], sentence_lang_a.shape[1])
    assert tuple(tf.shape(weights[keys[1]]).numpy()) == shape1, f"Wrong shape. We expected {shape1}" 
    assert np.allclose(weights[keys[0]][0, 0, 1], [0.4992985, 0.5007015, 0., 0., 0.]), f"Wrong values in weights[{keys[0]}]"
    
    print(translation)
    
    print("\033[92mAll tests passed")

    
Transformer_test(Transformer)
tf.Tensor(
[0.02616474 0.02074358 0.01675757 0.025527   0.04473696 0.02171908
 0.01542725 0.0365863 ], shape=(8,), dtype=float32)
tf.Tensor(
[[[0.02616474 0.02074358 0.01675757 0.025527   0.04473696 0.02171908
   0.01542725 0.0365863  0.02433536 0.02948791 0.01698964 0.02147778
   0.05749574 0.02669399 0.01277918 0.03276358 0.0253941  0.01698772
   0.02758245 0.02529753 0.04394253 0.06258809 0.03667333 0.03009712
   0.05011232 0.01414333 0.01601288 0.01800467 0.02506283 0.01607273
   0.06204056 0.02099288 0.03005534 0.03070701 0.01854689]
  [0.02490053 0.017258   0.01794802 0.02998915 0.05038004 0.01997478
   0.01526351 0.03385608 0.03138068 0.02608407 0.01852771 0.01744511
   0.05923333 0.03287777 0.01450072 0.02815487 0.02676623 0.01684978
   0.02482791 0.02307897 0.04122656 0.05552057 0.03742857 0.03390089
   0.04666695 0.016675   0.01400229 0.01981527 0.02202851 0.01818
   0.05918451 0.02173372 0.03040997 0.03337187 0.02055808]
  [0.01867789 0.01225462 0.02509718 0.04180383 0.06244645 0.02000666
   0.01934387 0.03032456 0.05771374 0.02616111 0.01742368 0.01100331
   0.05456048 0.04248188 0.02078062 0.02245298 0.03337654 0.02052129
   0.0239658  0.02193134 0.0406813  0.03323279 0.04556257 0.03676545
   0.04394966 0.01574801 0.01223158 0.02734469 0.01154951 0.02240609
   0.03563078 0.02169302 0.02025472 0.02886864 0.02175328]
  [0.02305288 0.01215192 0.0224808  0.04188109 0.05324595 0.016529
   0.01626855 0.02452859 0.05319849 0.01741914 0.02720063 0.01175193
   0.04887013 0.05262584 0.02324444 0.01787255 0.02867536 0.01768711
   0.01800393 0.01797925 0.02830287 0.03332608 0.0324963  0.04277937
   0.03038616 0.03231759 0.01166379 0.0261881  0.01842925 0.02784597
   0.0434657  0.02524558 0.0328582  0.0404315  0.02959606]
  [0.01859851 0.01163484 0.02560123 0.04363472 0.06270956 0.01928385
   0.01924486 0.02882556 0.06161032 0.02436098 0.01855855 0.01041807
   0.05321557 0.04556077 0.0220504  0.02093103 0.03341144 0.02041205
   0.02265851 0.02099104 0.03823084 0.03121314 0.04416507 0.03813417
   0.04104865 0.01757099 0.01183266 0.0281889  0.0114538  0.02377768
   0.03464995 0.02217591 0.02084129 0.03000083 0.02300426]]], shape=(1, 5, 35), dtype=float32)
All tests passed

Conclusion

您已經結束了作業的評分部分。到目前為止,您已經:

  • 建立位置編碼以捕獲資料中的順序關係
  • 使用詞嵌入計算縮放的點積自注意
  • 實現遮蔽多頭注意
  • 生成和訓練轉換器模型

你應該記住什麼:

  • 自我注意和卷積網路層的結合允許訓練的平行化和更快的訓練。
  • 使用生成的查詢 Q、鍵 K 和值 V 矩陣計算自我注意。
  • 將位置編碼新增到單詞嵌入中是在自我注意計算中包含序列資訊的有效方法。
  • 多頭注意力可以幫助檢測句子中的多個特徵。
  • 掩碼會阻止模型在訓練期間“向前看”,或者在處理裁剪的句子時過多地加權零。

 

相關文章