Keras 自定义loss函数 focal loss + triplet loss

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Keras 自定义loss函数 focal loss + triplet loss相关的知识,希望对你有一定的参考价值。

参考技术A 上一节中已经阐述清楚了,keras.Model的输入输出与loss的关系。

https://spaces.ac.cn/archives/4493/comment-page-1#comments
非常简单,其实和官方写的方法一样。比如MSE:

注意的是,损失函数def mean_squared_error(y_true, y_pred)中的两个参数是固定的,由Keras自动注入。第一个参数来自于model.fit(x=[],y=[])中的y中的第n个,代表的是真实标签。第二个参数来自于推理后model.outputs相应位置的输出。
同时,model.compile()方法中loss传入的是方法体名称,非方法的return。

https://github.com/umbertogriffo/focal-loss-keras/blob/master/losses.py

为了传入超参数,使用了python的wrapper模式构建函数,函数实际返回的是内部函数的名称,符合上述定义。

https://stackoverflow.com/questions/53996020/keras-model-with-tf-contrib-losses-metric-learning-triplet-semihard-loss-asserti
https://github.com/rsalesc/TCC/blob/master/scpd/tf/keras/common.py

由于triplet loss的输入比较特殊,是label(非one-hot格式)与嵌入层向量,因此,对应的,我们在keras的数据输入阶段,提供的第二个label就得是非one-hot格式。同时,model构造中得定义嵌入层,并使用L2正则化,且作为model的一个output以方便loss中调用。

实例中,定义模型时,我们分开定义嵌入层的logits与激活函数,以提取出来嵌入层的值。

当然,在输入数据的生成器中,也必须每次:
yield img,[one_hot_label, label] 以对应。

之后即可构造自定义的triplet loss func:

最后在compile中调用即可:

网易云课堂-吴恩达深度学习的triplet loss章节
https://blog.csdn.net/weixin_40400177/article/details/105213578

https://blog.csdn.net/qq_36387683/article/details/83583099
https://zhuanlan.zhihu.com/p/121763855

Easy Triplets 显然不应加入训练,因为它的损失为0,加在loss里面会拉低loss的平均值。Hard Triplets 和 Semi-Hard Triplets 的选择则见仁见智,针对不同的任务需求,可以只选择Semi-Hard Triplets或者Hard Triplets,也可以两者混用。

如图中所示,其实最难分类的是
semi-hard triplets:d(a,p) < d(a,n) < d(a,p) + margin
我们试图找出这样的图片对来加以训练。

可以使用离线学习,每次训练先找到难分类的图片对,然后喂入网络,但是这样很麻烦,且网络结构同样不好设计。因此使用在线挖掘,即每次在一个batch即B个特征向量中,去挖掘出(a,p)和最难分类的(a,n)来计算loss并反向传播。

官方API:

官方解释的很清楚了,就是想让处于semi-hard区域的最小的d(a,n)尽量去远离>d(a,p)+margin,而由于该(a,n)处于semi-hard区域因此该d(a,n)必须至少>d(a,p)。若找不到这样的(a,n),则表明可能(a,n)比起(a,p)更小,因此使用最大的(a,n)代替。

用条件在keras中实现自定义丢失函数

我需要一些keras损失功能的帮助。我一直在使用Tensorflow后端在keras上实现自定义丢失功能。

我已经在numpy中实现了自定义丢失函数,但如果它可以转换为keras损失函数则会很棒。 loss函数采用数据帧和一系列用户id。如果user_id不同,则相同user_id的欧几里德距离为正和负。该函数返回数据帧的标量距离的总和。

def custom_loss_numpy (encodings, user_id):
# user_id: a pandas series of users
# encodings: a pandas dataframe of encodings

    batch_dist = 0

    for i in range(len(user_id)):
         first_row = encodings.iloc[i,:].values
         first_user = user_id[i]

         for j in range(i+1, len(user_id)):
              second_user = user_id[j]
              second_row = encodings.iloc[j,:].values

        # compute distance: if the users are same then Euclidean distance is positive otherwise negative.
            if first_user == second_user:
                tmp_dist = np.linalg.norm(first_row - second_row)
            else:
                tmp_dist = -np.linalg.norm(first_row - second_row)

            batch_dist += tmp_dist

    return batch_dist

我试图实现keras损失功能。我从y_true和y_pred张量对象中提取了numpy数组。

def custom_loss_keras(y_true, y_pred):
    # session of my program
    sess = tf_session.TF_Session().get()

    with sess.as_default():
        array_pred = y_pred.eval()
        print(array_pred)

但是我收到以下错误。

tensorflow.python.framework.errors_impl.InvalidArgumentError: You must feed a value for placeholder tensor 'dense_1_input' with dtype float and shape [?,102]
 [[Node: dense_1_input = Placeholder[dtype=DT_FLOAT, shape=[?,102], _device="/job:localhost/replica:0/task:0/device:CPU:0"]()]]

