Repository navigation
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
potentially leave out of mandatory dependencies
There was a problem hiding this comment.
addressed, not a mandatory dependency anymore
…RONA into henba1-foolbox-interface
| if data.dim() == 3: | ||
| data = data.unsqueeze(0) | ||
|
|
||
| if target.dim() == 0: |
There was a problem hiding this comment.
What happends when its a targeted attack?
There was a problem hiding this comment.
targeted attacks are currently not supported - the target here is the correct label
AttackEstimationModule.verify() currently always passes the true label (verification_context.data_point.label) as the "target"
There was a problem hiding this comment.
covered now in 62bf5da : targeted attacks are supported and handled through foolbox criterion obj criterion object. The untargeted wrapper passes the true label, which foolbox auto-wraps as Misclassification . Targeted mode just swaps that for TargetedMisclassification ("reach this class"). No changes to the Attack.execute signature or the downstream pipeline needed.
| BinarySearchEpsilonValueEstimator, | ||
| ) | ||
| from ada_verona.verification_module.attack_estimation_module import AttackEstimationModule | ||
| from ada_verona.verification_module.attacks.foolbox_attack import FoolboxAttack |
There was a problem hiding this comment.
add the check whether foolbox is installed like we have for the other scripts
| from ada_verona.database.dataset.image_file_dataset import ImageFileDataset | ||
| from ada_verona.database.experiment_repository import ExperimentRepository | ||
| from ada_verona.dataset_sampler.predictions_based_sampler import PredictionsBasedSampler | ||
| from ada_verona.epsilon_value_estimator.binary_search_epsilon_value_estimator import ( |
There was a problem hiding this comment.
why do we need two example scripts for such a small different?
There was a problem hiding this comment.
its not mandatory though, treat like AutoAttack and AutoVerify\
add check for foolbox like we have for auto-verify and autoattack
|
I will check the tests later |
- Move foolbox from mandatory to optional dependency in pyproject.toml (add [project.optional-dependencies].foolbox and keep in dev for CI) - Merge two foolbox example scripts into one configurable script; add foolbox installation check matching the autoattack pattern - Remove duplicate create_robustness_dist_foolbox_cw.py - Document that only untargeted attacks are supported in FoolboxAttack docstring - Fix broken IdentitySampler import in __init__.py (file never existed) - Apply ruff formatting across the codebase (pre-commit hook)
- Pin foolbox as third-party and ada_verona as first-party in ruff isort config so import sorting is identical locally and in CI. A local foolbox/ source checkout in the repo root was being misdetected as first-party, hiding the I001 errors that CI (ruff 0.15.18) reports. - Reorder imports in the 3 affected files accordingly. - Guard 'uv venv' with '[ -d .venv ]' in both pytest workflows so a restored .venv cache no longer fails virtual-environment creation. The ruff config change adds no dependencies, so requirements.txt is unchanged (the sync pre-commit hook is skipped for this commit only).
FoolboxAttack now accepts keyword-only 'targeted' and 'target_class' options. By default it stays untargeted (Misclassification of the true label), so all existing usage is unchanged. With targeted=True it uses foolbox's TargetedMisclassification criterion to drive predictions toward target_class. Targeted mode is attack-dependent: iterative attacks (LinfPGD, BIM, DeepFool) support it; single-step FGSM does not and foolbox raises 'unsupported criterion'. No changes to the One2Any pipeline are needed: reaching target_class also implies the true label is no longer predicted, so it scores as a (harder) counterexample. Adds tests for untargeted defaults, the target_class validation error, and a targeted PGD run.
f98df8e to
0230f44
Compare
ExperimentRepository.get_result_df() unconditionally did:
df["network"] = df.network_path.str.split("/")...
but the result CSVs written by save_result()/save_results() come from
VerificationContext.get_dict_for_epsilon_result(), which stores the network
name in a 'network' column and never writes 'network_path'. Reading any such
result file therefore raised:
AttributeError: 'DataFrame' object has no attribute 'network_path'
which also broke save_plots() (it calls get_result_df()).
This bug is pre-existing on main and is independent of the foolbox feature.
Fix: only derive 'network' from 'network_path' for the legacy schema; when a
'network' column is already present, use it as-is.
0230f44 to
1d64106
Compare
The example now showcases more of the package: - Part 1: three L-inf attacks (FGSM, PGD, DeepFool) on ImageFileDataset, with the cross-attack comparison plots (early stopping removed; the bundled dataset is small enough to use in full). - Part 2: an L2 attack on a torchvision dataset wrapped in PytorchExperimentDataset, using an L2-appropriate (larger) epsilon grid, to show the pipeline is norm-agnostic and how to use that dataset loader. DEPENDENCY: the comparison plots use the grouping-agnostic ReportCreator (group_by=...), added in branch henba1-enhance-report-creator commit 53f7c65. This script requires that change on the path until it is merged.
|
The comparison plots in the new foolbox example script use the grouping-agnostic ReportCreator ( |
Aaron99B
left a comment
There was a problem hiding this comment.
There is a lot of noise in the PR due to the linting changes. Could you maybe do the small changes like additional lines, changing spaces in a separate PR first? Then this one will be way easier to review
Agreed, raised the issue already in #179. Let's do this first. I will create an issue from this and open a PR. |
Aaron99B
left a comment
There was a problem hiding this comment.
Looks good. Just some minor things
| torchvision>=0.18.1 | ||
| typing_extensions No newline at end of file | ||
| # This file was autogenerated by uv via the following command: | ||
| # uv pip compile /gpfs/home2/jvrijn/code/VERONA/pyproject.toml -o /gpfs/home2/jvrijn/code/VERONA/requirements.txt |
There was a problem hiding this comment.
These are a lot of requirements. Are we sure we need them all for foolbox?
| if result_df_path.exists(): | ||
| df = pd.read_csv(result_df_path, index_col=0) | ||
| df["network"] = df.network_path.str.split("/").apply(lambda x: x[-1]).apply(lambda x: x.split(".")[0]) | ||
| if "network" not in df.columns and "network_path" in df.columns: |
There was a problem hiding this comment.
Why do we need this change for foolbox?
| so it is scored as a (typically harder) counterexample; `target_class` should | ||
| therefore differ from the true label, otherwise the search is degenerate. | ||
|
|
||
| Attributes: |
There was a problem hiding this comment.
Why are the attributes listed here and in the init?
| """ | ||
| fmodel = foolbox.PyTorchModel(model, bounds=self.bounds) | ||
|
|
||
| attack = self.attack_cls(**self.kwargs) |
There was a problem hiding this comment.
Why do we create the attack here. Can't we already do it in the init?
Purpose
Adds a generic interface to foolbox so any of its attacks (FGSM, PGD, DeepFool, Carlini-Wagner, boundary attacks, L2/L-inf/L1 variants, ...) can be used as a VERONA
Attackwithout a dedicated wrapper per attack, alongside the existingFGSMAttack,PGDAttack, andAutoAttackWrapper.What changed
FoolboxAttack(ada_verona/verification_module/attacks/foolbox_attack.py) — a thinAttacksubclass that wraps anyfoolbox.attacks.*class:bounds(default(0, 1)), and arbitrary constructorkwargs(e.g.steps=40), and runs it throughfoolbox.PyTorchModelinsideexecute().foolbox.criteria.Misclassification, default) and targeted (targeted=True+target_class,foolbox.criteria.TargetedMisclassification) attacks; targeted mode is attack-dependent (single-step attacks like FGSM don't support it and raiseValueErrorfrom foolbox).foolboxas an optional dependency — added topyproject.toml/requirements.txt/uv.lock; not required for the rest of VERONA.examples/scripts/create_robustness_dist_foolbox.py: an end-to-end tutorial comparing L-inf attacks (FGSM, PGD, DeepFool) on anImageFileDataset, plus an L2 attack demo loading data viaPytorchExperimentDatasetto show the pipeline is norm- and dataset-loader-agnostic.docs/how-to-guides.mdlists foolbox attacks alongside the existing methods;README.mdcredits the foolbox authors (Rauber et al., 2017 and 2020) in the acknowledgements section.ExperimentRepository.get_result_df()crashed withAttributeError: 'DataFrame' object has no attribute 'network_path'on result CSVs that already contain anetworkcolumn (the schema actually written bysave_result()/save_results()) — it now only derivesnetworkfromnetwork_pathfor the legacy schema.ada_verona/foolboxas first-/third-party in the ruff isort config (a localfoolbox/checkout in the repo root was being misdetected as first-party, hiding real import-order errors) and guardeduv venvso a restored.venvcache doesn't fail environment creation.How it was tested
tests/test_verification_module/attacks/test_foolbox_attack.pycovering untargeted/targeted execution, the missing-target_classvalidation error, and tensor-shape normalization branches (added incrementally, including a pass specifically to close codecov patch-coverage gaps).tests/test_database/test_experiment_repository.pycovers theget_result_dffix.ruff check/ruff format --checkpass.create_robustness_dist_foolbox.pyexample end-to-end (both the L-inf comparison and the L2/PytorchExperimentDatasetpart) to confirm the interface produces sensible robustness distributions for both the targeted as well as untargeted case via a dedicated repo shared with @Aaron99B (the reviewer): https://github.com/henba1/verona-foolbox-debug