【问题标题】:What is the purpose of the add_loss function in Keras?Keras 中 add_loss 函数的作用是什么?
【发布时间】:2018-10-08 08:53:54
【问题描述】:

目前我偶然发现了变分自动编码器,并试图让它们使用 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')

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

ValueError: The model cannot be compiled because it has no loss to optimize.

此函数与自定义损失函数有什么区别,我可以将其添加为 Model.fit() 的参数?

提前致谢!

P.S.:我知道 github 上有几个与此相关的问题,但其中大多数是开放的且未注释的。如果问题已经解决,请分享链接!


编辑 1

我删除了将损失添加到模型的行并使用了编译函数的损失参数。现在看起来像这样:

# 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)

这会引发 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.

编辑 2

感谢@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

由于某种奇怪的原因,我不得不在拟合模型时明确指定 y 和 y_test。最初,我不需要这样做。生产的样品对我来说似乎是合理的。

虽然我可以解决这个问题,但我仍然不知道这两种方法的区别和缺点是什么(除了需要不同的语法)。谁能给我更多的见解?

【问题讨论】:

  • 因为我对此有点挣扎 - 我的 Keras 版本拒绝在没有指定损失的情况下进行编译,解决方案显然是在 compile() 语句中添加 loss=None。
  • 原始代码的链接已损坏。我认为this 是原始代码的来源。

标签: neural-network keras autoencoder


【解决方案1】:

我也想知道相同的查询和一些相关的东西,比如如何在中间层中添加损失函数。在这里我分享一些观察到的信息,希望它可以帮助其他人。确实,标准的keras 损失函数只接受两个参数,y_truey_pred。但是在实验过程中,在某些情况下,我们需要一些外部参数或系数来计算这两个值(y_truey_pred)。这可以像往常一样在最后一层或模型层中间的某个地方需要。

model.add_loss()

接受的答案正确地说明了model.add_loss() 函数。它可能取决于层输入(张量)。根据官方doc 的说法,在编写自定义层或子类模型的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

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

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

另请注意,当使用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))

Custom losses

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

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

这是一个自定义MSE 的示例,它通过继承tf.keras.losses.Loss 类。而且这里我们也不再只绑定两个参数,即y_turey_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())

【讨论】:

  • 你的回答真是令人难以置信。谢谢。
  • 自定义损失函数的 init 部分是否允许有 input_tensor。我尝试但我得到 TypeError: Cannot convert a symbolic Keras input/output to a numpy array。此错误可能表明您正在尝试将符号值传递给不支持的 NumPy 调用。或者,您可能试图将 Keras 符号输入/输出传递给未注册调度的 TF API,从而阻止 Keras 自动将 API 调用转换为函数模型中的 lambda 层。
  • 请询问一些细节问题。我不清楚。
  • 所以,最后你的课 CustomMSE 真的是一门很棒的课。但是,假设您想在调用部分中再添加一个与输入张量相关的部分。为了做到这一点,我在 init 部分和 self.input_tensor=input_tensor 中添加了一个新功能“input_tensor”。所以,一切都很好,但是当我在调用方法中更改损失部分时,我收到了我发送给你的错误。在使用 Model 和 Input 创建模型后,我使用 Input 的输出作为该损失函数的 init 的输入。所以,model.compile(loss=CustomMSE(input_tensor=tensor_from_Input_layer))。这种方法可以吗?
  • 谢谢@M.Innat! 这非常有帮助!您的最后一个示例正是我需要但无法找到的。 (建议:为了完整起见,可以添加from tensorflow.keras.losses import Loss,然后使用class CustomMSE(Loss):
【解决方案2】:

需要将编译行改为

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

【讨论】:

  • 我已经提到它不起作用。不过,感谢您的参与。
  • 'vae.compile(optimizer='rmsprop', loss=vae_loss)' without vae.add... 或 'vae.add(vae_loss) vae.compile(optimizer='rmsprop', loss =无)'
  • 对于我的测试,我已经删除了vae.add_loss(vae_loss) 并且只是指定了编译操作期间的损失。它抛出一个类型错误。我将错误编辑到我的问题中。
  • def vae_loss(x, x_decoded_mean): xent_loss = objectives.binary_crossentropy(x, x_decoded_mean) kl_loss = -0.5 * K.mean(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var)) loss = xent_loss + kl_loss return loss 然后 vae.compile(optimizer='rmsprop', loss=vae_loss)
  • 我也试过这个,但是以这种方式定义自定义损失会引发另一个错误:AttributeError: 'NoneType' object has no attribute 'shape'。我目前正在研究如何实现自定义损失函数。这已经讨论过here。不幸的是,它让我无法了解这两种方法之间的区别。
