CCPP/ex.py

376 lines
16 KiB
Python
Raw Normal View History

2025-04-20 20:55:06 +08:00
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 tuplex,e),x(int) is the x-coordinatee(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()