Skip to content

Commit d92f951

Browse files
authored
Update2 (#9)
* removed epsilon * moving clipping to swarm * adding clipping here * TODO: running on the gpu and setting it as a flag * updating setup.py * TODO using cifar10 * running simulation * removed PDB * updating clipping * fixing training * updaing position clipping (#10) * adding model 3 for MNIST * modularized the code and segemented it out for multi study * removed results and updated git ignore * applied a scaling factor to reduce the size of the perterbation by alpha * working reduction with minimal error * updating for poison paper * updates * optimizing setup * training and attacking manuscript 2
1 parent 425f011 commit d92f951

16 files changed

Lines changed: 1395 additions & 252 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,34 @@
77
"dockerfile": "Dockerfile"
88
},
99
"features": {
10-
"ghcr.io/rocker-org/devcontainer-features/miniforge:2": {}
11-
}
10+
"ghcr.io/devcontainers/features/anaconda:1": {
11+
"version": "latest"
12+
},
13+
"ghcr.io/devcontainers/features/nvidia-cuda:2": {
14+
"installCudnn": true,
15+
"installCudnnDev": true,
16+
"installNvtx": true,
17+
"installToolkit": true,
18+
"cudaVersion": "11.8",
19+
"cudnnVersion": "automatic"
20+
},
21+
"ghcr.io/raucha/devcontainer-features/pytorch:1": {}
22+
},
23+
"runArgs": [
24+
"--gpus=all"
25+
]
26+
// Features to add to the dev container. More info: https://containers.dev/features.
27+
// "features": {},
28+
29+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
30+
// "forwardPorts": [],
31+
32+
// Use 'postCreateCommand' to run commands after the container is created.
33+
// "postCreateCommand": "python --version",
34+
35+
// Configure tool-specific properties.
36+
// "customizations": {},
37+
38+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
39+
// "remoteUser": "root"
1240
}

Adversarial_Observation/BirdParticle.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,38 @@ class BirdParticle:
99
fitness evaluation, and the updates to its velocity and position based on the PSO algorithm.
1010
"""
1111

12-
def __init__(self, model: tf.keras.Model, input_data: tf.Tensor, target_class: int, epsilon: float,
12+
def __init__(self, model: tf.keras.Model, input_data: tf.Tensor, target_class: int, num_iterations: int = 20,
1313
velocity: tf.Tensor = None, inertia_weight: float = 0.5,
14-
cognitive_weight: float = 1.0, social_weight: float = 1.0, momentum: float = 0.9,
15-
velocity_clamp: float = 0.1):
14+
cognitive_weight: float = 1.0, social_weight: float = 1.0,
15+
momentum: float = 0.9, clip_value_position: float = 1.0):
1616
"""
1717
Initialize a particle in the PSO algorithm.
1818
1919
Args:
2020
model (tf.keras.Model): The model to attack.
2121
input_data (tf.Tensor): The input data (image) to attack.
2222
target_class (int): The target class for misclassification.
23-
epsilon (float): The perturbation bound (maximum amount the image can be altered).
2423
velocity (tf.Tensor, optional): The initial velocity for the particle's movement. Defaults to zero velocity if not provided.
2524
inertia_weight (float): The inertia weight for the velocity update. Default is 0.5.
2625
cognitive_weight (float): The cognitive weight for the velocity update. Default is 1.0.
2726
social_weight (float): The social weight for the velocity update. Default is 1.0.
2827
momentum (float): The momentum for the velocity update. Default is 0.9.
29-
velocity_clamp (float): The velocity clamp for limiting the maximum velocity. Default is 0.1.
3028
"""
3129
self.model = model
30+
self.num_iterations = num_iterations
3231
self.original_data = tf.identity(input_data) # Clone the input data
3332
self.target_class = target_class
34-
self.epsilon = epsilon
3533
self.best_position = tf.identity(input_data) # Clone the input data
3634
self.best_score = -np.inf
3735
self.position = tf.identity(input_data) # Clone the input data
3836
self.velocity = velocity if velocity is not None else tf.zeros_like(input_data)
3937
self.history = [self.position]
40-
38+
self.clip_value_position = clip_value_position
4139
# Class attributes
4240
self.inertia_weight = inertia_weight
4341
self.cognitive_weight = cognitive_weight
4442
self.social_weight = social_weight
4543
self.momentum = momentum
46-
self.velocity_clamp = velocity_clamp
4744

4845
def fitness(self) -> float:
4946
"""
@@ -71,29 +68,29 @@ def update_velocity(self, global_best_position: tf.Tensor) -> None:
7168
cognitive = self.cognitive_weight * tf.random.uniform(self.position.shape) * (self.best_position - self.position)
7269
social = self.social_weight * tf.random.uniform(self.position.shape) * (global_best_position - self.position)
7370

