Как повторно использовать слой LSTM и переменные в области переменных (механизм внимания)

У меня есть проблема в моем коде, когда я хотел бы поделиться весами в моем lstm_decoder (поэтому, по сути, просто используйте один LSTM). Я знаю, что в Интернете есть несколько ресурсов по этому вопросу, но я до сих пор не могу понять, почему следующее не имеет общего веса:

initial_input = tf.unstack(tf.zeros(shape=(1,1,hidden_size2)))

for index in range(window_size):
    with tf.variable_scope('lstm_cell_decoder', reuse = index > 0):
        rnn_decoder_cell = tf.nn.rnn_cell.LSTMCell(hidden_size, state_is_tuple = True)

        output_decoder, state_decoder = tf.nn.static_rnn(rnn_decoder_cell, initial_input, initial_state=last_encoder_state, dtype=tf.float32)

        # Compute the score for source output vector
        scores = tf.matmul(concat_lstm_outputs, tf.reshape(output_decoder[-1],(hidden_size,1)))
        attention_coef = tf.nn.softmax(scores)
        context_vector = tf.reduce_sum(tf.multiply(concat_lstm_outputs, tf.reshape(attention_coef, (window_size, 1))),0)
        context_vector = tf.reshape(context_vector, (1,hidden_size))

        # compute the tilda hidden state \tilde{h}_t=tanh(W[c_t, h_t]+b_t)
        concat_context = tf.concat([context_vector, output_decoder[-1]], axis = 1)
        W_tilde = tf.Variable(tf.random_normal(shape = [hidden_size*2, hidden_size2], stddev = 0.1), name = "weights_tilde", trainable = True)
        b_tilde = tf.Variable(tf.zeros([1, hidden_size2]), name="bias_tilde", trainable = True)
        hidden_tilde = tf.nn.tanh(tf.matmul(concat_context, W_tilde)+b_tilde) # hidden_tilde is [1*64]

        # update for next time step
        initial_input = tf.unstack(tf.reshape(hidden_tilde, (1,1,hidden_size2)))
        last_encoder_state = state_decoder
        print(initial_input, last_encoder_state)

        # predict the target
        W_target = tf.Variable(tf.random_normal(shape = [hidden_size2, 1], stddev = 0.1), name = "weights_target", trainable = True)
        print(W_target)
        logit = tf.matmul(hidden_tilde, W_target)
        logits = tf.concat([logits, logit], axis = 0)

logits = logits[1:]

Я хотел бы использовать одну и ту же ячейку LSTM и один и тот же W_target для каждой итерации цикла. Однако я получаю следующий вывод для print(initial_input, last_encoder_state) и print(W_target) для window_size = 2 в цикле.

[<tf.Tensor 'lstm_cell_decoder/unstack:0' shape=(1, 64) dtype=float32>] 
LSTMStateTuple(c=<tf.Tensor 
'lstm_cell_decoder/rnn/rnn/lstm_cell/lstm_cell/add_1:0' shape=(1, 64) 
dtype=float32>, h=<tf.Tensor 
'lstm_cell_decoder/rnn/rnn/lstm_cell/lstm_cell/mul_2:0' shape=(1, 64) 
dtype=float32>)
<tf.Variable 'lstm_cell_decoder/weights_target:0' shape=(64, 1) 
dtype=float32_ref>
[<tf.Tensor 'lstm_cell_decoder_1/unstack:0' shape=(1, 64) dtype=float32>] 
LSTMStateTuple(c=<tf.Tensor 
'lstm_cell_decoder_1/rnn/rnn/lstm_cell/lstm_cell/add_1:0' shape=(1, 64) 
dtype=float32>, h=<tf.Tensor 
'lstm_cell_decoder_1/rnn/rnn/lstm_cell/lstm_cell/mul_2:0' shape=(1, 64) 
dtype=float32>)
<tf.Variable 'lstm_cell_decoder_1/weights_target:0' shape=(64, 1) 
dtype=float32_ref>

Обновление: после комментариев Максима я попробовал следующий синтаксис

for index in range(window_size):
  with tf.variable_scope('lstm_cell_decoder', reuse = index > 0):
     rnn_decoder_cell = tf.nn.rnn_cell.LSTMCell(hidden_size,reuse=index > 0)
     output_decoder, state_decoder = tf.nn.static_rnn(rnn_decoder_cell, ...)
     W_target = tf.get_variable(...)

Теперь он правильно использует переменную W_target, но по-прежнему существует проблема с совместным использованием ячейки/веса lstm:

