tensorflow實現triplet loss

2020.1.14 Updates

原來 tensorflow 自帶一個 triplet loss 實現,支持單標籤數據,見 [3]。可以參考寫法。

Triplet Loss

triplet loss 的形式:
L=max{0,d(xa,xp)+αd(xa,xn)}L=\max\{0, d(x_a,x_p)+\alpha-d(x_a,x_n)\}
其中 d(,)d(\cdot,\cdot) 表示一種距離,如歐氏距離
涉及三個樣本,其中 anchor 樣本 xax_a 與 positive 樣本 xpx_p 相似(有共同 label),與 negative 樣本 xnx_n 不相似(無共同 label);而且要求它們是三個不同的樣本。
目標效果是使每個樣本(的 embedding)與不相似樣本的距離至少比與相似樣本的距離大一個 margin α\alpha,即 d(xa,xn)d(xa,xp)+αd(x_a,x_n)\geq d(x_a,x_p)+\alpha

Sampling

計算此損失需要採樣若個 (xa,xp,xn)(x_a,x_p,x_n)
對於某個 anchor,positive 和 negative sample 的採樣可以 off-line 也可以 on-line。
這裏是在一個 batch 裏 on-line 地採樣,可以用到樣本最新的 embedding。

Mask & Distance

大思路是用 mask:把 batch 內所有 n3 個距離算出來,然後用 mask 篩出需要的部分。
需要用到兩種 mask:

  • 關於 index 的,因爲 (xa,xp,xn)(x_a,x_p,x_n) 中三個樣本要是不同的樣本;
  • 關於 label 的,因爲 anchor 要和 positive 相似、和 negative 不相似;

然後兩個 mask 取交集。
下面假設 label 是傳入的一個 batch 的 label 的 one-/multi-hot 向量,是 tf 的 tensor。

index mask

求一個三階張量 M,使得 Mi,j,k=1M_{i,j,k}=1 當且僅當 i,j,ki,j,k 各不相等。
單位陣 II 表示 index 相同的集合,即 I[i][j]=1I[i][j] = 1 當且僅當 i=ji=j;而 Iˉ=1I\bar I=1-I 就相反。
Iˉ\bar I 在沿 axis = 0 處升維,得到 A,A[][i][j]=1A[\cdot][i][j]=1 當且僅當 iji\neq j(即第 2、3 維下標不等);類似地沿 axis = 1,2 處升維得到 B[i][][j]B[i][\cdot][j]C[i][j][]C[i][j][\cdot],三者取交就得到 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,使得 Mi,j,k=1M_{i,j,k}=1 當且僅當 xix_ixjx_j 相似,而和 xkx_k 不相似。
先求個相似矩陣 S,Si,j=1S_{i,j}=1 當且僅當 xix_ixjx_j 相似。這可以由 label 矩陣算出來,單標籤、多標籤都行。
類似上面,Sˉ=1S\bar S=1-S,然後 S 沿 axis = 2 升維到 E[i][j][]E[i][j][\cdot]Sˉ\bar S 沿 axis = 1 升維到 F[i][][j]F[i][\cdot][j],兩者取交。

# 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,使得 Li,j,k=max{0,d(i,j)d(i,k)+α}L_{i,j,k}=\max\{0,d(i,j)-d(i,k)+\alpha\},即加 mask 前的 triplet loss。
距離自選。算出樣本兩兩之間的距離矩陣 D 之後,沿 axis = 1,2 升維得到 N[i][][j]N[i][\cdot][j]P[i][j][]P[i][j][\cdot]。可以將 P 當成 anchor 和 positive 的距離、N 當成 anchor 和 negative 的距離,於是 L=max{0,PN+α}L=max\{0,P-N+\alpha\}

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

References

  1. Triplet Loss and Online Triplet Mining in TensorFlow
  2. Tensorflow實現Triplet Loss([1] 的譯文)
  3. tf.contrib.losses.metric_learning.triplet_semihard_loss
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章