【问题标题】:Keras : How to create a custom layer with weights when the input shape is unknow during compilation?Keras:在编译期间输入形状未知时如何创建具有权重的自定义层?
【发布时间】:2019-04-08 08:29:46
【问题描述】:

我想在我的输入层之后定义一个预处理层,即它将使用之前计算的缩放器的均值和方差,并将其应用于我的输入,然后再将它们传递给密集网络。

Lambda 层在我的情况下不起作用,因为我想保存模型,目的是当应用于数据时,不需要处理输入,因为它将在网络的早期阶段完成。

对均值和 var 使用 K.variables 是可行的,但我想改用权重并设置 trainable=False。这样,它们将被保存在网络的权重中,而我不必每次都提供它们。

class PreprocessLayer(Layer):                                                                                                                                                                               
    """                                                                                                                                                                                                     
    Defines a layer that applies the preprocessing from a scaler                                                                                                                                            
    Needed because lambda layers are too fragile to be saved in a model                                                                                                                                     
    """                                                                                                                                                                                                     
    def __init__(self, batch_size, mean, var, **kwargs):                                                                                                                                                    
        self.b = batch_size                                                                                                                                                                                 
        self.m = mean                                                                                                                                                                                       
        self.v = var                                                                                                                                                                                        
        super(PreprocessLayer, self).__init__(**kwargs)                                                                                                                                                     

    def build(self, input_shape):                                                                                                                                                                           
        self.mean = self.add_weight(name='mean',                                                                                                                                                            
                                  shape=(self.b,input_shape[1]),                                                                                                                                            
                                  initializer=tf.constant_initializer(self.m),                                                                                                                              
                                  trainable=False)                                                                                                                                                          
        self.var = self.add_weight(name='var',                                                                                                                                                              
                                  shape=(self.b,input_shape[1]),                                                                                                                                            
                                  initializer=tf.constant_initializer(self.v),                                                                                                                              
                                  trainable=False)                                                                                                                                                          
        super(PreprocessLayer, self).build(input_shape)  # Be sure to call this at the end                                                                                                                  

    def call(self, x):                                                                                                                                                                                      
        return (x-self.mean)/self.var                                                                                                                                                                       

    def compute_output_shape(self, input_shape):                                                                                                                                                            
        return (input_shape[0],input_shape[1])                                                                                                                                                              
    def get_config(self):                                                                                                                                                                                   
        config = super(PreprocessLayer, self).get_config()                                                                                                                                                  
        config['mean'] = self.m                                                                                                                                                                             
        config['var'] = self.v                                                                                                                                                                              
        return config                                                                                                                                                                                       

我用

来称呼这一层
L0 = PreprocessLayer(batch_size=20,mean=scaler.mean_,var=scaler.scale_)(IN)

问题出现在

shape=(self.b,input_shape[1]),

这给了我错误(当 batch_size 为 20 时)

tensorflow.python.framework.errors_impl.InvalidArgumentError: Incompatible shapes: [32,15] vs. [20,15]
     [[Node: preprocess_layer_1/sub = Sub[T=DT_FLOAT, _device="/job:localhost/replica:0/task:0/device:CPU:0"](_arg_IN_0_0, preprocess_layer_1/mean/read)]]

据我了解,由于我的权重(mean 和 var)需要与输入 x 具有相同的形状,因此当 batch_size 不是训练大小的除数时,第一个轴会出现问题,因为它在训练期间会有不同的值培训。这会导致崩溃,因为形状必须在编译时确定,我不能将其留空。

有没有办法为 shape 的第一个值设置动态值?如果没有,是否可以解决此问题?

