Python:为什么子类方法的**kwarg会作为基类的_call__()签名中的参数传递?导致打字错误

2024-06-28 19:56:09 发布

您现在位置:Python中文网/ 问答频道 /正文

很抱歉标题太模糊,不能有太多字符。

简要说明
我正在为一个图像分析程序实现一个自动编码器CNN架构,该程序需要定制丢失功能,而这些功能在keras后端或tensorflow中的任何地方都不存在。这没什么大不了的;我喜欢数值计算,并很乐意提高我的OOP技能。 为了让我的程序顺利运行,我需要我的loss函数是可调用的对象(基本上是keras实现其loss对象的方式,因此我基于keras loss源代码镜像了我的loss类)。到目前为止,我已经学到了很多。我有一个类ReconstructionLoss(LossWrapper):,它是LossWrapper(Loss):的一个子类,它是父Loss(object):的一个子类。具体来说,重建损失的实例需要一个张量“DecodeOut”kwarg,但其他不同类型的损失不需要这个kwarg张量。它们都需要y_true并对参数进行编码。

问题
ReconstructionLoss实例应该能够采用**kwargs(始终为“DecodeOut”=某个张量)。但不知何故,kwarg被传递到父类Loss()中的__call__()签名,导致TypeError: __call__() got an unexpected keyword argument 'DecodeOut'。我认为当Loss类中的__call__(self, arg1, arg2):主体中使用losswrapercall()方法时,就会发生这种情况。如果我继续谈论它,它会变得更加复杂(哈哈),所以我会让你自己看看代码,看看其余的细节。我确信我犯了一个非常微妙的新手错误,因为我已经有一分钟没有做OOP了。为任何奇怪的凹痕道歉;复制并粘贴到此处的代码块中是一件麻烦事。(我在这里也只展示了相关模块):

from __future__ import absolute_import, division, print_function, unicode_literals

import sys,os 
from pathlib import Path
from matplotlib import pyplot, cm
import tensorflow as tf
import tensorflow_io as tfio
import keras
from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model
from tensorflow.python.framework import tensor_util
from tensorflow.python.keras import backend as KB
from tensorflow.python.keras.utils import tf_utils
from tensorflow.python.ops.losses import util as tf_losses_util
from tensorflow.python.keras.utils import losses_utils

class Loss(object):

    def __init__(self): 
        print('Loss Object Instantiated')
    #HERE IS WHERE I GET THE ERROR once compilation gets here. 
    #kwarg 'DecodeOut' is being passed as a parameter
    # in __call__(self, y_true, EncodeOut, sample_weight = None).
    #It should only be called in the *return value* of the call() method
    # defined in LossWrapper class.
    def __call__(self, y_true, EncodeOut, sample_weight = None):#boom, error
    
        graph_ctx = tf_utils.graph_context_for_symbolic_tensors(
            y_true,EncodeOut, sample_weight)
        with KB.name_scope(self.__class__.__name__), graph_ctx:

            losses = self.call(y_true,EncodeOut) 
            return losses_utils.compute_weighted_loss(losses, sample_weight)


    def call(self, y_true, EncodeOut):
    """Invokes the `Loss` instance.

    Args:
      y_true: Ground truth values, with the same shape as 'y_pred'.
      y_pred: The predicted values.
    """
    NotImplementedError('Must be implemented in subclasses.')
    
 class LossWrapper(Loss):
        def __init__(self, func, **kwargs):
            super(LossWrapper, self).__init__()
            self.func = func
            self.func_kwargs = kwargs

     #See below, def call(self, y_true, EncodeOut), RETURNS
     #reconstruction_loss(y_true,EncodeOut, **self.func_kwargs),
     #but the kwarg = 'DecodeOut' is getting passed in the __call()__ signature when I implement:
     # recon_loss_obj = ReconstructionLoss()
     # reconLoss = recon_loss_obj (trueLabels, someTensor, DecodeOut = someOtherTensor)

     def call(self, y_true, EncodeOut):
             return self.func(y_true,EncodeOut, **self.func_kwargs)
    

class ReconstructionLoss(LossWrapper):
     def __init__(self, DecodeOut = [0]):
    
        super(ReconstructionLoss, self).__init__(
            reconstruction_loss, #this is the value of 'func' in all instances, 
                                 #function defined at below
            DecodeOut = DecodeOut)
        self.DecodeOut = DecodeOut

 def reconstruction_loss( y_true, residual, DecodeOut = [0]):
      print(DecodeOut.shape)
      K  = tf.size(residual[0,:,:,:]).numpy()
      L1_norm_batches = tf.norm(residual-DecodeOut, ord = 1, axis = [-3,-2])
      reconstruction_loss = np.sum(L1_norm_batches.numpy())/K
      return reconstruction_loss 

