-
Notifications
You must be signed in to change notification settings - Fork 8
feat: MetaAttack model #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
270f6b9
feat: initial skeleton files with docstring and signatures
ssrhaso c550567
feat: QMIA CatBoost attack with multiclass and (x,y) conditioning
shamykyzer 6e93c3f
test: QMIA hinge score, multiclass, and attack tests
shamykyzer 094a282
chore: clean up unused files and ignore catboost artifacts
shamykyzer 3c155a0
feat: QMIA benchmark scripts with formatted comparison tables
shamykyzer d2b7074
docs: add QMIA usage and benchmark documentation to README
shamykyzer fcd8a19
Merge remote-tracking branch 'origin/main' into HEAD
shamykyzer 1ecc311
feat: QMIA hinge score and label remapping utilities
shamykyzer 9e63e1c
feat: add QMIAAttack quantile regression membership inference attack
ssrhaso e984a9d
refactor: switch QMIA to HistGradientBoostingRegressor, fix review is…
shamykyzer a981711
fix: remove stale CatBoost references from README, factory test, and …
ssrhaso 8489312
fix: resolve ruff lint errors in benchmarks, tests, and summarize script
ssrhaso c964ad5
style: pre-commit fixes
pre-commit-ci[bot] 45b41d5
Merge branch 'main' into 306-quantile-regression
ssrhaso 293376c
fix: tighten factory test assertions, fix conftest RNG seed
shamykyzer 7e3415f
Merge remote-tracking branch 'origin/306-quantile-regression' into 42…
shamykyzer e17e4e7
feat: add MetaAttack class skeleton and factory registration
shamykyzer 61707ef
feat: implement sub-attack orchestration in MetaAttack
shamykyzer a902292
feat: implement per-record score extraction from sub-attacks
shamykyzer 3b01eb7
fix: address review issues in MetaAttack stages 1-3
shamykyzer 4454852
feat: build vulnerability DataFrame with two-level aggregation
shamykyzer eccd60b
fix: address Stage 4 review issues
shamykyzer 822cf3d
feat: add global metrics, JSON report, and CSV export
shamykyzer b1db337
test: add MetaAttack test suite
shamykyzer 4774fe0
docs: add MetaAttack example script
shamykyzer 5515e43
style: pre-commit fixes
pre-commit-ci[bot] 4654005
fix: harden QMIA regressor, calibration drift, margin rank
shamykyzer a968996
docs: fix smart quotes in QMIA README example
shamykyzer 93c9ac2
docs: update citation.cff
shamykyzer ff802f0
style: pre-commit fixes
pre-commit-ci[bot] ad016c6
refactor: drop extract_true_label_probs, resolve ruff errors
shamykyzer ce8388e
test: tighten QMIA public FPR tolerance
shamykyzer 3b335b6
fix: warn when QMIA skips non-sklearn target remapping
shamykyzer 34213b9
fix: reject non-finite predict_proba in QMIA
shamykyzer 215664c
perf: enable HGBR early stopping for QMIA fits with n >= 1000
shamykyzer 21d67aa
Merge branch '306-quantile-regression' into 428-meta-attack
shamykyzer f0213a5
fix: resolve merge conflict in utils.py (merge main into 428-meta-att…
shamykyzer 65762bb
style: fix ruff lint errors in meta_attack and test
shamykyzer 1b9a03a
merge origin/main into 428-meta-attack
shamykyzer 124f459
chore: remove QMIA files (out of scope)
shamykyzer 2502eb5
feat: MetaAttack review-response and audit fixes
shamykyzer d9b50a6
feat: MetaAttack reporting (append-to-report.json + PDF)
shamykyzer 75b517b
style: pre-commit fixes
pre-commit-ci[bot] 25d0700
fix: MetaAttack reads canonical single-file report.json layout
shamykyzer 0c11c8d
style: pre-commit fixes
pre-commit-ci[bot] bcad245
refactor: move MetaAttack constants to module level
shamykyzer b5dd7b6
refactor: extract MetaAttack EPS_META and DEFAULT_MIA_THRESHOLD to at…
shamykyzer 34a499d
docs: add behaviour kwarg to MetaAttack example
shamykyzer ed088f0
Merge branch 'main' into 428-meta-attack
shamykyzer 38ca47c
test(meta): cover MetaAttack/report branches to reach 100% patch cove…
ssrhaso e69b5ee
test(meta): cover non-finite sub-attack AUC branch in create_meta_report
shamykyzer 4abeed4
docs: add behaviour kwarg to MetaAttack README example
shamykyzer 97c07e2
fix(structural): gate per-record results behind report_individual fla…
shamykyzer 02804f4
Merge branch 'main' into 428-meta-attack
jim-smith File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| """Example: run a MetaAttack combining QMIA and structural attacks. | ||
|
|
||
| Trains a RandomForest on synthetic data, wraps it in a Target, then | ||
| runs MetaAttack to produce a cross-attack vulnerability DataFrame. | ||
|
|
||
| Usage:: | ||
|
|
||
| python examples/sklearn/meta_attack_example.py | ||
| """ | ||
|
|
||
| import logging | ||
|
|
||
| from sklearn.datasets import make_classification | ||
| from sklearn.ensemble import RandomForestClassifier | ||
| from sklearn.model_selection import train_test_split | ||
|
|
||
| from sacroml.attacks.meta_attack import MetaAttack | ||
| from sacroml.attacks.target import Target | ||
|
|
||
| logging.basicConfig(level=logging.INFO) | ||
|
|
||
| output_dir = "output_meta_attack" | ||
|
|
||
| if __name__ == "__main__": | ||
| # --- Prepare target --- | ||
| X, y = make_classification( | ||
| n_samples=300, | ||
| n_features=10, | ||
| n_informative=5, | ||
| n_classes=2, | ||
| random_state=42, | ||
| ) | ||
| X_train, X_test, y_train, y_test = train_test_split( | ||
| X, y, test_size=0.4, stratify=y, random_state=42 | ||
| ) | ||
|
|
||
| model = RandomForestClassifier(n_estimators=100, random_state=42) | ||
| model.fit(X_train, y_train) | ||
|
|
||
| target = Target( | ||
| model=model, | ||
| dataset_name="synthetic", | ||
| X_train=X_train, | ||
| y_train=y_train, | ||
| X_test=X_test, | ||
| y_test=y_test, | ||
| X_train_orig=X_train, | ||
| y_train_orig=y_train, | ||
| X_test_orig=X_test, | ||
| y_test_orig=y_test, | ||
| ) | ||
| for idx in range(X.shape[1]): | ||
| target.add_feature(f"feature_{idx}", [idx], "float") | ||
|
|
||
| # --- Run MetaAttack --- | ||
| meta = MetaAttack( | ||
| attacks=[ | ||
| ("qmia", {}, 2), # QMIA with 2 repetitions | ||
| ("structural", {}), # Structural (single run) | ||
| ], | ||
| behaviour="run_all", # alternatives: "use_existing_only", "fill_missing" | ||
| mia_threshold=0.5, | ||
| output_dir=output_dir, | ||
| ) | ||
| meta.attack(target) | ||
|
|
||
| # --- Inspect results --- | ||
| df = meta.vulnerability_df | ||
|
|
||
| print("\n=== Vulnerability Matrix (first 10 records) ===") | ||
| print(df.head(10).to_string()) | ||
|
|
||
| print("\n=== Summary Statistics ===") | ||
| n_train = int(df["is_member"].sum()) | ||
| n_test = len(df) - n_train | ||
| print(f"Training records: {n_train}") | ||
| print(f"Test records: {n_test}") | ||
|
|
||
| # MIA vulnerability | ||
| if "qmia_vuln" in df.columns: | ||
| n_qmia = int(df["qmia_vuln"].sum()) | ||
| print(f"QMIA vulnerable: {n_qmia}") | ||
|
|
||
| # Structural vulnerability (training records only) | ||
| if "struct_vuln" in df.columns: | ||
| train_df = df[df["is_member"] == 1] | ||
| n_struct = int(train_df["struct_vuln"].sum()) | ||
| print(f"Struct vulnerable: {n_struct} (of {n_train} training)") | ||
|
|
||
| # Records vulnerable to all attacks | ||
| max_attacks = int(df["n_vulnerable"].max()) | ||
| n_all = int((df["n_vulnerable"] == max_attacks).sum()) | ||
| print(f"Vulnerable to all: {n_all} (flagged by {max_attacks} attacks)") | ||
|
|
||
| # Top-10 most vulnerable training records by MIA mean | ||
| if "mia_mean" in df.columns: | ||
| top10 = df[df["is_member"] == 1].nlargest(10, "mia_mean")[ | ||
| ["mia_mean", "mia_gmean", "n_vulnerable"] | ||
| ] | ||
| print("\n=== Top 10 Most Vulnerable Training Records ===") | ||
| print(top10.to_string()) | ||
|
|
||
| print(f"\nReport saved to: {output_dir}/") | ||
| print(f"CSV saved to: {output_dir}/vulnerability_matrix.csv") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| """Shared numerical and default-value constants for the attacks package. | ||
|
|
||
| Centralising these here avoids duplication across attack modules and makes | ||
| the *why* of each magic number visible at a glance. | ||
|
|
||
| Notes | ||
| ----- | ||
| A separate :data:`sacroml.attacks.utils.EPS` (``1e-16``) and an identical | ||
| ``EPS`` in :mod:`sacroml.attacks.likelihood_attack` are kept independently | ||
| for now because they predate this module and migrating them is a wider | ||
| refactor. A follow-up PR can converge those onto a single constant defined | ||
| here once the call sites have been audited. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| EPS_META: float = 1e-10 | ||
| """Tolerance added before ``log()`` in geometric-mean aggregation. | ||
|
|
||
| Looser than :data:`sacroml.attacks.utils.EPS` (``1e-16``) because the | ||
| geometric mean of MIA scores in :class:`~sacroml.attacks.meta_attack.MetaAttack` | ||
| does not need the same precision as normal-distribution CDF/PDF | ||
| calculations and benefits from a value comfortably above floating-point | ||
| denormals. | ||
| """ | ||
|
|
||
| DEFAULT_MIA_THRESHOLD: float = 0.5 | ||
| """Default cutoff above which a per-record membership-inference score is | ||
| flagged as vulnerable. | ||
|
|
||
| Used as the ``mia_threshold`` default for | ||
| :class:`~sacroml.attacks.meta_attack.MetaAttack` so the value can be | ||
| referenced symbolically from tests, examples, and documentation. | ||
| """ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.