Нет, но они (или могут быть сделаны) не так уж и отличаются.
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
.
В любом случае, я не могу охватить все возможные крайние случаи, но я надеюсь, что выможете использовать этот скрипт, чтобы разобраться в этом.