【问题讨论】:

  • 为什么不在输入后使用BatchNormalization 层?因为你似乎在做同样的事情
  • 我也不明白为什么您传递的batch_sizeinput_shape[0] 维度不同?
  • 因为我想掌握预处理中发生的事情,BatchNormalization 也可以批量处理,我想对整个数据集进行预处理。至于第二个问题是因为在编译时 input_shape = (None, 15)
  • 如果你的Input 层之后有一个PreprocessLayer,即使这样也可以批量工作,根据你的batch_size。你能告诉我你们每批有多少样品吗?是32还是20?在您的PreprocessLayer 中设置batch_size=batch_size_from_training,如果您仍然收到错误,请告诉我。
  • 起初我使用了 20 的 batch_size(PreprocessLayermodel.fit)然后我得到:tensorflow.python.framework.errors_impl.InvalidArgumentError: Incompatible shapes: [32,15] vs. [20,15] 然后我把它改成了 32,错误是 Incompatible shapes: [28,15] vs. [32,15] 在这种情况下我想这是有道理的,因为我的训练大小是 152540 和 152540%32=28 (因此第一批需要更小,以便后面的大小可以是 32),但是我不明白在自 152540%20 = 0 以来的第一个案例

标签: python tensorflow keras layer


【解决方案1】:

对于任何有相同问题的人 - 这是一个与 epoch 结束时的 batch_size 不同的余数(由于训练和测试大小不是批量大小的倍数)导致 InvalidArgumentError: Incompatible shapes - 这里是我的解决方法。

由于这个余数的大小总是小于 batch_size,所以我在调用函数中所做的就是像这样对权重进行切片:

def call(self, x):                                                                                                                                            
        mean = self.mean[:K.shape(x)[0],:]                                                                                                                       
        std = self.std[:K.shape(x)[0],:]                                                                                                                        
        return (x-mean)/std

这可行,但这意味着如果使用大于初始化层的批次大小来评估模型,错误将再次弹出。

这就是我在 __init__ 中添加的原因: self.b = max(32,batch_size).

因为 predict() 默认使用 batch_size = 32

【讨论】:

    【解决方案2】:

    我认为您不需要添加 meanvar 作为权重。您可以在 call 函数中计算它们。我也不完全明白你为什么要使用它而不是 BatchNormalization 但无论如何,也许你可以试试这个代码

    class PreprocessLayer(Layer):
        def __init__(self, eps=1e-6, **kwargs):
            self.eps = eps
            super(PreprocessLayer, self).__init__(**kwargs)
        def build(self, input_shape):
            super(PreprocessLayer, self).build(input_shape)
        def call(self, x):
            mean = K.mean(x, axis=-1, keepdims=True)
            std = K.std(x, axis=-1, keepdims=True)
            return (x - mean) / (std + self.eps)
        def compute_output_shape(self, input_shape):
            return input_shape
    

    eps是为了避免被0除。

    我不保证这会奏效,但不妨试一试。

    【讨论】:

    • 这可能会奏效,我之前做过类似的事情,但我使用了 K.variables 来表示 meanstd(使用训练集中的均值和标准值初始化),因为这些是在整个样本(而在这里您只对可能很小的批次进行此操作)。
    • 我唯一的问题是,加载模型时,我必须提供均值和标准差参数,而我不想这样做,因为当应用于另一个样本时,它们可能会有所不同。这就是我想将meanstd 定义为权重的原因,这样它们将保存在与模型一起加载的weights.h5 文件中。重点是要有一个可移植的模型,它可以直接用于未知数据,而无需对其进行预处理。
    • 是的,我不明白你为什么要它们作为权重。是否有特定理由通过训练数据的meanvar 对测试数据进行归一化?否则,BatchNormalization 或类似这样的简单自定义层负责处理每个批次的过程。
    • 您不需要为BatchNormalization 提供均值和标准差,对于自定义层也不需要。他们将计算每个批次的平均值和标准差,并据此标准化您的数据。在输入网络之前,您还可以在预处理步骤中轻松地标准化您的数据。所以,我实际上没有看到你提到的问题。
    • 我可以在神经网络之前对数据进行归一化,这其实是我之前一直在做的。我现在只想将该步骤包含在网络中,以后使用起来更容易,并且避免了这种外部预处理。我不喜欢BatchNormalization,因为没有句柄可以检查它在做什么。此外,当我加载模型并将其应用于未知数据时,我也不知道它的行为方式。
    猜你喜欢
    • 2020-11-06
    • 1970-01-01
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 1970-01-01
    • 2020-07-05
    • 2019-08-12
    • 2019-01-24
    相关资源
    最近更新 更多