keras中的add_loss函数

Doc*_*ven 17 neural-network autoencoder keras

目前我偶然发现了变量自动编码器,并尝试使用keras使它们在MNIST上运行.我在github上找到了一个教程.

我的问题涉及以下几行代码:

# Build model
vae = Model(x, x_decoded_mean)

# Calculate custom loss
xent_loss = original_dim * metrics.binary_crossentropy(x, x_decoded_mean)
kl_loss = - 0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
vae_loss = K.mean(xent_loss + kl_loss)

# Compile
vae.add_loss(vae_loss)
vae.compile(optimizer='rmsprop')
Run Code Online (Sandbox Code Playgroud)

为什么使用add_loss而不是将其指定为编译选项?vae.compile(optimizer='rmsprop', loss=vae_loss)似乎没有工作的东西 ,并抛出以下错误:

ValueError: The model cannot be compiled because it has no loss to optimize.
Run Code Online (Sandbox Code Playgroud)

这个函数和自定义丢失函数有什么区别,我可以添加它作为Model.fit()的参数?

提前致谢!

PS:我知道在github上存在几个与此有关的问题,但大多数问题都是开放的,没有注释.如果已经解决,请分享链接!

编辑: 我删除了向模型添加损失的行,并使用了编译函数的loss参数.它现在看起来像这样:

# Build model
vae = Model(x, x_decoded_mean)

# Calculate custom loss
xent_loss = original_dim * metrics.binary_crossentropy(x, x_decoded_mean)
kl_loss = - 0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
vae_loss = K.mean(xent_loss + kl_loss)

# Compile
vae.compile(optimizer='rmsprop', loss=vae_loss)
Run Code Online (Sandbox Code Playgroud)

这会引发TypeError:

TypeError: Using a 'tf.Tensor' as a Python 'bool' is not allowed. Use 'if t is not None:' instead of 'if t:' to test if a tensor is defined, and use TensorFlow ops such as tf.cond to execute subgraphs conditioned on the value of a tensor.
Run Code Online (Sandbox Code Playgroud)

EDIT2:替代方法 感谢@ MarioZ的努力,我能够找到解决方法.

# Build model
vae = Model(x, x_decoded_mean)

# Calculate custom loss in separate function
def vae_loss(x, x_decoded_mean):
    xent_loss = original_dim * metrics.binary_crossentropy(x, x_decoded_mean)
    kl_loss = - 0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
    vae_loss = K.mean(xent_loss + kl_loss)
    return vae_loss

# Compile
vae.compile(optimizer='rmsprop', loss=vae_loss)

...

vae.fit(x_train, 
    x_train,        # <-- did not need this previously
    shuffle=True,
    epochs=epochs,
    batch_size=batch_size,
    validation_data=(x_test, x_test))     # <-- worked with (x_test, None) before
Run Code Online (Sandbox Code Playgroud)

出于某些奇怪的原因,我必须在拟合模型时明确指定y和y_test.最初,我不需要这样做.生产的样品对我来说似乎很合理.

虽然我可以解决这个问题,但我仍然不知道这两种方法的差异/(dis-)优点是什么(除了需要不同的语法).有人能给我更多的见解吗?谢谢!

jlh*_*jlh 18

我将尝试回答原始问题,为什么model.add_loss()被使用而不是指定自定义损失函数model.compile(loss=...).

Keras中的所有损失函数总是采用两个参数y_truey_pred.看看Keras中可用的各种标准损耗函数的定义,它们都有这两个参数.它们是'目标'(许多教科书中的Y变量)和模型的实际输出.大多数标准损失函数可以写成这两个张量的表达式.但是,一些更复杂的损失不能以这种方式写出来.为了您的VAE例如,这是因为失去功能也依赖于额外的张量,即案件z_log_varz_mean,这是不提供给丢失的功能.使用model.add_loss()没有这样的限制,并允许你写出更复杂的损失,这取决于许多其他张量,但它更不依赖于模型,而标准损失函数只适用于任何模型.

(注:在这里其他的答案提出的代码有点尽可能多的欺骗,因为他们只使用全局变量所需的额外的依赖偷偷这使得损失函数不是在数学意义上的真正的功能,我认为这是多大.不太干净的代码,我希望它更容易出错.)


M.I*_*nat 7

我还想知道相同的查询和一些相关的东西,例如如何在中间层中添加损失函数。在这里,我将一些观察到的信息分享给大家,希望对其他人有帮助。确实,标准keras损失函数只接受两个参数,y_truey_pred。但在实验过程中,有时我们需要一些外部参数或系数,同时用这两个值(y_truey_pred)进行计算。像往常一样,这可能需要在最后一层或模型层中间的某个位置。

model.add_loss()

接受的答案正确地说明了model.add_loss()功能。它可能取决于层输入(张量)。根据官方文档,在编写call自定义层或子类模型的方法时,我们可能想要计算在训练期间要最小化的标量(例如regularization losses)。我们可以使用add_loss()分层方法来跟踪此类损失项。例如,活动正则化损失取决于调用层时传递的输入。下面是一个基于输入的 L2 范数添加稀疏正则化损失的层示例:

from tensorflow.keras.layers import Layer

