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

问题描述

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

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

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

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

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,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.,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):
        
        inv_denominator = tf.divide(tf.constant(1.,self.range)
        
        scaling_multiplier = tf.multiply(inv_denominator,self.min_data))
        
        return tf.transpose(tf.cast(tf.add( tf.multiply( tf.subtract(tf.transpose(tf.cast(input,self.range_min),scaling_multiplier ),self.min_data ),dtype='float32'))

    def get_config(self):
        data = {    'limits': self.limits,'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)

但是,下面是由此代码产生的错误。老实说,我不知道这意味着什么。我已经搜索了有关它的信息,但目前还不清楚它应该如何工作/帮助。我曾尝试将它分别并同时放入 SampleMinMaxScaler.__init__()SampleMinMaxDescaler.__init__() 函数中,但这些尝试均无效。

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 在模型之外缩放和去缩放,事实上,这就是我一直在做的。我构建这些缩放/去缩放层的原因是我试图执行将三个独立模型端到端链接在一起的转移学习。当然,它们最初是单独训练的,但是由于每个中间模型都在第一个模型的输出上进行训练以创建最后一个模型的预期输入,并且每个模型都有自己的缩放/去缩放步骤,需要在组合模型。

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

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

解决方法

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

类似的东西:

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,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,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,self.range)

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

        return tf.transpose(
            tf.cast(
                tf.add(
                    tf.multiply(
                        tf.subtract(
                            tf.transpose(tf.cast(input,self.min_data,)
        )

    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)