376 lines
16 KiB
Python
376 lines
16 KiB
Python
|
import warnings
|
|||
|
|
|||
|
import matplotlib
|
|||
|
|
|||
|
# matplotlib.use('Agg')
|
|||
|
|
|||
|
import networkx as nx
|
|||
|
import matplotlib.pyplot as plt
|
|||
|
matplotlib.use('Agg')
|
|||
|
import numpy as np
|
|||
|
import torch
|
|||
|
import pandas as pd
|
|||
|
import ruptures as rpt
|
|||
|
from scipy.optimize import differential_evolution
|
|||
|
from sklearn.metrics import mean_squared_error
|
|||
|
from query_probability import query_one, load_ucr
|
|||
|
warnings.filterwarnings('ignore')
|
|||
|
|
|||
|
|
|||
|
def detect_change_points(data, model="l2", pen=1):
|
|||
|
algo = rpt.Pelt(model=model,min_size=1,jump=1).fit(data)
|
|||
|
#algo = rpt.Binseg(model=model, min_size=1, jump=1).fit(data)
|
|||
|
bkps = algo.predict(pen=pen)
|
|||
|
#print('=======',bkps)
|
|||
|
return bkps
|
|||
|
|
|||
|
def build_local_graph(data, center_index, window_size):
|
|||
|
"""构建局部图"""
|
|||
|
start = max(0, center_index - window_size // 2)
|
|||
|
end = min(len(data), center_index + window_size // 2)
|
|||
|
G = nx.Graph()
|
|||
|
for i in range(start, end):
|
|||
|
for j in range(i + 1, end):
|
|||
|
weight = np.linalg.norm(data[i] - data[j])
|
|||
|
G.add_edge(i, j, weight=weight)
|
|||
|
return G
|
|||
|
|
|||
|
def find_important_nodes_via_components(G):
|
|||
|
"""使用连通分量动态确定次要变点的数量"""
|
|||
|
components = list(nx.connected_components(G))
|
|||
|
important_nodes = []
|
|||
|
for component in components:
|
|||
|
if len(component) > 1: # 考虑大于一个节点的组件
|
|||
|
important_nodes.extend(component)
|
|||
|
return list(important_nodes)
|
|||
|
|
|||
|
def process_change_points(data, change_points, window_size):
|
|||
|
all_important_nodes = set()
|
|||
|
for cp in change_points:
|
|||
|
G = build_local_graph(data, cp, window_size)
|
|||
|
important_nodes = find_important_nodes_via_components(G)
|
|||
|
print(cp, '------', important_nodes)
|
|||
|
all_important_nodes.update(important_nodes)
|
|||
|
all_important_nodes.update((change_points))
|
|||
|
return all_important_nodes
|
|||
|
|
|||
|
def get_magnitude(run_tag, factor, normalize,gpu):
|
|||
|
'''
|
|||
|
:param run_tag:
|
|||
|
:param factor:
|
|||
|
:return: Perturbed Magnitude
|
|||
|
'''
|
|||
|
data = load_ucr('data/' + run_tag + '/' + run_tag + '_attack'+gpu+'.txt', normalize=normalize)
|
|||
|
X = data[:, 1:]
|
|||
|
|
|||
|
max_magnitude = X.max(1)
|
|||
|
min_magnitude = X.min(1)
|
|||
|
mean_magnitude = np.mean(max_magnitude - min_magnitude)
|
|||
|
|
|||
|
perturbed_mag = mean_magnitude * factor
|
|||
|
print('Perturbed Magnitude:', perturbed_mag)
|
|||
|
|
|||
|
return perturbed_mag
|
|||
|
|
|||
|
|
|||
|
class Attacker:
|
|||
|
|
|||
|
def __init__(self, run_tag, model_type, cuda, normalize, e,device,gpu):
|
|||
|
self.run_tag = run_tag
|
|||
|
self.model_type = model_type
|
|||
|
self.cuda = cuda
|
|||
|
#self.intervals = get_attack_position(self.run_tag, self.top_k)
|
|||
|
self.normalize = normalize
|
|||
|
self.e = e
|
|||
|
self.device = device
|
|||
|
self.gpu = gpu
|
|||
|
|
|||
|
def perturb_ts(self, perturbations, ts, attack_pos):
|
|||
|
'''
|
|||
|
:param perturbations:formalized as a tuple(x,e),x(int) is the x-coordinate,e(float) is the epsilon,e.g.,(2,0.01)
|
|||
|
:param ts: time series
|
|||
|
:return: perturbed ts
|
|||
|
'''
|
|||
|
# first we copy a ts
|
|||
|
|
|||
|
ts_tmp = np.copy(ts)
|
|||
|
|
|||
|
coordinate = 0 # 初始化perturbations数组的索引
|
|||
|
for i in range(len(attack_pos)):
|
|||
|
if attack_pos[i] == 1:
|
|||
|
ts_tmp[i] += perturbations[coordinate]
|
|||
|
coordinate += 1
|
|||
|
# for interval in self.intervals:
|
|||
|
# for i in range(int(interval[0]), int(interval[1])):
|
|||
|
# ts_tmp[i] += perturbations[coordinate]
|
|||
|
# coordinate += 1
|
|||
|
return ts_tmp
|
|||
|
|
|||
|
def plot_per(self, perturbations, ts, target_class, sample_idx,attack_pos, prior_probs, attack_probs, factor):
|
|||
|
|
|||
|
# Obtain the perturbed ts
|
|||
|
ts_tmp = np.copy(ts)
|
|||
|
ts_perturbed = self.perturb_ts(perturbations=perturbations, ts=ts, attack_pos=attack_pos)
|
|||
|
# Start to plot
|
|||
|
plt.figure(figsize=(6, 4))
|
|||
|
plt.plot(ts_tmp, color='b', label='Original %.2f' % prior_probs)
|
|||
|
plt.plot(ts_perturbed, color='r', label='Perturbed %.2f' % attack_probs)
|
|||
|
plt.xlabel('Time', fontsize=12)
|
|||
|
|
|||
|
if target_class == -1:
|
|||
|
plt.title('Untargeted: Sample %d, eps_factor=%.3f' %
|
|||
|
(sample_idx, factor), fontsize=14)
|
|||
|
else:
|
|||
|
plt.title('Targeted(%d): Sample %d, eps_factor=%.3f' %
|
|||
|
(target_class, sample_idx, factor), fontsize=14)
|
|||
|
|
|||
|
plt.legend(loc='upper right', fontsize=8)
|
|||
|
plt.savefig('result_' + str(factor) + '_' + str(self.model_type) + '/'
|
|||
|
+ self.run_tag + '/figures'+self.gpu+'/' + self.run_tag +'_' + str(sample_idx) + '.png')
|
|||
|
# plt.show()
|
|||
|
|
|||
|
def fitness(self, device,perturbations, ts, sample_idx, queries,attack_pos, target_class=-1):
|
|||
|
#device = torch.device("cuda:0" if self.cuda else "cpu")
|
|||
|
perturbations = torch.tensor(perturbations).to(device)
|
|||
|
#ts = torch.tensor(ts, device='cuda')
|
|||
|
|
|||
|
queries[0] += 1
|
|||
|
|
|||
|
ts_perturbed = self.perturb_ts(perturbations, ts,attack_pos = attack_pos)
|
|||
|
prob, _, _, _, _ = query_one(run_tag=self.run_tag,device=device, idx=sample_idx, attack_ts=ts_perturbed,
|
|||
|
target_class=target_class, normalize=self.normalize,
|
|||
|
cuda=self.cuda, model_type=self.model_type, e=self.e,gpu=self.gpu)
|
|||
|
prob = torch.tensor(prob)
|
|||
|
|
|||
|
if target_class != -1:
|
|||
|
prob = 1 - prob
|
|||
|
|
|||
|
return prob # The fitness function is to minimize the fitness value
|
|||
|
|
|||
|
def attack_success(self,device,perturbations, ts, sample_idx, attack_pos, iterations, target_class=-1, verbose=True):
|
|||
|
iterations[0] += 1
|
|||
|
print('The %d iteration' % iterations[0])
|
|||
|
ts_perturbed = self.perturb_ts(perturbations, ts, attack_pos)
|
|||
|
#ts_perturbed = torch.tensor(ts_perturbed, device='cuda')
|
|||
|
# Obtain the perturbed probability vector and the prior probability vector
|
|||
|
prob, prob_vector, prior_prob, prior_prob_vec, real_label = query_one(self.run_tag, device,idx=sample_idx,
|
|||
|
attack_ts=ts_perturbed,
|
|||
|
target_class=target_class,
|
|||
|
normalize=self.normalize,
|
|||
|
verbose=verbose, cuda=self.cuda,
|
|||
|
model_type=self.model_type,
|
|||
|
e=self.e,gpu=self.gpu)
|
|||
|
|
|||
|
predict_class = torch.argmax(prob_vector).to(device)
|
|||
|
prior_class = torch.argmax(prior_prob_vec).to(device)
|
|||
|
real_label = real_label.to(device)
|
|||
|
|
|||
|
# Conditions for early termination(empirical-based estimation), leading to save the attacking time
|
|||
|
# But it may judge incorrectly that this may decrease the success rate of the attack.
|
|||
|
if (iterations[0] > 5 and prob > 0.99) or \
|
|||
|
(iterations[0] > 20 and prob > 0.9):
|
|||
|
print('The %d sample is not expected to successfully attack.' % sample_idx)
|
|||
|
print('prob: ',prob)
|
|||
|
return True
|
|||
|
|
|||
|
if prior_class != real_label:
|
|||
|
print('The %d sample cannot be classified correctly, no need to attack' % sample_idx)
|
|||
|
return True
|
|||
|
|
|||
|
if prior_class == target_class:
|
|||
|
print(
|
|||
|
'The true label of %d sample equals to target label, no need to attack' % sample_idx)
|
|||
|
return True
|
|||
|
|
|||
|
if verbose:
|
|||
|
print('The Confidence of current iteration: %.4f' % prob)
|
|||
|
print('########################################################')
|
|||
|
|
|||
|
# The criterion of attacking successfully:
|
|||
|
# Untargeted attack: predicted label is not equal to the original label.
|
|||
|
# Targeted attack: predicted label is equal to the target label.
|
|||
|
if ((target_class == -1 and predict_class != prior_class) or
|
|||
|
(target_class != -1 and predict_class == target_class)):
|
|||
|
print('##################### Attack Successfully! ##########################')
|
|||
|
|
|||
|
return True
|
|||
|
|
|||
|
def attack(self, sample_idx,device, target_class=-1, factor=0.04,
|
|||
|
max_iteration=50, popsize=200, verbose=True):
|
|||
|
|
|||
|
test = load_ucr('data/' + self.run_tag + '/' + self.run_tag + '_attack'+self.gpu+'.txt'
|
|||
|
, normalize=self.normalize)
|
|||
|
#attack_poses = np.loadtxt('data/' + self.run_tag + '/' + self.run_tag + '_attackPos'+self.gpu+'.txt')
|
|||
|
|
|||
|
ori_ts = test[sample_idx][1:]
|
|||
|
#变点检测找区间
|
|||
|
length = ori_ts.shape
|
|||
|
|
|||
|
result = detect_change_points(ori_ts)
|
|||
|
sampled_indices = np.zeros(len(ori_ts), dtype=int)
|
|||
|
if length[0] > 500 or len(result) > 0.4 * length[0]:
|
|||
|
|
|||
|
# 设置基本窗口大小和基本采样密度
|
|||
|
base_window_size = 0
|
|||
|
base_density_factor = 0.01
|
|||
|
else:
|
|||
|
# 设置基本窗口大小和基本采样密度
|
|||
|
base_window_size = int(0.1 * length[0])
|
|||
|
base_density_factor = 0.5
|
|||
|
|
|||
|
# 计算变点的重要性并调整窗口大小和采样密度
|
|||
|
for i, cp in enumerate(result[:-1]):
|
|||
|
# 计算变点前后的统计差异
|
|||
|
if i == 0:
|
|||
|
pre_mean = np.mean(ori_ts[:cp])
|
|||
|
else:
|
|||
|
pre_mean = np.mean(ori_ts[result[i - 1]:cp])
|
|||
|
post_mean = np.mean(ori_ts[cp:result[i + 1]])
|
|||
|
|
|||
|
# 重要性评估:差异的绝对值
|
|||
|
importance = abs(post_mean - pre_mean)
|
|||
|
|
|||
|
# 动态调整窗口大小和采样密度
|
|||
|
if length[0] > 500 or len(result) > 0.1 * length[0]:
|
|||
|
window_size = int(base_window_size + int(importance * 5)) # 重要性越大,窗口越大
|
|||
|
density_factor = base_density_factor + importance * 0.1 # 重要性越大,采样密度越高
|
|||
|
else:
|
|||
|
window_size = int(base_window_size + int(importance * 10)) # 重要性越大,窗口越大
|
|||
|
density_factor = base_density_factor + importance * 0.3 # 重要性越大,采样密度越高
|
|||
|
|
|||
|
|
|||
|
# 设置窗口的开始和结束位置
|
|||
|
start = max(cp - window_size // 2, 0)
|
|||
|
end = min(cp + window_size // 2, len(ori_ts))
|
|||
|
|
|||
|
# 在窗口内随机采样
|
|||
|
sample_count = int((end - start) * density_factor)
|
|||
|
if sample_count > 0:
|
|||
|
samples = np.random.choice(range(start, end), sample_count, replace=True)
|
|||
|
sampled_indices[samples] = 1
|
|||
|
attack_pos = sampled_indices
|
|||
|
|
|||
|
#attack_pos = attack_poses[sample_idx,:]
|
|||
|
# attack_pos = attack_poses.head(sample_idx)
|
|||
|
# attack_pos = attack_pos.apply(pd.to_numeric, errors='coerce').fillna(0)
|
|||
|
|
|||
|
#ori_ts = torch.tensor(ori_ts).to(device)
|
|||
|
attacked_probs, attacked_vec, prior_probs, prior_vec, real_label = query_one(self.run_tag,device,idx=sample_idx,
|
|||
|
attack_ts=ori_ts,
|
|||
|
target_class=target_class,
|
|||
|
normalize=self.normalize,
|
|||
|
verbose=False,
|
|||
|
cuda=self.cuda, e=self.e,
|
|||
|
model_type=self.model_type,gpu=self.gpu)
|
|||
|
prior_class = torch.argmax(prior_vec).to(device)
|
|||
|
if prior_class != real_label:
|
|||
|
print('The %d sample cannot be classified correctly, no need to attack' % sample_idx)
|
|||
|
return ori_ts,ori_ts, [prior_probs, attacked_probs, 0, 0, 0, 0, 0, 'WrongSample']
|
|||
|
|
|||
|
|
|||
|
# Get the maximum perturbed magnitude
|
|||
|
perturbed_magnitude = get_magnitude(self.run_tag, factor, normalize=self.normalize,gpu=self.gpu)
|
|||
|
|
|||
|
bounds = []
|
|||
|
|
|||
|
# for interval in self.intervals:
|
|||
|
# steps_count += int(interval[1]) - int(interval[0])
|
|||
|
# for i in range(int(interval[0]), int(interval[1])):
|
|||
|
# bounds.append((-1 * perturbed_magnitude, perturbed_magnitude))
|
|||
|
steps_count = attack_pos.sum()
|
|||
|
for i in range(len(attack_pos)):
|
|||
|
if attack_pos[i] == 1:
|
|||
|
bounds.append((-1 * perturbed_magnitude, perturbed_magnitude))
|
|||
|
|
|||
|
print('The length of shapelet interval', steps_count)
|
|||
|
#print('The length of bounds', len(bounds))
|
|||
|
popmul = max(1, popsize // len(bounds))
|
|||
|
# Record of the number of iterations
|
|||
|
iterations = [0]
|
|||
|
queries = [0]
|
|||
|
|
|||
|
def fitness_fn(perturbations):
|
|||
|
|
|||
|
return self.fitness(perturbations=perturbations, ts=ori_ts, queries=queries,
|
|||
|
sample_idx=sample_idx, attack_pos=attack_pos,target_class=target_class,device=device)
|
|||
|
|
|||
|
def callback_fn(x, convergence):
|
|||
|
|
|||
|
return self.attack_success(perturbations=x, ts=ori_ts,
|
|||
|
sample_idx=sample_idx,
|
|||
|
attack_pos = attack_pos,
|
|||
|
iterations=iterations,
|
|||
|
target_class=target_class,
|
|||
|
verbose=verbose,device=device)
|
|||
|
|
|||
|
attack_result = differential_evolution(func=fitness_fn, bounds=bounds
|
|||
|
, maxiter=max_iteration, popsize=popmul
|
|||
|
, recombination=0.7, callback=callback_fn,
|
|||
|
atol=-1, polish=False)
|
|||
|
|
|||
|
attack_ts = self.perturb_ts(attack_result.x, ori_ts, attack_pos)
|
|||
|
|
|||
|
mse = mean_squared_error(ori_ts, attack_ts)
|
|||
|
|
|||
|
attacked_probs, attacked_vec, prior_probs, prior_vec, real_label = query_one(self.run_tag,device, idx=sample_idx,
|
|||
|
attack_ts=attack_ts,
|
|||
|
target_class=target_class,
|
|||
|
normalize=self.normalize,
|
|||
|
verbose=False,
|
|||
|
cuda=self.cuda, e=self.e,
|
|||
|
model_type=self.model_type,gpu=self.gpu)
|
|||
|
|
|||
|
predicted_class = torch.argmax(attacked_vec).to(device)
|
|||
|
prior_class = torch.argmax(prior_vec).to(device)
|
|||
|
|
|||
|
if prior_class != real_label:
|
|||
|
success = 'WrongSample'
|
|||
|
|
|||
|
elif prior_class == target_class:
|
|||
|
success = 'NoNeedAttack'
|
|||
|
|
|||
|
else:
|
|||
|
if (predicted_class.item() != prior_class.item() and target_class == -1) \
|
|||
|
or (predicted_class.item() == target_class and target_class != -1):
|
|||
|
success = 'Success'
|
|||
|
else:
|
|||
|
success = 'Fail'
|
|||
|
|
|||
|
if success == 'Success':
|
|||
|
self.plot_per(perturbations=attack_result.x, ts=ori_ts, target_class=target_class,
|
|||
|
sample_idx=sample_idx, attack_pos=attack_pos, prior_probs=prior_probs, attack_probs=attacked_probs, factor=factor)
|
|||
|
|
|||
|
return ori_ts, attack_ts, [prior_probs, attacked_probs, prior_class.item(),
|
|||
|
predicted_class.item(), queries[0], mse, iterations[0], success]
|
|||
|
|
|||
|
|
|||
|
if __name__ == '__main__':
|
|||
|
idx = [1]
|
|||
|
attacker = Attacker(run_tag='ECG200', top_k=3, model_type='f', e=1499,
|
|||
|
cuda=True, normalize=False)
|
|||
|
for idx in idx:
|
|||
|
attack_ts, info = attacker.attack(sample_idx=idx, target_class=-1, factor=0.01,
|
|||
|
max_iteration=200, popsize=1, verbose=True)
|
|||
|
#这里是生成对抗样本
|
|||
|
if info[-1] == 'Success':
|
|||
|
file = open('attack_time_series.txt', 'w+')
|
|||
|
file.write('%d %d ' % (idx, info[3])) # save the sample index and the perturbed label
|
|||
|
for i in attack_ts:
|
|||
|
file.write('%.4f ' % i)
|
|||
|
file.write('\n')
|
|||
|
file.close()
|
|||
|
|
|||
|
print(info)
|
|||
|
file = open('info.txt', 'w+')
|
|||
|
file.write('%d ' % idx)
|
|||
|
for i in info:
|
|||
|
if isinstance(i, int):
|
|||
|
file.write('%d ' % i)
|
|||
|
elif isinstance(i, float):
|
|||
|
file.write('%.4f ' % i)
|
|||
|
else:
|
|||
|
file.write(i + ' ')
|
|||
|
|
|||
|
file.write('\n')
|
|||
|
file.close()
|