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

406 lines
19 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
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
from pymoo.core.problem import ElementwiseProblem
from pymoo.algorithms.soo.nonconvex.pso import PSO
from pymoo.optimize import minimize
from pymoo.core.callback import Callback
warnings.filterwarnings('ignore')
def detect_change_points(data):
models = ['l2', 'rbf', 'l1'] # 使用三种模型
all_bkps = set() # 使用集合来自动去重
for model in models:
algo = rpt.Binseg(model=model, min_size=1, jump=1).fit(data)
bkps = algo.predict(pen=1)
all_bkps.update(bkps) # 将检测到的变点添加到集合中
return sorted(all_bkps) # 返回排序后的唯一变点列表
# def process_change_points(length, change_points, window_size):
# # 初始化次要变点集合
# all_important_nodes = set()
# # 遍历每一个主要变点
# for bkp in change_points:
# # 计算该变点左右窗口的边界
# start = max(0, int(bkp - window_size/2))
# end = min(length, int(bkp + window_size/2))
# a = 0.4
# b = 0.6
# prob = a + (b - a) * np.random.random()
# # 遍历窗口内的每一个点
# for i in range(start, end):
# # 生成一个[0, 1]之间的随机数
# random_value = np.random.random()
# # 如果随机数大于0.5,将该点添加到次要变点集合中
# if random_value >= 0.4:
# all_important_nodes.add(i)
#
# return all_important_nodes
def process_change_points(ori_ts, length, change_points, window_size):
# 计算每个变点的变化激烈程度
changes_intensity = [(bkp, abs(ori_ts[bkp] - ori_ts[bkp - 1])) for bkp in change_points]
# 根据变化激烈程度对主要变点进行排序
changes_intensity.sort(key=lambda x: x[1], reverse=True)
# 初始化次要变点集合
all_important_nodes = set()
# 分配阈值
# num_points = len(changes_intensity)
# thresholds = {bkp: 0.7 - 0.4 * ((rank) / (num_points - 1))**2 for rank, (bkp, _) in enumerate(changes_intensity)}
# 初始化一个空字典来存储每个变点及其对应的阈值
thresholds = {}
# 获取变点总数
num_points = len(changes_intensity)
# 使用enumerate函数遍历排序后的变点列表及其索引即排名
for rank, (bkp, _) in enumerate(changes_intensity):
# 计算归一化排名比例
normalized_rank = 1 - ((num_points - 1-rank) / (num_points - 1))
# 计算阈值降低的量,即缩放因子乘以归一化排名比例的平方
decrement = 0.4 * (normalized_rank ** 2)
# 计算并设置当前变点的阈值
thresholds[bkp] = 0.3+decrement
# 此时thresholds字典中包含了每个变点的位置和对应的阈值
# 遍历每一个主要变点
for bkp, intensity in changes_intensity:
# 计算该变点左右窗口的边界
start = max(0, int(bkp - window_size / 2))
end = min(length, int(bkp + window_size / 2))
threshold = thresholds[bkp]
# 遍历窗口内的每一个点
for i in range(start, end):
# 生成一个[0, 1]之间的随机数
random_value = np.random.random()
# 如果随机数大于分配的阈值,将该点添加到次要变点集合中
if random_value >= threshold:
all_important_nodes.add(i)
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,classes):
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
self.classes = classes
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)
#ts = torch.tensor(ts, device='cuda')
# perturbations = torch.tensor(perturbations, dtype=torch.float32)
# perturbations = perturbations.to(device)
queries[0] += 1
ts_perturbed = self.perturb_ts(perturbations, ts,attack_pos = attack_pos)
mse = mean_squared_error(ts, ts_perturbed)
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,n_class=self.classes)
# decimal_places = -int(np.floor(np.log10(prob))) + 1
# # 计算需要标准化的数字的标准化因子
# scale_factor = 10 ** decimal_places
# # 标准化目标数字
# normalized = np.round(scale_factor * mse, 0) / scale_factor
# 确保标准化后的数字至少为0.01
#normalized = max(normalized, 0.01)
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,n_class=self.classes)
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] > 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):
class MyProblem(ElementwiseProblem):
def __init__(self, outer, ts, sample_idx, queries, attack_pos, target_class, device, bounds,perturbed_magnitude):
#self, device, perturbations, ts, sample_idx, queries, attack_pos, target_class = -1
self.perturbed_magnitude=perturbed_magnitude
super().__init__(n_var=len(bounds), n_obj=1, n_constr=0, xl=-perturbed_magnitude * np.ones(len(bounds)), # 下界
xu=perturbed_magnitude * np.ones(len(bounds)))
self.outer = outer
self.ts = ts
self.sample_idx = sample_idx
self.queries = queries
self.attack_pos = attack_pos
self.target_class = target_class
self.device = device
def _evaluate(self, x, out, *args, **kwargs):
f = self.outer.fitness(self.device,x , self.ts, self.sample_idx,self.queries, self.attack_pos, self.target_class,
)
# self, device,perturbations, ts, sample_idx, queries,attack_pos, target_class=-1
out["F"] = np.array([f])
class MyCallback(Callback):
def __init__(self, outer, device, ts, sample_idx, queries, attack_pos, target_class, verbose,iterations):
super().__init__()
self.outer = outer
self.device = device
self.ts = ts
self.sample_idx = sample_idx
self.queries = queries
self.attack_pos = attack_pos
self.target_class = target_class
self.iterations = iterations
self.verbose = verbose
self.generation = 0
def notify(self, algorithm):
x = algorithm.pop.get("X")[np.argmin(algorithm.pop.get("F"))] # Current best solution
ifsuccess=self.outer.attack_success(self.device, x, self.ts,
self.sample_idx, self.attack_pos,
self.iterations, self.target_class,
self.verbose)
#self,device,perturbations, ts, sample_idx, attack_pos, iterations, target_class=-1, verbose=True
# best_f = min(algorithm.pop.get("F"))
# best_x = algorithm.pop.get("X")[np.argmin(algorithm.pop.get("F"))]
# print(f"Generation {self.generation}: Best solution so far: {best_x}, Fitness: {best_f}")
# print('-------',should_terminate)
if ifsuccess:
algorithm.termination.force_termination = True
self.generation += 1
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:]
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,n_class=self.classes)
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 = []
# 变点检测找区间
length = ori_ts.shape
all_bkps = detect_change_points(ori_ts)
all_bkps = [x - 1 for x in all_bkps]
window_size = int(length[0] / len(all_bkps)) # 窗口大小
sequence = np.zeros(len(ori_ts), dtype=int)
# secondary_changes = process_change_points(length[0], all_bkps, window_size)
# sequence[list(secondary_changes)] = 1
# sequence[list(all_bkps)] = 1
# 处理变点
if len(all_bkps)<0.7*length[0] :
secondary_changes = process_change_points(ori_ts, length[0], all_bkps, window_size)
sequence[list(secondary_changes)] = 1
sequence[list(all_bkps)] = 1
else:
sequence[list(all_bkps)] = 1
attack_pos = sequence
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]
problem = MyProblem(self, ori_ts, sample_idx, queries, attack_pos, target_class, device,bounds,perturbed_magnitude)
algorithm = PSO(pop_size=50, w=0.9, c1=1.2, c2=2.2)
callback1 = MyCallback(self, device, ori_ts, sample_idx, queries, attack_pos, target_class, verbose,iterations)
res = minimize(problem, algorithm, ('n_gen', max_iteration), callback=callback1, seed=1, verbose=verbose)
attack_result = res.X # 最优解
# 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, 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,n_class=self.classes)
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, 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]