74-
self.velocity = inertia + cognitive + social # Update velocity based on PSO formula
75-
76-
# Apply momentum and velocity clamping
77-
self.velocity = self.velocity * self.momentum # Apply momentum
78-
self.velocity = tf.clip_by_value(self.velocity, -self.velocity_clamp, self.velocity_clamp) # Apply velocity clamp
71+
# Apply momentum to velocity update:
72+
self.velocity = self.momentum * self.velocity + inertia + cognitive + social # Apply momentum
7973

8074
def update_position(self) -> None:
8175
"""
8276
Update the position of the particle based on the updated velocity.
8377
84-
Ensures that the position stays within the valid input range [0, 1] (normalized pixel values).
78+
The position is updated directly without any bounds checking.
8579
"""
86-
self.position = tf.clip_by_value(self.position + self.velocity, 0, 1) # Ensure position stays within bounds
80+
self.position = self.position + self.velocity # Update position directly without clipping
81+
self.position = tf.clip_by_value(self.position + self.velocity, 0, 1) # Ensure position stays within bounds
8782
self.history.append(tf.identity(self.position)) # Store the position history
83+
self.position = tf.clip_by_value(self.position, -self.clip_value_position, self.clip_value_position)
8884

8985
def evaluate(self) -> None:
9086
"""
9187
Evaluate the fitness of the current particle and update its personal best.
9288
9389
The fitness score is calculated using the target class probability. If the current fitness score
94-
is better than the personal best, update the personal best position and score.
90+
is better than the personal best, update the best position and score.
9591
"""
9692
score = self.fitness() # Get the current fitness score based on the perturbation
9793
if score > self.best_score: # If score is better than the personal best, update the best position
9894
self.best_score = score
9995
self.best_position = tf.identity(self.position) # Clone the current position
96+

Adversarial_Observation/Swarm.py

