【问题标题】:How does the Tensorflow's TripletSemiHardLoss and TripletHardLoss and how to use with Siamese Network?Tensorflow 的 TripletSemiHardLoss 和 TripletHardLoss 如何以及如何与 Siamese Network 一起使用?
【发布时间】:2021-01-05 12:47:47
【问题描述】:

据我所知,Triplet Loss 是一个损失函数,它减少了锚点和正数之间的距离,但减少了锚点和负数之间的距离。此外,还添加了一个边距。

例如,让我们假设:Siamese Network,它提供嵌入:

anchor_output = [1,2,3,4,5...] # embedding given by the CNN model
positive_output = [1,2,3,4,4...]
negative_output= [53,43,33,23,13...]

我认为我可以得到三元组损失,例如:(我认为我必须使用 Lambda 层左右将其作为损失)

# calculate triplet loss
d_pos = tf.reduce_sum(tf.square(anchor_output - positive_output), 1)
d_neg = tf.reduce_sum(tf.square(anchor_output - negative_output), 1)

loss = tf.maximum(0., margin + d_pos - d_neg)
loss = tf.reduce_mean(loss)

那么到底是什么: tfa.losses.TripletHardLosstfa.losses.TripletSemiHardLoss

据我所知,Semi 和 hard 是 Siamese Techniques 的数据生成技术类型,它们推动模型了解更多信息。

我的想法:正如我在This Post 学到的,我认为你可以这样做:

  1. 生成一个批次,比如 3 张图片,并制作一对 3 张具有 27 图像的图片
  2. 丢弃每个 无效 对(所有 i、j、k 都应该是唯一的)。剩余批次B
  3. 批量获取每对的嵌入B

所以我认为HardTripletLoss 每批只考虑那些具有最大锚正距离和最低锚负距离的3张图像。

对于Semi Hard,我认为它丢弃了距离为0的每个图像对计算的所有损失。

