-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathutils.py
123 lines (104 loc) · 4.47 KB
/
utils.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
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
import torch.nn.functional as F
def cosine(x,w):
# x, w shape: [B, d], where B = batch size, d = feature dim.
x_norm = F.normalize(x,dim=1)
w_norm = F.normalize(w,dim=1)
cos_sim = torch.mm(x_norm, w_norm.T).clamp(-1, 1)
return cos_sim
def NAC(sim,k=16,s=1):
"""
Neighborhood Aware Cosine (NAC) matcher
args:
sim: cosine similarity
k: k for kNN
s: scale (=1/T) # In the paper scale is not used (no difference)
returns:
conf, pred : confidence and predicted class of shape [B,]
"""
logit, pred = sim.topk(k,dim=1) # logit, label: [B,k]
conf = (logit*s).softmax(1) # norm. scale. >> use largest as conf.
return conf[:,0],pred[:,0] # return Top-1 confidence & prediction
def compute_dir_far(Gfeat, Glabel, Pfeat, Plabel, matcher="cos", nac_s=1, nac_k=16):
num_cls = Plabel[-1].item()
temp = torch.zeros(num_cls, Gfeat.size(1))
for i in range(num_cls):
mask = Glabel.eq(i)
temp[i] = Gfeat[mask].mean(dim=0) # make 1x512 vector
Gfeat = temp.clone()
num_cls = Plabel[-1].item()
Umask = Plabel.eq(num_cls)
Klabel = Plabel[~Umask]
Kfeat = Pfeat[~Umask]
Ufeat = Pfeat[Umask]
# compute cosine similarity
Kcos = cosine(Kfeat, Gfeat)
Ucos = cosine(Ufeat, Gfeat)
# get prediction & confidence
if matcher == "cos":
Kconf, Kidx = Kcos.max(1)
Uconf, _ = Ucos.max(1)
elif matcher == "NAC":
Kconf, Kidx = NAC(Kcos, k=nac_k, s=nac_s)
Uconf, _ = NAC(Ucos, k=nac_k, s=nac_s)
corr_mask = Kidx.eq(Klabel)
dir_far_tensor = torch.zeros(1000, 3) # intervals: 1000
for i, th in enumerate(torch.linspace(Uconf.min(), Uconf.max(), 1000)):
mask = (corr_mask) & (Kconf > th)
dir = torch.sum(mask).item() / Kcos.size(0)
far = torch.sum(Uconf > th).item() / Ucos.size(0)
dir_far_tensor[i] = torch.FloatTensor([th, dir, far]) # [threshold, DIR, FAR] for each row
return dir_far_tensor
def dir_at_far(dir_far_tensor,far):
# deal with exceptions: there can be multiple thresholds that meets the given FAR (e.g., FAR=1.000)
# if so, we must choose maximum DIR value among those cases
abs_diff = torch.abs(dir_far_tensor[:,2]-far)
minval = abs_diff.min()
mask = abs_diff.eq(minval)
dir_far = dir_far_tensor[mask]
dir = dir_far[:,1].max().item()
return dir
# area under DIR@FAR curve
def AUC(dir_far_tensor):
auc = 0
eps = 1e-5
for i in range(dir_far_tensor.size(0)-1):
if dir_far_tensor[i,1].ge(eps) and dir_far_tensor[i,2].ge(eps)\
and dir_far_tensor[i+1,1].ge(eps) and dir_far_tensor[i+1,2].ge(eps):
height = (dir_far_tensor[i,1] + dir_far_tensor[i+1,1])/2
width = torch.abs(dir_far_tensor[i,2] - dir_far_tensor[i+1,2])
auc += (height*width).item()
return auc
def save_dir_far_curve(Gfeat, Glabel, Pfeat, Plabel, save_dir):
cos_tensor = compute_dir_far(Gfeat, Glabel, Pfeat, Plabel, matcher='cos')
nac_tensor = compute_dir_far(Gfeat, Glabel, Pfeat, Plabel, matcher='NAC')
cos_auc = AUC(cos_tensor)
nac_auc = AUC(nac_tensor)
fig,ax = plt.subplots(1,1)
ax.plot(cos_tensor[:,2], cos_tensor[:,1])
ax.plot(nac_tensor[:,2], nac_tensor[:,1])
ax.set_xscale('log')
ax.set_xlabel('FAR')
ax.set_ylabel('DIR')
ax.legend(['cos-AUC: {:.3f}'.format(cos_auc),
'NAC-AUC: {:.3f}'.format(nac_auc)])
ax.grid()
fig.savefig(save_dir+'/DIR_FAR_curve.png', bbox_inches='tight')
def save_dir_far_excel(Gfeat, Glabel, Pfeat, Plabel, save_dir):
cos_tensor = compute_dir_far(Gfeat, Glabel, Pfeat, Plabel, matcher='cos')
nac_tensor = compute_dir_far(Gfeat, Glabel, Pfeat, Plabel, matcher='NAC')
cos_list, nac_list = [], []
for far in [0.001, 0.01, 0.1, 1.0]:
cos_list.append('{:.2f}%'.format(dir_at_far(cos_tensor, far) * 100))
nac_list.append('{:.2f}%'.format(dir_at_far(nac_tensor, far) * 100))
cos_list = np.array(cos_list).reshape(1,4)
nac_list = np.array(nac_list).reshape(1,4)
dir_far = np.concatenate((cos_list, nac_list),axis=0)
# save as excel
columns = ['{:.1f}'.format(far) for far in [0.1, 1, 10, 100]]
index = ['cos', 'NAC']
df = pd.DataFrame(dir_far, index=index, columns=columns)
df.to_excel(save_dir+'/DIR_FAR.xls')