Lines changed: 106 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from typing import List
44
from Adversarial_Observation.BirdParticle import BirdParticle
5-
5+
from tqdm import tqdm
66
import tensorflow as tf
77
import numpy as np
88
import matplotlib.pyplot as plt
@@ -15,10 +15,10 @@ class ParticleSwarm:
1515
to misclassify it into the target class.
1616
"""
1717

18-
def __init__(self, model: tf.keras.Model, input_set: np.ndarray, target_class: int,
19-
num_iterations: int = 20, epsilon: float = 0.8, save_dir: str = 'results',
20-
inertia_weight: float = 0.5, cognitive_weight: float = .5, social_weight: float = .5,
21-
momentum: float = 0.9, velocity_clamp: float = 0.1):
18+
def __init__(self, model: tf.keras.Model, input_set: np.ndarray, starting_class: int, target_class: int,
19+
num_iterations: int = 20, save_dir: str = 'results', inertia_weight: float = 0.5,
20+
cognitive_weight: float = .5, social_weight: float = .5, momentum: float = 0.9,
21+
clip_value_position: float = 0.2, enable_logging: bool = False, device: str = 'cpu'):
2222
"""
2323
Initialize the Particle Swarm Optimization (PSO) for adversarial attacks.
2424
@@ -27,44 +27,48 @@ def __init__(self, model: tf.keras.Model, input_set: np.ndarray, target_class: i
2727
input_set (np.ndarray): The batch of input images to attack as a NumPy array.
2828
target_class (int): The target class for misclassification.
2929
num_iterations (int): The number of optimization iterations.
30-
epsilon (float): The perturbation bound.
3130
save_dir (str): The directory to save output images and logs.
3231
inertia_weight (float): The inertia weight for the velocity update.
3332
cognitive_weight (float): The cognitive weight for the velocity update.
3433
social_weight (float): The social weight for the velocity update.
3534
momentum (float): The momentum for the velocity update.
36-
velocity_clamp (float): The velocity clamp to limit the velocity.
35+
clip_value_position (float): The velocity clamp to limit the velocity.
36+
device (str): The device for computation ('cpu' or 'gpu'). Default is 'cpu'.
3737
"""
3838
self.model = model
3939
self.input_set = tf.convert_to_tensor(input_set, dtype=tf.float32) # Convert NumPy array to TensorFlow tensor
40+
self.start_class = starting_class # The starting class index
4041
self.target_class = target_class # The target class index
4142
self.num_iterations = num_iterations
42-
self.epsilon = epsilon # Perturbation bound
4343
self.save_dir = save_dir # Directory to save perturbed images
44-
44+
self.enable_logging = enable_logging
45+
self.device = device # Device ('cpu' or 'gpu')
46+
4547
self.particles: List[BirdParticle] = [
46-
BirdParticle(model, self.input_set[i:i + 1], target_class, epsilon,
47-
inertia_weight=inertia_weight, cognitive_weight=cognitive_weight,
48-
social_weight=social_weight, momentum=momentum, velocity_clamp=velocity_clamp)
48+
BirdParticle(model, self.input_set[i:i + 1], target_class,
49+
inertia_weight=inertia_weight, cognitive_weight=cognitive_weight, social_weight=social_weight,
50+
momentum=momentum, clip_value_position=clip_value_position)
4951
for i in range(len(input_set))
5052
]
5153

5254
self.global_best_position = tf.zeros_like(self.input_set[0]) # Global best position
5355
self.global_best_score = -float('inf') # Initialize with a very low score
5456

5557
self.fitness_history: List[float] = [] # History of fitness scores to track progress
56-
self.setup_logging() # Set up logging
57-
self.log_progress(-1) # Log initial state (before optimization)
58+
59+
# Make output folder
60+
iteration_dir = self.save_dir
61+
os.makedirs(iteration_dir, exist_ok=True)
62+
if self.enable_logging:
63+
self.setup_logging()
64+
self.log_progress(-1)
5865

5966
def setup_logging(self):
6067
"""
6168
Set up logging for each iteration. Each iteration will have a separate log file.
6269
Also prints logs to the terminal.
6370
"""
64-
iteration_dir = self.save_dir
65-
os.makedirs(iteration_dir, exist_ok=True)
66-
67-
log_file = os.path.join(iteration_dir, f'iteration_log.log')
71+
log_file = os.path.join(self.save_dir, f'iteration_log.log')
6872
self.logger = logging.getLogger()
6973

7074
# Create a file handler to save logs to a file
@@ -86,7 +90,6 @@ def setup_logging(self):
8690
self.logger.info(f"Model: {self.model.__class__.__name__}")
8791
self.logger.info(f"Target Class: {self.target_class} (This is the class we want to misclassify the image into)")
8892
self.logger.info(f"Number of Iterations: {self.num_iterations} (Optimization steps)")
89-
self.logger.info(f"Epsilon (perturbation bound): {self.epsilon} (Maximum perturbation allowed)")
9093
self.logger.info(f"Save Directory: {self.save_dir}")
9194
self.logger.info(f"{'*' * 60}")
9295

@@ -97,13 +100,16 @@ def log_progress(self, iteration: int):
97100
Args:
98101
iteration (int): The current iteration of the optimization process.
99102
"""
103+
if not self.enable_logging:
104+
return
105+
100106
# Log the header for the iteration
101107
self.logger.info(f"\n{'-'*60}")
102108
self.logger.info(f"Iteration {iteration + 1}/{self.num_iterations}")
103109
self.logger.info(f"{'='*60}")
104110

105111
# Table header
106-
header = f"{'Particle':<10}{'Original Pred':<15}{'Perturbed Pred':<18}{'Orig Target Prob':<20}" \
112+
header = f"{'Particle':<10}{'Original Pred':<15}{'Perturbed Pred':<18}{'Orig Start Prob':<20}{'Pert Start Prob':<20}{'Orig Target Prob':<20}" \
107113
f"{'Pert Target Prob':<20}{'Personal Best':<20}{'Global Best':<20}"
108114
self.logger.info(header)
109115
self.logger.info(f"{'-'*60}")
@@ -121,51 +127,96 @@ def log_progress(self, iteration: int):
121127
# Get softmax probabilities
122128
original_probs = tf.nn.softmax(original_output, axis=1)
123129
perturbed_probs = tf.nn.softmax(perturbed_output, axis=1)
124-
130+
131+
# Get starting class probabilities (how far away we've moved)
132+
original_prob_start = original_probs[0, self.start_class].numpy().item()
133+
perturbed_prob_start = perturbed_probs[0, self.start_class].numpy().item()
134+
125135
# Get target class probabilities
126136
original_prob_target = original_probs[0, self.target_class].numpy().item()
127137
perturbed_prob_target = perturbed_probs[0, self.target_class].numpy().item()
128138

129139
# Log each particle's data in a formatted row
130-
self.logger.info(f"{i+1:<10}{original_pred:<15}{perturbed_pred:<18}{original_prob_target:<20.4f}"
131-
f"{perturbed_prob_target:<20.4f}{particle.best_score:<20.4f}{self.global_best_score:<20.4f}")
140+
self.logger.info(f"{i+1:<10}{original_pred:<15}{perturbed_pred:<18}{original_prob_start:<20.4f}{perturbed_prob_start:<20.4f}"
141+
f"{original_prob_target:<20.4f}{perturbed_prob_target:<20.4f}{particle.best_score:<20.4f}{self.global_best_score:<20.4f}")
132142

133143
self.logger.info(f"{'='*60}")
134144

135-
def save_images(self, iteration: int):
145+
def optimize(self):
136146
"""
137-
Save the perturbed images for the current iteration.
138-
139-
Args:
140-
iteration (int): The current iteration number.
147+
Run the Particle Swarm Optimization process to optimize the perturbations.
141148
"""
142-
iteration_folder = os.path.join(self.save_dir, f"iteration_{iteration + 1}")
143-
os.makedirs(iteration_folder, exist_ok=True)
144-
145-
for i, particle in enumerate(self.particles):
146-
# Convert TensorFlow tensor to NumPy array
147-
position_numpy = particle.position.numpy()
148-
# Remove extra batch dimension (if it exists)
149-
position_numpy = np.squeeze(position_numpy) # Now shape is (28, 28)
150-
plt.imsave(os.path.join(iteration_folder, f"perturbed_image_{i + 1}.png"), position_numpy, cmap="gray", vmin=0, vmax=1)
149+
with tf.device(f"/{self.device}:0"): # Use the GPU/CPU based on the flag
150+
for iteration in tqdm(range(self.num_iterations), desc="Running Swarm"):
151+
# Update particles and velocities, evaluate them, and track global best
152+
for particle in self.particles:
153+
particle.evaluate()
154+
particle.update_velocity(self.global_best_position) # No need to pass inertia_weight explicitly
155+
particle.update_position()
156+
157+
# Update the global best based on the personal best scores of particles
158+
best_particle = max(self.particles, key=lambda p: p.best_score)
159+
if best_particle.best_score > self.global_best_score:
160+
self.global_best_score = best_particle.best_score
161+
self.global_best_position = tf.identity(best_particle.best_position)
162+
163+
self.log_progress(iteration)
151164

152-
def optimize(self):
165+
def reduce_excess_perturbations(self, original_img: np.ndarray, target_label: int, model_shape: tuple = (1, 28, 28, 1)) -> np.ndarray:
153166
"""
154-
Run the Particle Swarm Optimization process to optimize the perturbations.
167+
Reduces excess perturbations in adversarial images while ensuring the misclassification remains.
168+
169+
Args:
170+
original_img (np.ndarray): The original (clean) image.
171+
target_label (int): The label we want the adversarial image to produce.
172+
model_shape (tuple): The expected shape of the model input.
173+
174+
Returns:
175+
list[np.ndarray]: A list of denoised adversarial images.
155176
"""
156-
for iteration in range(self.num_iterations):
157-
158-
# Update particles and velocities, evaluate them, and track global best
159-
for particle in self.particles:
160-
particle.evaluate()
161-
particle.update_velocity(self.global_best_position) # No need to pass inertia_weight explicitly
162-
particle.update_position()
163-
164-
# Update the global best based on the personal best scores of particles
165-
best_particle = max(self.particles, key=lambda p: p.best_score)
166-
if best_particle.best_score > self.global_best_score:
167-
self.global_best_score = best_particle.best_score
168-
self.global_best_position = tf.identity(best_particle.best_position)
169-
170-
self.save_images(iteration)
171-
self.log_progress(iteration)
177+
denoised_adv = []
178+
total_pixels = np.prod(original_img.shape) # Total pixels in the image
179+
180+
for adv_particle in tqdm(self.particles, desc="Processing Particles", unit="particle"):
181+
adv_img = np.copy(adv_particle.position).reshape(original_img.shape) # Copy to avoid modifying the original
182+
183+
if original_img.shape != adv_img.shape:
184+
raise ValueError("original_img and adv_img must have the same shape.")
185+
186+
# Iterate over every pixel coordinate in the image with tqdm progress bar
187+
with tqdm(total=total_pixels, desc="Processing Pixels", unit="pixel", leave=False) as pbar:
188+
for idx in np.ndindex(original_img.shape):
189+
if original_img[idx] == adv_img[idx]: # Ignore unchanged pixels
190+
pbar.update(1)
191+
continue
192+
193+
# Store old adversarial value
194+
old_val = adv_img[idx]
195+
196+
# Try restoring the pixel to original
197+
adv_img[idx] = original_img[idx]
198+
199+
# Check if the label is still the target label
200+
output = self.model(adv_img.reshape(model_shape))
201+
softmax_output = tf.nn.softmax(tf.squeeze(output), axis=0).numpy()
202+
current_label = np.argmax(softmax_output)
203+
204+
if current_label != target_label:
205+
# If misclassification is lost, try halfway adjustment
206+
adv_img[idx] = old_val
207+
adv_img[idx] += (original_img[idx] - adv_img[idx]) * 0.5
208+
209+
# Recheck if the label is still the target
210+
output = self.model(adv_img.reshape(model_shape))
211+
softmax_output = tf.nn.softmax(tf.squeeze(output), axis=0).numpy()
212+
current_label = np.argmax(softmax_output)
213+
214+
if current_label != target_label:
215+
# If misclassification is still lost, revert back
216+
adv_img[idx] = old_val
217+
218+
pbar.update(1) # Update pixel progress
219+
220+
denoised_adv.append(adv_img)
221+
222+
return denoised_adv

manuscripts/Posion25/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
AudioMNIST*
2+
results/
3+
*.pyc
4+
__pycache__/
5+
*.pkl
6+
*.keras

0 commit comments

Comments
 (0)