Hope is a dangerous thing, but I have it.


【语言特性】tf.keras多输出和自定义loss

基础Loss

   tf.keras中自带了很多loss函数,比如回归问题的MSE和分类问题的交叉熵等,通常我们会在model.compile中设置,如下代码所示:

model.compile(optimizer='adam', loss='mean_squared_error', metrics=[])

多个输出,loss计算独立

   但是最近做的工作是一个多任务的问题,需要计算多个输出的loss,然后将它们加起来求平均作为最终的Loss,由于多个输出不可以合并,那用上面的方法就不合适了。tf.keras的模型是函数式的,有输入,也有输出,并且输出也可以是多个,而loss函数也可以根据对应的输出计算出来。比如我的模型有两个输出output1和output2,需要对这两个输出都计算一个MSE,并以平均值作为loss,那么model初始化和model.compile函数可以改为如下形式:

model = tf.keras.Model(inputs=x, outputs=[output1, output2])

model.compile(optimizer='adam', loss=['mean_squared_error', 'mean_squared_error'], loss_weights=[0.5, 0.5], metrics=[])

history = model.fit(x, [y1, y2], epochs=10, batch_size=32, verbose=1, validation_data=(vx, [vy1, vy2])

   之前由于传入对应的y时只传入了一个y,所以报了如下的错:

ValueError: Error when checking model target: the list of Numpy arrays that you are passing to your model is not the size the model expected. Expected to see 2 array(s), but instead got the following list of 1 arrays: [array([[2],
       [5],
       [4],
       ...,
       [5],
       [5],
       [3]])]...

   后来发现是因为传入的标签需要和输出一一对应计算Loss,即y1与output1对应,y2与output2对应,即使我output1和output2都是和y1计算loss,y1也应该传入两次。所以说也可以自定义Loss为一个函数形式(下为一个MAEloss):

def lossFunc(y_true, y_pred):
    return tf.reduce_mean(tf.abs(y_true - y_pred))

   然后将model.compile中loss的列表改为:

model.compile(optimizer='adam', loss=[lossFunc, 'mean_squared_error'], loss_weights=[0.5, 0.5], metrics=[])

多个输出, loss计算不独立

   上面那种方法的计算要求虽然有多个输出和多个loss,但是每个输出的Loss计算是只依赖于当前的输出,不可以用其他的输出,但是这在有些时候是不满足的。比如我在建模不确定性时,网络会输出当前结果的不确定性,需要将它加入到结果计算的MSE的loss函数中,那这用上面的方法就不行了。后来参考:Keras中自定义复杂的loss函数,将loss的计算添加到网络中,作为结果输出,并在loss的计算中定义loss为y_pred即可,代码如下:

  1. 首先是计算有不确定性的loss:
loss = tf.exp(-1 * vx) * tf.square(shift_prediction - ratings) + vx
  1. 然后修改model初始化和model.compile
model = tf.keras.Model(inputs=[reviews, ratings], outputs=[prediction, loss])
model.compile(optimizer='adam', loss=[lambda y_true, y_pred: 0.0, lambda y_true, y_pred: tf.reduce_mean(y_pred)], loss_weights=[0.0, 1], metrics=[])
  1. 修改model.fit
       因为我要输出prediction来进行进一步指标的计算,所以我用了如上的方法。在这个过程中,我有报一个维度不匹配的错,但是将loss计算的过程中的tf.reduce_mean移到model.compile中就可以了。

其他

   在计算多个任务loss的时候会出现一种情况:

loss: 0.8164 - dense_2_loss: 0.7369 - tf_op_layer_add_1_loss: 0.6580

   就是即使权重是0.5和0.5,最后的loss也大于我的子任务的loss,这是因为loss的计算中有加入l2正则化项来避免过拟合。
   而且在loss或者loss_weight中用字典指定损失函数或者损失的权重,key值的名字不是随便取的,要根据loss的名字,不知道的话可以run得看看。