File size: 13,110 Bytes
f291f4a |
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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
from re import sub
import torch
import math
import tqdm
# from tqdm import tqdm
import numpy as np
import json
import time
from pynndescent import NNDescent
from sklearn.neighbors import KDTree
from sklearn.metrics import pairwise_distances
from scipy import stats as stats
def mixup_bi(model, image1, image2, label, target_cls, device, diff=0.1, max_iter=8, l_bound=0.8):
'''Get BPs based on mixup method, fast
:param model: subject model
:param image1: images, torch.Tensor of shape (N, C, H, W)
:param image2: images, torch.Tensor of shape (N, C, H, W)
:param label: int, 0-9, prediction for image1
:param target_cls: int, 0-9, prediction for image2
:param device: the device to run code, torch cpu or torch cuda
:param diff: the difference between top1 and top2 logits we define as boundary, float by default 0.1
:param max_iter: binary search iteration maximum value, int by default 8
:param verbose: whether to print verbose message, int by default 1
'''
def f(x):
# New prediction
with torch.no_grad():
x = x.to(device, dtype=torch.float)
pred_new = model(x)
conf_max = torch.max(pred_new.detach().cpu(), dim=1)[0]
conf_min = torch.min(pred_new.detach().cpu(), dim=1)[0]
normalized = (pred_new.detach().cpu() - conf_min) / (conf_max - conf_min) # min-max rescaling
return pred_new, normalized
# initialze upper and lower bound
# set a limitation to upper bound
upper = 1
lower = l_bound
successful = False
for step in range(max_iter):
# take middle point
lamb = (upper + lower) / 2
image_mix = lamb * image1 + (1 - lamb) * image2
pred_new, normalized = f(image_mix)
# Bisection method
if normalized[0, label] - normalized[0, target_cls] > 0:
# shall decrease weight on image 1
upper = lamb
else:
# shall increase weight on image 1
lower = lamb
# Stop when ...
# reach the decision boundary, and
# abs(upper-lower) < 0.1 or step>1
sorted, _ = torch.sort(normalized[0])
curr_diff = sorted[-1] - sorted[-2]
if curr_diff < diff and (upper-lower) < 0.1:
# if torch.abs(normalized[0, label] - normalized[0, target_cls]).item() < diff and (upper-lower) < 0.1:
successful = True
break
return image_mix, successful, step
def get_border_points(model, input_x, confs, predictions, device, num_adv_eg, l_bound=0.6, lambd=0.2, verbose=1):
'''Get BPs
:param model: subject model
:param input_x: images, torch.Tensor of shape (N, C, H, W)
:param confs: logits, numpy.ndarray of shape (N, class_num)
:param predictions: class prediction, numpy.ndarray of shape (N,)
:param num_adv_eg: number of adversarial examples to be generated, int
:param l_bound: lower bound to conduct mix-up attack, range (0, 1)
:param lambd: trade-off between efficiency and diversity, (0, 1)
:return adv_examples: adversarial images, torch.Tensor of shape (N, C, H, W)
'''
adv_examples = torch.tensor([]).to(device)
num_adv = 0
a = lambd
# count valid classes
valid_cls = np.unique(predictions)
valid_cls_num = len(valid_cls)
if valid_cls_num < 2:
raise Exception("Valid prediction classes less than 2!")
succ_rate = np.ones(int(valid_cls_num*(valid_cls_num-1)/2))
tot_num = np.zeros(int(valid_cls_num*(valid_cls_num-1)/2))
curr_samples = np.zeros(int(valid_cls_num*(valid_cls_num-1)/2))
# record index dictionary for query index pair
idx = 0
index_dict = dict()
for i in range(valid_cls_num):
for j in range(i+1, len(valid_cls)):
index_dict[idx] = (valid_cls[i], valid_cls[j])
idx += 1
t0 = time.time()
pbar = tqdm.tqdm(total=num_adv_eg, desc="Generating adversarial examples")
while num_adv < num_adv_eg:
idxs = np.argwhere(tot_num != 0).squeeze()
succ = curr_samples[idxs] / tot_num[idxs]
succ_rate[idxs] = succ
curr_mean = np.mean(curr_samples)
curr_rate = curr_mean - curr_samples
curr_rate[curr_rate < 0] = 0
if np.std(curr_rate) == 0:
curr_rate = 1. / len(curr_rate)
else:
curr_rate = curr_rate / (1e-4 + np.sum(curr_rate))
p = a*succ_rate + (1-a)*curr_rate
p = p/(np.sum(p))
selected = np.random.choice(range(len(curr_samples)), size=1, p=p)[0]
(cls1, cls2) = index_dict[selected]
data1_index = np.argwhere(predictions == cls1).squeeze()
data2_index = np.argwhere(predictions == cls2).squeeze()
conf1 = confs[data1_index]
conf2 = confs[data2_index]
# probability to be sampled is inversely proportional to the distance to "targeted" decision boundary
# smaller class1-class2 is preferred
pvec1 = (1 / (conf1[:, cls1] - conf1[:, cls2] + 1e-4)) / np.sum(
(1 / (conf1[:, cls1] - conf1[:, cls2] + 1e-4)))
pvec2 = (1 / (conf2[:, cls2] - conf2[:, cls1] + 1e-4)) / np.sum(
(1 / (conf2[:, cls2] - conf2[:, cls1] + 1e-4)))
image1_idx = np.random.choice(range(len(data1_index)), size=1, p=pvec1)
image2_idx = np.random.choice(range(len(data2_index)), size=1, p=pvec2)
image1 = input_x[data1_index[image1_idx]]
image2 = input_x[data2_index[image2_idx]]
attack1, successful1, _ = mixup_bi(model, image1, image2, cls1, cls2, device, l_bound=l_bound)
if successful1:
adv_examples = torch.cat((adv_examples, attack1), dim=0)
num_adv += 1
curr_samples[selected] += 1
pbar.update(1)
tot_num[selected] += 1
if num_adv < num_adv_eg:
attack2, successful2, _ = mixup_bi(model, image2, image1, cls2, cls1, device, l_bound=l_bound)
tot_num[selected] += 1
if successful2:
adv_examples = torch.cat((adv_examples, attack2), dim=0)
num_adv += 1
curr_samples[selected] += 1
pbar.update(1)
pbar.close()
t1 = time.time()
if verbose:
print('Total time {:2f}'.format(t1 - t0))
return adv_examples, curr_samples, tot_num
def batch_run(model, data, batch_size=200):
"""batch run, in case memory error"""
data = data.to(dtype=torch.float)
output = None
n_batches = max(math.ceil(len(data) / batch_size), 1)
for b in tqdm.tqdm(range(n_batches)):
r1, r2 = b * batch_size, (b + 1) * batch_size
inputs = data[r1:r2]
with torch.no_grad():
pred = model(inputs).cpu().numpy()
if output is None:
output = pred
else:
output = np.concatenate((output, pred), axis=0)
return output
def load_labelled_data_index(filename):
with open(filename, 'r') as f:
index = json.load(f)
return index
def jaccard_similarity(l1, l2):
u = np.union1d(l1,l2)
i = np.intersect1d(l1,l2)
return float(len(i)) / len(u)
def knn(data, k):
# number of trees in random projection forest
n_trees = min(64, 5 + int(round((data.shape[0]) ** 0.5 / 20.0)))
# max number of nearest neighbor iters to perform
n_iters = max(5, int(round(np.log2(data.shape[0]))))
# distance metric
metric = "euclidean"
# get nearest neighbors
nnd = NNDescent(
data,
n_neighbors=k,
metric=metric,
n_trees=n_trees,
n_iters=n_iters,
max_candidates=60,
verbose=True
)
knn_indices, knn_dists = nnd.neighbor_graph
return knn_indices, knn_dists
def hausdorff_dist(X, subset_idxs, metric="euclidean", verbose=1):
'''
Calculate the hausdorff distance of X and its subset
'''
t_s = time.time()
tree = KDTree(X[subset_idxs], metric=metric)
knn_dists, _ = tree.query(X, k=1)
hausdorff = knn_dists[:, 0].max()
t_e = time.time()
if verbose>0:
print("Calculate hausdorff distance {:.2f} for {:d}/{:d} in {:.3f} seconds...".format(hausdorff, len(subset_idxs),len(X), t_e-t_s))
return hausdorff, round(t_e-t_s,3)
def hausdorff_dist_cus(X, subset_idxs, metric="euclidean", verbose=1):
t_s = time.time()
dist = pairwise_distances(X, X[subset_idxs], metric=metric)
min_distances = np.min(dist, axis=1).reshape(-1,1)
hausdorff = np.max(min_distances)
t_e = time.time()
if verbose > 0:
print("Hausdorff distance {:.2f} for {:d}/{:d} in {:.3f} seconds...".format(hausdorff, len(subset_idxs),len(X), t_e-t_s))
print(f'mean min_dist:\t{np.mean(min_distances)}')
return hausdorff, round(t_e-t_s,3)
def is_B(preds):
"""
given N points' prediction (N, class_num), we evaluate whether they are \delta-boundary points or not
Please check the formal definition of \delta-boundary from our paper DVI
:param preds: ndarray, (N, class_num), the output of model prediction before softmax layer
:return: ndarray, (N:bool,),
"""
preds = preds + 1e-8
sort_preds = np.sort(preds)
diff = (sort_preds[:, -1] - sort_preds[:, -2]) / (sort_preds[:, -1] - sort_preds[:, 0])
is_border = np.zeros(len(diff), dtype=np.bool)
is_border[diff < 0.1] = 1
return is_border
def find_nearest(query):
"""
find the distance to the nearest neighbor in the pool
:param query: ndarray, shape (N,dim)
:param pool: ndarray (N, dim)
:return dists: ndarray (N,)
"""
# number of trees in random projection forest
n_trees = min(64, 5 + int(round((query.shape[0]) ** 0.5 / 20.0)))
# max number of nearest neighbor iters to perform
n_iters = max(5, int(round(np.log2(query.shape[0]))))
# distance metric
metric = "euclidean"
# get nearest neighbors
nnd = NNDescent(
query,
n_neighbors=2,
metric=metric,
n_trees=n_trees,
n_iters=n_iters,
max_candidates=60,
verbose=False
)
indices, distances = nnd.neighbor_graph
return indices[:, 1], distances[:, 1]
def find_neighbor_preserving_rate(prev_data, train_data, n_neighbors):
"""
neighbor preserving rate, (0, 1)
:param prev_data: ndarray, shape(N,2) low dimensional embedding from last epoch
:param train_data: ndarray, shape(N,2) low dimensional embedding from current epoch
:param n_neighbors:
:return alpha: ndarray, shape (N,)
"""
if prev_data is None:
return np.zeros(len(train_data))
# number of trees in random projection forest
n_trees = min(64, 5 + int(round((train_data.shape[0]) ** 0.5 / 20.0)))
# max number of nearest neighbor iters to perform
n_iters = max(5, int(round(np.log2(train_data.shape[0]))))
# distance metric
from pynndescent import NNDescent
# get nearest neighbors
nnd = NNDescent(
train_data,
n_neighbors=n_neighbors,
metric="euclidean",
n_trees=n_trees,
n_iters=n_iters,
max_candidates=60,
verbose=True
)
train_indices, _ = nnd.neighbor_graph
prev_nnd = NNDescent(
prev_data,
n_neighbors=n_neighbors,
metric="euclidean",
n_trees=n_trees,
n_iters=n_iters,
max_candidates=60,
verbose=True
)
prev_indices, _ = prev_nnd.neighbor_graph
temporal_pres = np.zeros(len(train_data))
for i in range(len(train_indices)):
pres = np.intersect1d(train_indices[i], prev_indices[i])
temporal_pres[i] = len(pres) / float(n_neighbors)
return temporal_pres
def kl_div(p, q):
return stats.entropy(p, q, base=2)
def js_div(p, q):
M = (p+q)/2
return .5*kl_div(p, M)+.5*kl_div(q, M)
def generate_random_trajectory(x_min, y_min, x_max, y_max, period):
xs = np.random.uniform(low=x_min, high=x_max, size=period)
ys = np.random.uniform(low=y_min, high=y_max, size=period)
trajectory = np.vstack((xs,ys)).transpose([1, 0])
return trajectory
def generate_random_trajectory_momentum(init_position, period, alpha, gamma, vx, vy):
xs = np.ones(period)
ys = np.ones(period)
xs[0] = init_position[0]
ys[0] = init_position[1]
for i in range(1, period):
v_sample = np.zeros(2)
v_sample[0] = np.random.normal(vx[i-1], 5, 1)[0]
v_sample[1] = np.random.normal(vy[i-1], 5, 1)[0]
# normalize
history_direction = np.array([xs[i-1]-xs[0], ys[i-1]-ys[0]])
if i > 1:
history_direction = history_direction/np.linalg.norm(history_direction)*np.linalg.norm(v_sample)
v = gamma*v_sample+alpha*history_direction
xs[i] = xs[i-1] + v[0]
ys[i] = ys[i-1] + v[1]
return np.vstack((xs,ys)).transpose([1, 0])
# return xs, ys
def ranking_dist(a,b):
n = len(a)
assert len(b) == n
i, j = np.meshgrid(np.arange(n), np.arange(n))
ndisordered = np.logical_or(np.logical_and(a[i] < a[j], b[i] > b[j]), np.logical_and(a[i] > a[j], b[i] < b[j])).sum()
return ndisordered / (n * (n - 1))
|