Tensorflow 的 TripletSemiHardLoss 和 TripletHardLoss 是如何实现的,如何与 Siamese Network 一起使用?

Des*_*wal 2 neural-network deep-learning conv-neural-network keras tensorflow

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

所以对于例子让我们假设: a 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...]
Run Code Online (Sandbox Code Playgroud)

而且我认为我可以获得三重损失,例如:(我认为我必须使用 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)

Run Code Online (Sandbox Code Playgroud)

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

据我所知,Semi 和 hard 是Siamese Techniques推动模型学习更多的数据生成技术类型。

我的想法:正如我在这篇文章中学到的,我认为你可以:

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

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

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

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

Mr.*_*ple 7

什么是TripletHardLoss

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

在此处输入图片说明

看到源代码tfa.losses.TripletHardLoss,我们可以看到上面的公式得到确切落实:

# 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)
Run Code Online (Sandbox Code Playgroud)

注意soft参数tfa.losses.TripletHardLoss没有用下面的公式计算普通TripletLoss

在此处输入图片说明

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

什么是TripletSemiHardLoss

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

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

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

在此处输入图片说明

p对于正数和n负数,如果 wan 找不到满足此条件的负距离,则我们使用最大的负距离代替。

正如我们在 的源代码中可以看到上面的条件过程很清楚tfa.losses.TripletSemiHardLoss,其中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,
)
Run Code Online (Sandbox Code Playgroud)

那些损失怎么用?

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

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

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]))
Run Code Online (Sandbox Code Playgroud)

输出:

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]
Run Code Online (Sandbox Code Playgroud)

如果您在使用时遇到问题,请遵循有关如何使用TripletSemiHardLossTripletHardLoss以及)的官方教程