Tensorflow 2.4.0:如何从另一层访问层属性?

Tensorflow 2.4.0: How do I access a layer attribute from another layer?

如何从另一个图层内部访问一个图层的属性?以下是我一直在尝试完成此任务的方式。

我正在尝试做我称之为 Sample-wise MinMax Scaling 的事情,正如人们可能猜到的那样,MinMax 在每个样本上进行缩放,而不是缩小每个特征。这是针对自动编码器的,因此这意味着需要“保存”初始缩放参数,或者更确切地说,另一个不同的层可以访问它来反转缩放(或去缩放)。此外,由于这种缩放取决于单个样本的最小值和最大值,因此无论是在训练期间还是 prediction/inference,都必须“实时”完成,并且每个样本的 minmax 参数必须与它们的样本顺序相同与相关联,因为,再一次,这是样本方面的 minmax 缩放。

我曾尝试在其他地方查找信息,但我找到的信息要么是针对 Tensorflow 1.x,要么是建议使用标准方法访问普通 python class 对象的属性。我发现了一些措辞相似的问题,但它们并不直接适用于我的情况。

我已经成功构建了许多不同的缩放层,但是 none 必须从该层外部访问层属性。

import numpy as np
import tensorflow as tf
import tensorflow.keras as krs
from sklearn.preprocessing import MinMaxScaler

(x,y),(xtest,ytest) = tf.keras.datasets.boston_housing.load_data()
print(x.shape)
# x[:,-1] = 0
scaler = MinMaxScaler()
scaler2 = MinMaxScaler()
scaler.fit(x)
scaler2.fit(x.T)
print(scaler.data_min_.shape)
transformed = scaler.transform(x)
transformed2 = scaler2.transform(x.T).T

class SampleMinMaxScaler( krs.layers.Layer ):
    def __init__(self, limits = (0.,1.), name="SampleMnMxScale" ):
        super(SampleMinMaxScaler, self).__init__()
        self.limits = tf.convert_to_tensor(np.array(limits),dtype='float64')
        self.range = tf.constant(limits[1]-limits[0],dtype='float64')
        self.range_min = tf.constant(limits[0],dtype='float64')
        self.max_data = tf.zeros((5,10),dtype='float64')
        self.min_data = tf.zeros((5,10),dtype='float64')
        # tf.print(self.limits.get_shape())
        # self.name = name
        
        

    def build(self, input_shape):
        pass

    def call(self, input):
        super(SampleMinMaxScaler, self).__init__()
        # input_data = tf.convert_to_tensor(input,dtype='float64')
        input_data = tf.cast(input,dtype='float64')
        self.max_data = tf.math.reduce_max(input_data,axis=1)
        self.min_data = tf.math.reduce_min(input_data,axis=1)
        
        self.inv_denominator = tf.divide(tf.constant(1.,dtype='float64'), tf.subtract(self.max_data,self.min_data) )
        self.inv_denominator = tf.where(tf.math.is_inf(self.inv_denominator),tf.constant(0.,dtype='float64'),self.inv_denominator)
        
        self.scaling_multiplier = tf.multiply(self.inv_denominator,self.range)
        
        return tf.transpose(tf.cast(tf.add( tf.multiply( tf.subtract(tf.transpose(tf.cast(input,dtype='float64')), self.min_data), self.scaling_multiplier ), self.range_min ),dtype='float32'))
    
    def get_config(self):
        data = {    'limits': self.limits,
                    'range': self.range,
                    'range_min': self.range_min}
        return data

class SampleMinMaxDescaler( krs.layers.Layer ):
    def __init__(self, max_data, min_data, limits, input_range, name="SampleMnMxDescale" ):
        super(SampleMinMaxDescaler, self).__init__()
        self.max_data = max_data 
        self.min_data = min_data
        self.limits = limits
        self.range = input_range
        # tf.print(input_range.get_shape())
        self.range_min = limits[0]
        # self.name=name
        

    def build(self, input_shape):
        pass

    def call(self, input):
        
        inv_denominator = tf.divide(tf.constant(1.,dtype='float64'), self.range)
        
        scaling_multiplier = tf.multiply(inv_denominator,tf.subtract(self.max_data,self.min_data))
        
        return tf.transpose(tf.cast(tf.add( tf.multiply( tf.subtract(tf.transpose(tf.cast(input,dtype='float64')), self.range_min), scaling_multiplier ), self.min_data ),dtype='float32'))

    def get_config(self):
        data = {    'limits': self.limits,
                    'range': self.range,
                    'range_min': self.range_min}
        return data