【解决方案3】:

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)

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

可能看起来比model.add_loss 版本更复杂,但损失仍然是模块化的。

【讨论】:

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

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

Keras 中的所有损失函数总是采用两个参数y_truey_pred。看看 Keras 中可用的各种标准损失函数的定义,它们都有这两个参数。它们是“目标”(许多教科书中的 Y 变量)和模型的实际输出。大多数标准损失函数可以写成这两个张量的表达式。但是一些更复杂的损失不能这样写。对于您的 VAE 示例,情况就是如此,因为损失函数还依赖于额外的张量,即 z_log_varz_mean,它们对损失函数不可用。使用model.add_loss() 没有这样的限制,并且允许您编写依赖于许多其他张量的更复杂的损失,但它具有更多依赖于模型的不便,而标准损失函数只适用于任何模型。

(注意:此处其他答案中提出的代码有点作弊,因为它们只是使用全局变量来潜入额外的必需依赖项。这使得损失函数在数学意义上不是真正的函数。我认为这一点更简洁的代码,我希望它更容易出错。)

【讨论】:

  • 在 image_ocr 示例中可以找到更加依赖模型的损失模板。这里损失函数被包装在一个 lambda 损失层中,一个额外的模型被实例化为 loss_layer 作为输出,使用额外的损失计算输入,这个模型是用一个虚拟的 lambda 损失函数编译的,它只是作为损失返回模型的输出.一直以来,数据生成器都会为损失生成虚拟 y 样本。
  • 但是如果将 z_log_varz_mean 设置为可以被装扮损失函数访问的变量,那么 add_lossmodel.compile(loss=...) 一样吗?
  • @storen 是的,它会导致相同的结果。由于损失函数取决于模型,因此它并不那么干净。
  • 在更复杂的模型中,有没有办法同时使用 model.add_loss() 来处理需要内部张量的 1 个损失(例如,这里的 KL 散度)和 model.compile(loss=...) 来处理需要内部张量的 1 个损失用户传递目标y_true?例如,在修改后的 VAE 中,潜在代码 z 也用于针对目标回归。
【解决方案5】:

试试这个:

import pandas as pd
import numpy as np
import pickle
import matplotlib.pyplot as plt
from scipy import stats
import tensorflow as tf
import seaborn as sns
from pylab import rcParams
from sklearn.model_selection import train_test_split
from keras.models import Model, load_model, Sequential
from keras.layers import Input, Lambda, Dense, Dropout, Layer, Bidirectional, Embedding, Lambda, LSTM, RepeatVector, TimeDistributed, BatchNormalization, Activation, Merge
from keras.callbacks import ModelCheckpoint, TensorBoard
from keras import regularizers
from keras import backend as K
from keras import metrics
from scipy.stats import norm
from keras.utils import to_categorical
from keras import initializers
bias = bias_initializer='zeros'

from keras import objectives




np.random.seed(22)



data1 = np.array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
       1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0], dtype='int32')

data2 = np.array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
       1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0], dtype='int32')


data3 = np.array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
       1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0], dtype='int32')

#train = np.zeros(shape=(992,54))
#test = np.zeros(shape=(921,54))

train = np.zeros(shape=(300,54))
test = np.zeros(shape=(300,54))

