|
from re import sub |
|
import torch |
|
import math |
|
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): |
|
|
|
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) |
|
return pred_new, normalized |
|
|
|
|
|
|
|
upper = 1 |
|
lower = l_bound |
|
successful = False |
|
|
|
for step in range(max_iter): |
|
|
|
lamb = (upper + lower) / 2 |
|
image_mix = lamb * image1 + (1 - lamb) * image2 |
|
|
|
pred_new, normalized = f(image_mix) |
|
|
|
|
|
if normalized[0, label] - normalized[0, target_cls] > 0: |
|
|
|
upper = lamb |
|
|
|
else: |
|
|
|
lower = lamb |
|
|
|
|
|
|
|
sorted, _ = torch.sort(normalized[0]) |
|
curr_diff = sorted[-1] - sorted[-2] |
|
if curr_diff < 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 |
|
|
|
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)) |
|
|
|
|
|
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] |
|
|
|
|
|
|
|
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): |
|
|
|
n_trees = min(64, 5 + int(round((data.shape[0]) ** 0.5 / 20.0))) |
|
|
|
n_iters = max(5, int(round(np.log2(data.shape[0])))) |
|
|
|
metric = "euclidean" |
|
|
|
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,) |
|
""" |
|
|
|
n_trees = min(64, 5 + int(round((query.shape[0]) ** 0.5 / 20.0))) |
|
|
|
n_iters = max(5, int(round(np.log2(query.shape[0])))) |
|
|
|
metric = "euclidean" |
|
|
|
|
|
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)) |
|
|
|
n_trees = min(64, 5 + int(round((train_data.shape[0]) ** 0.5 / 20.0))) |
|
|
|
n_iters = max(5, int(round(np.log2(train_data.shape[0])))) |
|
|
|
from pynndescent import NNDescent |
|
|
|
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] |
|
|
|
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]) |
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|