如何在 TensorFlow 中有效地分配给张量的切片

Zac*_*mzi 5 python keras tensorflow tensorflow2.x

我想在 TensorFlow 2.x 中的一个模型中为输入张量的切片分配一些值(我使用的是 2.2,但准备接受 2.1 的解决方案)。我正在尝试做的一个非工作模板是:

import tensorflow as tf
from tensorflow.keras.models import Model

class AddToEven(Model):
    def call(self, inputs):
        outputs = inputs
        outputs[:, ::2] += inputs[:, ::2]
        return outputs
Run Code Online (Sandbox Code Playgroud)

当然,在构建此 ( AddToEven().build(tf.TensorShape([None, None]))) 时,我收到以下错误:

TypeError: 'Tensor' object does not support item assignment
Run Code Online (Sandbox Code Playgroud)

我可以通过以下方式实现这个简单的例子:

TypeError: 'Tensor' object does not support item assignment
Run Code Online (Sandbox Code Playgroud)

(您可以通过以下方式进行完整性检查:

class AddToEvenScatter(Model):
    def call(self, inputs):
        batch_size = tf.shape(inputs)[0]
        n = tf.shape(inputs)[-1]
        update_indices = tf.range(0, n, delta=2)[:, None]
        scatter_nd_perm = [1, 0]
        inputs_reshaped = tf.transpose(inputs, scatter_nd_perm)
        outputs = tf.tensor_scatter_nd_add(
            inputs_reshaped,
            indices=update_indices,
            updates=inputs_reshaped[::2],
        )
        outputs = tf.transpose(outputs, scatter_nd_perm)
        return outputs
Run Code Online (Sandbox Code Playgroud)

)

但是正如您所看到的,编写起来非常复杂。这仅适用于 1D(+ 批量大小)张量上的静态更新次数(此处为 1)。

我想做的是多一点参与,我认为用它tensor_scatter_nd_add来写将是一场噩梦。

许多当前关于该主题的 QA 涵盖了变量而不是张量的情况(参见例如thisthis)。这里提到pytorch 确实支持这一点,所以我很惊讶地看到最近没有任何 tf 成员对此主题做出回应。 这个答案并没有真正帮助我,因为我将需要某种面具生成,这也会很糟糕。

问题是:我怎样才能有效地进行切片分配(计算方面,内存方面和代码方面)w/o tensor_scatter_nd_add?诀窍是我希望它尽可能动态,这意味着 的形状inputs可以是可变的。

(对于任何好奇的人,我正在尝试在 tf 中翻译此代码)。

这个问题最初发布在一个 GitHub 问题中

Eye*_*ear 3

这是另一种基于二进制掩码的解决方案。

"""Solution based on binary mask.
- We just add this mask to inputs, instead of multiplying."""
class AddToEven(tf.keras.Model):
    def __init__(self):
        super(AddToEven, self).__init__()        

    def build(self, inputshape):
        self.built = True # Actually nothing to build with, becuase we don't have any variables or weights here.

    @tf.function
    def call(self, inputs):
        w = inputs.get_shape()[-1]

        # 1-d mask generation for w-axis (activate even indices only)        
        m_w = tf.range(w)  # [0, 1, 2,... w-1]
        m_w = ((m_w%2)==0) # [True, False, True ,...] with dtype=tf.bool

        # Apply 1-d mask to 2-d input
        m_w = tf.expand_dims(m_w, axis=0) # just extend dimension as to be (1, W)
        m_w = tf.cast(m_w, dtype=inputs.dtype) # in advance, we need to convert dtype

        # Here, we just add this (1, W) mask to (H,W) input magically.
        outputs = inputs + m_w # This add operation is allowed in both TF and numpy!
        return tf.reshape(outputs, inputs.get_shape())
Run Code Online (Sandbox Code Playgroud)

在这里进行健全性检查。

# sanity-check as model
model = AddToEven()
model.build(tf.TensorShape([None, None]))
z = model(tf.zeros([2,4]))
print(z)
Run Code Online (Sandbox Code Playgroud)

结果(使用 TF 2.1)是这样的。

tf.Tensor(
[[1. 0. 1. 0.]
 [1. 0. 1. 0.]], shape=(2, 4), dtype=float32)
Run Code Online (Sandbox Code Playgroud)

-------- 以下是之前的回答 --------

您需要在 build() 方法中创建 tf.Variable 。它还允许通过 shape=(None,) 动态调整大小。在下面的代码中,我将输入形状指定为(无,无)。

class AddToEven(tf.keras.Model):
    def __init__(self):
        super(AddToEven, self).__init__()

    def build(self, inputshape):
        self.v = tf.Variable(initial_value=tf.zeros((0,0)), shape=(None, None), trainable=False, dtype=tf.float32)

    @tf.function
    def call(self, inputs):
        self.v.assign(inputs)
        self.v[:, ::2].assign(self.v[:, ::2] + 1)
        return self.v.value()
Run Code Online (Sandbox Code Playgroud)

我用 TF 2.1.0 和 TF1.15 测试了这段代码

# test
add_to_even = AddToEven()
z = add_to_even(tf.zeros((2,4)))
print(z)
Run Code Online (Sandbox Code Playgroud)

结果:

tf.Tensor(
[[1. 0. 1. 0.]
 [1. 0. 1. 0.]], shape=(2, 4), dtype=float32)
Run Code Online (Sandbox Code Playgroud)

PS 还有一些其他方法,例如使用 tf.numpy_function(),或生成掩码函数。