Skip to content

Commit 9af7b65

Browse files
committed
updating for poison paper
1 parent c4ab63f commit 9af7b65

File tree

11 files changed

+442
-190
lines changed

11 files changed

+442
-190
lines changed

.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
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import argparse
2+
import os
3+
from train import load_data, train_model, evaluate_model, train_model_and_save
4+
5+
6+
def main():
7+
# Command-line arguments
8+
parser = argparse.ArgumentParser()
9+
10+
# Data and model type arguments
11+
parser.add_argument('--data', type=str, choices=['MNIST', 'MNIST_Audio'], required=True, help='Dataset to use')
12+
parser.add_argument('--model_type', type=str, choices=['normal', 'complex', 'complex_augmented'], required=True, help='Model type to use')
13+
14+
# Training information arguments
15+
parser.add_argument('--batch_size', type=int, default=32, help='Batch size for training')
16+
parser.add_argument('--epochs', type=int, default=10, help='Number of epochs for training')
17+
18+
# Folder saving argument
19+
parser.add_argument('--save_dir', type=str, default='results', help='Directory to save model and results')
20+
21+
# Parse arguments
22+
args = parser.parse_args()
23+
24+
# Train the model and evaluate it
25+
train_model_and_save(args)
26+
27+
if __name__ == '__main__':
28+
main()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import argparse
2+
import os
3+
import pickle
4+
from taint import adversarial_attack_blackbox
5+
from analysis import *
6+
from train import train_model_and_save
7+
8+
def attack_model(args, model, test_ds, save_dir):
9+
# Path to the pickle file that stores the attacker object
10+
pickle_path = os.path.join(save_dir, 'attacker.pkl')
11+
12+
# Check if the adversarial attack has already been performed (if pickle exists)
13+
if os.path.exists(pickle_path):
14+
# If pickle exists, load the attacker from the file
15+
with open(pickle_path, 'rb') as f:
16+
attacker = pickle.load(f)
17+
print(f"Loaded attacker from {pickle_path}")
18+
else:
19+
# If pickle does not exist, run the attack and save the attacker
20+
print("Running adversarial attack...")
21+
adversarial_attack_blackbox(
22+
model, test_ds, image_index=0, output_dir=save_dir,
23+
num_iterations=args.iterations, num_particles=args.particles
24+
)
25+
26+
27+
def main():
28+
# Command-line arguments
29+
parser = argparse.ArgumentParser()
30+
31+
# Data and model type arguments (to align with the ones used in the training script)
32+
parser.add_argument('--data', type=str, choices=['MNIST', 'MNIST_Audio'], required=True, help='Dataset to use')
33+
parser.add_argument('--model_type', type=str, choices=['normal', 'complex', 'complex_augmented'], required=True, help='Model type to use')
34+
35+
# Attack parameters
36+
parser.add_argument('--iterations', type=int, default=10, help='Number of iterations for attack')
37+
parser.add_argument('--particles', type=int, default=100, help='Number of particles for attack')
38+
39+
# Folder saving argument
40+
parser.add_argument('--save_dir', type=str, default='results', help='Directory to save model and results')
41+
42+
# Parse arguments
43+
args = parser.parse_args()
44+
45+
# First, train the model and get the necessary details for attack
46+
model, test_ds, save_dir, model_path = train_model_and_save(args)
47+
48+
# Perform the adversarial attack
49+
attack_model(args, model, test_ds, save_dir)
50+
51+
if __name__ == '__main__':
52+
main()