实施示例:

      loss_object = ReconstructionLoss()
      a=loss_object(y_true_train, conv11, DecodeOut = select) 

      Loss Object Instantiated
      Traceback (most recent call last):

         File "C:\Python File\Tamper.py", line 794, in <module>
         a=loss_object(y_true_train, conv11, DecodeOut = select)

         TypeError: __call__() got an unexpected keyword argument 'DecodeOut'

我无法理解为什么“DecodeOut”首先要传递给__call__()签名,而它应该只传递给self.func(y_true, EncodeOut, **kwarg)(在这种情况下,每当ReconstructionLoss()对象被实例化时self.func = reconstruction_loss

我知道这可能是一个非常明显的错误,这是很多信息,但我正试图尽可能详尽。如果你想知道我为什么采用这种方法,那是因为我有几个其他损失对象(例如重建损失与激活损失)我需要能够调用,我这样做是为了学习。此外,我感到困惑,因为这个实现与Keras Loss源代码直接类似……就我而言,它们采用完全相同的方法。 如果您愿意,请查看他们的代码,特别是Loss、LossFunctionWrapper,然后是BinaryCrossEntropy类和函数BinaryCrossEntropy:

    https://keras-gym.readthedocs.io/en/stable/_modules/tensorflow/python/keras/losses.html

我很确定我遗漏了一些我不知道的开发人员所做的微妙的事情,或者我对继承发生的事情有严重的误解。 在调用super()之前,我已经尝试过定义所有的self属性,但没有成功…甚至不确定这是否合适。 对于那些想知道为什么要这样设计的人来说,下面是一个keras代码的示例片段,其中包含Loss、Wrapper、LossTypeClass、Loss类型的函数和kwargs:keras代码有点拥挤,因为它们还有几个可选参数,如reduce、name等

class Loss(object):


    def __init__(self, reduction=losses_utils.ReductionV2.AUTO,name=None):
         losses_utils.ReductionV2.validate(reduction)
         self.reduction = reduction
         self.name = name

    def __call__(self, y_true, y_pred, sample_weight=None):

        # If we are wrapping a lambda function strip '<>' from the name as it is not
        # accepted in scope name.
        scope_name = 'lambda' if self.name == '<lambda>' else self.name
        graph_ctx = tf_utils.graph_context_for_symbolic_tensors(
        y_true, y_pred, sample_weight)
        with K.name_scope(scope_name or self.__class__.__name__), graph_ctx:
            losses = self.call(y_true, y_pred)
        return losses_utils.compute_weighted_loss(
        losses, sample_weight, reduction=self._get_reduction())

class LossFunctionWrapper(Loss):

   def __init__(self,
                fn,
                reduction=losses_utils.ReductionV2.AUTO,
                name=None,
                **kwargs):
     super(LossFunctionWrapper, self).__init__(reduction=reduction, name=name)
     self.fn = fn
     self._fn_kwargs = kwargs

   def call(self, y_true, y_pred):

     if tensor_util.is_tensor(y_pred) and tensor_util.is_tensor(y_true):
       y_pred, y_true = tf_losses_util.squeeze_or_expand_dimensions(
           y_pred, y_true)
     return self.fn(y_true, y_pred, **self._fn_kwargs)

class BinaryCrossentropy(LossFunctionWrapper):

   def __init__(self,
           from_logits=False,# a kwarg not passed in __call__() of Loss(), passed when you call the instantiation of BinaryCrossentropy().
           label_smoothing=0,# same
           reduction=losses_utils.ReductionV2.AUTO,
           name='binary_crossentropy'):
     super(BinaryCrossentropy, self).__init__(
         binary_crossentropy,
         name=name,
         reduction=reduction,
         from_logits=from_logits,
         label_smoothing=label_smoothing)
     self.from_logits = from_logits


def binary_crossentropy(y_true, y_pred, from_logits=False,  label_smoothing=0):  # pylint: disable=missing-docstring
   y_pred = ops.convert_to_tensor(y_pred)
   y_true = math_ops.cast(y_true, y_pred.dtype)
   label_smoothing = ops.convert_to_tensor(label_smoothing,dtype=K.floatx())

   def _smooth_labels():
     return y_true * (1.0 - label_smoothing) + 0.5 * label_smoothing

   y_true = smart_cond.smart_cond(label_smoothing,
                             _smooth_labels, lambda: y_true)
  return K.mean(
      K.binary_crossentropy(y_true, y_pred, from_logits=from_logits), axis=-1)

~z~谢谢


Tags: namefromimportselftruedefutilscall
1条回答
网友
1楼 · 发布于 2024-06-28 19:56:09

代码没有什么问题,对OOP的理解很慢,代码实现不正确kwargs应该在实例化对象时传递,而不是在尝试调用实例时传递

loss_obj = ReconstructionLoss(kwargs_here)
value = loss_obj(args)

而不是:

loss_obj = ReconstructionLoss()
value = loss_obj(args, kwargs_here)

相关问题 更多 >