RNN в Tensorflow против Keras, снижение стоимости tf.nn.dynamic_rnn ()

Мой вопрос: действительно ли tf.nn.dynamic_rnn и keras.layers.RNN(cell) идентичны, как указано в документах ?

Я планирую создать RNN, однако кажется, что tf.nn.dynamic_rnn является деприминировали в пользу Кераса.

В частности, в нем говорится, что:

Предупреждение: ДАННАЯ ФУНКЦИЯ УСТАРЕЛА. Он будет удален в будущей версии. Инструкции по обновлению: используйте keras.layers.RNN (ячейка), что эквивалентно этому API

Но я не понимаю, насколько API эквивалентны в случае переменной длины последовательности!

В необработанном TF мы можем указать тензор формы (batch_size, seq_lengths). Таким образом, если наша последовательность [0, 1, 2, 3, 4], а самая длинная последовательность в пакете имеет размер 10, мы можем дополнить ее нулями и [0, 1, 2, 3, 4, 0, 0, 0, 0, 0], мы можем сказать seq_length=5 для обработки [0, 1, 2, 3, 4].

Однако в Керасе это не так! Что мы можем сделать, так это указать mask_zero=True в предыдущих слоях, например. слой внедрения. Это также замаскирует первый ноль!

Я могу обойти это, добавив единицы ко всему вектору, но тогда это дополнительная предварительная обработка, которую мне нужно выполнить после обработки с использованием tft.compute_vocabulary(), который отображает словарные слова на 0 индексированный вектор.


person GRS    schedule 04.03.2019    source источник
comment
ты про keras или tf.keras?   -  person MPękalski    schedule 15.03.2019
comment
Я сомневаюсь, стоит ли вам действительно заботиться об этом (т.е. о предыдущем seq_lengths). Из документации ... Так что это больше для производительности, чем для правильности.   -  person rst    schedule 15.03.2019
comment
@ MPękalski Я использую tf.keras   -  person GRS    schedule 15.03.2019
comment
@rst На самом деле я не понимаю, в чем проблема правильности. Если я ввожу 0, умножение матриц также будет равно 0, но затем я добавлю смещение 1, которое передается через функцию активации с его весом. Я, скорее всего, получу ненулевой результат из-за члена смещения. Значит вес смещения будет продолжать тренироваться? Или мое понимание неверно?   -  person GRS    schedule 15.03.2019
comment
@rst Предполагая, что они означают, что нет никакой разницы между передачей оставшихся «заполненных» нулей в RNN или их маскированием, например. не тренируясь на них.   -  person GRS    schedule 15.03.2019
comment
На данный момент вы можете использовать tf.keras.layers.Masking(), чтобы справиться с этим, но дело в том, что маскирование не поддерживается CuDNN RNN. Возможно проблема решится в TF. 2.0 github.com/tensorflow/tensorflow/issues/23269   -  person Nicolabo    schedule 10.04.2019


Ответы (1)


Нет, но они не такие уж и разные.

TL;DR

tf.nn.dynamic_rnn заменяет элементы после конца последовательности на 0. Насколько я знаю, это не может быть воспроизведено с tf.keras.layers.*, но вы можете получить аналогичное поведение с RNN(Masking(...) подходом: он просто останавливает вычисление и переносит последние выходные данные и состояния вперед. Вы получите те же выходные данные (без заполнения), что и для tf.nn.dynamic_rnn.

Эксперимент

Вот минимальный рабочий пример, демонстрирующий различия между tf.nn.dynamic_rnn и _ 6_ с использованием и без использования _ 7_ слой.

import numpy as np
import tensorflow as tf

test_input = np.array([
    [1, 2, 1, 0, 0],
    [0, 1, 2, 1, 0]
], dtype=int)
seq_length = tf.constant(np.array([3, 4], dtype=int))

emb_weights = (np.ones(shape=(3, 2)) * np.transpose([[0.37, 1, 2]])).astype(np.float32)
emb = tf.keras.layers.Embedding(
    *emb_weights.shape,
    weights=[emb_weights],
    trainable=False
)
mask = tf.keras.layers.Masking(mask_value=0.37)
rnn = tf.keras.layers.GRU(
    1,
    return_sequences=True,
    activation=None,
    recurrent_activation=None,
    kernel_initializer='ones',
    recurrent_initializer='zeros',
    use_bias=True,
    bias_initializer='ones'
)


def old_rnn(inputs):
    rnn_outputs, rnn_states = tf.nn.dynamic_rnn(
        rnn.cell,
        inputs,
        dtype=tf.float32,
        sequence_length=seq_length
    )
    return rnn_outputs


x = tf.keras.layers.Input(shape=test_input.shape[1:])
m0 = tf.keras.Model(inputs=x, outputs=emb(x))
m1 = tf.keras.Model(inputs=x, outputs=rnn(emb(x)))
m2 = tf.keras.Model(inputs=x, outputs=rnn(mask(emb(x))))

print(m0.predict(test_input).squeeze())
print(m1.predict(test_input).squeeze())
print(m2.predict(test_input).squeeze())

sess = tf.keras.backend.get_session()
print(sess.run(old_rnn(mask(emb(x))), feed_dict={x: test_input}).squeeze())

Выходные данные m0 показывают результат применения слоя внедрения. Обратите внимание, что здесь вообще нет нулевых записей:

[[[1.   1.  ]    [[0.37 0.37]
  [2.   2.  ]     [1.   1.  ]
  [1.   1.  ]     [2.   2.  ]
  [0.37 0.37]     [1.   1.  ]
  [0.37 0.37]]    [0.37 0.37]]]

Теперь вот фактические результаты работы архитектур m1, m2 и old_rnn:

m1: [[  -6.  -50. -156. -272.7276 -475.83362]
     [  -1.2876 -9.862801 -69.314 -213.94202 -373.54672 ]]
m2: [[  -6.  -50. -156. -156. -156.]
     [   0.   -6.  -50. -156. -156.]]
old [[  -6.  -50. -156.    0.    0.]
     [   0.   -6.  -50. -156.    0.]]

Резюме

  • Старый tf.nn.dynamic_rnn использовался для маскировки элементов заполнения нулями.
  • Новые слои RNN без маскировки проходят поверх элементов заполнения, как если бы они были данными.
  • Новый подход rnn(mask(...)) просто останавливает вычисления и переносит последние выходные данные и состояния вперед. Обратите внимание, что выходные данные (без заполнения), которые я получил для этого подхода, точно такие же, как и у tf.nn.dynamic_rnn.

В любом случае, я не могу охватить все возможные крайние случаи, но я надеюсь, что вы сможете использовать этот скрипт, чтобы разобраться во всем.

person Alex Tomberg    schedule 01.05.2019
comment
Я расширил этот в этом ответе, чтобы показать маскировку без слоя встраивания. Отличный ответ, это мне очень помогло. - person parrowdice; 19.06.2019
comment
Этим вечером я сделал интересное открытие - если вы оберните ячейку GRU в двунаправленный уровень, она преобразует передаваемые выходные данные в ноль, таким образом, получая выходные данные, идентичные старой реализации, без необходимости заранее заданной длины последовательности. - person Ryan Walden; 19.12.2019