manuscripts/Posion25/3_stats.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import argparse
2+
import os
3+
import numpy as np
4+
import pickle
5+
import tensorflow as tf
6+
import json
7+
from tqdm import tqdm
8+
from taint import adversarial_attack_blackbox
9+
from train import train_model_and_save
10+
from analysis import get_softmax_stats, save_softmax_stats
11+
12+
13+
def collect_statistics(model, dataset, model_type, attack_iterations=10, attack_particles=100, image_index=0, output_dir='results'):
14+
"""
15+
Run adversarial attack for the given model and dataset combination and collect statistics.
16+
17+
Args:
18+
- model: The model to attack.
19+
- dataset: The test dataset.
20+
- model_type: The model type (for logging).
21+
- attack_iterations: Number of iterations for attack.
22+
- attack_particles: Number of particles for attack.
23+
- image_index: Index of the image to perform the attack on.
24+
- output_dir: Directory to save results.
25+
26+
Returns:
27+
- statistics: A dictionary with softmax output, attack success, and other relevant data.
28+
"""
29+
pickle_path = os.path.join(output_dir, f"{model_type}_attacker.pkl")
30+
31+
dataset_list = list(dataset.as_numpy_iterator())
32+
all_images, all_labels = zip(*dataset_list)
33+
all_images = np.concatenate(all_images, axis=0)
34+
all_labels = np.concatenate(all_labels, axis=0)
35+
36+
if image_index < 0 or image_index >= len(all_images):
37+
raise ValueError(f"Image index {image_index} out of range")
38+
39+
single_input = all_images[image_index]
40+
single_target = np.argmax(all_labels[image_index])
41+
target_class = (single_target + 1) % 10 # Attack a different class
42+
43+
# Perform the attack (check if pickle exists first)
44+
if os.path.exists(pickle_path):
45+
with open(pickle_path, 'rb') as f:
46+
attacker = pickle.load(f)
47+
print(f"Loaded attacker from {pickle_path}")
48+
else:
49+
adversarial_attack_blackbox(
50+
model, dataset, image_index=image_index, output_dir=output_dir,
51+
num_iterations=attack_iterations, num_particles=attack_particles
52+
)
53+
with open(pickle_path, 'wb') as f:
54+
pickle.dump(attacker, f)
55+
print(f"Saved attacker to {pickle_path}")
56+
57+
# Analyze the attack results
58+
softmax_output, max_val, max_class = get_softmax_stats(model, single_input)
59+
attack_success = max_class != target_class
60+
61+
stats = {
62+
"model_type": model_type,
63+
"target_class": target_class,
64+
"attack_success": attack_success,
65+
"softmax_output": softmax_output.tolist(),
66+
"max_confidence": max_val,
67+
"max_class": max_class,
68+
}
69+
70+
# Save softmax statistics for this model and image
71+
save_softmax_stats(os.path.join(output_dir, f"{model_type}_softmax_stats.tsv"), softmax_output, max_class, max_val, target_class)
72+
73+
return stats
74+
75+
76+
def get_model_types_for_dataset(dataset):
77+
"""
78+
Dynamically search for model types for a given dataset.
79+
80+
Args:
81+
- dataset: The dataset name, e.g., 'MNIST' or 'AudioMNIST'.
82+
83+
Returns:
84+
- model_types: List of available model types for the dataset.
85+
"""
86+
model_types = ['normal', 'complex', 'complex_augmented'] # Can be extended if needed
87+
88+
return model_types
89+
90+
91+
def run_statistics(args):
92+
# Define output directory to save results
93+
results_dir = os.path.join(args.save_dir, f"{args.data}_stats")
94+
os.makedirs(results_dir, exist_ok=True)
95+
96+
# Store statistics for all combinations of dataset and model types
97+
all_stats = []
98+
99+
# Get all possible model types for the dataset
100+
model_types = get_model_types_for_dataset(args.data)
101+
102+
# Iterate through all pairs of model types
103+
for model_type in model_types:
104+
print(f"Training model: {model_type}...")
105+
model, test_ds, _, model_path = train_model_and_save(args) # Train the model for the current type
106+
107+
# Run adversarial attack and collect stats for each combination of different model types
108+
for other_model_type in model_types:
109+
if model_type != other_model_type: # Skip 1-1 combinations
110+
print(f"Attacking {other_model_type} model with {model_type} dataset...")
111+
stats = collect_statistics(model, test_ds, other_model_type, attack_iterations=args.iterations, attack_particles=args.particles, output_dir=results_dir)
112+
all_stats.append(stats)
113+
114+
# Save the collected statistics as a JSON file for later analysis
115+
stats_file = os.path.join(results_dir, f"{args.data}_attack_stats.json")
116+
with open(stats_file, 'w') as f:
117+
json.dump(all_stats, f, indent=4)
118+
119+
print(f"Statistics saved to {stats_file}")
120+
121+
122+
def main():
123+
# Command-line arguments
124+
parser = argparse.ArgumentParser()
125+
126+
# Data and model type arguments
127+
parser.add_argument('--data', type=str, choices=['MNIST', 'MNIST_Audio'], required=True, help='Dataset to use')
128+
129+
# Attack parameters
130+
parser.add_argument('--iterations', type=int, default=10, help='Number of iterations for attack')
131+
parser.add_argument('--particles', type=int, default=100, help='Number of particles for attack')
132+
133+
# Folder saving argument
134+
parser.add_argument('--save_dir', type=str, default='results', help='Directory to save model and results')
135+
136+
# Parse arguments
137+
args = parser.parse_args()
138+
139+
# Run the statistics collection
140+
run_statistics(args)
141+
142+
143+
if __name__ == '__main__':
144+
main()

manuscripts/Posion25/analysis.py

Lines changed: 0 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -124,129 +124,3 @@ def save_softmax_stats(path, softmax_output, max_class, max_val, target):
124124
f.write(f"{i}\t{val}\n")
125125
f.write(f"\nBest Class\t{max_class}\nMax Confidence\t{max_val}\nTarget Class\t{target}\n")
126126