<tf.Tensor 'lstm_cell_decoder/rnn/rnn/lstm_cell/lstm_cell/mul_2:0' shape=(1, 
 64) dtype=float32>]
 LSTMStateTuple(c=<tf.Tensor 
 'lstm_cell_decoder/rnn/rnn/lstm_cell/lstm_cell/add_1:0' shape=(1, 64) 
 dtype=float32>, h=<tf.Tensor 
'lstm_cell_decoder/rnn/rnn/lstm_cell/lstm_cell/mul_2:0' shape=(1, 64) 
 dtype=float32>)
 <tf.Variable 'lstm_cell_decoder/weights_target:0' shape=(64, 1) 
 dtype=float32_ref>

 [<tf.Tensor 'lstm_cell_decoder_1/rnn/rnn/lstm_cell/lstm_cell/mul_2:0' 
 shape=(1, 64) dtype=float32>]
 LSTMStateTuple(c=<tf.Tensor 
 'lstm_cell_decoder_1/rnn/rnn/lstm_cell/lstm_cell/add_1:0' shape=(1, 64) 
 dtype=float32>, h=<tf.Tensor 
 'lstm_cell_decoder_1/rnn/rnn/lstm_cell/lstm_cell/mul_2:0' shape=(1, 64) 
 dtype=float32>)
 <tf.Variable 'lstm_cell_decoder/weights_target:0' shape=(64, 1) 
 dtype=float32_ref>

person Tom    schedule 07.02.2018    source источник


Ответы (1)


Во-первых, создание переменных с помощью tf.Variable не сделает их пригодными для повторного использования. Это одно из ключевых различий между tf.Variable и tf.get_variable. См. этот пример:

with tf.variable_scope('foo', reuse=tf.AUTO_REUSE):
  for i in range(3):
    x = tf.Variable(0.0, name='x')
    y = tf.get_variable(name='y', shape=())

Если вы проверите созданные переменные, вы увидите:

<tf.Variable 'foo/x:0' shape=() dtype=float32_ref>
<tf.Variable 'foo/y:0' shape=() dtype=float32_ref>
<tf.Variable 'foo/x_1:0' shape=() dtype=float32_ref>
<tf.Variable 'foo/x_2:0' shape=() dtype=float32_ref>

Далее, ячейки RNN предоставляют собственный механизм повторного использования. Например, для tf.nn.rnn_cell.LSTMCell это reuse аргумент конструктора:

reuse = tf.AUTO_REUSE  # Try also True and False
cell1 = tf.nn.rnn_cell.LSTMCell(3, reuse=reuse)
cell2 = tf.nn.rnn_cell.LSTMCell(3, reuse=reuse)
outputs1, states1 = tf.nn.dynamic_rnn(cell1, X, dtype=tf.float32)
outputs2, states2 = tf.nn.dynamic_rnn(cell2, X, dtype=tf.float32)
person Maxim    schedule 07.02.2018
comment
Большое спасибо Максим за ответ! У меня есть еще пара вопросов: 1) в моей версии tf.AUTO_REUSE не существует, это просто то же самое, что и установка reuse=FALSE, когда, скажем, моя переменная сначала определяется, а затем становится TRUE? Что-то вроде этого for i in range(3): with tf.variable_scope('foo', reuse= i >0): x = tf.Variable(0.0, name='x') y = tf.get_variable(name='y', shape=()) print(x) print(y) - person Tom; 07.02.2018
comment
Да, это то же самое, но удобнее. Обновитесь до TF 1.5 (или даже 1.4.1), tf.AUTO_REUSE будет доступно. - person Maxim; 07.02.2018
comment
Пожалуйста, не оставляйте код в комментариях. Вместо этого отредактируйте и обновите вопрос - person Maxim; 07.02.2018
comment
Конечно, я думал, что форматирование было довольно ужасным. Я обновлю вопрос в ближайшее время, еще раз спасибо :) - person Tom; 07.02.2018
comment
Только что отредактировал вопрос, распечатанный вывод для window_size = 2 в цикле, как и раньше. - person Tom; 07.02.2018
comment
Хм, показывает, что дублируется не переменная, а сама область видимости. Я думаю, вам следует попробовать tf.AUTO_REUSE, потому что вы гарантированно получите одну область видимости переменной, и это решит эту проблему. - person Maxim; 07.02.2018
comment
Ладно, тогда попробую с tf.AUTO_REUSE. С дублированной областью действия это будет означать, что хотя имя переменной (lstm_cell?) такое же, веса все равно не распределяются? - person Tom; 07.02.2018
comment
Да, это идея областей: вы можете иметь одно и то же имя в двух областях, и это будут разные переменные. - person Maxim; 07.02.2018
comment
После обновления до 1.4.0 и использования tf.AUTO_REUSE (как в области действия, так и в ячейке rnn) я получаю lstm_cell_decoder/rnn/rnn/lstm_cell/mul_2:0 для первой итерации цикла и lstm_cell_decoder/rnn_1/rnn/lstm_cell/mul_2:0 для второй. - person Tom; 07.02.2018
comment
Я только что ответил на ваш другой вопрос - stackoverflow.com/q/48653507/712995. Я думаю, что вы не должны использовать цикл вообще. - person Maxim; 07.02.2018
comment
Что вы имеете в виду под не может воспроизвести это? Как у вас есть то же имя для переменной? - person Tom; 07.02.2018