Tensorflow:多个损失函数与多个训练操作

时间:2018-04-21 07:20:08

标签: python tensorflow

我正在创建一个预测多个输出(具有不同激活)的Tensorflow模型。我认为有两种方法可以做到这一点:

方法1:创建多个损失函数(每个输出一个),合并它们(使用tf.reduce_meantf.reduce_sum)并将其传递给训练操作,如下所示:

final_loss = tf.reduce_mean(loss1 + loss2)
train_op = tf.train.AdamOptimizer().minimize(final_loss)

方法2:创建多个培训操作,然后将其分组:

train_op1 = tf.train.AdamOptimizer().minimize(loss1)
train_op2 = tf.train.AdamOptimizer().minimize(loss2)
final_train_op = tf.group(train_op1 train_op2)

我的问题是一种方法是否优于另一种方法?还有第三种我不知道的方法吗?

由于

5 个答案:

答案 0 :(得分:7)

关于张量流中多任务学习的this post中清楚地证明了这两种方法之间的区别。

简而言之:

方法1 : 这被称为联合训练,因为它直接将损失加在一起,结果是针对两个损失同时进行所有梯度和更新。通常,当使用一组相同的输入功能训练多个输出时,将使用此方法。

方法2 : 这将创建两个单独的优化器,称为替代训练。当您为每个输出使用输入功能的子集时,将使用此功能。因此,当输入train_op1的特征子集时,train_op2的子图将保持不变。可以使用不同的输入功能以交替顺序调用每个优化器。

如果您使用相同的输入数据同时运行两个优化器,则与方法1的差异可能很小。

答案 1 :(得分:4)

方法1 正确,因为您只定义了一次渐变图(用于计算反向传播)。通过这种方式,您可以将单个损失函数与单个图形一起使用,以便对同一参数进行单个更新(更新会同时考虑损失的两个条款)。

第二种方法定义了2个用于计算渐变的不同图形,是错误的。 当您执行训练操作时,您将并行执行(因为您使用tf.group / tf.tuple / tf.control_dependencies)计算了训练操作。

操作将计算两种不同的损失和两组不同的更新变量。

当更新变量的那一刻到来时,你遇到了一个问题: 哪个更新操作首先执行,第一个图形或另一个图形定义的更新操作? 在任何情况下,你都放弃了一个计算,因为一个计算会覆盖另一个计算。更新中没有同步,并且计算的损失没有关系。

答案 2 :(得分:3)

我想提出一个微妙的观点,我认为以前的答案中没有提到。

如果您使用的是GradientDescentOptimizer之类的东西,这些操作将非常相似。那是因为采取梯度是线性操作,并且总和的梯度与梯度的总和相同。

但是,ADAM做一些特殊的事情:无论损失的规模如何,它都会缩放梯度,以使它们始终与您的学习率保持一致。如果您将损失乘以1000,则不会影响ADAM,因为更改将被归一化。

因此,如果您的两次损失的幅度大致相同,则不应有任何区别。如果一个比另一个大得多,则请记住,在最小化之前进行求和本质上会忽略较小的一个,而进行两个操作会花费相等的精力来最小化两者。

我个人喜欢将它们分成几部分,这使您可以更好地控制将精力集中在一种损失或另一种损失上。例如,如果这是一项多任务学习,并且一项任务比另一项任务重要,那么具有不同学习率的两个操作员就可以大致完成这项任务。

答案 3 :(得分:1)

您推荐的两种方法均正确。区别是非常细微的。主要区别在于,AdamOptimizer对于第二个解决方案中的每个损失均保留单独的梯度累加器。哪个效果更好需要实验。

答案 4 :(得分:0)

我将展示如何使用Tensorflow的功能API来实现回归模型。

在多任务学习中,我们需要一个在任务之间共享的基础网络以及每个任务的网络头:

from tensorflow.keras import layers, models, Model

def create_base_cnn(input_shape):
    model = models.Sequential()
    model.add(layers.Conv2D(filters=32, kernel_size=(3, 3), padding="same", activation="relu", input_shape=input_shape))
    model.add(layers.Conv2D(filters=32, kernel_size=(3, 3), padding="same", activation="relu"))
    # put more layers if you like
    model.add(layers.Dense(128, activation="relu"))
    return model

def create_head(input_shape, name):
    model = models.Sequential(name=name)
    model.add(layers.Dense(128, activation="relu", input_shape=input_shape))
    model.add(layers.Dense(64, activation="relu"))
    # put more layers if you like
    model.add(layers.Dense(1, activation="linear"))
    return model

我们现在可以将基本模型与头部组合起来。

# Create the model.
input_shape = (240, 180, 1)
base_model = create_base_cnn(input_shape)
head_model1 = create_head((128,), name="head1")
head_model2 = create_head((128,), name="head2")
model_input = layers.Input(shape=input_shape)

# Combine base with heads (using TF's functional API)
features = base_model(model_input)
model_output1 = head_model1(features)
model_output2 = head_model2(features)
model = Model(inputs=model_input, outputs=[model_output1, model_output2])

最后,要训练模型,我们可以按名称引用不同的输出(在我的情况下:“ head1”和“ head2”)。我们可以在损失函数中为每个头部的重量定义一个超参数:

HEAD1_WEIGHT = 0.4
HEAD2_WEIGHT = 0.6
model.compile(
    optimizer="Adam",
    loss={"head1": "mse", "head2": "mse"},
    loss_weights={"head1": HEAD1_WEIGHT, "head2": HEAD2_WEIGHT},
    metrics={"head1": ["mae"], "head2": ["mae"]}
)
model.fit(dataset_training, validation_data, epochs)
相关问题