如果没有,请有人纠正我并告诉我如何使用这些。 (我知道我们可以在 model.complie() 中使用它,但我的问题不同。

【问题讨论】:

    标签: tensorflow keras deep-learning neural-network conv-neural-network


    【解决方案1】:

    什么是TripletHardLoss

    这个损失遵循普通的TripletLoss形式,但是在计算损失时使用了最大正距离和最小负距离加上批次内的边距常数,我们可以在公式中看到:

    查看tfa.losses.TripletHardLosssource code 我们可以看到上面的公式已经完全实现了:

    # Build pairwise binary adjacency matrix.
    adjacency = tf.math.equal(labels, tf.transpose(labels))
    # Invert so we can select negatives only.
    adjacency_not = tf.math.logical_not(adjacency)
    
    adjacency_not = tf.cast(adjacency_not, dtype=tf.dtypes.float32)
    # hard negatives: smallest D_an.
    hard_negatives = _masked_minimum(pdist_matrix, adjacency_not)
    
    batch_size = tf.size(labels)
    
    adjacency = tf.cast(adjacency, dtype=tf.dtypes.float32)
    
    mask_positives = tf.cast(adjacency, dtype=tf.dtypes.float32) - tf.linalg.diag(
        tf.ones([batch_size])
    )
    
    # hard positives: largest D_ap.
    hard_positives = _masked_maximum(pdist_matrix, mask_positives)
    
    if soft:
        triplet_loss = tf.math.log1p(tf.math.exp(hard_positives - hard_negatives))
    else:
        triplet_loss = tf.maximum(hard_positives - hard_negatives + margin, 0.0)
    
    # Get final mean triplet loss
    triplet_loss = tf.reduce_mean(triplet_loss)
    

    注意tfa.losses.TripletHardLoss中的soft参数不是使用下面的公式来计算普通的TripletLoss

    因为我们在上面的源代码中可以看到,它仍然使用最大正距离和最小负距离,它决定是否使用软边距

    TripletSemiHardLoss 是什么?

    这种损失也遵循普通的TripletLoss形式,正距离与普通TripletLoss相同,负距离使用半硬负

    其中的最小负距离至少大于 正距离加上边距常数,如果没有这样的负数 存在,使用最大的负距离代替。

    即我们首先要找到满足以下条件的负距离:

    p 为正,n 为负,如果 wan 找不到满足此条件的负距离,则我们使用最大的负距离。

    正如我们在tfa.losses.TripletSemiHardLosssource code 中看到的上述条件过程,其中negatives_outside 是满足此条件的距离,negatives_inside 是最大负距离:

    # Build pairwise binary adjacency matrix.
    adjacency = tf.math.equal(labels, tf.transpose(labels))
    # Invert so we can select negatives only.
    adjacency_not = tf.math.logical_not(adjacency)
    
    batch_size = tf.size(labels)
    
    # Compute the mask.
    pdist_matrix_tile = tf.tile(pdist_matrix, [batch_size, 1])
    mask = tf.math.logical_and(
        tf.tile(adjacency_not, [batch_size, 1]),
        tf.math.greater(
            pdist_matrix_tile, tf.reshape(tf.transpose(pdist_matrix), [-1, 1])
        ),
    )
    mask_final = tf.reshape(
        tf.math.greater(
            tf.math.reduce_sum(
                tf.cast(mask, dtype=tf.dtypes.float32), 1, keepdims=True
            ),
            0.0,
        ),
        [batch_size, batch_size],
    )
    mask_final = tf.transpose(mask_final)
    
    adjacency_not = tf.cast(adjacency_not, dtype=tf.dtypes.float32)
    mask = tf.cast(mask, dtype=tf.dtypes.float32)
    
    # negatives_outside: smallest D_an where D_an > D_ap.
    negatives_outside = tf.reshape(
        _masked_minimum(pdist_matrix_tile, mask), [batch_size, batch_size]
    )
    negatives_outside = tf.transpose(negatives_outside)
    
    # negatives_inside: largest D_an.
    negatives_inside = tf.tile(
        _masked_maximum(pdist_matrix, adjacency_not), [1, batch_size]
    )
    semi_hard_negatives = tf.where(mask_final, negatives_outside, negatives_inside)
    
    loss_mat = tf.math.add(margin, pdist_matrix - semi_hard_negatives)
    
    mask_positives = tf.cast(adjacency, dtype=tf.dtypes.float32) - tf.linalg.diag(
        tf.ones([batch_size])
    )
    
    # In lifted-struct, the authors multiply 0.5 for upper triangular
    #   in semihard, they take all positive pairs except the diagonal.
    num_positives = tf.math.reduce_sum(mask_positives)
    
    triplet_loss = tf.math.truediv(
        tf.math.reduce_sum(
            tf.math.maximum(tf.math.multiply(loss_mat, mask_positives), 0.0)
        ),
        num_positives,
    )
    

    如何使用这些损失?

    两种损失都期望 y_true 以具有多类整数标签的形状 [batch_size] 的一维整数 Tensor 形式提供。并且嵌入 y_pred 必须是 l2 归一化嵌入向量的二维浮点 Tensor

    准备输入和标签的示例代码:

    import tensorflow as tf
    import tensorflow_addons as tfa
    import tensorflow_datasets as tfds
    
    def _normalize_img(img, label):
        img = tf.cast(img, tf.float32) / 255.
        return (img, label)
    
    train_dataset, test_dataset = tfds.load(name="mnist", split=['train', 'test'], as_supervised=True)
    
    # Build your input pipelines
    train_dataset = train_dataset.shuffle(1024).batch(16)
    train_dataset = train_dataset.map(_normalize_img)
    
    # Take one batch of data
    for data in train_dataset.take(1):
        print("Batch of images shape:\n{}\nBatch of labels:\n{}\n".format(data[0].shape, data[1]))
    

    输出:

    Batch of images shape:
    (16, 28, 28, 1)
    Batch of labels:
    [8 4 0 3 2 4 5 1 0 5 7 0 2 6 4 9]
    

    如果您在使用时遇到问题,请关注official tutorial about how to using TripletSemiHardLoss (TripletHardLoss as well) in general

    【讨论】:

    • 非常感谢先生。这实际上是实际问题。您认为我对这些tf 损失的理解正确吗?我的意思是他们正在实施Online Triplet Mining 对吗?我们甚至不必选择三元组,而损失会照顾到这一点。对吗?
    • 我在看这个例子时问了这个问题。但问题是我看不到数据的结构。我的意思是使用train_dataset = train_dataset.shuffle(1024).batch(32) 创建批处理,然后使用train_dataset = train_dataset.map(_normalize_img) 映射到函数。我看不到数据是如何存储的。你能告诉我数据是如何构成的吗?或者我可以看到trainlabels和`test等内部结构的方法
    • 我更新了答案,现在您可以看到您应该提供的数据结构,您对TripletHardLoss 的理解是正确的,但TripletSemiHardLoss 是不正确的,请查看我的答案中的引用semi-hard negative 的定义,如果你关心用法,那么你可能不想考虑太多细节,比如必须选择三元组,因为这些损失会处理它
    • @Deshwal 如果您仍有疑问,请与我联系
    • 非常感谢!意味深长。这是非常详细的解释。您能否将一个虚拟代码发布到另一个答案中,以获得这个损失的虚拟resnet Siamese,以便我可以奖励您赏金。我知道如何创建数据集并使用Triplet Loss
    猜你喜欢
    • 2017-08-25
    • 2021-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多