Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4ac80dd
Initial commit
sarakodeiri Nov 3, 2025
3a74ac9
Finalized run_ept_attack for feature extraction.
sarakodeiri Nov 4, 2025
468429e
Merge branch 'main' into sk/ept
sarakodeiri Nov 4, 2025
d1bdd9c
Add pre_process_and_train in feature extraction
sarakodeiri Nov 4, 2025
c9fbb79
First draft of attribute prediction train and test
sarakodeiri Nov 5, 2025
171643f
Add tests
sarakodeiri Nov 6, 2025
c8c0c39
Minor change
sarakodeiri Nov 7, 2025
c764598
mypy fix
sarakodeiri Nov 7, 2025
3c03fb3
Merge branch 'main' into sk/ept
sarakodeiri Nov 7, 2025
0f562be
Resolve applicable coderabbit comments
sarakodeiri Nov 7, 2025
678fadb
Merge branch 'main' into sk/ept
sarakodeiri Nov 7, 2025
84bf255
Merge branch 'main' into classifier
sarakodeiri Nov 7, 2025
f67a58c
First draft
sarakodeiri Nov 10, 2025
045181d
Applied first round of reviews
sarakodeiri Nov 10, 2025
d50b250
Fix test
sarakodeiri Nov 10, 2025
ee2ed11
Merge branch 'main' into sk/ept
sarakodeiri Nov 10, 2025
1430a95
Merge sk/ept to classifier
sarakodeiri Nov 10, 2025
21bf14d
Merged main
sarakodeiri Nov 18, 2025
41e1c46
Fix feature extraction tests
sarakodeiri Nov 18, 2025
3a8edd5
Initial label handling
sarakodeiri Nov 21, 2025
b7a0d47
Merge branch 'main' into sk/ept-classifier
sarakodeiri Dec 17, 2025
a3d433b
Initial classifying process implementation
sarakodeiri Dec 18, 2025
c85923e
Merge branch 'main' into sk/ept-classifier
sarakodeiri Jan 14, 2026
346a982
Full classifier training first draft
sarakodeiri Jan 14, 2026
85f03de
Finalized classification and added tests
sarakodeiri Jan 15, 2026
cbbd129
Remove catbooost_info
sarakodeiri Jan 15, 2026
b2c47a3
Resolve coderabbit comments
sarakodeiri Jan 15, 2026
1d3f018
Fix gitignore
sarakodeiri Jan 15, 2026
2c6442a
Upgrade uv.lock
sarakodeiri Jan 15, 2026
ce534ee
Fix test assertions
sarakodeiri Jan 16, 2026
4a0674b
Second set of test fix
sarakodeiri Jan 16, 2026
c77e490
Ruff fix
sarakodeiri Jan 16, 2026
de38092
Scipy downgrade
sarakodeiri Jan 16, 2026
7e8d310
Ruff fix
sarakodeiri Jan 16, 2026
1205e11
First edits: David's comments
sarakodeiri Jan 19, 2026
e329ab2
Modify EPT classification tests
sarakodeiri Jan 19, 2026
942fcd1
Ruff fix
sarakodeiri Jan 19, 2026
56dc5b5
Second round of David's comments
sarakodeiri Jan 20, 2026
1d50172
Add training final classifier
sarakodeiri Jan 23, 2026
05bfdf2
Minor refactoring
sarakodeiri Jan 23, 2026
72033b9
Start inference
sarakodeiri Jan 24, 2026
f579f23
Add inference and tests
sarakodeiri Jan 24, 2026
4157238
Final modification
sarakodeiri Jan 24, 2026
77d616e
Resolve conflict
sarakodeiri Jan 24, 2026
0e9cbe1
Merge branch 'main' into sk/ept-classifier
emersodb Jan 26, 2026
7675f9a
Resolve coderabbit comments and ruff fix
sarakodeiri Jan 26, 2026
36cd862
Merge branch 'main' into sk/ept-classifier
sarakodeiri Jan 26, 2026
9931f52
Minor change to tabddpm
sarakodeiri Jan 27, 2026
fb82ccc
Resolved David's comments
sarakodeiri Jan 31, 2026
7e08320
Merge branch 'main' into sk/ept-classifier
sarakodeiri Jan 31, 2026
c8bac82
Fix test
sarakodeiri Jan 31, 2026
9635a8b
Second test fix
sarakodeiri Feb 2, 2026
d64ea02
Merge branch 'main' into sk/ept-classifier
emersodb Feb 2, 2026
c42794c
Third test fix
sarakodeiri Feb 2, 2026
a55f5d2
Fourth test fix
sarakodeiri Feb 2, 2026
58fde33
Second round of David's comments
sarakodeiri Feb 2, 2026
af83f44
Clean up
sarakodeiri Feb 3, 2026
cdd0e6a
Pip upgrade
sarakodeiri Feb 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/ept_attack/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ data_paths:
output_data_path: ${base_data_dir}/output # Directory to save processed data and results
data_types_file_path: ${base_data_dir}/data_configs/data_types.json # Path to the JSON file defining column types
attribute_features_path: ${data_paths.output_data_path}/attribute_prediction_features # Path to save attribute prediction features
inference_results_path: ${data_paths.output_data_path}/inference_results # Path to save inference (membership prediction) results
# Pipeline control
pipeline:
run_data_processing: false # Whether to run data processing
run_shadow_model_training: false # Whether to run shadow model training
run_feature_extraction: false # Whether to run attribute prediction model training
run_attack_classifier_training: true # Whether to run attack classifier training
run_inference: true # Whether to run inference on the target model

