CCPP/ex.py
2025-04-20 20:55:06 +08:00

376 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()