Python 更新TensorFlow自定义度量的内部状态(即在度量计算中使用非更新状态变量)

Python 更新TensorFlow自定义度量的内部状态(即在度量计算中使用非更新状态变量),python,tensorflow,graph,metrics,Python,Tensorflow,Graph,Metrics,版本:python 3.8.2(我也尝试过3.6.8,但我认为python版本在这里并不重要)、tensorflow 2.3.0、numpy 1.18.5 我正在用稀疏标签张量训练一个分类问题的模型。我如何定义一个度量来计算“0”标签在该点之前出现的次数?在下面的代码示例中,我试图存储度量在数组中看到的所有标签,并在每次调用update\u state时将现有数组与新的y\u true连接起来。(我知道我可以只存储一个count变量并使用+=,但在实际使用场景中,串联是理想的,内存不是问题。)下

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

我正在用稀疏标签张量训练一个分类问题的模型。我如何定义一个度量来计算“0”标签在该点之前出现的次数?在下面的代码示例中,我试图存储度量在数组中看到的所有标签,并在每次调用
update\u state
时将现有数组与新的
y\u true
连接起来。(我知道我可以只存储一个
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)
这段代码本身就可以工作,但当我尝试使用此度量训练模型时,它会抛出以下错误:

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
我认为这可能与调用
update\u state
时,
self.labels
不是图形的直接部分这一事实有关。以下是我尝试过的其他一些事情:

  • 存储
    tf.int32
    shape=()
    count
    变量,并增加该变量,而不是连接新标签
  • 使用
    .numpy()
    将所有内容转换为numpy,并将它们连接起来(我希望强制TensorFlow不使用图形)
  • 使用
    尝试
    除了上面带有numpy转换的
  • 创建一个全新的类(而不是子类化
    tf.keras.metrics.Metric
    ),在可能的情况下专门使用numpy,但这种方法会导致一些加载问题,即使在
    tf.keras.models.load\u model
  • 使用
    @tf.autograph.experimental.don\u not \u convert
    装饰器进行所有方法
  • 修改全局变量而不是属性,并使用
    global
    关键字
  • 使用非tensorflow属性(不使用
    self.labels=self.add\u weight…

如果有帮助的话,下面是这个问题的一个更一般的版本:我们如何在
update\u state
计算中合并没有作为参数传递到
update\u state
的张量?任何帮助都将不胜感激。提前谢谢你

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

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

因此:

但是,如果您只需要一个变量来计算数据集的0,我建议您使用一个整数变量来计算这些元素,因为在每个批处理过程之后,labels数组将增加其大小,并且获取其所有元素的总和将花费越来越多的时间,从而降低您的训练速度

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)

我希望这能帮助您(并为英语水平道歉)

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

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

因此:

但是,如果您只需要一个变量来计算数据集的0,我建议您使用一个整数变量来计算这些元素,因为在每个批处理过程之后,labels数组将增加其大小,并且获取其所有元素的总和将花费越来越多的时间,从而降低您的训练速度

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)
我希望这能帮助你(并为英语水平道歉)