classifier_settings:
results_output_path: ${data_paths.output_data_path}/evaluation_ML
Expand Down
159 changes: 156 additions & 3 deletions examples/ept_attack/run_ept_attack.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import itertools
import json
import pickle
from collections import defaultdict
from datetime import datetime
from logging import INFO
Expand All @@ -19,7 +20,7 @@

from examples.common.utils import directory_checks, iterate_model_folders
from midst_toolkit.attacks.ensemble.data_utils import load_dataframe, save_dataframe
from midst_toolkit.attacks.ept.classification import ClassifierType, train_attack_classifier
from midst_toolkit.attacks.ept.classification import ClassifierType, filter_data, train_attack_classifier
from midst_toolkit.attacks.ept.feature_extraction import extract_features
from midst_toolkit.common.logger import log
from midst_toolkit.common.random import set_all_random_seeds
Expand Down Expand Up @@ -137,6 +138,64 @@ def _summarize_and_save_training_results(
return summary_df


def _train_and_save_best_attack_classifier(
config: DictConfig, best_result: pd.DataFrame, diffusion_model_name: str, model_save_path: Path
) -> None:
"""
Trains and saves the best attack classifier based on the summary DataFrame.

Args:
config: Configuration object set in config.yaml. Used to access attribute features path.
best_result: DataFrame containing the best attack configuration (classifier and column types).
diffusion_model_name: Name of the diffusion model (e.g., 'tabddpm', 'tabsyn', 'clavaddpm').
Used to locate the training features and labels.
model_save_path: Path where the trained model will be saved.
"""
# Train and save the best attack classifier
best_classifier_name = best_result["classifier"].iloc[0]
best_column_types_str = best_result["column_types"].iloc[0]
best_column_types = best_column_types_str.split(" ")

log(
INFO,
f"Training final attack model for {diffusion_model_name} with classifier: {best_classifier_name} and features: {best_column_types}",
)

train_features_data_path = (
Path(config.data_paths.attribute_features_path) / f"{diffusion_model_name}_black_box" / "train"
)

# Concatenate all train features and labels for final training
train_feature_files = train_features_data_path.glob("*.csv")
df_train_features = pd.concat([pd.read_csv(f) for f in train_feature_files], ignore_index=True)
train_labels = df_train_features["is_train"]
df_train_features = df_train_features.drop(columns=["is_train"])

# Train the final model
final_model_results = train_attack_classifier(
classifier_type=ClassifierType(best_classifier_name),
column_types=best_column_types,
x_train=df_train_features,
y_train=train_labels,
x_test=None, # No test set, training on all available data
y_test=None,
)

final_model = final_model_results["trained_model"]

model_save_path = Path(model_save_path) / f"{diffusion_model_name}_best_attack_classifier.pkl"

final_model_metadata = {
"trained_model": final_model,
"column_types": best_column_types,
}

with open(model_save_path, "wb") as file:
pickle.dump(final_model_metadata, file)

log(INFO, f"Saved the best attack model to {model_save_path}")


# Step 4: Attack classifier training
def run_attack_classifier_training(config: DictConfig) -> None:
"""
Expand All @@ -161,6 +220,7 @@ def run_attack_classifier_training(config: DictConfig) -> None:
metric ('final_tpr_fpr_10') representing the best TPR at 10% FPR across
all diffusion models for a given classifier and feature set.
7. Logging the best-performing attack configuration based on this final metric.
8. Training and saving the best attack classifier using all available training data.

Args:
config: Configuration object set in config.yaml.
Expand Down Expand Up @@ -191,6 +251,9 @@ def run_attack_classifier_training(config: DictConfig) -> None:
# ...
# }

# TODO: Move this part of code to a separate function (hyper-parameter tuning)
# TODO: Move some of the code to midst_toolkit.attacks.ept.classification module

summary_results: dict[tuple[str, str], list[tuple[str, dict[str, float]]]] = defaultdict(list)

for diffusion_model_name in diffusion_models:
Expand Down Expand Up @@ -262,11 +325,98 @@ def run_attack_classifier_training(config: DictConfig) -> None:
summary_results, output_summary_path, "attack_classifier_summary.csv"
)

summary_df.sort_values(by=["final_tpr_fpr_10"], ascending=False, inplace=True)
if data_format == "single_table":
# For single-table data, focus on tabddpm results
summary_df.sort_values(by=["tabddpm_tpr_fpr_10"], ascending=False, inplace=True)
else:
# For multi-table data, get the clavaddpm results
summary_df.sort_values(by=["clavaddpm_tpr_fpr_10"], ascending=False, inplace=True)

best_result = summary_df.head(1)
log(INFO, f"Best performing attack configuration:\n{best_result}")

log(INFO, f"Best performing attack configuration:\n{best_result}")
for diffusion_model_name in diffusion_models:
model_save_path = Path(config.classifier_settings.results_output_path) / data_format
_train_and_save_best_attack_classifier(config, best_result, diffusion_model_name, model_save_path)


def run_inference(config: DictConfig, diffusion_model_name_override: str | None = None) -> None:
"""
Runs inference using the trained attack classifier on the challenge data.

Args:
config: Configuration object set in config.yaml.
diffusion_model_name_override: If provided and valid, runs inference
only for this model. If None or invalid, runs for all applicable models.

Throws:
FileNotFoundError: If the trained attack classifier model file is not found.
"""
log(INFO, "Running inference with the trained attack classifier.")

# Determine which diffusion models to run inference on. If an override is provided
# use that; otherwise, use all applicable models based on the specified data format

is_single_table = config.attack_settings.single_table
default_single_table_models = ["tabddpm", "tabsyn"]
default_multi_table_models = ["clavaddpm"]

data_format = "single_table" if is_single_table else "multi_table"

if diffusion_model_name_override is not None:
diffusion_models = [diffusion_model_name_override]
elif is_single_table:
diffusion_models = default_single_table_models
else:
diffusion_models = default_multi_table_models

for diffusion_model_name in diffusion_models:
# Load the trained attack classifier
model_path = (
Path(config.classifier_settings.results_output_path)
/ data_format
/ f"{diffusion_model_name}_best_attack_classifier.pkl"
)

if not model_path.exists():
raise FileNotFoundError(
f"No trained model found at {model_path} for {diffusion_model_name}. "
"Please run the attack classifier training step first."
)

with open(model_path, "rb") as file:
final_model_metadata = pickle.load(file)
trained_model = final_model_metadata["trained_model"]
column_types = final_model_metadata["column_types"]

# Load new feature data for inference
features_data_path = Path(config.data_paths.attribute_features_path)
inference_features_path = features_data_path / f"{diffusion_model_name}_black_box" / "final"

directory_checks(inference_features_path, "Make sure to run feature extraction on final data first.")

challenge_feature_files = inference_features_path.glob("*.csv")

df_inference_features = pd.concat([pd.read_csv(f) for f in challenge_feature_files], ignore_index=True)
filtered_features = filter_data(df_inference_features, column_types)
predictions = trained_model.predict(filtered_features)

# Save inference results
inference_output_path = Path(config.data_paths.inference_results_path)
inference_output_path.mkdir(parents=True, exist_ok=True)

inference_results_file_name = f"{diffusion_model_name}_attack_inference_results.csv"

save_dataframe(
df=pd.DataFrame({"prediction": predictions}),
file_path=inference_output_path,
file_name=inference_results_file_name,
)

log(INFO, f"Saved inference results to {inference_output_path / inference_results_file_name}")

# TODO: Implement evaluation of inference results using the challenge labels
# _evaluate_inference_results(predictions, diffusion_model_name)


@hydra.main(config_path=".", config_name="config", version_base=None)
Expand Down Expand Up @@ -298,6 +448,9 @@ def main(config: DictConfig) -> None:
if config.pipeline.run_attack_classifier_training:
run_attack_classifier_training(config)

if config.pipeline.run_inference:
run_inference(config)


if __name__ == "__main__":
main()
Loading