2020.1.14 Updates
原來 tensorflow 自帶一個 triplet loss 實現,支持單標籤數據,見 [3]。可以參考寫法。
Triplet Loss
triplet loss 的形式:
其中 表示一種距離,如歐氏距離。
涉及三個樣本,其中 anchor 樣本 與 positive 樣本 相似(有共同 label),與 negative 樣本 不相似(無共同 label);而且要求它們是三個不同的樣本。
目標效果是使每個樣本(的 embedding)與不相似樣本的距離至少比與相似樣本的距離大一個 margin ,即 。
Sampling
計算此損失需要採樣若個 。
對於某個 anchor,positive 和 negative sample 的採樣可以 off-line 也可以 on-line。
這裏是在一個 batch 裏 on-line 地採樣,可以用到樣本最新的 embedding。
Mask & Distance
大思路是用 mask:把 batch 內所有 n3 個距離算出來,然後用 mask 篩出需要的部分。
需要用到兩種 mask:
- 關於 index 的,因爲 中三個樣本要是不同的樣本;
- 關於 label 的,因爲 anchor 要和 positive 相似、和 negative 不相似;
然後兩個 mask 取交集。
下面假設 label
是傳入的一個 batch 的 label 的 one-/multi-hot 向量,是 tf 的 tensor。
index mask
求一個三階張量 M,使得 當且僅當 各不相等。
單位陣 表示 index 相同的集合,即 當且僅當 ;而 就相反。
在沿 axis = 0 處升維,得到 A, 當且僅當 (即第 2、3 維下標不等);類似地沿 axis = 1,2 處升維得到 、,三者取交就得到 M。
# import tensorflow as tf
def index_mask(label):
batch_size = tf.shape(label)[0]
I = tf.cast(tf.eye(batch_size), tf.bool) # 單位陣 I
I_bar = tf.logical_not(I) # 1 - I
A = tf.expand_dims(I_bar, 0) # 2, 3 維不等
B = tf.expand_dims(I_bar, 1) # 1, 3 維不等
C = tf.expand_dims(I_bar, 2) # 1, 2 維不等
M = tf.logical_and(tf.logical_and(A, B), C) # 三者同時成立
return M
similarity mask
求一個三階張量 M,使得 當且僅當 和 相似,而和 不相似。
先求個相似矩陣 S, 當且僅當 和 相似。這可以由 label 矩陣算出來,單標籤、多標籤都行。
類似上面,,然後 S 沿 axis = 2 升維到 , 沿 axis = 1 升維到 ,兩者取交。
# import tensorflow as tf
def similarity_mask(label):
S = tf.matmul(label, tf.transpose(label)) > 0 # 相似矩陣 S
S_bar = tf.logical_not(S) # 1 - S
E = tf.expand_dims(S, 2) # 1, 2 維相似
F = tf.expand_dims(S_bar, 1) # 1, 3 維不相似
M = tf.logical_and(E, F) # 兩者者同時成立
return M
distance
求三階張量 L,使得 ,即加 mask 前的 triplet loss。
距離自選。算出樣本兩兩之間的距離矩陣 D 之後,沿 axis = 1,2 升維得到 和 。可以將 P 當成 anchor 和 positive 的距離、N 當成 anchor 和 negative 的距離,於是 。
Code
對應 [1] 中的 batch all 策略
# import tensorflow as tf
def index_mask(label):
batch_size = tf.shape(label)[0]
I = tf.cast(tf.eye(batch_size), tf.bool) # 單位陣 I
I_bar = tf.logical_not(I) # 1 - I
A = tf.expand_dims(I_bar, 0) # 2, 3 維不等
B = tf.expand_dims(I_bar, 1) # 1, 3 維不等
C = tf.expand_dims(I_bar, 2) # 1, 2 維不等
M = tf.logical_and(tf.logical_and(A, B), C) # 三者同時成立
return M
def similarity_mask(label):
S = tf.matmul(label, tf.transpose(label)) > 0 # 相似矩陣 S
S_bar = tf.logical_not(S) # 1 - S
E = tf.expand_dims(S, 2) # 1, 2 維相似
F = tf.expand_dims(S_bar, 1) # 1, 3 維不相似
M = tf.logical_and(E, F) # 兩者者同時成立
return M
def distance(x):
"""某種距離"""
return
def triplet_loss(x, label, alpha):
D = distance(x)
P = tf.expand_dims(D, 2) # d(a,p)
N = tf.expand_dims(D, 1) # d(a,n)
L = tf.maximum(0.0, P - N + alpha)
Mi = index_mask(label)
Ms = similarity_mask(label)
M = tf.logical_and(Mi, Ms)
triplet_loss = tf.multiply(L, M) # 篩選
# 算平均
valid_triplets = tf.to_float(tf.greater(triplet_loss, 1e-16))
num_positive_triplets = tf.reduce_sum(valid_triplets)
mean_triplet_loss = tf.reduce_sum(triplet_loss) / (num_positive_triplets + 1e-16)
return mean_triplet_loss