RNN в Tensorflow против Keras, амортизация tf.nn.dynamic_rnn () - PullRequest
0 голосов
/ 04 марта 2019

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

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

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

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

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

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

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

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

1 Ответ

0 голосов
/ 01 мая 2019

Нет, но они (или могут быть сделаны) не так уж и отличаются.

TL; DR

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

Эксперимент

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

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.

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

...