任何形式的帮助将非常感激。

答案

首先,不可能在Keras损失函数中“从y_truey_pred中提取numpy数组”。您必须使用Keras后端功能(或TF功能)来操作张量来计算损耗。

换句话说,最好不要使用if-else和循环来考虑计算损失的“矢量化”方法。

您的损失函数可以通过以下步骤计算:

  1. encodings中的所有矢量对之间生成成对欧几里德距离的矩阵。
  2. 生成一个矩阵I,如果I_ij,其元素user_i == user_j为1,如果user_i != user_j,则为-1。
  3. 元素方式乘以两个矩阵,并总结元素以获得最终损失。

这是一个实现:

def custom_loss_keras(user_id, encodings):
    # calculate pairwise Euclidean distance matrix
    pairwise_diff = K.expand_dims(encodings, 0) - K.expand_dims(encodings, 1)
    pairwise_squared_distance = K.sum(K.square(pairwise_diff), axis=-1)

    # add a small number before taking K.sqrt for numerical safety
    # (K.sqrt(0) sometimes becomes nan)
    pairwise_distance = K.sqrt(pairwise_squared_distance + K.epsilon())

    # this will be a pairwise matrix of True and False, with shape (batch_size, batch_size)
    pairwise_equal = K.equal(K.expand_dims(user_id, 0), K.expand_dims(user_id, 1))

    # convert True and False to 1 and -1
    pos_neg = K.cast(pairwise_equal, K.floatx()) * 2 - 1

    # divide by 2 to match the output of `custom_loss_numpy`, but it's not really necessary
    return K.sum(pairwise_distance * pos_neg, axis=-1) / 2

我假设user_id是上面代码中的整数。这里的技巧是使用K.expand_dims来实现成对操作。乍一看可能有点难以理解,但它非常有用。

它应该给出与custom_loss_numpy相同的损失值(由于K.epsilon()会有一点点差异):

encodings = np.random.rand(32, 10)
user_id = np.random.randint(10, size=32)

print(K.eval(custom_loss_keras(K.variable(user_id), K.variable(encodings))).sum())
-478.4245

print(custom_loss_numpy(pd.DataFrame(encodings), pd.Series(user_id)))
-478.42953553795815

我在损失函数中犯了一个错误。

当此函数用于训练时,由于Keras自动将y_true更改为至少2D,因此参数user_id不再是1D张量。它的形状将是(batch_size, 1)

要使用此功能,必须删除额外的轴:

def custom_loss_keras(user_id, encodings):
    pairwise_diff = K.expand_dims(encodings, 0) - K.expand_dims(encodings, 1)
    pairwise_squared_distance = K.sum(K.square(pairwise_diff), axis=-1)
    pairwise_distance = K.sqrt(pairwise_squared_distance + K.epsilon())

    user_id = K.squeeze(user_id, axis=1)  # remove the axis added by Keras
    pairwise_equal = K.equal(K.expand_dims(user_id, 0), K.expand_dims(user_id, 1))

    pos_neg = K.cast(pairwise_equal, K.floatx()) * 2 - 1
    return K.sum(pairwise_distance * pos_neg, axis=-1) / 2
另一答案

在Keras中实现参数化自定义丢失功能有两个步骤。首先,编写系数/度量的方法。其次,编写一个包装函数来按照Keras需要的方式格式化事物。

  1. 实际上使用Keras后端而不是tensorflow直接用于简单的自定义丢失功能(如DICE)更加清晰。以下是以这种方式实现的系数示例: import keras.backend as K def dice_coef(y_true, y_pred, smooth, thresh): y_pred = y_pred > thresh y_true_f = K.flatten(y_true) y_pred_f = K.flatten(y_pred) intersection = K.sum(y_true_f * y_pred_f) return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)
  1. 现在是棘手的部分。 Keras损失函数必须仅将(y_true, y_pred)作为参数。所以我们需要一个单独的函数来返回另一个函数: def dice_loss(smooth, thresh): def dice(y_true, y_pred) return -dice_coef(y_true, y_pred, smooth, thresh) return dice

最后,你可以在Keras compile中使用它:

# build model 
model = my_model()
# get the loss function
model_dice = dice_loss(smooth=1e-5, thresh=0.5)
# compile model
model.compile(loss=model_dice)

以上是关于Keras 自定义loss函数 focal loss + triplet loss的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Keras 中创建一个依赖于纪元数的损失函数参数?

Keras 中的自定义损失函数 - 遍历 TensorFlow

交叉熵损失函数和focal loss

用条件在keras中实现自定义丢失函数

Keras中带有权重的自定义损失函数

Focal Loss 的理解