更新 TensorFlow 自定义指标的内部状态(也称为在指标计算中使用非 update_state 变量)

cod*_*tle 0 python metrics graph tensorflow

版本:python 3.8.2(我也尝试过3.6.8,但我认为python版本在这里并不重要),tensorflow 2.3.0,numpy 1.18.5

我正在使用稀疏标签张量训练一个用于分类问题的模型。我将如何定义一个指标来计算“0”标签在此之前出现的次数?我在下面的代码示例中尝试做的是将指标在数组中看到的所有标签存储起来,并在y_true每次update_state调用时不断地将现有数组与新数组连接起来。(我知道我可以只存储一个count变量并使用+=,但在实际使用场景中,连接是理想的并且内存不是问题。)以下是重现问题的最少代码:

import tensorflow as tf

class ZeroLabels(tf.keras.metrics.Metric):
    """Accumulates a list of all y_true sparse categorical labels (ints) and calculates the number of times the '0' label has appeared."""
    def __init__(self, *args, **kwargs):
        super(ZeroLabels, self).__init__(name="ZeroLabels")
        self.labels = self.add_weight(name="labels", shape=(), initializer="zeros", dtype=tf.int32)

    def update_state(self, y_true, y_pred, sample_weight=None):
        """I'm using sparse categorical crossentropy, so labels are 1D array of integers."""
        if self.labels.shape == (): # if this is the first time update_state is being called
            self.labels = y_true
        else:
            self.labels = tf.concat((self.labels, y_true), axis=0)

    def result(self):
        return tf.reduce_sum(tf.cast(self.labels == 0, dtype=tf.int32))

    def reset_states(self):
        self.labels = tf.constant(0, dtype=tf.int32)
Run Code Online (Sandbox Code Playgroud)

该代码可以单独运行,但当我尝试使用该指标训练模型时,它会抛出以下错误:

TypeError: An op outside of the function building code is being passed
a "Graph" tensor. It is possible to have Graph tensors
leak out of the function building context by including a
tf.init_scope in your function building code.
For example, the following function will fail:
  @tf.function
  def has_init_scope():
    my_constant = tf.constant(1.)
    with tf.init_scope():
      added = my_constant * 2
Run Code Online (Sandbox Code Playgroud)

我认为这可能与调用self.labels时不直接属于图表的一部分有关。update_state以下是我尝试过的其他一些方法:

  • 存储tf.int32,shape=() count变量并递增该变量,而不是连接新标签
  • 使用并连接它们将所有内容转换为 numpy .numpy()(我希望强制 TensorFlow 不使用该图)
  • 使用上面的 numpy 转换来try阻止except
  • 创建一个全新的类(而不是子类化tf.keras.metrics.Metric),在可能的情况下专门使用 numpy ,但这种方法会导致一些加载问题,即使我custom_objectstf.keras.models.load_model
  • @tf.autograph.experimental.do_not_convert在所有方法上使用装饰器
  • 修改全局变量而不是属性并使用global关键字
  • 使用非张量流属性(不使用self.labels = self.add_weight...

如果有帮助,这是这个问题的更一般版本:我们如何将未作为参数传入的张量合并到计算update_stateupdate_state?任何帮助将不胜感激。先感谢您!

小智 7

主要问题是第一次迭代分配,当没有初始值时:

if self.labels.shape == ():
    self.labels = y_true
else:
    self.labels = tf.concat((self.labels, y_true), axis=0)
Run Code Online (Sandbox Code Playgroud)

在 if 块内,构造函数中定义的变量“标签”消失并被 tf.Tensor 对象 (y_true) 替换。因此,您必须使用 tf.Variable 方法(分配、add_assing)来修改其内容但保留对象。此外,为了能够更改 tf.variable 形状,您必须以允许您拥有未定义形状的方式创建它,在本例中:(None,1),因为您在轴上连接=0。

所以:

class ZeroLabels(tf.keras.metrics.Metric):
    def __init__(self, *args, **kwargs):
        super(ZeroLabels, self).__init__(name="ZeroLabels")

        # Define a variable with unknown shape. This will allow you have dynamically sized variables (validate_shape=False)
        self.labels = tf.Variable([], shape=(None,), validate_shape=False)

    def update_state(self, y_true, y_pred, sample_weight=None):
        # On update method, just assign as new value the prevoius one joined with y_true
        self.labels.assign(tf.concat([self.labels.value(), y_true[:,0]], axis=0))

    def result(self):
        return tf.reduce_sum(tf.cast(self.labels.value() == 0, dtype=tf.int32))

    def reset_states(self):
        # To reset the metric, assign again an empty tensor
        self.labels.assign([])
Run Code Online (Sandbox Code Playgroud)

但是,如果您只计算数据集的 0,我建议您使用一个整数变量来计算这些元素,因为在每批处理后,标签数组都会增加其大小并获取其所有元素的总和时间越来越长,训练速度减慢。

class ZeroLabels_2(tf.keras.metrics.Metric):
    """Accumulates a list of all y_true sparse categorical labels (ints) and calculates the number of times the '0' label has appeared."""
    def __init__(self, *args, **kwargs):
        super(ZeroLabels_2, self).__init__(name="ZeroLabels")

        # Define an integer variable
        self.labels = tf.Variable(0, dtype=tf.int32)

    def update_state(self, y_true, y_pred, sample_weight=None):
        # Increase variable with every batch
        self.labels.assign_add(tf.cast(tf.reduce_sum(tf.cast(y_true == 0, dtype=tf.int32)), dtype=tf.int32 ))

    def result(self):
        # Simply return variable's content
        return self.labels.value()

    def reset_states(self):
        self.labels.assign(0)
Run Code Online (Sandbox Code Playgroud)

我希望这可以帮助你(并对英语水平表示歉意)