feat(attack-ui): add ground-truth aware evaluation and sample GT auto-selection
Browse files- add a searchable Ground Truth dropdown in the Adversarial Attack + Attention tab
- show the Ground Truth field only when class labels are available from the loaded model
- auto-populate Ground Truth when a sample image is selected, using data/sample_images/metadata.json mapping
- initialize Ground Truth choices on app load for the default model (not only on change events)
- update attack report logic to use Ground Truth as the reference label when provided
- keep backward-compatible fallback to original prediction when Ground Truth is not set
- make class-dependent metrics Ground Truth aware in utils/metrics (ASR, confidence_drop, topk_drop)
- propagate Ground Truth handling to the sweep runner and add ground_truth column to CSV output
- restore correct tab structure so Apuana Cluster UI remains in its own tab
- update README notes to document Ground Truth behavior in class-related metrics
- README.md +2 -1
- app.py +427 -265
- experiments/run_attack_sweep.py +37 -1
- utils/metrics.py +19 -14
|
@@ -111,7 +111,8 @@ cd notebooks && jupyter notebook
|
|
| 111 |
|
| 112 |
- **PerturbaΓ§Γ£o de imagem**: Lβ, PSNR, SSIM, LPIPS, Modified Pixels
|
| 113 |
- **Deslocamento de atenΓ§Γ£o**: Attention W1 (Rollout, padrΓ£o), Attention JSD (Rollout)
|
| 114 |
-
- **Ataque**: ASR, Confidence Drop, Top-k Overlap Drop
|
|
|
|
| 115 |
|
| 116 |
## Apuana Cluster Integration (UI)
|
| 117 |
|
|
|
|
| 111 |
|
| 112 |
- **PerturbaΓ§Γ£o de imagem**: Lβ, PSNR, SSIM, LPIPS, Modified Pixels
|
| 113 |
- **Deslocamento de atenΓ§Γ£o**: Attention W1 (Rollout, padrΓ£o), Attention JSD (Rollout)
|
| 114 |
+
- **Ataque**: ASR, Confidence Drop, Top-k Overlap Drop
|
| 115 |
+
- Na aba de ataque, quando **Ground Truth Label** Γ© informado, as mΓ©tricas de classe usam esse rΓ³tulo como referΓͺncia; sem GT, usam fallback para a prediΓ§Γ£o original.
|
| 116 |
|
| 117 |
## Apuana Cluster Integration (UI)
|
| 118 |
|
|
@@ -145,6 +145,50 @@ def _to_path(file_like: Optional[object]) -> Optional[str]:
|
|
| 145 |
return path
|
| 146 |
return None
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
def _print_model_heads(model):
|
| 149 |
"""Imprime quantidade de heads do ViT ao carregar o modelo. Mais usada para debug."""
|
| 150 |
try:
|
|
@@ -313,6 +357,7 @@ def run_attack(
|
|
| 313 |
decay: float,
|
| 314 |
vit_weight: float,
|
| 315 |
selected_metrics,
|
|
|
|
| 316 |
) -> Tuple[List[Image.Image], str, List[List[torch.Tensor]]]:
|
| 317 |
"""
|
| 318 |
Executa ataque adversarial (FGSM ou PGD) untargeted e extrai atenΓ§Γ£o.
|
|
@@ -385,6 +430,16 @@ def run_attack(
|
|
| 385 |
adv_class = top_idx_adv[0].item()
|
| 386 |
adv_prob = top_prob_adv[0].item()
|
| 387 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
# Resolver seleΓ§Γ£o de mΓ©tricas:
|
| 389 |
# - sem seleΓ§Γ£o ou com "Select All" => mostra todas
|
| 390 |
# - caso contrΓ‘rio, mostra apenas as escolhidas
|
|
@@ -425,9 +480,9 @@ def run_attack(
|
|
| 425 |
if "Modified Pixels" in requested_metrics:
|
| 426 |
metric_values["Modified Pixels"] = f"{compute_modified_pixels(img_tensor, adv_tensor):.1f}%"
|
| 427 |
if "Confidence Drop" in requested_metrics:
|
| 428 |
-
metric_values["Confidence Drop"] = f"{compute_confidence_drop(orig_probs_full, adv_probs_full,
|
| 429 |
if "Top-K Accuracy Drop" in requested_metrics:
|
| 430 |
-
topk_drop = compute_topk_drop(orig_probs_full, adv_probs_full,
|
| 431 |
metric_values["Top-K Accuracy Drop"] = "Yes" if topk_drop >= 0.5 else "No"
|
| 432 |
|
| 433 |
metric_tooltips = {
|
|
@@ -438,8 +493,8 @@ def run_attack(
|
|
| 438 |
"SSIM": "Structural Similarity Index comparing luminance, contrast, and structure. Values near 1 mean images are structurally similar.",
|
| 439 |
"LPIPS": "Learned Perceptual Image Patch Similarity based on deep features. Lower values indicate greater perceptual similarity.",
|
| 440 |
"Modified Pixels": "Percentage of pixels where perturbation exceeds 1e-5 (channel max). It approximates a sparse L0/Hamming-like change count.",
|
| 441 |
-
"Confidence Drop": "Decrease in
|
| 442 |
-
"Top-K Accuracy Drop": "Yes means the
|
| 443 |
}
|
| 444 |
|
| 445 |
def metric_label_with_info(label: str, tooltip_text: str) -> str:
|
|
@@ -458,7 +513,7 @@ def run_attack(
|
|
| 458 |
result = f"## {attack_type} Attack Result (Untargeted)\n\n"
|
| 459 |
|
| 460 |
# Status visual
|
| 461 |
-
if adv_class !=
|
| 462 |
status_icon = ICON_SUCCESS
|
| 463 |
status_text = "Success"
|
| 464 |
else:
|
|
@@ -470,6 +525,18 @@ def run_attack(
|
|
| 470 |
# Prediction Comparison (duas tabelas)
|
| 471 |
result += f"### {ICON_CHART} Prediction Comparison (Top-5)\n\n"
|
| 472 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
top_k_display = min(5, len(top_prob_orig), len(top_prob_adv))
|
| 474 |
result += "<div class=\"vitviz-panels\">\n"
|
| 475 |
|
|
@@ -1205,6 +1272,13 @@ def create_app():
|
|
| 1205 |
height: 100% !important;
|
| 1206 |
object-fit: contain !important;
|
| 1207 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1208 |
|
| 1209 |
@keyframes spin {
|
| 1210 |
0% { transform: rotate(0deg); }
|
|
@@ -1437,6 +1511,13 @@ def create_app():
|
|
| 1437 |
|
| 1438 |
default_eps_csv = ", ".join(str(v) for v in default_sweep_cfg.get("evaluation", {}).get("epsilons", [0.00784]))
|
| 1439 |
default_seeds_csv = ", ".join(str(v) for v in default_sweep_cfg.get("evaluation", {}).get("seeds", [42]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1440 |
initial_history_choices = _history_choices(limit=100)
|
| 1441 |
initial_history_value = initial_history_choices[0][1] if initial_history_choices else None
|
| 1442 |
show_apuana_tab = _should_show_apuana_tab()
|
|
@@ -1593,12 +1674,93 @@ def create_app():
|
|
| 1593 |
label="Upload Image",
|
| 1594 |
type="pil"
|
| 1595 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1596 |
|
| 1597 |
model_select_attack.change(
|
| 1598 |
fn=toggle_model_upload,
|
| 1599 |
inputs=[model_select_attack],
|
| 1600 |
outputs=[model_upload_attack]
|
| 1601 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1602 |
gr.Markdown("---")
|
| 1603 |
|
| 1604 |
# Attack Configuration Row
|
|
@@ -1916,7 +2078,7 @@ def create_app():
|
|
| 1916 |
inputs=[
|
| 1917 |
model_upload_attack, model_select_attack, image_upload_attack,
|
| 1918 |
attack_type, eps_input, alpha_input, steps_input, decay_input, vit_weight_slider,
|
| 1919 |
-
metrics_selector
|
| 1920 |
],
|
| 1921 |
outputs=[iteration_images_state, output_text_attack, cached_attentions_state]
|
| 1922 |
).then(
|
|
@@ -2088,273 +2250,273 @@ def create_app():
|
|
| 2088 |
with gr.Tab("Apuana Cluster"):
|
| 2089 |
gr.Markdown("### Configure experiments and submit jobs to Apuana via SSH")
|
| 2090 |
|
| 2091 |
-
|
| 2092 |
-
|
| 2093 |
-
with gr.Row():
|
| 2094 |
-
with gr.Column(scale=1):
|
| 2095 |
-
remote_user = gr.Textbox(label="Remote User", placeholder="seu_login_cin")
|
| 2096 |
-
remote_host = gr.Textbox(label="Remote Host", value=APUANA_DEFAULT_HOST)
|
| 2097 |
-
remote_project_dir = gr.Textbox(label="Remote Project Dir", value="~/ViTViz")
|
| 2098 |
-
remote_base_dir = gr.Textbox(label="Remote Jobs Base Dir", value="~/vitviz_jobs")
|
| 2099 |
-
env_activate = gr.Textbox(label="Env Activate Script", value="$HOME/ViTViz/.venv/bin/activate")
|
| 2100 |
-
python_module = gr.Textbox(label="Python Module", value="Python3.10")
|
| 2101 |
-
|
| 2102 |
-
with gr.Column(scale=1):
|
| 2103 |
-
config_mode = gr.Radio(
|
| 2104 |
-
choices=["Use Config File", "Build in UI"],
|
| 2105 |
-
value="Use Config File",
|
| 2106 |
-
label="Config Source",
|
| 2107 |
-
)
|
| 2108 |
-
config_file_group = gr.Group(visible=True)
|
| 2109 |
-
with config_file_group:
|
| 2110 |
-
experiment_config = gr.Dropdown(
|
| 2111 |
-
choices=cluster_experiment_choices,
|
| 2112 |
-
value=default_cluster_config,
|
| 2113 |
-
label="Experiment Config (local)",
|
| 2114 |
-
)
|
| 2115 |
-
|
| 2116 |
-
job_name = gr.Textbox(label="Job Name", value="vitviz_sweep")
|
| 2117 |
-
partition = gr.Dropdown(choices=APUANA_PARTITIONS, value="short-simple", label="Partition")
|
| 2118 |
-
qos_note = gr.Markdown(value="QoS auto: `simple`")
|
| 2119 |
-
|
| 2120 |
-
with gr.Column(scale=1):
|
| 2121 |
-
cpus = gr.Slider(1, 48, value=8, step=1, label="CPUs per task")
|
| 2122 |
-
mem = gr.Textbox(label="Memory", value="16G")
|
| 2123 |
-
gpus = gr.Slider(0, 4, value=1, step=1, label="GPUs")
|
| 2124 |
-
time_limit = gr.Textbox(label="Time Limit", value="02:00:00")
|
| 2125 |
-
|
| 2126 |
-
config_builder_group = gr.Group(visible=False)
|
| 2127 |
-
with config_builder_group:
|
| 2128 |
-
gr.Markdown("#### Dynamic Experiment Builder")
|
| 2129 |
with gr.Row():
|
| 2130 |
with gr.Column(scale=1):
|
| 2131 |
-
|
| 2132 |
-
|
| 2133 |
-
|
| 2134 |
-
|
| 2135 |
-
|
| 2136 |
-
)
|
| 2137 |
-
|
| 2138 |
-
label="Custom Models (one per line)",
|
| 2139 |
-
lines=5,
|
| 2140 |
-
placeholder="name|path|img_size|num_classes|type|dataset",
|
| 2141 |
-
info="Required fields: name|path. Optional fields use defaults.",
|
| 2142 |
-
)
|
| 2143 |
with gr.Column(scale=1):
|
| 2144 |
-
|
| 2145 |
-
choices=
|
| 2146 |
-
value=
|
| 2147 |
-
|
| 2148 |
-
label="Attacks",
|
| 2149 |
-
)
|
| 2150 |
-
eps_csv = gr.Textbox(
|
| 2151 |
-
label="Epsilons (CSV)",
|
| 2152 |
-
value=default_eps_csv,
|
| 2153 |
-
placeholder="0.00784,0.01568",
|
| 2154 |
-
)
|
| 2155 |
-
seeds_csv = gr.Textbox(
|
| 2156 |
-
label="Seeds (CSV)",
|
| 2157 |
-
value=default_seeds_csv,
|
| 2158 |
-
placeholder="42,123,456",
|
| 2159 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2160 |
with gr.Column(scale=1):
|
| 2161 |
-
|
| 2162 |
-
|
| 2163 |
-
|
| 2164 |
-
|
| 2165 |
-
|
| 2166 |
-
|
| 2167 |
-
|
| 2168 |
-
|
| 2169 |
-
|
| 2170 |
-
|
| 2171 |
-
|
| 2172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2173 |
with gr.Row():
|
| 2174 |
-
|
| 2175 |
-
|
| 2176 |
-
|
| 2177 |
-
|
| 2178 |
-
|
| 2179 |
-
|
| 2180 |
-
|
| 2181 |
-
|
| 2182 |
-
|
| 2183 |
-
|
| 2184 |
-
|
| 2185 |
-
|
| 2186 |
-
|
| 2187 |
-
|
| 2188 |
-
|
| 2189 |
-
|
| 2190 |
-
|
| 2191 |
-
|
| 2192 |
-
|
| 2193 |
-
|
| 2194 |
-
|
| 2195 |
-
|
| 2196 |
-
|
| 2197 |
-
|
| 2198 |
-
|
| 2199 |
-
|
| 2200 |
-
|
| 2201 |
-
|
| 2202 |
-
|
| 2203 |
-
|
| 2204 |
-
|
| 2205 |
-
|
| 2206 |
-
|
| 2207 |
-
|
| 2208 |
-
|
| 2209 |
-
|
| 2210 |
-
|
| 2211 |
-
|
| 2212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2213 |
)
|
| 2214 |
-
|
| 2215 |
-
|
| 2216 |
-
|
| 2217 |
-
|
| 2218 |
-
|
| 2219 |
-
|
| 2220 |
-
|
| 2221 |
-
|
| 2222 |
-
|
| 2223 |
-
|
| 2224 |
-
|
| 2225 |
-
|
| 2226 |
-
|
| 2227 |
-
|
| 2228 |
-
|
| 2229 |
-
|
| 2230 |
-
|
| 2231 |
-
|
| 2232 |
-
|
| 2233 |
-
|
| 2234 |
-
|
| 2235 |
-
|
| 2236 |
-
|
| 2237 |
-
|
| 2238 |
-
|
| 2239 |
-
|
| 2240 |
-
|
| 2241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2242 |
)
|
| 2243 |
-
with gr.Row():
|
| 2244 |
-
btn_refresh_history = gr.Button("Refresh History", variant="secondary")
|
| 2245 |
-
btn_load_selected = gr.Button("Load Selected Bundle", variant="secondary")
|
| 2246 |
-
btn_load_last = gr.Button("Load Last Bundle", variant="secondary")
|
| 2247 |
-
|
| 2248 |
-
with gr.Row():
|
| 2249 |
-
job_id_box = gr.Textbox(label="Job ID", placeholder="Preenchido apos submissao ou manual")
|
| 2250 |
-
btn_status = gr.Button("Check Job Status")
|
| 2251 |
-
btn_queue = gr.Button("List My Queue")
|
| 2252 |
-
btn_logs = gr.Button("Check Last Job Logs")
|
| 2253 |
-
btn_cancel = gr.Button("Cancel Job")
|
| 2254 |
-
|
| 2255 |
-
gr.Markdown("#### Optional: Download remote outputs")
|
| 2256 |
-
with gr.Row():
|
| 2257 |
-
remote_download_path = gr.Textbox(label="Remote Path", placeholder="~/vitviz_jobs/.../logs/")
|
| 2258 |
-
local_download_path = gr.Textbox(label="Local Destination", value="results/raw/apuana_downloads")
|
| 2259 |
-
btn_download = gr.Button("Download via rsync")
|
| 2260 |
-
|
| 2261 |
-
apuana_output = gr.Markdown(value="Ready to configure Apuana job.")
|
| 2262 |
-
|
| 2263 |
-
btn_generate.click(
|
| 2264 |
-
fn=apuana_generate_bundle,
|
| 2265 |
-
inputs=[
|
| 2266 |
-
remote_user,
|
| 2267 |
-
remote_host,
|
| 2268 |
-
remote_project_dir,
|
| 2269 |
-
remote_base_dir,
|
| 2270 |
-
env_activate,
|
| 2271 |
-
python_module,
|
| 2272 |
-
config_mode,
|
| 2273 |
-
experiment_config,
|
| 2274 |
-
job_name,
|
| 2275 |
-
partition,
|
| 2276 |
-
cpus,
|
| 2277 |
-
mem,
|
| 2278 |
-
gpus,
|
| 2279 |
-
time_limit,
|
| 2280 |
-
selected_default_models,
|
| 2281 |
-
custom_models_raw,
|
| 2282 |
-
selected_attacks,
|
| 2283 |
-
fgsm_params_json,
|
| 2284 |
-
pgd_params_json,
|
| 2285 |
-
mim_params_json,
|
| 2286 |
-
tgr_params_json,
|
| 2287 |
-
saga_params_json,
|
| 2288 |
-
eps_csv,
|
| 2289 |
-
seeds_csv,
|
| 2290 |
-
selected_metrics,
|
| 2291 |
-
topk_eval,
|
| 2292 |
-
compute_lpips_eval,
|
| 2293 |
-
],
|
| 2294 |
-
outputs=[apuana_output, apuana_bundle_state],
|
| 2295 |
-
)
|
| 2296 |
-
|
| 2297 |
-
btn_sync.click(
|
| 2298 |
-
fn=apuana_sync_bundle,
|
| 2299 |
-
inputs=[remote_user, remote_host, apuana_bundle_state],
|
| 2300 |
-
outputs=[apuana_output],
|
| 2301 |
-
)
|
| 2302 |
-
|
| 2303 |
-
btn_submit.click(
|
| 2304 |
-
fn=apuana_submit_bundle,
|
| 2305 |
-
inputs=[remote_user, remote_host, apuana_bundle_state],
|
| 2306 |
-
outputs=[apuana_output, job_id_box],
|
| 2307 |
-
)
|
| 2308 |
-
|
| 2309 |
-
btn_status.click(
|
| 2310 |
-
fn=apuana_check_job,
|
| 2311 |
-
inputs=[remote_user, remote_host, job_id_box],
|
| 2312 |
-
outputs=[apuana_output],
|
| 2313 |
-
)
|
| 2314 |
-
|
| 2315 |
-
btn_queue.click(
|
| 2316 |
-
fn=apuana_list_jobs,
|
| 2317 |
-
inputs=[remote_user, remote_host],
|
| 2318 |
-
outputs=[apuana_output],
|
| 2319 |
-
)
|
| 2320 |
-
|
| 2321 |
-
btn_logs.click(
|
| 2322 |
-
fn=apuana_check_last_logs,
|
| 2323 |
-
inputs=[remote_user, remote_host, apuana_bundle_state, job_id_box],
|
| 2324 |
-
outputs=[apuana_output],
|
| 2325 |
-
)
|
| 2326 |
-
|
| 2327 |
-
btn_cancel.click(
|
| 2328 |
-
fn=apuana_cancel,
|
| 2329 |
-
inputs=[remote_user, remote_host, job_id_box],
|
| 2330 |
-
outputs=[apuana_output],
|
| 2331 |
-
)
|
| 2332 |
-
|
| 2333 |
-
btn_download.click(
|
| 2334 |
-
fn=apuana_download_results,
|
| 2335 |
-
inputs=[remote_user, remote_host, remote_download_path, local_download_path],
|
| 2336 |
-
outputs=[apuana_output],
|
| 2337 |
-
)
|
| 2338 |
-
|
| 2339 |
-
btn_refresh_history.click(
|
| 2340 |
-
fn=apuana_refresh_history,
|
| 2341 |
-
inputs=[],
|
| 2342 |
-
outputs=[apuana_output, bundle_history],
|
| 2343 |
-
)
|
| 2344 |
-
|
| 2345 |
-
btn_load_selected.click(
|
| 2346 |
-
fn=apuana_load_selected_bundle,
|
| 2347 |
-
inputs=[bundle_history],
|
| 2348 |
-
outputs=[apuana_output, apuana_bundle_state, job_id_box, remote_download_path, bundle_history],
|
| 2349 |
-
)
|
| 2350 |
-
|
| 2351 |
-
btn_load_last.click(
|
| 2352 |
-
fn=apuana_load_last_bundle,
|
| 2353 |
-
inputs=[],
|
| 2354 |
-
outputs=[apuana_output, apuana_bundle_state, job_id_box, remote_download_path, bundle_history],
|
| 2355 |
-
)
|
| 2356 |
-
|
| 2357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2358 |
return app
|
| 2359 |
|
| 2360 |
if __name__ == "__main__":
|
|
@@ -2368,4 +2530,4 @@ if __name__ == "__main__":
|
|
| 2368 |
)
|
| 2369 |
|
| 2370 |
except KeyboardInterrupt:
|
| 2371 |
-
print("\nShutting down gracefully...")
|
|
|
|
| 145 |
return path
|
| 146 |
return None
|
| 147 |
|
| 148 |
+
|
| 149 |
+
def _load_sample_ground_truth_map(metadata_path: Path) -> Dict[str, int]:
|
| 150 |
+
"""Load sample filename -> ground truth class index mapping from metadata.json."""
|
| 151 |
+
if not metadata_path.exists():
|
| 152 |
+
return {}
|
| 153 |
+
try:
|
| 154 |
+
with open(metadata_path, "r", encoding="utf-8") as f:
|
| 155 |
+
payload = json.load(f) or {}
|
| 156 |
+
except Exception:
|
| 157 |
+
return {}
|
| 158 |
+
|
| 159 |
+
files = payload.get("downloaded_files") or []
|
| 160 |
+
classes = payload.get("suggested_classes") or []
|
| 161 |
+
n = min(len(files), len(classes))
|
| 162 |
+
out: Dict[str, int] = {}
|
| 163 |
+
for i in range(n):
|
| 164 |
+
filename = str(files[i]).strip()
|
| 165 |
+
cls = classes[i] if isinstance(classes[i], dict) else {}
|
| 166 |
+
cls_id = cls.get("imagenet_id")
|
| 167 |
+
if not filename:
|
| 168 |
+
continue
|
| 169 |
+
try:
|
| 170 |
+
out[filename] = int(cls_id)
|
| 171 |
+
except (TypeError, ValueError):
|
| 172 |
+
continue
|
| 173 |
+
return out
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def _build_ground_truth_choices(class_names: Optional[Dict[int, str]]) -> List[Tuple[str, str]]:
|
| 177 |
+
"""Create dropdown choices [(display_label, value)] for ground truth selection."""
|
| 178 |
+
if not class_names:
|
| 179 |
+
return []
|
| 180 |
+
|
| 181 |
+
normalized: List[Tuple[int, str]] = []
|
| 182 |
+
for key, value in class_names.items():
|
| 183 |
+
try:
|
| 184 |
+
idx = int(key)
|
| 185 |
+
except (TypeError, ValueError):
|
| 186 |
+
continue
|
| 187 |
+
normalized.append((idx, str(value)))
|
| 188 |
+
|
| 189 |
+
normalized.sort(key=lambda x: x[0])
|
| 190 |
+
return [(f"{idx:04d} - {name}", str(idx)) for idx, name in normalized]
|
| 191 |
+
|
| 192 |
def _print_model_heads(model):
|
| 193 |
"""Imprime quantidade de heads do ViT ao carregar o modelo. Mais usada para debug."""
|
| 194 |
try:
|
|
|
|
| 357 |
decay: float,
|
| 358 |
vit_weight: float,
|
| 359 |
selected_metrics,
|
| 360 |
+
ground_truth_label,
|
| 361 |
) -> Tuple[List[Image.Image], str, List[List[torch.Tensor]]]:
|
| 362 |
"""
|
| 363 |
Executa ataque adversarial (FGSM ou PGD) untargeted e extrai atenΓ§Γ£o.
|
|
|
|
| 430 |
adv_class = top_idx_adv[0].item()
|
| 431 |
adv_prob = top_prob_adv[0].item()
|
| 432 |
|
| 433 |
+
gt_class: Optional[int] = None
|
| 434 |
+
if ground_truth_label not in (None, ""):
|
| 435 |
+
try:
|
| 436 |
+
gt_class = int(ground_truth_label)
|
| 437 |
+
except (TypeError, ValueError):
|
| 438 |
+
gt_class = None
|
| 439 |
+
|
| 440 |
+
# If GT is present, class-dependent metrics are evaluated against GT.
|
| 441 |
+
eval_label = gt_class if gt_class is not None else orig_class
|
| 442 |
+
|
| 443 |
# Resolver seleΓ§Γ£o de mΓ©tricas:
|
| 444 |
# - sem seleΓ§Γ£o ou com "Select All" => mostra todas
|
| 445 |
# - caso contrΓ‘rio, mostra apenas as escolhidas
|
|
|
|
| 480 |
if "Modified Pixels" in requested_metrics:
|
| 481 |
metric_values["Modified Pixels"] = f"{compute_modified_pixels(img_tensor, adv_tensor):.1f}%"
|
| 482 |
if "Confidence Drop" in requested_metrics:
|
| 483 |
+
metric_values["Confidence Drop"] = f"{compute_confidence_drop(orig_probs_full, adv_probs_full, eval_label):.4f}"
|
| 484 |
if "Top-K Accuracy Drop" in requested_metrics:
|
| 485 |
+
topk_drop = compute_topk_drop(orig_probs_full, adv_probs_full, eval_label, k=5)
|
| 486 |
metric_values["Top-K Accuracy Drop"] = "Yes" if topk_drop >= 0.5 else "No"
|
| 487 |
|
| 488 |
metric_tooltips = {
|
|
|
|
| 493 |
"SSIM": "Structural Similarity Index comparing luminance, contrast, and structure. Values near 1 mean images are structurally similar.",
|
| 494 |
"LPIPS": "Learned Perceptual Image Patch Similarity based on deep features. Lower values indicate greater perceptual similarity.",
|
| 495 |
"Modified Pixels": "Percentage of pixels where perturbation exceeds 1e-5 (channel max). It approximates a sparse L0/Hamming-like change count.",
|
| 496 |
+
"Confidence Drop": "Decrease in reference-class probability after attack: p_orig(reference class) - p_adv(reference class). If Ground Truth is set, reference class = Ground Truth; otherwise it falls back to original prediction.",
|
| 497 |
+
"Top-K Accuracy Drop": "Yes means the reference class left top-5 after attack. If Ground Truth is set, reference class = Ground Truth; otherwise it falls back to original prediction.",
|
| 498 |
}
|
| 499 |
|
| 500 |
def metric_label_with_info(label: str, tooltip_text: str) -> str:
|
|
|
|
| 513 |
result = f"## {attack_type} Attack Result (Untargeted)\n\n"
|
| 514 |
|
| 515 |
# Status visual
|
| 516 |
+
if adv_class != eval_label:
|
| 517 |
status_icon = ICON_SUCCESS
|
| 518 |
status_text = "Success"
|
| 519 |
else:
|
|
|
|
| 525 |
# Prediction Comparison (duas tabelas)
|
| 526 |
result += f"### {ICON_CHART} Prediction Comparison (Top-5)\n\n"
|
| 527 |
|
| 528 |
+
if gt_class is not None:
|
| 529 |
+
gt_name = None
|
| 530 |
+
if class_names:
|
| 531 |
+
gt_name = class_names.get(gt_class) if isinstance(class_names, dict) else None
|
| 532 |
+
if gt_name is None and isinstance(class_names, dict):
|
| 533 |
+
gt_name = class_names.get(str(gt_class))
|
| 534 |
+
if gt_name is not None:
|
| 535 |
+
gt_display = f"{gt_name} (class {gt_class})"
|
| 536 |
+
else:
|
| 537 |
+
gt_display = f"Class {gt_class}"
|
| 538 |
+
result += f"**Ground Truth:** {gt_display}\n\n"
|
| 539 |
+
|
| 540 |
top_k_display = min(5, len(top_prob_orig), len(top_prob_adv))
|
| 541 |
result += "<div class=\"vitviz-panels\">\n"
|
| 542 |
|
|
|
|
| 1272 |
height: 100% !important;
|
| 1273 |
object-fit: contain !important;
|
| 1274 |
}
|
| 1275 |
+
|
| 1276 |
+
/* Keep native Gradio Gallery captions visible across themes. */
|
| 1277 |
+
.gradio-container .caption-label {
|
| 1278 |
+
color: #111111 !important;
|
| 1279 |
+
background: rgba(255, 255, 255, 0.92) !important;
|
| 1280 |
+
border: 1px solid rgba(0, 0, 0, 0.18) !important;
|
| 1281 |
+
}
|
| 1282 |
|
| 1283 |
@keyframes spin {
|
| 1284 |
0% { transform: rotate(0deg); }
|
|
|
|
| 1511 |
|
| 1512 |
default_eps_csv = ", ".join(str(v) for v in default_sweep_cfg.get("evaluation", {}).get("epsilons", [0.00784]))
|
| 1513 |
default_seeds_csv = ", ".join(str(v) for v in default_sweep_cfg.get("evaluation", {}).get("seeds", [42]))
|
| 1514 |
+
sample_images_dir = PROJECT_ROOT / "data" / "sample_images"
|
| 1515 |
+
sample_image_paths = sorted(
|
| 1516 |
+
p for p in sample_images_dir.glob("*")
|
| 1517 |
+
if p.suffix.lower() in {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
|
| 1518 |
+
) if sample_images_dir.exists() else []
|
| 1519 |
+
sample_gallery_items = [(str(p), p.name) for p in sample_image_paths]
|
| 1520 |
+
sample_gt_map = _load_sample_ground_truth_map(sample_images_dir / "metadata.json")
|
| 1521 |
initial_history_choices = _history_choices(limit=100)
|
| 1522 |
initial_history_value = initial_history_choices[0][1] if initial_history_choices else None
|
| 1523 |
show_apuana_tab = _should_show_apuana_tab()
|
|
|
|
| 1674 |
label="Upload Image",
|
| 1675 |
type="pil"
|
| 1676 |
)
|
| 1677 |
+
if sample_gallery_items:
|
| 1678 |
+
gr.Markdown("**Or choose a sample image:**")
|
| 1679 |
+
sample_gallery_attack = gr.Gallery(
|
| 1680 |
+
value=sample_gallery_items,
|
| 1681 |
+
label="Sample Image Carousel (click to select)",
|
| 1682 |
+
columns=min(6, max(1, len(sample_gallery_items))),
|
| 1683 |
+
rows=1,
|
| 1684 |
+
object_fit="contain",
|
| 1685 |
+
height=180,
|
| 1686 |
+
allow_preview=False,
|
| 1687 |
+
)
|
| 1688 |
+
else:
|
| 1689 |
+
sample_gallery_attack = None
|
| 1690 |
+
gr.Markdown("_No sample images found in data/sample_images._")
|
| 1691 |
+
|
| 1692 |
+
ground_truth_dropdown = gr.Dropdown(
|
| 1693 |
+
choices=[],
|
| 1694 |
+
value=None,
|
| 1695 |
+
multiselect=False,
|
| 1696 |
+
filterable=True,
|
| 1697 |
+
interactive=True,
|
| 1698 |
+
visible=False,
|
| 1699 |
+
label="Ground Truth Label",
|
| 1700 |
+
info="Visible only when model classes are available. Supports search.",
|
| 1701 |
+
)
|
| 1702 |
+
|
| 1703 |
+
ground_truth_values_state = gr.State([])
|
| 1704 |
|
| 1705 |
model_select_attack.change(
|
| 1706 |
fn=toggle_model_upload,
|
| 1707 |
inputs=[model_select_attack],
|
| 1708 |
outputs=[model_upload_attack]
|
| 1709 |
)
|
| 1710 |
+
|
| 1711 |
+
def refresh_ground_truth_dropdown(model_file, model_selection):
|
| 1712 |
+
model_path = _get_model_path_from_selection(model_selection, model_file)
|
| 1713 |
+
if model_path is None:
|
| 1714 |
+
return gr.update(visible=False, choices=[], value=None), []
|
| 1715 |
+
try:
|
| 1716 |
+
_, class_names, _, _ = load_model_and_labels(model_path, None, device=DEVICE)
|
| 1717 |
+
except Exception:
|
| 1718 |
+
return gr.update(visible=False, choices=[], value=None), []
|
| 1719 |
+
|
| 1720 |
+
choices = _build_ground_truth_choices(class_names)
|
| 1721 |
+
values = [v for _, v in choices]
|
| 1722 |
+
if not choices:
|
| 1723 |
+
return gr.update(visible=False, choices=[], value=None), []
|
| 1724 |
+
return gr.update(visible=True, choices=choices, value=None), values
|
| 1725 |
+
|
| 1726 |
+
model_select_attack.change(
|
| 1727 |
+
fn=refresh_ground_truth_dropdown,
|
| 1728 |
+
inputs=[model_upload_attack, model_select_attack],
|
| 1729 |
+
outputs=[ground_truth_dropdown, ground_truth_values_state],
|
| 1730 |
+
)
|
| 1731 |
+
|
| 1732 |
+
model_upload_attack.change(
|
| 1733 |
+
fn=refresh_ground_truth_dropdown,
|
| 1734 |
+
inputs=[model_upload_attack, model_select_attack],
|
| 1735 |
+
outputs=[ground_truth_dropdown, ground_truth_values_state],
|
| 1736 |
+
)
|
| 1737 |
+
|
| 1738 |
+
app.load(
|
| 1739 |
+
fn=refresh_ground_truth_dropdown,
|
| 1740 |
+
inputs=[model_upload_attack, model_select_attack],
|
| 1741 |
+
outputs=[ground_truth_dropdown, ground_truth_values_state],
|
| 1742 |
+
)
|
| 1743 |
+
|
| 1744 |
+
if sample_gallery_attack is not None:
|
| 1745 |
+
def select_sample_image_for_attack(valid_values, evt: gr.SelectData):
|
| 1746 |
+
idx = evt.index
|
| 1747 |
+
if isinstance(idx, (tuple, list)):
|
| 1748 |
+
idx = idx[0] if idx else 0
|
| 1749 |
+
try:
|
| 1750 |
+
selected = sample_image_paths[int(idx)]
|
| 1751 |
+
gt_idx = sample_gt_map.get(selected.name)
|
| 1752 |
+
gt_update = None
|
| 1753 |
+
if gt_idx is not None and str(gt_idx) in set(valid_values or []):
|
| 1754 |
+
gt_update = str(gt_idx)
|
| 1755 |
+
return Image.open(selected).convert("RGB"), gr.update(value=gt_update)
|
| 1756 |
+
except Exception:
|
| 1757 |
+
return None, gr.update()
|
| 1758 |
+
|
| 1759 |
+
sample_gallery_attack.select(
|
| 1760 |
+
fn=select_sample_image_for_attack,
|
| 1761 |
+
inputs=[ground_truth_values_state],
|
| 1762 |
+
outputs=[image_upload_attack, ground_truth_dropdown],
|
| 1763 |
+
)
|
| 1764 |
gr.Markdown("---")
|
| 1765 |
|
| 1766 |
# Attack Configuration Row
|
|
|
|
| 2078 |
inputs=[
|
| 2079 |
model_upload_attack, model_select_attack, image_upload_attack,
|
| 2080 |
attack_type, eps_input, alpha_input, steps_input, decay_input, vit_weight_slider,
|
| 2081 |
+
metrics_selector, ground_truth_dropdown
|
| 2082 |
],
|
| 2083 |
outputs=[iteration_images_state, output_text_attack, cached_attentions_state]
|
| 2084 |
).then(
|
|
|
|
| 2250 |
with gr.Tab("Apuana Cluster"):
|
| 2251 |
gr.Markdown("### Configure experiments and submit jobs to Apuana via SSH")
|
| 2252 |
|
| 2253 |
+
apuana_bundle_state = gr.State({})
|
| 2254 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2255 |
with gr.Row():
|
| 2256 |
with gr.Column(scale=1):
|
| 2257 |
+
remote_user = gr.Textbox(label="Remote User", placeholder="seu_login_cin")
|
| 2258 |
+
remote_host = gr.Textbox(label="Remote Host", value=APUANA_DEFAULT_HOST)
|
| 2259 |
+
remote_project_dir = gr.Textbox(label="Remote Project Dir", value="~/ViTViz")
|
| 2260 |
+
remote_base_dir = gr.Textbox(label="Remote Jobs Base Dir", value="~/vitviz_jobs")
|
| 2261 |
+
env_activate = gr.Textbox(label="Env Activate Script", value="$HOME/ViTViz/.venv/bin/activate")
|
| 2262 |
+
python_module = gr.Textbox(label="Python Module", value="Python3.10")
|
| 2263 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2264 |
with gr.Column(scale=1):
|
| 2265 |
+
config_mode = gr.Radio(
|
| 2266 |
+
choices=["Use Config File", "Build in UI"],
|
| 2267 |
+
value="Use Config File",
|
| 2268 |
+
label="Config Source",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2269 |
)
|
| 2270 |
+
config_file_group = gr.Group(visible=True)
|
| 2271 |
+
with config_file_group:
|
| 2272 |
+
experiment_config = gr.Dropdown(
|
| 2273 |
+
choices=cluster_experiment_choices,
|
| 2274 |
+
value=default_cluster_config,
|
| 2275 |
+
label="Experiment Config (local)",
|
| 2276 |
+
)
|
| 2277 |
+
|
| 2278 |
+
job_name = gr.Textbox(label="Job Name", value="vitviz_sweep")
|
| 2279 |
+
partition = gr.Dropdown(choices=APUANA_PARTITIONS, value="short-simple", label="Partition")
|
| 2280 |
+
qos_note = gr.Markdown(value="QoS auto: `simple`")
|
| 2281 |
+
|
| 2282 |
with gr.Column(scale=1):
|
| 2283 |
+
cpus = gr.Slider(1, 48, value=8, step=1, label="CPUs per task")
|
| 2284 |
+
mem = gr.Textbox(label="Memory", value="16G")
|
| 2285 |
+
gpus = gr.Slider(0, 4, value=1, step=1, label="GPUs")
|
| 2286 |
+
time_limit = gr.Textbox(label="Time Limit", value="02:00:00")
|
| 2287 |
+
|
| 2288 |
+
config_builder_group = gr.Group(visible=False)
|
| 2289 |
+
with config_builder_group:
|
| 2290 |
+
gr.Markdown("#### Dynamic Experiment Builder")
|
| 2291 |
+
with gr.Row():
|
| 2292 |
+
with gr.Column(scale=1):
|
| 2293 |
+
selected_default_models = gr.Dropdown(
|
| 2294 |
+
choices=default_model_catalog,
|
| 2295 |
+
value=default_model_catalog,
|
| 2296 |
+
multiselect=True,
|
| 2297 |
+
label="Default Models",
|
| 2298 |
+
)
|
| 2299 |
+
custom_models_raw = gr.Textbox(
|
| 2300 |
+
label="Custom Models (one per line)",
|
| 2301 |
+
lines=5,
|
| 2302 |
+
placeholder="name|path|img_size|num_classes|type|dataset",
|
| 2303 |
+
info="Required fields: name|path. Optional fields use defaults.",
|
| 2304 |
+
)
|
| 2305 |
+
with gr.Column(scale=1):
|
| 2306 |
+
selected_attacks = gr.Dropdown(
|
| 2307 |
+
choices=default_attack_catalog,
|
| 2308 |
+
value=default_attack_catalog,
|
| 2309 |
+
multiselect=True,
|
| 2310 |
+
label="Attacks",
|
| 2311 |
+
)
|
| 2312 |
+
eps_csv = gr.Textbox(
|
| 2313 |
+
label="Epsilons (CSV)",
|
| 2314 |
+
value=default_eps_csv,
|
| 2315 |
+
placeholder="0.00784,0.01568",
|
| 2316 |
+
)
|
| 2317 |
+
seeds_csv = gr.Textbox(
|
| 2318 |
+
label="Seeds (CSV)",
|
| 2319 |
+
value=default_seeds_csv,
|
| 2320 |
+
placeholder="42,123,456",
|
| 2321 |
+
)
|
| 2322 |
+
with gr.Column(scale=1):
|
| 2323 |
+
selected_metrics = gr.Dropdown(
|
| 2324 |
+
choices=SWEEP_METRIC_CHOICES,
|
| 2325 |
+
value=SWEEP_METRIC_DEFAULT_SELECTION,
|
| 2326 |
+
multiselect=True,
|
| 2327 |
+
label="Metrics Included",
|
| 2328 |
+
)
|
| 2329 |
+
topk_eval = gr.Slider(1, 20, value=int(default_sweep_cfg.get("evaluation", {}).get("topk", 5)), step=1, label="Top-k")
|
| 2330 |
+
compute_lpips_eval = gr.Checkbox(
|
| 2331 |
+
value=bool(default_sweep_cfg.get("evaluation", {}).get("compute_lpips", True)),
|
| 2332 |
+
label="Compute LPIPS",
|
| 2333 |
+
)
|
| 2334 |
+
|
| 2335 |
+
with gr.Row():
|
| 2336 |
+
with gr.Column(scale=1):
|
| 2337 |
+
fgsm_params_json = gr.Code(
|
| 2338 |
+
label="FGSM params (JSON)",
|
| 2339 |
+
value=json.dumps(attack_default_params.get("FGSM", {"eps": None}), indent=2),
|
| 2340 |
+
language="json",
|
| 2341 |
+
lines=8,
|
| 2342 |
+
)
|
| 2343 |
+
pgd_params_json = gr.Code(
|
| 2344 |
+
label="PGD params (JSON)",
|
| 2345 |
+
value=json.dumps(attack_default_params.get("PGD", {"eps": None, "alpha_ratio": 0.25, "steps": 10}), indent=2),
|
| 2346 |
+
language="json",
|
| 2347 |
+
lines=8,
|
| 2348 |
+
)
|
| 2349 |
+
with gr.Column(scale=1):
|
| 2350 |
+
mim_params_json = gr.Code(
|
| 2351 |
+
label="MIM params (JSON)",
|
| 2352 |
+
value=json.dumps(attack_default_params.get("MIM", {"eps": None, "alpha_ratio": 0.25, "steps": 10, "decay": 1.0}), indent=2),
|
| 2353 |
+
language="json",
|
| 2354 |
+
lines=8,
|
| 2355 |
+
)
|
| 2356 |
+
tgr_params_json = gr.Code(
|
| 2357 |
+
label="TGR params (JSON)",
|
| 2358 |
+
value=json.dumps(attack_default_params.get("TGR", {"eps": None, "alpha_ratio": 0.25, "steps": 10, "gamma_attn": 0.25, "gamma_qkv": 0.75, "gamma_mlp": 0.5}), indent=2),
|
| 2359 |
+
language="json",
|
| 2360 |
+
lines=8,
|
| 2361 |
+
)
|
| 2362 |
+
with gr.Column(scale=1):
|
| 2363 |
+
saga_params_json = gr.Code(
|
| 2364 |
+
label="SAGA params (JSON)",
|
| 2365 |
+
value=json.dumps(attack_default_params.get("SAGA", {"eps": None, "alpha_ratio": 0.25, "steps": 10, "cnn_backbone": None}), indent=2),
|
| 2366 |
+
language="json",
|
| 2367 |
+
lines=8,
|
| 2368 |
+
)
|
| 2369 |
+
|
| 2370 |
+
def toggle_apuana_config_mode(mode):
|
| 2371 |
+
use_builder = mode == "Build in UI"
|
| 2372 |
+
return (
|
| 2373 |
+
gr.update(visible=not use_builder),
|
| 2374 |
+
gr.update(visible=use_builder),
|
| 2375 |
+
)
|
| 2376 |
+
|
| 2377 |
+
def update_qos_note(partition_value):
|
| 2378 |
+
return f"QoS auto: `{_qos_from_partition(partition_value)}`"
|
| 2379 |
+
|
| 2380 |
+
partition.change(
|
| 2381 |
+
fn=update_qos_note,
|
| 2382 |
+
inputs=[partition],
|
| 2383 |
+
outputs=[qos_note],
|
| 2384 |
+
)
|
| 2385 |
+
|
| 2386 |
+
config_mode.change(
|
| 2387 |
+
fn=toggle_apuana_config_mode,
|
| 2388 |
+
inputs=[config_mode],
|
| 2389 |
+
outputs=[config_file_group, config_builder_group],
|
| 2390 |
+
)
|
| 2391 |
+
|
| 2392 |
with gr.Row():
|
| 2393 |
+
btn_generate = gr.Button("1) Generate Bundle", variant="secondary")
|
| 2394 |
+
btn_sync = gr.Button("2) Sync Bundle", variant="secondary")
|
| 2395 |
+
btn_submit = gr.Button("3) Submit Job", variant="primary")
|
| 2396 |
+
|
| 2397 |
+
gr.Markdown("#### Saved Bundle History")
|
| 2398 |
+
with gr.Row():
|
| 2399 |
+
bundle_history = gr.Dropdown(
|
| 2400 |
+
label="Saved Bundles",
|
| 2401 |
+
choices=initial_history_choices,
|
| 2402 |
+
value=initial_history_value,
|
| 2403 |
+
allow_custom_value=False,
|
| 2404 |
+
)
|
| 2405 |
+
with gr.Row():
|
| 2406 |
+
btn_refresh_history = gr.Button("Refresh History", variant="secondary")
|
| 2407 |
+
btn_load_selected = gr.Button("Load Selected Bundle", variant="secondary")
|
| 2408 |
+
btn_load_last = gr.Button("Load Last Bundle", variant="secondary")
|
| 2409 |
+
|
| 2410 |
+
with gr.Row():
|
| 2411 |
+
job_id_box = gr.Textbox(label="Job ID", placeholder="Preenchido apos submissao ou manual")
|
| 2412 |
+
btn_status = gr.Button("Check Job Status")
|
| 2413 |
+
btn_queue = gr.Button("List My Queue")
|
| 2414 |
+
btn_logs = gr.Button("Check Last Job Logs")
|
| 2415 |
+
btn_cancel = gr.Button("Cancel Job")
|
| 2416 |
+
|
| 2417 |
+
gr.Markdown("#### Optional: Download remote outputs")
|
| 2418 |
+
with gr.Row():
|
| 2419 |
+
remote_download_path = gr.Textbox(label="Remote Path", placeholder="~/vitviz_jobs/.../logs/")
|
| 2420 |
+
local_download_path = gr.Textbox(label="Local Destination", value="results/raw/apuana_downloads")
|
| 2421 |
+
btn_download = gr.Button("Download via rsync")
|
| 2422 |
+
|
| 2423 |
+
apuana_output = gr.Markdown(value="Ready to configure Apuana job.")
|
| 2424 |
+
|
| 2425 |
+
btn_generate.click(
|
| 2426 |
+
fn=apuana_generate_bundle,
|
| 2427 |
+
inputs=[
|
| 2428 |
+
remote_user,
|
| 2429 |
+
remote_host,
|
| 2430 |
+
remote_project_dir,
|
| 2431 |
+
remote_base_dir,
|
| 2432 |
+
env_activate,
|
| 2433 |
+
python_module,
|
| 2434 |
+
config_mode,
|
| 2435 |
+
experiment_config,
|
| 2436 |
+
job_name,
|
| 2437 |
+
partition,
|
| 2438 |
+
cpus,
|
| 2439 |
+
mem,
|
| 2440 |
+
gpus,
|
| 2441 |
+
time_limit,
|
| 2442 |
+
selected_default_models,
|
| 2443 |
+
custom_models_raw,
|
| 2444 |
+
selected_attacks,
|
| 2445 |
+
fgsm_params_json,
|
| 2446 |
+
pgd_params_json,
|
| 2447 |
+
mim_params_json,
|
| 2448 |
+
tgr_params_json,
|
| 2449 |
+
saga_params_json,
|
| 2450 |
+
eps_csv,
|
| 2451 |
+
seeds_csv,
|
| 2452 |
+
selected_metrics,
|
| 2453 |
+
topk_eval,
|
| 2454 |
+
compute_lpips_eval,
|
| 2455 |
+
],
|
| 2456 |
+
outputs=[apuana_output, apuana_bundle_state],
|
| 2457 |
)
|
| 2458 |
+
|
| 2459 |
+
btn_sync.click(
|
| 2460 |
+
fn=apuana_sync_bundle,
|
| 2461 |
+
inputs=[remote_user, remote_host, apuana_bundle_state],
|
| 2462 |
+
outputs=[apuana_output],
|
| 2463 |
+
)
|
| 2464 |
+
|
| 2465 |
+
btn_submit.click(
|
| 2466 |
+
fn=apuana_submit_bundle,
|
| 2467 |
+
inputs=[remote_user, remote_host, apuana_bundle_state],
|
| 2468 |
+
outputs=[apuana_output, job_id_box],
|
| 2469 |
+
)
|
| 2470 |
+
|
| 2471 |
+
btn_status.click(
|
| 2472 |
+
fn=apuana_check_job,
|
| 2473 |
+
inputs=[remote_user, remote_host, job_id_box],
|
| 2474 |
+
outputs=[apuana_output],
|
| 2475 |
+
)
|
| 2476 |
+
|
| 2477 |
+
btn_queue.click(
|
| 2478 |
+
fn=apuana_list_jobs,
|
| 2479 |
+
inputs=[remote_user, remote_host],
|
| 2480 |
+
outputs=[apuana_output],
|
| 2481 |
+
)
|
| 2482 |
+
|
| 2483 |
+
btn_logs.click(
|
| 2484 |
+
fn=apuana_check_last_logs,
|
| 2485 |
+
inputs=[remote_user, remote_host, apuana_bundle_state, job_id_box],
|
| 2486 |
+
outputs=[apuana_output],
|
| 2487 |
+
)
|
| 2488 |
+
|
| 2489 |
+
btn_cancel.click(
|
| 2490 |
+
fn=apuana_cancel,
|
| 2491 |
+
inputs=[remote_user, remote_host, job_id_box],
|
| 2492 |
+
outputs=[apuana_output],
|
| 2493 |
+
)
|
| 2494 |
+
|
| 2495 |
+
btn_download.click(
|
| 2496 |
+
fn=apuana_download_results,
|
| 2497 |
+
inputs=[remote_user, remote_host, remote_download_path, local_download_path],
|
| 2498 |
+
outputs=[apuana_output],
|
| 2499 |
+
)
|
| 2500 |
+
|
| 2501 |
+
btn_refresh_history.click(
|
| 2502 |
+
fn=apuana_refresh_history,
|
| 2503 |
+
inputs=[],
|
| 2504 |
+
outputs=[apuana_output, bundle_history],
|
| 2505 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2506 |
|
| 2507 |
+
btn_load_selected.click(
|
| 2508 |
+
fn=apuana_load_selected_bundle,
|
| 2509 |
+
inputs=[bundle_history],
|
| 2510 |
+
outputs=[apuana_output, apuana_bundle_state, job_id_box, remote_download_path, bundle_history],
|
| 2511 |
+
)
|
| 2512 |
+
|
| 2513 |
+
btn_load_last.click(
|
| 2514 |
+
fn=apuana_load_last_bundle,
|
| 2515 |
+
inputs=[],
|
| 2516 |
+
outputs=[apuana_output, apuana_bundle_state, job_id_box, remote_download_path, bundle_history],
|
| 2517 |
+
)
|
| 2518 |
+
|
| 2519 |
+
|
| 2520 |
return app
|
| 2521 |
|
| 2522 |
if __name__ == "__main__":
|
|
|
|
| 2530 |
)
|
| 2531 |
|
| 2532 |
except KeyboardInterrupt:
|
| 2533 |
+
print("\nShutting down gracefully...")
|
|
@@ -20,6 +20,7 @@ Usage:
|
|
| 20 |
|
| 21 |
import argparse
|
| 22 |
import csv
|
|
|
|
| 23 |
import os
|
| 24 |
import sys
|
| 25 |
import time
|
|
@@ -137,7 +138,7 @@ def create_attack(model, attack_cfg: dict, eps: float):
|
|
| 137 |
|
| 138 |
CSV_COLUMNS = [
|
| 139 |
"model", "attack", "epsilon", "seed", "image",
|
| 140 |
-
"orig_pred", "orig_conf", "adv_pred", "adv_conf",
|
| 141 |
# Attack success
|
| 142 |
"asr", "confidence_drop", "topk_drop",
|
| 143 |
# Image quality
|
|
@@ -155,6 +156,35 @@ ATTENTION_METRIC_COLUMNS = [
|
|
| 155 |
]
|
| 156 |
|
| 157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 159 |
# Main sweep
|
| 160 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -209,6 +239,7 @@ def run_single(
|
|
| 209 |
img_path: Path, device: torch.device,
|
| 210 |
eval_cfg: dict, attn_cfg: dict,
|
| 211 |
selected_metrics: set,
|
|
|
|
| 212 |
) -> dict:
|
| 213 |
"""Run a single attack and compute all metrics. Returns a dict row."""
|
| 214 |
set_seed(seed)
|
|
@@ -257,12 +288,14 @@ def run_single(
|
|
| 257 |
|
| 258 |
# Attack success metrics (compute only when selected; keep fixed CSV schema)
|
| 259 |
atk_metrics = {k: float("nan") for k in ATTACK_METRIC_COLUMNS}
|
|
|
|
| 260 |
if selected_metrics.intersection(ATTACK_METRIC_COLUMNS):
|
| 261 |
raw_atk_metrics = compute_all_attack_metrics(
|
| 262 |
orig_probs,
|
| 263 |
adv_probs,
|
| 264 |
orig_pred,
|
| 265 |
adv_pred,
|
|
|
|
| 266 |
k=eval_cfg.get("topk", 5),
|
| 267 |
)
|
| 268 |
for k in ATTACK_METRIC_COLUMNS:
|
|
@@ -303,6 +336,7 @@ def run_single(
|
|
| 303 |
"orig_conf": f"{orig_conf:.6f}",
|
| 304 |
"adv_pred": str(adv_pred),
|
| 305 |
"adv_conf": f"{adv_conf:.6f}",
|
|
|
|
| 306 |
**{k: ("" if np.isnan(v) else f"{v:.6f}") for k, v in atk_metrics.items()},
|
| 307 |
**{k: ("" if np.isnan(v) else f"{v:.6f}") for k, v in img_metrics.items()},
|
| 308 |
**{k: ("" if np.isnan(v) else f"{v:.6f}") for k, v in attn_metrics.items()},
|
|
@@ -335,6 +369,7 @@ def main():
|
|
| 335 |
results_dir = Path(config["output"]["results_dir"])
|
| 336 |
results_dir.mkdir(parents=True, exist_ok=True)
|
| 337 |
images_dir = Path(config["data"]["images_dir"])
|
|
|
|
| 338 |
|
| 339 |
if not images_dir.exists():
|
| 340 |
images_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -426,6 +461,7 @@ def main():
|
|
| 426 |
attack_cfg_copy, eps, seed, img_path, device,
|
| 427 |
config["evaluation"], config.get("attention", {}),
|
| 428 |
selected_metrics,
|
|
|
|
| 429 |
)
|
| 430 |
|
| 431 |
# Append to CSV
|
|
|
|
| 20 |
|
| 21 |
import argparse
|
| 22 |
import csv
|
| 23 |
+
import json
|
| 24 |
import os
|
| 25 |
import sys
|
| 26 |
import time
|
|
|
|
| 138 |
|
| 139 |
CSV_COLUMNS = [
|
| 140 |
"model", "attack", "epsilon", "seed", "image",
|
| 141 |
+
"orig_pred", "orig_conf", "adv_pred", "adv_conf", "ground_truth",
|
| 142 |
# Attack success
|
| 143 |
"asr", "confidence_drop", "topk_drop",
|
| 144 |
# Image quality
|
|
|
|
| 156 |
]
|
| 157 |
|
| 158 |
|
| 159 |
+
def load_sample_ground_truth_map(sample_dir: Path) -> dict[str, int]:
|
| 160 |
+
"""Load sample filename -> class index mapping from metadata.json (if present)."""
|
| 161 |
+
metadata_path = sample_dir / "metadata.json"
|
| 162 |
+
if not metadata_path.exists():
|
| 163 |
+
return {}
|
| 164 |
+
|
| 165 |
+
try:
|
| 166 |
+
with open(metadata_path, "r", encoding="utf-8") as f:
|
| 167 |
+
payload = json.load(f) or {}
|
| 168 |
+
except Exception:
|
| 169 |
+
return {}
|
| 170 |
+
|
| 171 |
+
files = payload.get("downloaded_files") or []
|
| 172 |
+
classes = payload.get("suggested_classes") or []
|
| 173 |
+
n = min(len(files), len(classes))
|
| 174 |
+
mapping: dict[str, int] = {}
|
| 175 |
+
for i in range(n):
|
| 176 |
+
filename = str(files[i]).strip()
|
| 177 |
+
cls = classes[i] if isinstance(classes[i], dict) else {}
|
| 178 |
+
cls_id = cls.get("imagenet_id")
|
| 179 |
+
if not filename:
|
| 180 |
+
continue
|
| 181 |
+
try:
|
| 182 |
+
mapping[filename] = int(cls_id)
|
| 183 |
+
except (TypeError, ValueError):
|
| 184 |
+
continue
|
| 185 |
+
return mapping
|
| 186 |
+
|
| 187 |
+
|
| 188 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 189 |
# Main sweep
|
| 190 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 239 |
img_path: Path, device: torch.device,
|
| 240 |
eval_cfg: dict, attn_cfg: dict,
|
| 241 |
selected_metrics: set,
|
| 242 |
+
sample_gt_map: dict[str, int],
|
| 243 |
) -> dict:
|
| 244 |
"""Run a single attack and compute all metrics. Returns a dict row."""
|
| 245 |
set_seed(seed)
|
|
|
|
| 288 |
|
| 289 |
# Attack success metrics (compute only when selected; keep fixed CSV schema)
|
| 290 |
atk_metrics = {k: float("nan") for k in ATTACK_METRIC_COLUMNS}
|
| 291 |
+
ground_truth = sample_gt_map.get(img_path.name)
|
| 292 |
if selected_metrics.intersection(ATTACK_METRIC_COLUMNS):
|
| 293 |
raw_atk_metrics = compute_all_attack_metrics(
|
| 294 |
orig_probs,
|
| 295 |
adv_probs,
|
| 296 |
orig_pred,
|
| 297 |
adv_pred,
|
| 298 |
+
ground_truth=ground_truth,
|
| 299 |
k=eval_cfg.get("topk", 5),
|
| 300 |
)
|
| 301 |
for k in ATTACK_METRIC_COLUMNS:
|
|
|
|
| 336 |
"orig_conf": f"{orig_conf:.6f}",
|
| 337 |
"adv_pred": str(adv_pred),
|
| 338 |
"adv_conf": f"{adv_conf:.6f}",
|
| 339 |
+
"ground_truth": "" if ground_truth is None else str(ground_truth),
|
| 340 |
**{k: ("" if np.isnan(v) else f"{v:.6f}") for k, v in atk_metrics.items()},
|
| 341 |
**{k: ("" if np.isnan(v) else f"{v:.6f}") for k, v in img_metrics.items()},
|
| 342 |
**{k: ("" if np.isnan(v) else f"{v:.6f}") for k, v in attn_metrics.items()},
|
|
|
|
| 369 |
results_dir = Path(config["output"]["results_dir"])
|
| 370 |
results_dir.mkdir(parents=True, exist_ok=True)
|
| 371 |
images_dir = Path(config["data"]["images_dir"])
|
| 372 |
+
sample_gt_map = load_sample_ground_truth_map(images_dir)
|
| 373 |
|
| 374 |
if not images_dir.exists():
|
| 375 |
images_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
| 461 |
attack_cfg_copy, eps, seed, img_path, device,
|
| 462 |
config["evaluation"], config.get("attention", {}),
|
| 463 |
selected_metrics,
|
| 464 |
+
sample_gt_map,
|
| 465 |
)
|
| 466 |
|
| 467 |
# Append to CSV
|
|
@@ -259,42 +259,42 @@ def compute_modified_pixels(orig: torch.Tensor, adv: torch.Tensor,
|
|
| 259 |
# Attack Success Metrics
|
| 260 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 261 |
|
| 262 |
-
def compute_asr(
|
| 263 |
"""Attack Success Rate for a single sample.
|
| 264 |
|
| 265 |
-
Returns 1.0 if attack
|
| 266 |
For batch ASR, average across samples.
|
| 267 |
"""
|
| 268 |
-
return 1.0 if
|
| 269 |
|
| 270 |
|
| 271 |
def compute_confidence_drop(orig_probs: torch.Tensor,
|
| 272 |
adv_probs: torch.Tensor,
|
| 273 |
-
|
| 274 |
-
"""Confidence drop on
|
| 275 |
|
| 276 |
Args:
|
| 277 |
orig_probs: Softmax probabilities for original image (1D tensor).
|
| 278 |
adv_probs: Softmax probabilities for adversarial image (1D tensor).
|
| 279 |
-
|
| 280 |
|
| 281 |
Returns:
|
| 282 |
Drop in confidence (positive = confidence decreased).
|
| 283 |
"""
|
| 284 |
-
return (orig_probs[
|
| 285 |
|
| 286 |
|
| 287 |
def compute_topk_drop(orig_probs: torch.Tensor,
|
| 288 |
adv_probs: torch.Tensor,
|
| 289 |
-
|
| 290 |
k: int = 5) -> float:
|
| 291 |
"""Top-k accuracy drop.
|
| 292 |
|
| 293 |
-
Checks if
|
| 294 |
-
Returns 1.0 if
|
| 295 |
"""
|
| 296 |
_, adv_topk = torch.topk(adv_probs, min(k, len(adv_probs)))
|
| 297 |
-
return 0.0 if
|
| 298 |
|
| 299 |
|
| 300 |
|
|
@@ -341,17 +341,22 @@ def compute_all_attack_metrics(
|
|
| 341 |
adv_probs: torch.Tensor,
|
| 342 |
orig_label: int,
|
| 343 |
adv_label: int,
|
|
|
|
| 344 |
k: int = 5,
|
| 345 |
) -> Dict[str, float]:
|
| 346 |
"""Compute all attack success metrics at once.
|
| 347 |
|
|
|
|
|
|
|
|
|
|
| 348 |
Returns:
|
| 349 |
Dictionary with keys: asr, confidence_drop, topk_drop.
|
| 350 |
"""
|
|
|
|
| 351 |
results = {
|
| 352 |
-
"asr": compute_asr(
|
| 353 |
-
"confidence_drop": compute_confidence_drop(orig_probs, adv_probs,
|
| 354 |
-
"topk_drop": compute_topk_drop(orig_probs, adv_probs,
|
| 355 |
}
|
| 356 |
|
| 357 |
return results
|
|
|
|
| 259 |
# Attack Success Metrics
|
| 260 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 261 |
|
| 262 |
+
def compute_asr(reference_label: int, adv_pred: int) -> float:
|
| 263 |
"""Attack Success Rate for a single sample.
|
| 264 |
|
| 265 |
+
Returns 1.0 if attack prediction differs from the reference label, 0.0 otherwise.
|
| 266 |
For batch ASR, average across samples.
|
| 267 |
"""
|
| 268 |
+
return 1.0 if reference_label != adv_pred else 0.0
|
| 269 |
|
| 270 |
|
| 271 |
def compute_confidence_drop(orig_probs: torch.Tensor,
|
| 272 |
adv_probs: torch.Tensor,
|
| 273 |
+
reference_label: int) -> float:
|
| 274 |
+
"""Confidence drop on a reference class.
|
| 275 |
|
| 276 |
Args:
|
| 277 |
orig_probs: Softmax probabilities for original image (1D tensor).
|
| 278 |
adv_probs: Softmax probabilities for adversarial image (1D tensor).
|
| 279 |
+
reference_label: Reference class index (ground truth when available).
|
| 280 |
|
| 281 |
Returns:
|
| 282 |
Drop in confidence (positive = confidence decreased).
|
| 283 |
"""
|
| 284 |
+
return (orig_probs[reference_label] - adv_probs[reference_label]).item()
|
| 285 |
|
| 286 |
|
| 287 |
def compute_topk_drop(orig_probs: torch.Tensor,
|
| 288 |
adv_probs: torch.Tensor,
|
| 289 |
+
reference_label: int,
|
| 290 |
k: int = 5) -> float:
|
| 291 |
"""Top-k accuracy drop.
|
| 292 |
|
| 293 |
+
Checks if reference class remains in top-k predictions after attack.
|
| 294 |
+
Returns 1.0 if reference class dropped out of top-k, 0.0 if still in.
|
| 295 |
"""
|
| 296 |
_, adv_topk = torch.topk(adv_probs, min(k, len(adv_probs)))
|
| 297 |
+
return 0.0 if reference_label in adv_topk.tolist() else 1.0
|
| 298 |
|
| 299 |
|
| 300 |
|
|
|
|
| 341 |
adv_probs: torch.Tensor,
|
| 342 |
orig_label: int,
|
| 343 |
adv_label: int,
|
| 344 |
+
ground_truth: Optional[int] = None,
|
| 345 |
k: int = 5,
|
| 346 |
) -> Dict[str, float]:
|
| 347 |
"""Compute all attack success metrics at once.
|
| 348 |
|
| 349 |
+
If ground_truth is provided, class-dependent metrics use it as reference.
|
| 350 |
+
Otherwise, the original predicted label is used (backward-compatible behavior).
|
| 351 |
+
|
| 352 |
Returns:
|
| 353 |
Dictionary with keys: asr, confidence_drop, topk_drop.
|
| 354 |
"""
|
| 355 |
+
ref_label = int(ground_truth) if ground_truth is not None else int(orig_label)
|
| 356 |
results = {
|
| 357 |
+
"asr": compute_asr(ref_label, adv_label),
|
| 358 |
+
"confidence_drop": compute_confidence_drop(orig_probs, adv_probs, ref_label),
|
| 359 |
+
"topk_drop": compute_topk_drop(orig_probs, adv_probs, ref_label, k=k),
|
| 360 |
}
|
| 361 |
|
| 362 |
return results
|