在所謂的 seq2seq 問題中,如機器翻譯(如 第 10.5 節所述),其中輸入和輸出均由可變長度的未對齊序列組成,我們通常依賴編碼器-解碼器架構(第10.6 節)。在本節中,我們將演示編碼器-解碼器架構在機器翻譯任務中的應用,其中編碼器和解碼器均作為 RNN 實現( Cho等人,2014 年,Sutskever等人,2014 年)。
在這里,編碼器 RNN 將可變長度序列作為輸入并將其轉換為固定形狀的隱藏狀態。稍后,在 第 11 節中,我們將介紹注意力機制,它允許我們訪問編碼輸入,而無需將整個輸入壓縮為單個固定長度的表示形式。
然后,為了生成輸出序列,一次一個標記,由一個單獨的 RNN 組成的解碼器模型將在給定輸入序列和輸出中的前一個標記的情況下預測每個連續的目標標記。在訓練期間,解碼器通常會以官方“ground-truth”標簽中的前面標記為條件。然而,在測試時,我們希望根據已經預測的標記來調節解碼器的每個輸出。請注意,如果我們忽略編碼器,則 seq2seq 架構中的解碼器的行為就像普通語言模型一樣。圖 10.7.1說明了如何在機器翻譯中使用兩個 RNN 進行序列到序列學習。
在圖 10.7.1中,特殊的“”標記標志著序列的結束。一旦生成此令牌,我們的模型就可以停止進行預測。在 RNN 解碼器的初始時間步,有兩個特殊的設計決策需要注意:首先,我們以特殊的序列開始“”標記開始每個輸入。其次,我們可以在每個解碼時間步將編碼器的最終隱藏狀態輸入解碼器(Cho等人,2014 年)。在其他一些設計中,例如Sutskever等人。( 2014 ),RNN 編碼器的最終隱藏狀態僅在第一個解碼步驟用于啟動解碼器的隱藏狀態。
import collections
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
import collections
import math
import tensorflow as tf
from d2l import tensorflow as d2l
10.7.1。教師強迫
雖然在輸入序列上運行編碼器相對簡單,但如何處理解碼器的輸入和輸出則需要更加小心。最常見的方法有時稱為 教師強制。在這里,原始目標序列(標記標簽)作為輸入被送入解碼器。更具體地說,特殊的序列開始標記和原始目標序列(不包括最終標記)被連接起來作為解碼器的輸入,而解碼器輸出(用于訓練的標簽)是原始目標序列,移動了一個標記: “”,“Ils”,“regardent”,“。” →“Ils”、“regardent”、“.”、“”(圖 10.7.1)。
我們在10.5.3 節中的實施為教師強制準備了訓練數據,其中用于自監督學習的轉移標記類似于9.3 節中的語言模型訓練。另一種方法是將來自前一個時間步的預測標記作為當前輸入提供給解碼器。
下面,我們 將更詳細地解釋圖 10.7.1中描繪的設計。我們將在第 10.5 節中介紹的英語-法語數據集上訓練該模型進行機器翻譯 。
10.7.2。編碼器
回想一下,編碼器將可變長度的輸入序列轉換為固定形狀的上下文變量 c(見圖 10.7.1)。
考慮一個單序列示例(批量大小 1)。假設輸入序列是x1,…,xT, 這樣xt是個 tth令牌。在時間步t, RNN 變換輸入特征向量xt為了xt 和隱藏狀態ht?1從上一次進入當前隱藏狀態ht. 我們可以使用一個函數f表達RNN循環層的變換:
通常,編碼器通過自定義函數將所有時間步的隱藏狀態轉換為上下文變量q:
例如,在圖 10.7.1中,上下文變量只是隱藏狀態hT對應于編碼器 RNN 在處理輸入序列的最終標記后的表示。
在這個例子中,我們使用單向 RNN 來設計編碼器,其中隱藏狀態僅取決于隱藏狀態時間步和之前的輸入子序列。我們還可以使用雙向 RNN 構建編碼器。在這種情況下,隱藏狀態取決于時間步長前后的子序列(包括當前時間步長的輸入),它編碼了整個序列的信息。
現在讓我們來實現 RNN 編碼器。請注意,我們使用嵌入層來獲取輸入序列中每個標記的特征向量。嵌入層的權重是一個矩陣,其中行數對應于輸入詞匯表的大小 ( vocab_size
),列數對應于特征向量的維度 ( embed_size
)。對于任何輸入令牌索引i,嵌入層獲取ith權矩陣的行(從 0 開始)返回其特征向量。在這里,我們使用多層 GRU 實現編碼器。
def init_seq2seq(module): #@save
"""Initialize weights for Seq2Seq."""
if type(module) == nn.Linear:
nn.init.xavier_uniform_(module.weight)
if type(module) == nn.GRU:
for param in module._flat_weights_names:
if "weight" in param:
nn.init.xavier_uniform_(module._parameters[param])
class Seq2SeqEncoder(d2l.Encoder): #@save
"""The RNN encoder for sequence to sequence learning."""
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = d2l.GRU(embed_size, num_hiddens, num_layers, dropout)
self.apply(init_seq2seq)
def forward(self, X, *args):
# X shape: (batch_size, num_steps)
embs = self.embedding(X.t().type(torch.in
評論
查看更多