Tensorflow与Keras中的RNN,tf.nn.dynamic_rnn()的折旧

GRS*_*GRS 12 keras tensorflow tf.keras

我的问题是:文档中所述tf.nn.dynamic_rnnkeras.layers.RNN(cell)真的相同吗?

我正在计划构建RNN,但是似乎tf.nn.dynamic_rnn偏向于Keras。

它特别指出:

警告:不建议使用此功能。它将在将来的版本中删除。更新说明:请使用keras.layers.RNN(cell),它等效于此API

但是在可变长度的情况下,我看不出API的等效性!

在原始TF中,我们可以指定shape的张量(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]

但是,在Keras中,它不是这样工作的!我们能做的是mask_zero=True在先前的层中指定,例如嵌入层。这也将掩盖第一个零!

我可以通过将它们添加到整个向量中来解决它,但是那是我在使用处理后需要做的额外预处理,tft.compute_vocabulary()它将词汇表单词映射到0索引向量。

小智 7

不,但它们(或可以做成)也没有那么不同。

TL; 博士

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())
Run Code Online (Sandbox Code Playgroud)

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]]]
Run Code Online (Sandbox Code Playgroud)

现在这里是m1,m2old_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.]]
Run Code Online (Sandbox Code Playgroud)

概括

  • 旧的tf.nn.dynamic_rnn用于用零掩盖填充元素。
  • 没有屏蔽的新 RNN 层在填充元素上运行,就好像它们是数据一样。
  • rnn(mask(...))方法只是停止计算并将最后的输出和状态向前推进。请注意,我为此方法获得的(非填充)输出与tf.nn.dynamic_rnn.

无论如何,我无法涵盖所有​​可能的边缘情况,但我希望您可以使用此脚本进一步解决问题。