forked from wangz10/contrastive_loss
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlosses.py
150 lines (130 loc) · 5.05 KB
/
losses.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import numpy as np
import tensorflow as tf
import tensorflow_addons as tfa
def pdist_euclidean(A):
# Euclidean pdist
# https://stackoverflow.com/questions/37009647/compute-pairwise-distance-in-a-batch-without-replicating-tensor-in-tensorflow
r = tf.reduce_sum(A*A, 1)
# turn r into column vector
r = tf.reshape(r, [-1, 1])
D = r - 2*tf.matmul(A, tf.transpose(A)) + tf.transpose(r)
return tf.sqrt(D)
def square_to_vec(D):
'''Convert a squared form pdist matrix to vector form.
'''
n = D.shape[0]
triu_idx = np.triu_indices(n, k=1)
d_vec = tf.gather_nd(D, list(zip(triu_idx[0], triu_idx[1])))
return d_vec
def get_contrast_batch_labels(y):
'''
Make contrast labels by taking all the pairwise in y
y: tensor with shape: (batch_size, )
returns:
tensor with shape: (batch_size * (batch_size-1) // 2, )
'''
y_col_vec = tf.reshape(tf.cast(y, tf.float32), [-1, 1])
D_y = pdist_euclidean(y_col_vec)
d_y = square_to_vec(D_y)
y_contrasts = tf.cast(d_y == 0, tf.int32)
return y_contrasts
def get_contrast_batch_labels_regression(y):
'''
Make contrast labels for regression by taking all the pairwise in y
y: tensor with shape: (batch_size, )
returns:
tensor with shape: (batch_size * (batch_size-1) // 2, )
'''
raise NotImplementedError
def max_margin_contrastive_loss(z, y, margin=1.0, metric='euclidean'):
'''
Wrapper for the maximum margin contrastive loss (Hadsell et al. 2006)
`tfa.losses.contrastive_loss`
Args:
z: hidden vector of shape [bsz, n_features].
y: ground truth of shape [bsz].
metric: one of ('euclidean', 'cosine')
'''
# compute pair-wise distance matrix
if metric == 'euclidean':
D = pdist_euclidean(z)
elif metric == 'cosine':
D = 1 - tf.matmul(z, z, transpose_a=False, transpose_b=True)
# convert squareform matrix to vector form
d_vec = square_to_vec(D)
# make contrastive labels
y_contrasts = get_contrast_batch_labels(y)
loss = tfa.losses.contrastive_loss(y_contrasts, d_vec, margin=margin)
# exploding/varnishing gradients on large batch?
return tf.reduce_mean(loss)
def multiclass_npairs_loss(z, y):
'''
Wrapper for the multiclass N-pair loss (Sohn 2016)
`tfa.losses.npairs_loss`
Args:
z: hidden vector of shape [bsz, n_features].
y: ground truth of shape [bsz].
'''
# cosine similarity matrix
S = tf.matmul(z, z, transpose_a=False, transpose_b=True)
loss = tfa.losses.npairs_loss(y, S)
return loss
def triplet_loss(z, y, margin=1.0, kind='hard'):
'''
Wrapper for the triplet losses
`tfa.losses.triplet_hard_loss` and `tfa.losses.triplet_semihard_loss`
Args:
z: hidden vector of shape [bsz, n_features], assumes it is l2-normalized.
y: ground truth of shape [bsz].
'''
if kind == 'hard':
loss = tfa.losses.triplet_hard_loss(y, z, margin=margin, soft=False)
elif kind == 'soft':
loss = tfa.losses.triplet_hard_loss(y, z, margin=margin, soft=True)
elif kind == 'semihard':
loss = tfa.losses.triplet_semihard_loss(y, z, margin=margin)
return loss
def supervised_nt_xent_loss(z, y, temperature=0.5, base_temperature=0.07):
'''
Supervised normalized temperature-scaled cross entropy loss.
A variant of Multi-class N-pair Loss from (Sohn 2016)
Later used in SimCLR (Chen et al. 2020, Khosla et al. 2020).
Implementation modified from:
- https://github.com/google-research/simclr/blob/master/objective.py
- https://github.com/HobbitLong/SupContrast/blob/master/losses.py
Args:
z: hidden vector of shape [bsz, n_features].
y: ground truth of shape [bsz].
'''
batch_size = tf.shape(z)[0]
contrast_count = 1
anchor_count = contrast_count
y = tf.expand_dims(y, -1)
# mask: contrastive mask of shape [bsz, bsz], mask_{i,j}=1 if sample j
# has the same class as sample i. Can be asymmetric.
mask = tf.cast(tf.equal(y, tf.transpose(y)), tf.float32)
anchor_dot_contrast = tf.divide(
tf.matmul(z, tf.transpose(z)),
temperature
)
# # for numerical stability
logits_max = tf.reduce_max(anchor_dot_contrast, axis=1, keepdims=True)
logits = anchor_dot_contrast - logits_max
# # tile mask
logits_mask = tf.ones_like(mask) - tf.eye(batch_size)
mask = mask * logits_mask
# compute log_prob
exp_logits = tf.exp(logits) * logits_mask
log_prob = logits - \
tf.math.log(tf.reduce_sum(exp_logits, axis=1, keepdims=True))
# compute mean of log-likelihood over positive
# this may introduce NaNs due to zero division,
# when a class only has one example in the batch
mask_sum = tf.reduce_sum(mask, axis=1)
mean_log_prob_pos = tf.reduce_sum(
mask * log_prob, axis=1)[mask_sum > 0] / mask_sum[mask_sum > 0]
# loss
loss = -(temperature / base_temperature) * mean_log_prob_pos
# loss = tf.reduce_mean(tf.reshape(loss, [anchor_count, batch_size]))
loss = tf.reduce_mean(loss)
return loss