本文首發於我的博客,知乎排版可能有問題,建議直接看我的博客


起因

事情的起因是這樣的,我已經用tensorflow實現了一個帶attention的encoder-decoder(都是單層的RNN)的結構,代碼組織結構如下所示

encoder_cell = tf.nn.rnn_cell.LSTMCell(num_units=rnn_size)
decoder_cell = tf.nn.rnn_cell.LSTMCell(num_units=rnn_size)

def Encoder(cell, inputs):
根據輸入得到輸出
......
return outputs

# shape: (batch_size, max_seq_len, rnn_size)
encoder_outputs = Encoder(encoder_cell, inputs)

# 下面是attention
attn_mech = tf.contrib.seq2seq.LuongAttention(...)
decoder_cell = tf.contrib.seq2seq.AttentionWrapper(decoder_cell, attn_mech, attention_layer_size=attn_size,...)

# 下面的就不重要了
......

上面這段代碼在attn_size為任何值的時候都是可以正常執行的。這也很符合預期,因為上面這段代碼所乾的事情如下:

  • 用encoder將input編碼成encoder_output(即attention的keys或者memory);
  • 對於decode的每一個時刻t,將t-1時刻得到的attention context(shape[-1]為attn_size)和decoder的輸入合併在一起輸入到decoder;

    ......

可以看到attn_size確實是任何值都可以, 也即decoder的輸入維度(attn_size + input_x_size)為任何都可以。

注意TensorFlow中的RNN cell不需要顯式指定輸入的維度(而是自己推斷出來),這和pytorch不一樣:pytorch_rnn = torch.nn.LSTM(input_size = attn_size + input_x_size, hidden_size=rnn_size)

經過

後來我又想將decoder改成多層的RNN,decoder結構就像下面右邊這樣:

於是我將decoder_cell的定義做了如下修改:

......

one_cell = tf.nn.rnn_cell.LSTMCell(num_units=rnn_size)
decoder_cell = tf.nn.rnn_cell.MultiRNNCell([one_cell for _ in range(dec_num_layers)])
......

除非把attn_size設置成rnn_size - input_x_size,否則會報類似下面的維度不對的錯誤(假設rnn_size=256,attn_size + input_x_size = 356)

ValueError: Dimensions must be equal, but are 256 and 356 for rnn/while/rnn/multi_rnn_cell/cell_0/cell_0/lstm_cell/MatMul_1 (op: MatMul) with input shapes: [30,256], [356,1200].

這是為什麼呢?明明按照前面的分析,明明attn_size設置成任何值都可以的啊。

解決

一開始我一直以為是我的attention寫得不對,於是google了好久都沒發現attention問題在哪?

直到我看到了這個issue才發現是我的多層RNN沒寫對,還是自己太菜了??

正確的多層decoder_cell應該是如下定義:

......
cell_list = [tf.nn.rnn_cell.LSTMCell(num_units=rnn_size) for _ in range(dec_num_layers)]
decoder_cell = tf.nn.rnn_cell.MultiRNNCell(cell_list)
......

咋一看上面這段代碼貌似和之前的錯誤代碼沒什麼區別,但是如下代碼你就應該意識到哪兒不對了

>>> str = "bug"
>>> strs = [str for _ in range(2)]
>>> print(strs)
[bug, bug]
>>> for str in strs:
print(id(str)) # id()函數用於獲取對象的內存地址
4367049200
4367049200

注意到上面輸出的兩個地址都是一樣的。因此,我們就知道問題出在哪兒了:

對於前面錯誤的多層rnn實現, 每一層的LSTMCell其實都是同一個(指向它們的指針是相同的),那麼每一層的LSTMCell的weights維度就也是一樣的,但其實第一層的輸入維度(attn_size + input_x_size)和其它層的(rnn_size)一般都是不一樣的,如下圖所示,這樣就會報維度錯誤了。

而正確代碼中,每一個LSTMCell都是通過tf.nn.rnn_cell.LSTMCell(num_units=rnn_size)定義的,因此可以有不同的結構,自然不會報錯。

總結

  • TensorFlow中錯誤的多層RNN實現方式:

one_cell = tf.nn.rnn_cell.LSTMCell(num_units=rnn_size)
decoder_cell = tf.nn.rnn_cell.MultiRNNCell([one_cell for _ in range(dec_num_layers)])
# decoder_cell = tf.nn.rnn_cell.MultiRNNCell([one_cell]*dec_num_layers])也是錯誤的

  • TensorFlow中正確的多層RNN實現方式:

cell_list = [tf.nn.rnn_cell.LSTMCell(num_units=rnn_size) for _ in range(dec_num_layers)]
decoder_cell = tf.nn.rnn_cell.MultiRNNCell(cell_list)

參考

  1. Cannot stack LSTM with MultiRNNCell and dynamic_rnn
  2. using dynamic_rnn with multiRNN gives error

推薦閱讀:

相關文章