histogram loss[1] 用來做 embedding learning,思路是最小化 negative pair 相似性比 positive pair 還大的可能性/概率,其中相似性用 embeddings 的內積表示:,按 positive/negative 分成 、 兩撥,對應文中 、 兩個集合。將 embeddings 做 L2 normalization,相似性範圍就是 。
上述的那個概率就是文中的公式 3:
其中 是 的分佈, 是 的分佈,需要估計。
Density Estimation
密度估計的思路見 [3],這裏符號儘量沿用 [1],省去 +/- 上標因爲兩者都是這麼估計。先將密度函數的極限定義式寫出來:
文中在 均勻插了 R 個點, 是組距, 是其中一個插的點,, 是 對應的累積分佈函數。於是離散地估計 (a) 式:
此處 是離散的累積分佈函數,
這可以看成直方圖:第 r 個矩形, 爲組距,分子統計落入 鄰域的點數,第一項是區間的概率和(分母的 2 是因爲對於 ,會分別在考慮 和 被考慮,相當於被算了兩次),除以組距得到密度。
由 [3] 可以看到,這種估計可以改寫成核函數的形式:
其中所用的核可以看成:。於是一種自然的擴展就是:用別的核函數。文中用了 triangular kernel[8],公式 (2) 可以改寫成:
於是公式 (1) 就是:
和 (b) 式對比着看,換這個核使得 裏帶有 ,從而可以回傳梯度。
可以驗證這個 序列是一個合法的概率分佈:易知 ,而要計算 ,考慮到對於 ,它會分別在 時貢獻 、在 時貢獻 ,所以:
Histogram Loss
最終對 (a) 式的估計就寫成文中公式 (4),即 histogram loss:
Code
- tensorflow 1.12
#import tensorflow as tf
def cos(X, Y=None):
"""C(i,j) = cos(Xi, Yj)"""
X_n = tf.math.l2_normalize(X, axis=1)
if (Y is None) or (X is Y):
return tf.matmul(X_n, tf.transpose(X_n))
Y_n = tf.math.l2_normalize(Y, axis=1)
return tf.matmul(X_n, tf.transpose(Y_n))
def sim_mat(label, label2=None):
"""S[i][j] = 1 <=> i- & j-th share at lease 1 label"""
if label2 is None:
label2 = label
return tf.cast(tf.matmul(label, tf.transpose(label2)) > 0, "float32")
def histogram_loss(X, L, R=151):
"""histogram loss
X: [n, d], feature WITHOUT L2 norm
L: [n, c], label
R: scalar, num of estimating point, same as the paper
"""
delta = 2. / (R - 1) # step
# t = (t_1, ..., t_R)
t = tf.lin_space(-1., 1., R)[:, None] # [R, 1]
# gound-truth similarity matrix
M = sim_mat(L) # [n, n]
# cosine similarity, in [-1, 1]
S = cos(X) # [n, n]
# get indices of upper triangular (without diag)
S_hat = S + 2 # shift value to [1, 3] to ensure triu > 0
S_triu = tf.linalg.band_part(S_hat, 0, -1) * (1 - tf.eye(tf.shape(S)[0]))
triu_id = tf.where(S_triu > 0)
# extract triu -> vector of [n(n - 1) / 2]
S = tf.gather_nd(S, triu_id)[None, :] # [1, n(n-1)/2]
M_pos = tf.gather_nd(M, triu_id)[None, :]
M_neg = 1 - M_pos
scaled_abs_diff = tf.math.abs(S - t) / delta # [R, n(n-1)/2]
# mask_near = tf.cast(scaled_abs_diff <= 1, "float32")
# delta_ijr = (1 - scaled_abs_diff) * mask_near
delta_ijr = tf.maximum(0, 1 - scaled_abs_diff)
def histogram(mask):
"""h = (h_1, ..., h_R)"""
sum_delta = tf.reduce_sum(delta_ijr * mask, 1) # [R]
return sum_delta / tf.maximum(1, tf.reduce_sum(mask))
h_pos = histogram(M_pos)[None, :] # [1, R]
h_neg = histogram(M_neg) # [R]
# all 1 in lower triangular (with diag)
mask_cdf = tf.linalg.band_part(tf.ones([R, R]), -1, 0)
cdf_pos = tf.reduce_sum(mask_cdf * h_pos, 1) # [R]
loss = tf.reduce_sum(h_neg * cdf_pos)
return loss