首页 > 解决方案 > 为什么这个 tf.keras 模型在切片输入上的行为与预期不同?

问题描述

我正在编写一个 Keras 模型,给定(小)批张量,将相同的层应用于它们的每个元素。只是为了提供一点上下文,我将字符串的输入组(固定大小)作为输入组,这些字符串必须由编码层一个一个地编码。因此,包含 (mini)-batch 大小的输入大小是 (None, n_sentences_per_sample, ),其中 n_sentences_per_sample 是先验已知的固定值。

为此,我在功能 API 中创建模型时使用了这个自定义函数:

def _branch_execute(layer_in: keras.layers.Layer, sublayer: [keras.layers.Layer, Callable], **args) -> keras.layers.Layer:
    instance_cnt = layer_in.shape[1]
    sliced_inputs = [tf.keras.layers.Lambda(lambda x: x[:, i])(layer_in) for i in range(instance_cnt)]
    branch_layers = [sublayer(**{**{'layer_in': sliced_inputs[i]}, **args}) for i in range(instance_cnt)]
    expand_layer = tf.keras.layers.Lambda(lambda x: K.expand_dims(x, axis=1))            
    expanded_layers = [expand_layer(branch_layers[i]) for i in range(instance_cnt)]
    concated_layer = tf.keras.layers.Concatenate(axis=1)(expanded_layers)    
    return concated_layer

我以这种方式使用

model_input = keras.layers.Input(shape=(self.max_sents, ),
                                 dtype=tf.string,
                                 )
sentences_embedded = self._branch_execute(model_input, self._get_nnlm_128)
model = keras.models.Model(model_input, sentences_embedded)

其中self._get_nnlm_128()只是一个函数,它返回将缓存的预训练嵌入层应用于输入的结果,即

def _get_nnlm_128(self, layer_in: keras.layers.Layer, trainable: bool = False):
    if 'nnlm_128_layer_shared' not in self.shared_layers_cache:
        self.shared_layers_cache['nnlm_128_layer_shared'] = {
            'encoder': hub.KerasLayer("https://tfhub.dev/google/nnlm-en-dim128-with-normalization/2", trainable=trainable)
        }
    shared_layers = self.shared_layers_cache['nnlm_128_layer_shared']
    encoder = shared_layers['encoder'](layer_in)
    return encoder

我遇到的问题如下:

  1. 如果我调用self._branch_execute(input_tensor, self._get_nnlm_128)输入张量只是一个形状良好的张量,它可以完美地工作;
  2. 如果我在同一个input_tensor上调用模型(无论是直接还是通过 .predict(),无论是否编译),我都会得到样本中每个句子的重复结果(奇怪的是,它是与最后一个句子相对应的输出,重复 -见下文);

举个例子(尽管我对每个可能的输入都有同样的问题),让我们考虑一个由 7 个句子(7 个字符串)组成的input_tensor ,重新整形为 (1, 7, ) 以包含 minibatch 轴。1) 的结果是

[[[ 0.216900051 0.037066862 0.163929373 ... 0.050420273 0.082906663 0.059960182],
  [ 0.531883411 -0.000807280 0.107559107 ... -0.079948671 -0.020143294 0.007032406],
  ...
  [ 0.15044811 0.00890037  0.10413752 ... -0.05391502 -1.2199926 -0.13466084]]]

我得到 7 个大小为 128 的向量/嵌入,如预期的那样彼此不同;2) 的结果是,奇怪的是,

[[[ 0.15044811  0.00890037  0.10413752 ... -0.05391502 -0.12199926 -0.13466084],  
  [ 0.15044811  0.00890037  0.10413752 ... -0.05391502 -0.12199926 -0.13466084], 
  [ 0.15044811  0.00890037  0.10413752 ... -0.05391502 -0.12199926 -0.13466084],
  [ 0.15044811  0.00890037  0.10413752 ... -0.05391502 -0.12199926 -0.13466084],
  [ 0.15044811  0.00890037  0.10413752 ... -0.05391502 -0.12199926 -0.13466084],
  [ 0.15044811  0.00890037  0.10413752 ... -0.05391502 -0.12199926 -0.13466084],
  [ 0.15044811  0.00890037  0.10413752 ... -0.05391502 -0.12199926 -0.13466084]]]

我得到了 7 次相同的向量(正如我所说,它总是对应于所有句子重复的最后一个)。我从实际运行中获取了结果。

在我进行的许多试验中,我尝试从模型中输出model_input,效果很好,即它对应于输入字符串。嵌入模型直接取自 Tensorflow hub,所以应该没有问题。此外,任何其他嵌入层(无论是自定义的还是预训练的)都观察到相同的行为。因此,我认为问题可能出在_branch_execute()函数中,但我不知道单独使用它可以正常工作的问题是什么。也许它可能与 keras 模型内部的一些特殊的广播行为有关,但我不知道如何测试它,更不用说如何解决它了。

如果您有任何关于为什么会出现此问题以及如何解决此问题的建议,我将不胜感激。我不是 Tensorflow 的专家,所以也许我只是误判了一些东西(以防万一,请原谅我!)。我很乐意根据需要分享更多信息以帮助解决问题。非常感谢 :)

标签: pythontensorflowkerasnlptensorflow2.0

解决方案


我终于得出结论,问题出在线路上

sliced_inputs = [tf.keras.layers.Lambda(lambda x: x[:, i])(layer_in) for i in range(instance_cnt)]

这显然没有按预期工作(我正在运行 Tensorflow 2.4.0,但我也遇到了与 Tensorflow 2.5.0-nightly 相同的问题)。我只是用一个做同样事情的自定义层替换了 Lambda 层,即

class Slicer(keras.layers.Layer):
    def __init__(self, i, **kwargs):
        self.i = i
        super(Slicer, self).__init__(**kwargs)

    def call(self, inputs, **kwargs):
        return inputs[:, self.i]

然后我在_branch_execute()函数中使用它就像这样

def _branch_execute(self, layer_in: keras.layers.Layer, sublayer: [keras.layers.Layer, Callable], **args) -> keras.layers.Layer:
    instance_cnt = layer_in.shape[1]
    sliced_inputs = [Slicer(i)(layer_in) for i in range(instance_cnt)]
    branch_layers = [sublayer(**{**{'layer_in': sliced_inputs[i]}, **args}) for i in range(instance_cnt)]
    expand_layer = tf.keras.layers.Lambda(lambda x: K.expand_dims(x, axis=1))
    expanded_layers = [expand_layer(branch_layers[i]) for i in range(instance_cnt)]
    concated_layer = tf.keras.layers.Concatenate(axis=1)(expanded_layers)
    return concated_layer

我不确定这是否是解决问题的最佳选择,但它看起来很整洁并且效果很好。

由于这可能是 Lambda 层的意外行为,我将在 Tensorflow github 上打开一个问题并在此处发布回复以供参考。


推荐阅读