127-
128-
def best_analysis(attacker, original_data, target):
129-
adv = attacker.global_best_position.numpy()
130-
save_dir = attacker.save_dir
131-
ensure_dir(save_dir)
132-
133-
# save the original data
134-
save_array_csv(os.path.join(save_dir, "original_data.csv"), original_data)
135-
save_ndarray_visualization(os.path.join(save_dir, "original_data.png"), original_data)
136-
137-
# Save best particle
138-
save_array_csv(os.path.join(save_dir, "best_particle.csv"), adv)
139-
save_ndarray_visualization(os.path.join(save_dir, "best_particle.png"), adv)
140-
141-
# Save difference
142-
diff = original_data - adv
143-
save_array_csv(os.path.join(save_dir, "attack_vector_best_particle.csv"), diff)
144-
save_ndarray_visualization(
145-
os.path.join(save_dir, "attack_vector_best_particle.png"),
146-
diff, mode="auto", cmap="seismic", vmin=-1, vmax=1
147-
)
148-
149-
# Save stats
150-
softmax_output, max_val, max_class = get_softmax_stats(attacker.model, adv)
151-
save_softmax_stats(os.path.join(save_dir, "best_particle_stats.tsv"), softmax_output, max_class, max_val, target)
152-
153-
154-
def denoise_analysis(attacker, original_data, denoised_data, target):
155-
save_dir = attacker.save_dir
156-
ensure_dir(save_dir)
157-
158-
save_array_csv(os.path.join(save_dir, "best_particle-clean.csv"), denoised_data)
159-
save_ndarray_visualization(os.path.join(save_dir, "best_particle-clean.png"), denoised_data)
160-
161-
diff = original_data - denoised_data
162-
save_array_csv(os.path.join(save_dir, "attack_vector_best_particle-clean.csv"), diff)
163-
save_ndarray_visualization(
164-
os.path.join(save_dir, "attack_vector_best_particle-clean.png"),
165-
diff, mode="auto", cmap="seismic", vmin=-1, vmax=1
166-
)
167-
168-
softmax_output, max_val, max_class = get_softmax_stats(attacker.model, denoised_data)
169-
save_softmax_stats(os.path.join(save_dir, "best_particle-clean_stats.tsv"), softmax_output, max_class, max_val, target)
170-
171-
172-
def reduce_excess_perturbations(attacker, original_data, adv_data, target_label):
173-
"""
174-
Reduce unnecessary perturbations in adversarial data while maintaining misclassification.
175-
Works for data of any shape.
176-
"""
177-
original_data = np.squeeze(original_data)
178-
adv_data = np.squeeze(adv_data)
179-
180-
if original_data.shape != adv_data.shape:
181-
raise ValueError("Original and adversarial data must have the same shape after squeezing.")
182-
183-
adv_data = adv_data.copy()
184-
changed = True
185-
186-
# Wrap the iteration with tqdm to monitor progress
187-
while changed:
188-
changed = False
189-
indices = list(np.ndindex(original_data.shape))
190-
for idx in tqdm(indices, desc="Reducing perturbations"):
191-
if np.isclose(original_data[idx], adv_data[idx]):
192-
continue
193-
194-
original_val = original_data[idx]
195-
current_val = adv_data[idx]
196-
197-
# Try reverting completely
198-
adv_data[idx] = original_val
199-
pred = predict_class(attacker.model, adv_data)
200-
201-
if pred != target_label:
202-
# Try partial revert
203-
adv_data[idx] = current_val + 0.5 * (original_val - current_val)
204-
pred = predict_class(attacker.model, adv_data)
205-
if pred != target_label:
206-
adv_data[idx] = current_val
207-
else:
208-
changed = True
209-
else:
210-
changed = True
211-
212-
return adv_data
213-
214-
215-
def full_analysis(attacker, input_data, target):
216-
"""
217-
Save full analysis of all particles' histories and confidences.
218-
"""
219-
analysis = {
220-
"original_misclassification_input": input_data.tolist(),
221-
"original_misclassification_target": int(target),
222-
"particles": []
223-
}
224-
225-
for i, particle in tqdm(enumerate(attacker.particles), total=len(attacker.particles), desc="Full Analysis"):
226-
pdata = {
227-
"particle_index": i,
228-
"positions": [],
229-
"confidence_values": [],
230-
"max_output_values": [],
231-
"max_output_classes": [],
232-
"differences_from_original": []
233-
}
234-
235-
for pos in tqdm(particle.history, desc=f"Particle {i} history", leave=False):
236-
pos_np = pos.numpy() if isinstance(pos, tf.Tensor) else np.array(pos)
237-
softmax, max_val, max_class = get_softmax_stats(attacker.model, pos_np)
238-
diff = float(np.linalg.norm(pos_np - input_data))
239-
240-
pdata["positions"].append(pos_np.tolist())
241-
pdata["confidence_values"].append(softmax.tolist())
242-
pdata["max_output_values"].append(max_val)
243-
pdata["max_output_classes"].append(max_class)
244-
pdata["differences_from_original"].append(diff)
245-
246-
analysis["particles"].append(pdata)
247-
248-
path = os.path.join(attacker.save_dir, "attack_analysis.json")
249-
with open(path, "w") as f:
250-
json.dump(analysis, f, indent=4)
251-
252-
print(f"Full analysis saved to {path}")

0 commit comments

Comments
 (0)