1. 程式人生 > >tensorflow中的seq2seq文件解讀

tensorflow中的seq2seq文件解讀

sequence-to-sequence 模型的種類很多,每一種模型都可以使用不同的RNN單元,但是都必須以 encoder inputs 和decoder inputs作為引數。在tf.nn.seq2seq介面中有各種RNN encoder-decoder sequence-to-sequence模型,最基本的是這個樣子:

outputs, states = basic_rnn_seq2seq(encoder_inputs, decoder_inputs, cell)

encoder_inputs 是一個tensors的列表,代表encoder的輸入,例如上圖的A,B,C。同樣,decoder_inputs是代表decoder的tensors,例如上圖的GO, W, X, Y, Z。

cell是類models.rnn.rnn_cell.RNNCell 的例項,決定了模型使用哪種cell,你可以用GRUCell 或者 LSTMCellrnn_cell提供了wrappers建立多層cells,也可以 cell inputs 或者outputs新增dropout ,等等,具體可以參考RNN Tutorial 。

basic_rnn_seq2seq返回2個引數,outputsstates。它們都是tensors列表,長度與decoder_inputs相同。outputs對應於每個時間步長中的解碼器的輸出,在上圖中是W,X,Y,Z,EOS。states表示在每個時間步長的解碼器的內部狀態。

在sequence-to-sequence 模型的很多應用中,t時刻解碼器的輸出將反饋成為t+1時刻的輸入。測試時,這就是通過解碼構建序列的方法。另一方面,訓練的時候,通常在每個時間步驟向解碼器提供正確的輸入,即使解碼器之前犯了錯誤。 seq2seq.py中的函式使用feed_previous引數支援這兩種模式。例如,以embedding RNN模型為例。

outputs, states = embedding_rnn_seq2seq(
    encoder_inputs, decoder_inputs, cell,
    num_encoder_symbols, num_decoder_symbols,
    output_projection=None, feed_previous=False)

embedding_rnn_seq2seq模型中,所有的輸入(encoder_inputsdecoder_inputs)都是表示離散值的 整形張量。它們將嵌入到一個密集表示中(有關嵌入的更多細節,請參閱矢量表示法教程),但是要構造這些嵌入,我們需要指定將出現的離散符號的最大數量:在編碼端是num_encoder_symbols ,解碼端是num_decoder_symbols

在上面的呼叫中,我們將feed_previous設定為False。這意味著在解碼器端,使用decoder_inputs作為輸入。例如,decoder_inputs 是‘GO, W, X, Y, Z ’,正確的輸出應該是’W, X, Y, Z, EOS’。假設第一個時刻的輸出不是’W’,在第二個時刻也要使用’W’作為輸入。當設為true時,只使用decoder_inputs的第一個時刻的輸入,即’GO’,以及解碼器的在每一時刻的真實輸出作為下一時刻的輸入。

上面使用的另一個重要引數是output_projection。如果沒有指定,嵌入模型的輸出將是batch-size × num_decoder_symbols形狀的張量,它們表示每個生成的符號的對數。當訓練具有很大輸出詞彙的模型時,即當num_decoder_symbols很大時,儲存這些張量是不實際的。相反,最好返回較小的輸出張量,稍後將使用output_projection將其投影到大的輸出張量上。這允許使用我們的具有采樣softmax損失的seq2seq模型,如Jean et。 2014年(pdf)。

取樣softmax 和輸出投影

如上所述,我們想使用抽樣softmax來處理大量輸出詞彙。要從中解碼,我們需要跟蹤輸出投影。通過seq2seq_model.py中的以下程式碼構造取樣softmax損失和輸出投影。

if num_samples > 0 and num_samples < self.target_vocab_size:
    w = tf.get_variable("proj_w", [size, self.target_vocab_size])
    w_t = tf.transpose(w)
    b = tf.get_variable("proj_b", [self.target_vocab_size])
    output_projection = (w, b)

    def sampled_loss(inputs, labels):
      labels = tf.reshape(labels, [-1, 1])
      return tf.nn.sampled_softmax_loss(w_t, b, inputs, labels, num_samples,self.target_vocab_size)

首先,請注意,如果樣本數(預設為512)小於目標詞彙量,我們使用的抽樣softmax。對於詞表大小小於512,使用標準softmax損失可能是一個更好的主意。

然後,構造一個輸出投影。它是由權重矩陣和偏差向量組成的對。如果使用的話,rnn單元返回 batch-size× size 形狀大小的張量,而不是 batch-size× target_vocab_size。為了恢復logit,我們需要乘以權重矩陣並新增偏差,如在seq2seq_model.py中的行124-126中所做的那樣。

if output_projection is not None:
  self.outputs[b] = [tf.matmul(output, output_projection[0]) +
                     output_projection[1] for ...]

Bucketing and padding

Bucketing是一種有效處理不同長度的句子的方法。例如將英語翻譯成法語時,輸入具有不同長度的英語句子L1,輸出是具有不同長度的法語句子L2,原則上應該為每一對(L1,L2 + 1)建立一個seq2seq模型。這會導致圖很大,包括許多非常相似的子圖。另一方面,我們可以用一個特殊的PAD符號填充每個句子。然後,只需要一個seq2seq模型。但是對於較短的句子,要編碼和解碼許多無用的PAD符號,這樣的模型也是低效的。作為折中,使用多個buckets 並且將每個句子填充為對應的bucket的長度。

buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]

如果輸入是3個tocken的英語句子,相應的輸出是6個tocken的法語句子,則它們將被放入第一個bucket中,編碼器輸入的長度將填充到5,解碼器輸入的長度將填充到10 。

當構造解碼器輸入時,我們在輸入資料前面加上特殊的GO符號。用seq2seq_model.py中的get_batch()函式中完成的,它會將輸入反轉。反轉的輸入對神經翻譯模型的結果有了一些改善(Sutskever等人,2014)。綜合來說,假設我們有句子[“I”,“go”,“。”]作為輸入,輸出是[“Je”,“vais”,“。”]。它會被放在(5,10)bucket中,編碼器的輸入是[PAD PAD“。 “go”“I”],解碼器的輸入[GO“Je”“vais”“。 EOS PAD PAD PAD PAD PAD]。