Leeps commited on
Commit
f7e20e0
·
1 Parent(s): c2a7cc0

Add adversarial playground app

Browse files
Files changed (3) hide show
  1. README.md +10 -6
  2. app.py +520 -0
  3. requirements.txt +5 -0
README.md CHANGED
@@ -1,13 +1,17 @@
1
  ---
2
- title: Adversarial Playground
3
- emoji: 📊
4
  colorFrom: blue
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: 6.19.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
1
  ---
2
+ title: ResNet50 Adversarial Image Playground
3
+ emoji: 🧠
4
  colorFrom: blue
5
+ colorTo: pink
6
  sdk: gradio
7
+ sdk_version: 5.22.0
 
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # ResNet50 Adversarial Image Playground
14
+
15
+ A CPU-friendly Gradio app adapted from `../reference/pset3.ipynb`.
16
+
17
+ The app keeps the notebook's core ideas visible: ResNet50 image classification, logits vs. softmax probabilities, and a targeted gradient-based image attack. The robust checkpoint section from the original notebook is omitted so the interface stays portable and does not depend on the external course-server checkpoint.
app.py ADDED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from functools import lru_cache
3
+
4
+ os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
5
+
6
+ import gradio as gr
7
+ import numpy as np
8
+ import torch
9
+ import torch.nn.functional as F
10
+ import torchvision.models as models
11
+ from PIL import Image, ImageDraw, ImageFilter
12
+
13
+
14
+ torch.set_num_threads(max(1, min(4, os.cpu_count() or 1)))
15
+
16
+ APP_TITLE = "ResNet50 Adversarial Image Playground"
17
+ DEVICE = torch.device("cpu")
18
+ IMAGE_SIZE = 224
19
+ RESIZE_SHORT_EDGE = 256
20
+ IMAGENET_MEAN = torch.tensor([0.485, 0.456, 0.406], device=DEVICE).view(1, 3, 1, 1)
21
+ IMAGENET_STD = torch.tensor([0.229, 0.224, 0.225], device=DEVICE).view(1, 3, 1, 1)
22
+ PRETRAINED = "Pretrained ImageNet"
23
+ RANDOM = "Random initialization"
24
+ DEFAULT_TARGET = "76: tarantula"
25
+
26
+
27
+ PREPARE_IMAGE_CODE = """normalize = transforms.Normalize(
28
+ mean=[0.485, 0.456, 0.406],
29
+ std=[0.229, 0.224, 0.225],
30
+ )
31
+
32
+ def prepare_image(image):
33
+ image = resize_short_edge(image, 256)
34
+ image = center_crop(image, 224)
35
+ tensor_img = transforms.functional.to_tensor(image)
36
+ tensor_img = normalize(tensor_img)
37
+ return torch.unsqueeze(tensor_img, 0)
38
+ """
39
+
40
+ SOFTMAX_CODE = """def output2prob(output):
41
+ prob = torch.nn.functional.softmax(output, dim=1)
42
+ return prob
43
+ """
44
+
45
+ ATTACK_CODE = """def targeted_attack(model, x_pixels, target_id, eps=8/255, alpha=1/255):
46
+ x_adv = x_pixels.clone()
47
+ target = torch.tensor([target_id])
48
+
49
+ for _ in range(n_iter):
50
+ x_adv.requires_grad_(True)
51
+ logits = model(normalize(x_adv))
52
+ loss = torch.nn.functional.cross_entropy(logits, target)
53
+ gradient, = torch.autograd.grad(loss, x_adv)
54
+
55
+ # Targeted attack: move the image in the direction that lowers
56
+ # the loss for the target class.
57
+ x_adv = x_adv - alpha * gradient.sign()
58
+ delta = torch.clamp(x_adv - x_pixels, -eps, eps)
59
+ x_adv = torch.clamp(x_pixels + delta, 0, 1).detach()
60
+
61
+ return x_adv
62
+ """
63
+
64
+
65
+ def make_sample_images():
66
+ samples = []
67
+ size = 384
68
+
69
+ def canvas(bg):
70
+ return Image.new("RGB", (size, size), bg)
71
+
72
+ img = canvas((242, 238, 228))
73
+ draw = ImageDraw.Draw(img)
74
+ draw.ellipse((86, 92, 300, 310), fill=(195, 124, 54), outline=(92, 56, 35), width=8)
75
+ draw.ellipse((104, 48, 184, 152), fill=(218, 155, 80), outline=(92, 56, 35), width=6)
76
+ draw.ellipse((216, 48, 296, 152), fill=(218, 155, 80), outline=(92, 56, 35), width=6)
77
+ draw.ellipse((138, 156, 166, 184), fill=(25, 25, 25))
78
+ draw.ellipse((234, 156, 262, 184), fill=(25, 25, 25))
79
+ draw.polygon([(192, 202), (172, 228), (212, 228)], fill=(48, 33, 30))
80
+ draw.arc((150, 216, 194, 264), 8, 78, fill=(48, 33, 30), width=6)
81
+ draw.arc((190, 216, 234, 264), 102, 172, fill=(48, 33, 30), width=6)
82
+ samples.append((img.filter(ImageFilter.SMOOTH), "simple dog sketch"))
83
+
84
+ img = canvas((18, 22, 28))
85
+ draw = ImageDraw.Draw(img)
86
+ for radius, color in zip(
87
+ range(170, 18, -24),
88
+ [(231, 76, 60), (241, 196, 15), (52, 152, 219), (46, 204, 113), (236, 240, 241), (230, 126, 34)],
89
+ ):
90
+ draw.ellipse((192 - radius, 192 - radius, 192 + radius, 192 + radius), outline=color, width=14)
91
+ samples.append((img, "concentric rings"))
92
+
93
+ img = canvas((236, 238, 230))
94
+ draw = ImageDraw.Draw(img)
95
+ for x in range(-160, size + 160, 34):
96
+ draw.line((x, 0, x + 210, size), fill=(33, 73, 110), width=10)
97
+ draw.line((x + 16, 0, x + 226, size), fill=(211, 71, 54), width=4)
98
+ samples.append((img, "diagonal stripes"))
99
+
100
+ img = canvas((34, 42, 45))
101
+ draw = ImageDraw.Draw(img)
102
+ rng = np.random.default_rng(7)
103
+ for _ in range(60):
104
+ x, y = rng.integers(10, size - 70, 2)
105
+ w, h = rng.integers(22, 110, 2)
106
+ color = tuple(int(v) for v in rng.integers(70, 245, 3))
107
+ draw.rounded_rectangle((x, y, x + w, y + h), radius=5, outline=color, width=4)
108
+ samples.append((img, "overlapping rectangles"))
109
+
110
+ return samples
111
+
112
+
113
+ SAMPLE_IMAGES = make_sample_images()
114
+
115
+
116
+ @lru_cache(maxsize=1)
117
+ def class_names():
118
+ return list(models.ResNet50_Weights.DEFAULT.meta["categories"])
119
+
120
+
121
+ @lru_cache(maxsize=1)
122
+ def target_choices():
123
+ return [f"{index}: {name}" for index, name in enumerate(class_names())]
124
+
125
+
126
+ @lru_cache(maxsize=2)
127
+ def load_model(weight_mode):
128
+ status = ""
129
+ if weight_mode == RANDOM:
130
+ model = models.resnet50(weights=None)
131
+ status = "Randomly initialized ResNet50. Predictions are intentionally not meaningful."
132
+ else:
133
+ try:
134
+ model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
135
+ status = "Pretrained ResNet50 loaded from torchvision ImageNet weights."
136
+ except Exception as exc:
137
+ model = models.resnet50(weights=None)
138
+ status = f"Could not load pretrained weights: {exc}. Using random weights instead."
139
+
140
+ model.to(DEVICE)
141
+ model.eval()
142
+ for param in model.parameters():
143
+ param.requires_grad_(False)
144
+ return model, status
145
+
146
+
147
+ def rgb_image(image):
148
+ if image is None:
149
+ return SAMPLE_IMAGES[0][0]
150
+ if isinstance(image, np.ndarray):
151
+ image = Image.fromarray(image)
152
+ return image.convert("RGB")
153
+
154
+
155
+ def resize_short_edge(image, short_edge=RESIZE_SHORT_EDGE):
156
+ width, height = image.size
157
+ scale = short_edge / min(width, height)
158
+ new_size = (round(width * scale), round(height * scale))
159
+ return image.resize(new_size, Image.Resampling.BICUBIC)
160
+
161
+
162
+ def center_crop(image, size=IMAGE_SIZE):
163
+ width, height = image.size
164
+ left = (width - size) // 2
165
+ top = (height - size) // 2
166
+ return image.crop((left, top, left + size, top + size))
167
+
168
+
169
+ def image_to_pixels(image):
170
+ image = center_crop(resize_short_edge(rgb_image(image)))
171
+ arr = np.asarray(image).astype(np.float32) / 255.0
172
+ tensor = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0).to(DEVICE)
173
+ return tensor
174
+
175
+
176
+ def pixels_to_image(tensor):
177
+ arr = tensor.detach().cpu().clamp(0, 1).squeeze(0).permute(1, 2, 0).numpy()
178
+ arr = (arr * 255).round().astype(np.uint8)
179
+ return Image.fromarray(arr, mode="RGB")
180
+
181
+
182
+ def normalize(pixel_tensor):
183
+ return (pixel_tensor - IMAGENET_MEAN) / IMAGENET_STD
184
+
185
+
186
+ def parse_target(label):
187
+ try:
188
+ return int(str(label).split(":", 1)[0])
189
+ except Exception:
190
+ return 76
191
+
192
+
193
+ def top_prediction_rows(logits, score_mode="probability", top_k=5):
194
+ probs = F.softmax(logits, dim=1)
195
+ source = probs if score_mode == "probability" else logits
196
+ k = max(1, min(int(top_k), logits.shape[1]))
197
+ values, indices = torch.topk(source, k=k, dim=1)
198
+ rows = []
199
+ names = class_names()
200
+ for rank, (value, class_id) in enumerate(zip(values[0], indices[0]), start=1):
201
+ cid = int(class_id)
202
+ rows.append(
203
+ [
204
+ rank,
205
+ cid,
206
+ names[cid],
207
+ round(float(probs[0, cid]), 6),
208
+ round(float(logits[0, cid]), 4),
209
+ round(float(value), 6),
210
+ ]
211
+ )
212
+ return rows
213
+
214
+
215
+ def classify_pixels(model, pixel_tensor):
216
+ with torch.inference_mode():
217
+ return model(normalize(pixel_tensor))
218
+
219
+
220
+ def classify_image(image, weight_mode=PRETRAINED, score_mode="probability", top_k=5):
221
+ model, status = load_model(weight_mode)
222
+ pixels = image_to_pixels(image)
223
+ logits = classify_pixels(model, pixels)
224
+ rows = top_prediction_rows(logits, score_mode, top_k)
225
+ return pixels_to_image(pixels), rows, status
226
+
227
+
228
+ def initial_classifier_view():
229
+ pixels = image_to_pixels(SAMPLE_IMAGES[0][0])
230
+ return pixels_to_image(pixels), [], "Choose an image, then run the classifier."
231
+
232
+
233
+ def classify_sample(weight_mode, score_mode, top_k, evt: gr.SelectData):
234
+ index = evt.index if isinstance(evt.index, int) else 0
235
+ image = SAMPLE_IMAGES[index][0]
236
+ return classify_image(image, weight_mode, score_mode, top_k)
237
+
238
+
239
+ def difference_image(original, attacked, epsilon_pixels):
240
+ diff = torch.abs(attacked - original).mean(dim=1).squeeze(0).detach().cpu().numpy()
241
+ scale = max(float(epsilon_pixels) / 255.0, 1e-6)
242
+ heat = np.clip(diff / scale, 0, 1)
243
+ rgb = np.zeros((IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.uint8)
244
+ rgb[:, :, 0] = (255 * heat).astype(np.uint8)
245
+ rgb[:, :, 1] = (210 * np.sqrt(heat)).astype(np.uint8)
246
+ rgb[:, :, 2] = (35 * (1 - heat)).astype(np.uint8)
247
+ return Image.fromarray(rgb, mode="RGB")
248
+
249
+
250
+ def targeted_attack(image, target_label, iterations, epsilon_pixels, step_pixels, weight_mode, top_k):
251
+ model, status = load_model(weight_mode)
252
+ original = image_to_pixels(image)
253
+ attacked = original.clone().detach()
254
+ target_id = parse_target(target_label)
255
+ target = torch.tensor([target_id], device=DEVICE)
256
+ eps = float(epsilon_pixels) / 255.0
257
+ alpha = float(step_pixels) / 255.0
258
+ iterations = max(1, int(iterations))
259
+ trace = []
260
+
261
+ start_logits = classify_pixels(model, original)
262
+ start_prob = float(F.softmax(start_logits, dim=1)[0, target_id])
263
+
264
+ for index in range(iterations):
265
+ attacked.requires_grad_(True)
266
+ logits = model(normalize(attacked))
267
+ loss = F.cross_entropy(logits, target)
268
+ gradient, = torch.autograd.grad(loss, attacked)
269
+
270
+ with torch.no_grad():
271
+ attacked = attacked - alpha * gradient.sign()
272
+ delta = torch.clamp(attacked - original, -eps, eps)
273
+ attacked = torch.clamp(original + delta, 0, 1).detach()
274
+
275
+ if index in {0, iterations // 2, iterations - 1}:
276
+ with torch.inference_mode():
277
+ trace_logits = model(normalize(attacked))
278
+ trace_prob = F.softmax(trace_logits, dim=1)
279
+ top_id = int(torch.argmax(trace_prob, dim=1)[0])
280
+ trace.append(
281
+ [
282
+ index + 1,
283
+ class_names()[target_id],
284
+ round(float(trace_prob[0, target_id]), 6),
285
+ class_names()[top_id],
286
+ round(float(trace_prob[0, top_id]), 6),
287
+ ]
288
+ )
289
+
290
+ final_logits = classify_pixels(model, attacked)
291
+ final_prob = float(F.softmax(final_logits, dim=1)[0, target_id])
292
+ before_rows = top_prediction_rows(start_logits, "probability", top_k)
293
+ after_rows = top_prediction_rows(final_logits, "probability", top_k)
294
+ summary = (
295
+ f"{status}\n"
296
+ f"Target class {target_id} ({class_names()[target_id]}): "
297
+ f"{start_prob:.4f} -> {final_prob:.4f} probability after {iterations} iterations. "
298
+ f"Perturbation budget: +/-{float(epsilon_pixels):.1f} pixel values."
299
+ )
300
+ return (
301
+ pixels_to_image(original),
302
+ pixels_to_image(attacked),
303
+ difference_image(original, attacked, epsilon_pixels),
304
+ before_rows,
305
+ after_rows,
306
+ trace,
307
+ summary,
308
+ )
309
+
310
+
311
+ def initial_attack_view():
312
+ pixels = image_to_pixels(SAMPLE_IMAGES[0][0])
313
+ blank = Image.new("RGB", (IMAGE_SIZE, IMAGE_SIZE), (28, 32, 38))
314
+ return pixels_to_image(pixels), blank, blank, [], [], [], "Choose an image and target class, then run the attack."
315
+
316
+
317
+ def attack_sample(target_label, iterations, epsilon_pixels, step_pixels, weight_mode, top_k, evt: gr.SelectData):
318
+ index = evt.index if isinstance(evt.index, int) else 0
319
+ image = SAMPLE_IMAGES[index][0]
320
+ return targeted_attack(image, target_label, iterations, epsilon_pixels, step_pixels, weight_mode, top_k)
321
+
322
+
323
+ def build_app():
324
+ theme = gr.themes.Soft(
325
+ primary_hue="teal",
326
+ secondary_hue="rose",
327
+ neutral_hue="slate",
328
+ radius_size="sm",
329
+ )
330
+
331
+ css = """
332
+ .sample-gallery img { object-fit: cover !important; }
333
+ .code-panel textarea, .code-panel pre { font-size: 13px !important; }
334
+ """
335
+
336
+ headers = ["rank", "class id", "class", "probability", "logit", "shown score"]
337
+ trace_headers = ["iteration", "target", "target probability", "top class", "top probability"]
338
+
339
+ with gr.Blocks(title=APP_TITLE, theme=theme, css=css) as demo:
340
+ gr.Markdown(f"# {APP_TITLE}")
341
+
342
+ with gr.Tab("Classifier"):
343
+ with gr.Row(equal_height=False):
344
+ with gr.Column(scale=1, min_width=300):
345
+ classifier_samples = gr.Gallery(
346
+ value=SAMPLE_IMAGES,
347
+ label="Sample images",
348
+ columns=2,
349
+ rows=2,
350
+ height=300,
351
+ object_fit="cover",
352
+ elem_classes=["sample-gallery"],
353
+ )
354
+ classifier_upload = gr.Image(label="Upload image", type="pil", sources=["upload", "clipboard"])
355
+ with gr.Row():
356
+ weight_mode = gr.Radio([PRETRAINED, RANDOM], value=PRETRAINED, label="Weights")
357
+ score_mode = gr.Radio(["probability", "logit"], value="probability", label="Displayed score")
358
+ top_k = gr.Slider(3, 10, value=5, step=1, label="Top classes")
359
+ classify_button = gr.Button("Run classifier", variant="primary")
360
+
361
+ with gr.Column(scale=1, min_width=300):
362
+ classifier_image = gr.Image(label="Prepared 224x224 crop", type="pil", interactive=False)
363
+ classifier_status = gr.Textbox(label="Model status", interactive=False, lines=3)
364
+ classifier_predictions = gr.Dataframe(
365
+ headers=headers,
366
+ datatype=["number", "number", "str", "number", "number", "number"],
367
+ label="Top predictions",
368
+ interactive=False,
369
+ )
370
+
371
+ with gr.Tab("Targeted attack"):
372
+ with gr.Row(equal_height=False):
373
+ with gr.Column(scale=1, min_width=300):
374
+ attack_samples = gr.Gallery(
375
+ value=SAMPLE_IMAGES,
376
+ label="Sample images",
377
+ columns=2,
378
+ rows=2,
379
+ height=300,
380
+ object_fit="cover",
381
+ elem_classes=["sample-gallery"],
382
+ )
383
+ attack_upload = gr.Image(label="Upload image", type="pil", sources=["upload", "clipboard"])
384
+ target = gr.Dropdown(choices=target_choices(), value=DEFAULT_TARGET, label="Target class")
385
+ with gr.Row():
386
+ attack_weight_mode = gr.Radio([PRETRAINED, RANDOM], value=PRETRAINED, label="Weights")
387
+ attack_top_k = gr.Slider(3, 10, value=5, step=1, label="Top classes")
388
+ with gr.Row():
389
+ iterations = gr.Slider(1, 60, value=16, step=1, label="Iterations")
390
+ epsilon = gr.Slider(1, 24, value=8, step=1, label="Pixel budget")
391
+ step_size = gr.Slider(0.25, 4, value=1, step=0.25, label="Step size")
392
+ attack_button = gr.Button("Run targeted attack", variant="primary")
393
+
394
+ with gr.Column(scale=1, min_width=300):
395
+ attack_summary = gr.Textbox(label="Attack summary", interactive=False, lines=4)
396
+ with gr.Row():
397
+ original_image = gr.Image(label="Original crop", type="pil", interactive=False)
398
+ attacked_image = gr.Image(label="Attacked crop", type="pil", interactive=False)
399
+ perturbation = gr.Image(label="Perturbation heat map", type="pil", interactive=False)
400
+
401
+ with gr.Row(equal_height=False):
402
+ before_predictions = gr.Dataframe(
403
+ headers=headers,
404
+ datatype=["number", "number", "str", "number", "number", "number"],
405
+ label="Before attack",
406
+ interactive=False,
407
+ )
408
+ after_predictions = gr.Dataframe(
409
+ headers=headers,
410
+ datatype=["number", "number", "str", "number", "number", "number"],
411
+ label="After attack",
412
+ interactive=False,
413
+ )
414
+ attack_trace = gr.Dataframe(
415
+ headers=trace_headers,
416
+ datatype=["number", "str", "number", "str", "number"],
417
+ label="Optimization trace",
418
+ interactive=False,
419
+ )
420
+
421
+ with gr.Tab("Code cells"):
422
+ with gr.Row(equal_height=False):
423
+ with gr.Column():
424
+ gr.Code(PREPARE_IMAGE_CODE, language="python", label="Prepare image", interactive=False, elem_classes=["code-panel"])
425
+ gr.Code(SOFTMAX_CODE, language="python", label="Logits to probabilities", interactive=False, elem_classes=["code-panel"])
426
+ with gr.Column():
427
+ gr.Code(ATTACK_CODE, language="python", label="Targeted attack loop", interactive=False, elem_classes=["code-panel"])
428
+
429
+ classifier_samples.select(
430
+ classify_sample,
431
+ inputs=[weight_mode, score_mode, top_k],
432
+ outputs=[classifier_image, classifier_predictions, classifier_status],
433
+ show_progress="minimal",
434
+ )
435
+ classify_button.click(
436
+ classify_image,
437
+ inputs=[classifier_upload, weight_mode, score_mode, top_k],
438
+ outputs=[classifier_image, classifier_predictions, classifier_status],
439
+ show_progress="minimal",
440
+ )
441
+ classifier_upload.change(
442
+ classify_image,
443
+ inputs=[classifier_upload, weight_mode, score_mode, top_k],
444
+ outputs=[classifier_image, classifier_predictions, classifier_status],
445
+ show_progress="minimal",
446
+ )
447
+ weight_mode.change(
448
+ classify_image,
449
+ inputs=[classifier_upload, weight_mode, score_mode, top_k],
450
+ outputs=[classifier_image, classifier_predictions, classifier_status],
451
+ show_progress="minimal",
452
+ )
453
+ score_mode.change(
454
+ classify_image,
455
+ inputs=[classifier_upload, weight_mode, score_mode, top_k],
456
+ outputs=[classifier_image, classifier_predictions, classifier_status],
457
+ show_progress="minimal",
458
+ )
459
+ top_k.change(
460
+ classify_image,
461
+ inputs=[classifier_upload, weight_mode, score_mode, top_k],
462
+ outputs=[classifier_image, classifier_predictions, classifier_status],
463
+ show_progress="minimal",
464
+ )
465
+
466
+ attack_samples.select(
467
+ attack_sample,
468
+ inputs=[target, iterations, epsilon, step_size, attack_weight_mode, attack_top_k],
469
+ outputs=[
470
+ original_image,
471
+ attacked_image,
472
+ perturbation,
473
+ before_predictions,
474
+ after_predictions,
475
+ attack_trace,
476
+ attack_summary,
477
+ ],
478
+ show_progress="minimal",
479
+ )
480
+ attack_button.click(
481
+ targeted_attack,
482
+ inputs=[attack_upload, target, iterations, epsilon, step_size, attack_weight_mode, attack_top_k],
483
+ outputs=[
484
+ original_image,
485
+ attacked_image,
486
+ perturbation,
487
+ before_predictions,
488
+ after_predictions,
489
+ attack_trace,
490
+ attack_summary,
491
+ ],
492
+ show_progress="minimal",
493
+ )
494
+
495
+ demo.load(
496
+ initial_classifier_view,
497
+ inputs=None,
498
+ outputs=[classifier_image, classifier_predictions, classifier_status],
499
+ show_progress="minimal",
500
+ )
501
+ demo.load(
502
+ initial_attack_view,
503
+ inputs=None,
504
+ outputs=[
505
+ original_image,
506
+ attacked_image,
507
+ perturbation,
508
+ before_predictions,
509
+ after_predictions,
510
+ attack_trace,
511
+ attack_summary,
512
+ ],
513
+ show_progress="minimal",
514
+ )
515
+
516
+ return demo
517
+
518
+
519
+ if __name__ == "__main__":
520
+ build_app().launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio>=5.22.0
2
+ torch
3
+ torchvision
4
+ pillow
5
+ numpy