inputs = krs.Input(shape=(x.shape[1],))    
samp_scaler = SampleMinMaxScaler()
outputs = samp_scaler(inputs)
outputs2 = SampleMinMaxDescaler(samp_scaler.max_data,samp_scaler.min_data,samp_scaler.limits,samp_scaler.range )(outputs)

model = krs.models.Model(inputs = inputs, outputs = outputs2 )

model.compile(optimizer='adam')

model.fit(x)
out = model.predict(x)
# print((out-transformed2)/(transformed2+1e-12)*100)
print((out-x)/(x+1e-12)*100)

但是,以下是此代码产生的错误。老实说,我不知道这是什么意思。我已经搜索了有关它的信息,但非常不清楚它应该如何 work/help。我曾尝试将它分别放入 SampleMinMaxScaler.__init__()SampleMinMaxDescaler.__init__() 函数中,并且同时放入这两个函数中,但是 none 这些尝试都奏效了。

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
The graph tensor has name: model_104/sample_min_max_scaler/Max:0.

最后,我知道我可以使用 numpy/sklearn 在模型外部进行缩放和去缩放,事实上,这就是我一直在做的事情。我构建这些 scaling/descaling 层的原因是我正在尝试执行迁移学习,将三个独立的模型端到端地连接在一起。当然,它们最初是单独训练的,但是由于每个中间模型都是根据第一个模型的输出进行训练以创建最后一个模型的预期输入,并且每个模型都有自己的 scaling/descaling 步骤需要在组合模型中实现。

最后一段的 TLDR 是“在模型之外使用 numpy and/or sklearn 不是一个有效的选项。”此外,由于与手头问题无关的原因,不能使用 GradientTape。

编辑:我犯了一个愚蠢的错误,现在在 post 和代码中都已更正;但是,我仍然收到同样的错误。

最简单的方法可能是重用图层来执行除垢操作。您不需要以这种方式在层之间传递变量。您只需要确保在除垢器之前调用洁牙器,并且只调用一次。

类似于此:

class SampleMinMaxScaler(tf.keras.layers.Layer):
    def __init__(self, limits=(0.0, 1.0), name="SampleMnMxScale"):
        super(SampleMinMaxScaler, self).__init__()
        self.limits = tf.convert_to_tensor(np.array(limits), dtype="float64")
        self.range = tf.constant(limits[1] - limits[0], dtype="float64")
        self.range_min = tf.constant(limits[0], dtype="float64")
        self.max_data = tf.zeros((5, 10), dtype="float64")
        self.min_data = tf.zeros((5, 10), dtype="float64")

    def scale(self, input):
        input_data = tf.cast(input, dtype="float64")
        self.max_data = tf.math.reduce_max(input_data, axis=1)
        self.min_data = tf.math.reduce_min(input_data, axis=1)

        inv_denominator = tf.divide(
            tf.constant(1.0, dtype="float64"), tf.subtract(self.max_data, self.min_data)
        )
        inv_denominator = tf.where(
            tf.math.is_inf(inv_denominator),
            tf.constant(0.0, dtype="float64"),
            inv_denominator,
        )

        scaling_multiplier = tf.multiply(inv_denominator, self.range)

        return tf.transpose(
            tf.cast(
                tf.add(
                    tf.multiply(
                        tf.subtract(
                            tf.transpose(tf.cast(input, dtype="float64")), self.min_data
                        ),
                        scaling_multiplier,
                    ),
                    self.range_min,
                ),
                dtype="float32",
            )
        )

    def descale(self, input):
        inv_denominator = tf.divide(tf.constant(1.0, dtype="float64"), self.range)

        scaling_multiplier = tf.multiply(
            inv_denominator, tf.subtract(self.max_data, self.min_data)
        )

        return tf.transpose(
            tf.cast(
                tf.add(
                    tf.multiply(
                        tf.subtract(
                            tf.transpose(tf.cast(input, dtype="float64")),
                            self.range_min,
                        ),
                        scaling_multiplier,
                    ),
                    self.min_data,
                ),
                dtype="float32",
            )
        )

    def call(self, input, descale=False):
        if descale:
            return self.descale(input)
        return self.scale(input)

然后按以下方式构建模型:

inputs = tf.keras.Input(shape=(x.shape[1],))
samp_scaler = SampleMinMaxScaler()
scaled = samp_scaler(inputs)
descaled = samp_scaler(scaled, descale=True)
model2 = tf.keras.models.Model(inputs=inputs, outputs=descaled)