class MyActivityRegularizer(Layer):
  """Layer that creates an activity sparsity regularization loss."""

  def __init__(self, rate=1e-2):
    super(MyActivityRegularizer, self).__init__()
    self.rate = rate

  def call(self, inputs):
    # We use `add_loss` to create a regularization loss
    # that depends on the inputs.
    self.add_loss(self.rate * tf.reduce_sum(tf.square(inputs)))
    return inputs
Run Code Online (Sandbox Code Playgroud)

通过添加的损失值可以在任何或的列表属性add_loss中检索(它们从每个底层递归检索):.lossesLayerModel

from tensorflow.keras import layers

class SparseMLP(Layer):
  """Stack of Linear layers with a sparsity regularization loss."""

  def __init__(self, output_dim):
      super(SparseMLP, self).__init__()
      self.dense_1 = layers.Dense(32, activation=tf.nn.relu)
      self.regularization = MyActivityRegularizer(1e-2)
      self.dense_2 = layers.Dense(output_dim)

  def call(self, inputs):
      x = self.dense_1(inputs)
      x = self.regularization(x)
      return self.dense_2(x)


mlp = SparseMLP(1)
y = mlp(tf.ones((10, 10)))

print(mlp.losses)  # List containing one float32 scalar
Run Code Online (Sandbox Code Playgroud)

另请注意,使用 时model.fit(),会自动处理此类损失项。在编写自定义训练循环时,我们应该从 手动检索这些术语model.losses,如下所示:

loss_fn = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
optimizer = tf.keras.optimizers.Adam()

# Iterate over the batches of a dataset.
for x, y in dataset:
    with tf.GradientTape() as tape:
        # Forward pass.
        logits = model(x)
        # Loss value for this batch.
        loss_value = loss_fn(y, logits)
        # Add extra loss terms to the loss value.
        loss_value += sum(model.losses) # < ------------- HERE ---------

    # Update the weights of the model to minimize the loss value.
    gradients = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(gradients, model.trainable_weights))
Run Code Online (Sandbox Code Playgroud)

Custom losses

有了model.add_loss(),(AFAIK),我们可以在网络中间的某个地方使用它。这里我们不再只绑定两个参数,即y_true, y_pred。但是,如果我们还想将外部参数或系数归咎于网络的最后一层损失函数,该怎么办?Nric 的答案是正确的。但也可以通过子类化tf.keras.losses.Loss类来实现,通过实现以下两个方法:

  • __init__(self):接受在调用损失函数期间传递的参数
  • call(self, y_true, y_pred):使用目标(y_true)和模型预测(y_pred)来计算模型的损失

MSE下面是一个通过子类化类进行自定义的示例tf.keras.losses.Loss。这里我们也不再只绑定两个参数,即y_ture, y_pred

class CustomMSE(keras.losses.Loss):
    def __init__(self, regularization_factor=0.1, name="custom_mse"):
        super().__init__(name=name)
        self.regularization_factor = regularization_factor

    def call(self, y_true, y_pred):
        mse = tf.math.reduce_mean(tf.square(y_true - y_pred))
        reg = tf.math.reduce_mean(tf.square(0.5 - y_pred))
        return mse + reg * self.regularization_factor

model.compile(optimizer=..., loss=CustomMSE())
Run Code Online (Sandbox Code Playgroud)

  • 你的回答真是令人难以置信。谢谢。 (2认同)

小智 6

JIH 的回答当然是正确的,但也许补充一下是有用的:

model.add_loss()没有限制,但它也消除了在model.fit().

如果您的损失取决于模型的附加参数、其他模型或外部变量,您仍然可以使用 Keras 类型的封装损失函数,方法是使用封装函数传递所有附加参数:

def loss_carrier(extra_param1, extra_param2):
    def loss(y_true, y_pred):
        #x = complicated math involving extra_param1, extraparam2, y_true, y_pred
        #remember to use tensor objects, so for example keras.sum, keras.square, keras.mean
        #also remember that if extra_param1, extra_maram2 are variable tensors instead of simple floats,
        #you need to have them defined as inputs=(main,extra_param1, extraparam2) in your keras.model instantiation.
        #and have them defind as keras.Input or tf.placeholder with the right shape.
        return x
    return loss

model.compile(optimizer='adam', loss=loss_carrier)
Run Code Online (Sandbox Code Playgroud)

诀窍是在最后一行返回一个函数,因为 Keras 期望它们只有两个参数y_truey_pred

可能看起来比model.add_loss版本更复杂,但损失保持模块化。

  • 这个例子其实是错误的。您可以在“compile”中调用损失函数,例如“model.compile(optimizer='adam', loss=loss_carrier(1.0, 2.0))”。您还可以传递层或中间张量,即 vaes。但是,您还需要在“compile”中设置“experimental_run_tf_function=False”。然而,当将“tf.Tensor”传递给这种包装损失函数时,该方法在 tf2.2 中不再起作用。 (5认同)
  • 在 tf2.4 中不起作用,抛出此错误:“无法将符号 Keras 输入/输出转换为 numpy 数组”。model.add_loss 版本有效。 (3认同)
  • 但是如何传递参数“extra_param1”和“extra_param2”呢?您能提供一个可以执行的完整且可行的示例吗? (2认同)