for n, i in enumerate(train):
    if (n<=100):
        train[n] = data1
    elif (n>100 and n<=200):
        train[n] = data2
    elif(n>200):
        train[n] = data3


for n, i in enumerate(test):
    if (n<=100):
        test[n] = data1
    elif(n>100 and n<=200):
        test[n] = data2
    elif(n>200):
        test[n] = data3


batch_size = 5
original_dim = train.shape[1]

intermediate_dim45 = 45
intermediate_dim35 = 35
intermediate_dim25 = 25
intermediate_dim15 = 15
intermediate_dim10 = 10
intermediate_dim5 = 5
latent_dim = 3
epochs = 50
epsilon_std = 1.0

def sampling(args):
    z_mean, z_log_var = args
    epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim), mean=0.,
                              stddev=epsilon_std)
    return z_mean + K.exp(z_log_var / 2) * epsilon

x = Input(shape=(original_dim,), name = 'first_input_mario')

h1 = Dense(intermediate_dim45, activation='relu', name='h1')(x)
hD = Dropout(0.5)(h1)
h2 = Dense(intermediate_dim25, activation='relu', name='h2')(hD)
h3 = Dense(intermediate_dim10, activation='relu', name='h3')(h2)
h = Dense(intermediate_dim5, activation='relu', name='h')(h3) #bilo je relu
h = Dropout(0.1)(h)

z_mean = Dense(latent_dim, activation='relu')(h)
z_log_var = Dense(latent_dim, activation='relu')(h)

z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var])

decoder_h = Dense(latent_dim, activation='relu')
decoder_h1 = Dense(intermediate_dim5, activation='relu')
decoder_h2 = Dense(intermediate_dim10, activation='relu')
decoder_h3 = Dense(intermediate_dim25, activation='relu')
decoder_h4 = Dense(intermediate_dim45, activation='relu')

decoder_mean = Dense(original_dim, activation='sigmoid')


h_decoded = decoder_h(z)
h_decoded1 = decoder_h1(h_decoded)
h_decoded2 = decoder_h2(h_decoded1)
h_decoded3 = decoder_h3(h_decoded2)
h_decoded4 = decoder_h4(h_decoded3)

x_decoded_mean = decoder_mean(h_decoded4)

vae = Model(x, x_decoded_mean)


def vae_loss(x, x_decoded_mean):
    xent_loss = objectives.binary_crossentropy(x, x_decoded_mean)
    kl_loss = -0.5 * K.mean(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var))
    loss = xent_loss + kl_loss
    return loss

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

vae.fit(train, train, batch_size = batch_size, epochs=epochs, shuffle=True,
        validation_data=(test, test))


vae = Model(x, x_decoded_mean)

encoder = Model(x, z_mean)

decoder_input = Input(shape=(latent_dim,))

_h_decoded = decoder_h  (decoder_input)
_h_decoded1 = decoder_h1  (_h_decoded)
_h_decoded2 = decoder_h2  (_h_decoded1)
_h_decoded3 = decoder_h3  (_h_decoded2)
_h_decoded4 = decoder_h4  (_h_decoded3)

_x_decoded_mean = decoder_mean(_h_decoded4)
generator = Model(decoder_input, _x_decoded_mean)
generator.summary()

【讨论】:

  • 谢谢,但很遗憾您的脚本不起作用。您似乎没有定义 X_train。请编辑您的示例,以便我可以将其作为独立脚本运行。
  • 我编辑了代码并在 jupyter notebook、python 3 中尝试过。现在它可以工作了。
  • 感谢您的更新。它现在在我的机器上运行,但不幸的是,自动编码器似乎没有以有意义的方式对数字进行编码。当我从学习分布中采样时,所有“数字”看起来就像所有数字堆叠在一起并且非常相似的混合。但是,由于您的努力,我能够找出问题的可能原因。请参阅问题编辑。
猜你喜欢
  • 2020-08-30
  • 1970-01-01
  • 2020-06-08
  • 1970-01-01
  • 2018-04-28
  • 2019-12-26
  • 2020-03-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多