Skip to content

Conversation

@fatemetkl
Copy link
Collaborator

PR Type

[ Feature ]

Short Description

Clickup Ticket(s): Link

Added a script for attack success rate computation in the setting of experiments where we are attacking several target models, and have previously saved attack probabilities for each target model.

Tests Added

NA

@fatemetkl fatemetkl changed the title Ft/experiment metric calculation Experiments: Final Attack Success Calculation Nov 21, 2025
@fatemetkl fatemetkl changed the title Experiments: Final Attack Success Calculation Experiments: Final Attack Success Nov 21, 2025
Copy link
Collaborator

@emersodb emersodb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this looks good. A few small comments.

targets = []
for target_id in target_ids:
# Override target model id in config
config.target_model.target_model_id = target_id
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overwriting the config object is a bit odd. If we go with my comments about sending specific pieces to the function instead of the config itself, I think that will also help you side-step this guy.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am now passing the specific pieces that are actually needed in this function. However, I still need to send target_model_config: DictConfig, and update target_model_config.target_model_id = target_id in the for loop to change the target_id at runtime. This is necessary since attack_results_path and challenge_label_path are defined based on target_model_config.target_model_id which lets us avoid manually setting paths for each target model.
In order to avoid overriding this config value, I would need to manually set the paths in the code based on target_id or ask the user to define a path for every target model in config. This overriding is not ideal, but seems faster and easier for running experiments (also a big advantage of hydro I guess). I might still be missing something, and happy to discuss if you have ideas about this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I see what you mean. Basically changing the target id has side-effects in the config for the other paths. That's good with me. It's just a bit more indirection than I'm used to 🙂

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right! Definitely, it is not ideal! We should think about improving it for sure.

@fatemetkl fatemetkl requested a review from emersodb November 25, 2025 19:46
Base automatically changed from ft/ensemble_experiments to main November 25, 2025 19:46
@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

📝 Walkthrough

Walkthrough

This pull request adds a new script for computing ensemble attack success metrics. The script loads challenge labels and prediction probabilities from saved model outputs, computes attack success across multiple target models by calculating TPR at FPR=0.1, and saves results to a text file. Configuration is updated with a new attack_success_computation block containing target model IDs for testing. Minor formatting changes are made to an existing test file.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • New script logic: Data loading, validation, and metric computation across multiple target models require careful review of the accumulation logic and assertions
  • Metric calculation: TPR at FPR=0.1 computation needs verification for correctness
  • File I/O operations: Path handling, NumPy array loading, and CSV parsing should be validated
  • Configuration integration: Hydra configuration usage and field validation in main entry point
  • Array concatenation and dimension handling: Ensure proper validation of prediction and target lengths across targets

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Experiments: Final Attack Success' is vague and generic, using non-descriptive terms that don't clearly convey what specific change was made. Make the title more specific; consider 'Add attack success rate computation script for ensemble attacks' to better describe the actual implementation.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description follows the template structure with PR Type and Short Description sections, includes a ClickUp ticket link, and adequately explains that a script for attack success rate computation was added.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ft/experiment-metric-calculation

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1cf674b and 2df7ba7.

📒 Files selected for processing (3)
  • examples/ensemble_attack/compute_attack_success.py (1 hunks)
  • examples/ensemble_attack/configs/experiment_config.yaml (1 hunks)
  • examples/ensemble_attack/test_attack_model.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
examples/ensemble_attack/compute_attack_success.py (1)
src/midst_toolkit/attacks/ensemble/metric_utils.py (1)
  • get_tpr_at_fpr (7-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: unit-tests
  • GitHub Check: integration-tests
  • GitHub Check: run-code-check
🔇 Additional comments (4)
examples/ensemble_attack/test_attack_model.py (1)

113-115: Only whitespace / formatting change

This hunk just adds surrounding blank lines; the assertion and overall control flow are unchanged, so there’s no behavioral impact.

examples/ensemble_attack/compute_attack_success.py (2)

17-50: Loader helper is consistent and defensively implemented

The loader correctly validates the presence of the .npy file, loads probabilities and labels, and enforces a length match before returning, which aligns with how the predictions are produced elsewhere in the pipeline.


104-126: Main entrypoint wiring looks good

Asserting the presence of attack_success_computation.target_ids_to_test, logging the IDs, and then delegating to compute_attack_success_for_given_targets with the specific config pieces keeps the entrypoint simple and readable.

examples/ensemble_attack/configs/experiment_config.yaml (1)

106-107: New config block matches the attack-success script’s expectations

attack_success_computation.target_ids_to_test is named and structured exactly as used in compute_attack_success.main, so the wiring between config and script is coherent.

Comment on lines +71 to +91
predictions = []
targets = []
for target_id in target_ids:
# Override target model id in config as ``attack_probabilities_result_path`` and
# ``challenge_label_path`` are dependent on it and change in runtime.
target_model_config.target_model_id = target_id
# Load challenge labels and prediction probabilities
log(INFO, f"Loading challenge labels and prediction probabilities for target model ID {target_id}...")
test_target, test_prediction_probabilities = load_target_challenge_labels_and_probabilities(
metaclassifier_model_name=metaclassifier_model_name,
attack_results_path=Path(target_model_config.attack_probabilities_result_path),
challenge_label_path=Path(target_model_config.challenge_label_path),
)
predictions.append(test_prediction_probabilities)
targets.append(test_target)

# Flatten arrays
predictions = np.concatenate(predictions)
targets = np.concatenate(targets)

assert len(predictions) == len(targets), "Number of predictions must match number of targets."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Script executed:

#!/bin/bash
# First, locate and examine the file
find . -type f -name "compute_attack_success.py" -path "*/ensemble_attack/*" | head -5

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 0

stdout:

./examples/ensemble_attack/compute_attack_success.py

</shell_result>


Script executed:

#!/bin/bash
# Check file size first
wc -l ./examples/ensemble_attack/compute_attack_success.py

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 0

stdout:

130 ./examples/ensemble_attack/compute_attack_success.py

</shell_result>


Script executed:

#!/bin/bash
# Read the entire file
cat -n ./examples/ensemble_attack/compute_attack_success.py

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 0

stdout:

     1	"""Provided test prediction probabilities of several attacked target models,
     2	this script computes and saves the attack success metric.
     3	"""
     4	
     5	from logging import INFO
     6	from pathlib import Path
     7	
     8	import hydra
     9	import numpy as np
    10	import pandas as pd
    11	from omegaconf import DictConfig
    12	
    13	from midst_toolkit.attacks.ensemble.metric_utils import get_tpr_at_fpr
    14	from midst_toolkit.common.logger import log
    15	
    16	
    17	def load_target_challenge_labels_and_probabilities(
    18	    metaclassifier_model_name: str, attack_results_path: Path, challenge_label_path: Path
    19	) -> tuple[np.ndarray, np.ndarray]:
    20	    """
    21	    Loads and returns the challenge labels and test prediction probabilities for
    22	    a given target model.
    23	
    24	    Args:
    25	        metaclassifier_model_name: Name of the metaclassifier model used in the attack.
    26	        attack_results_path: Path to the directory where attack results are saved.
    27	        challenge_label_path: Path to the CSV file containing challenge labels.
    28	
    29	    Return:
    30	        A tuple containing:
    31	            - test_target: Numpy array of true membership labels for the challenge points.
    32	            - test_prediction_probabilities: Numpy array of prediction probabilities
    33	              outputted by the metaclassifier for the challenge points.
    34	    """
    35	    attack_result_file_path = attack_results_path / f"{metaclassifier_model_name}_test_pred_proba.npy"
    36	    assert attack_result_file_path.exists(), (
    37	        f"No file found at {attack_result_file_path}. Make sure the path is correct, or run the attack on the target model first."
    38	    )
    39	
    40	    # Load the attack results containing test prediction probabilities.
    41	    test_prediction_probabilities = np.load(attack_result_file_path)
    42	
    43	    # Challenge labels are the true membership labels for the challenge points.
    44	    test_target = pd.read_csv(challenge_label_path).to_numpy().squeeze()
    45	
    46	    assert len(test_prediction_probabilities) == len(test_target), (
    47	        "Number of challenge labels must match number of prediction probabilities."
    48	    )
    49	
    50	    return test_target, test_prediction_probabilities
    51	
    52	
    53	def compute_attack_success_for_given_targets(
    54	    target_model_config: DictConfig,
    55	    target_ids: list[int],
    56	    experiment_directory: Path,
    57	    metaclassifier_model_name: str,
    58	) -> None:
    59	    """
    60	    Computes and saves the attack success metric given the test prediction probabilities
    61	    of several attacked target models by concatenating the target models' targets and predictions.
    62	    NOTE: This function does not compute the average success across all models but rather
    63	    treats all predictions and labels together for metric computation.
    64	
    65	    Args:
    66	        target_model_config: Configuration object for target models set in ``experiments_config.yaml``.
    67	        target_ids: List of target model IDs to compute the attack success for.
    68	        experiment_directory: Path to the base experiment directory where results are saved.
    69	        metaclassifier_model_name: Name of the metaclassifier model used in the attack.
    70	    """
    71	    predictions = []
    72	    targets = []
    73	    for target_id in target_ids:
    74	        # Override target model id in config as ``attack_probabilities_result_path`` and
    75	        # ``challenge_label_path`` are dependent on it and change in runtime.
    76	        target_model_config.target_model_id = target_id
    77	        # Load challenge labels and prediction probabilities
    78	        log(INFO, f"Loading challenge labels and prediction probabilities for target model ID {target_id}...")
    79	        test_target, test_prediction_probabilities = load_target_challenge_labels_and_probabilities(
    80	            metaclassifier_model_name=metaclassifier_model_name,
    81	            attack_results_path=Path(target_model_config.attack_probabilities_result_path),
    82	            challenge_label_path=Path(target_model_config.challenge_label_path),
    83	        )
    84	        predictions.append(test_prediction_probabilities)
    85	        targets.append(test_target)
    86	
    87	    # Flatten arrays
    88	    predictions = np.concatenate(predictions)
    89	    targets = np.concatenate(targets)
    90	
    91	    assert len(predictions) == len(targets), "Number of predictions must match number of targets."
    92	
    93	    # Compute TPR@FPR for all the target models
    94	    tpr_at_fpr = get_tpr_at_fpr(targets, predictions, max_fpr=0.1)
    95	
    96	    # Save the final attack success rate into a text file.
    97	    metric_save_path = experiment_directory / f"attack_success_for_{metaclassifier_model_name}.txt"
    98	
    99	    log(INFO, f"Saving attack success value of {tpr_at_fpr} TPR at FPR=0.1 to {metric_save_path}")
   100	    with open(metric_save_path, "w") as f:
   101	        f.write(f"Final TPR at FPR=0.1: {tpr_at_fpr:.4f}\n")
   102	
   103	
   104	@hydra.main(config_path="configs", config_name="experiment_config", version_base=None)
   105	def main(
   106	    config: DictConfig,
   107	) -> None:
   108	    """
   109	    Main function to compute and save the attack success metric given the test prediction probabilities
   110	    of several attacked target models.
   111	
   112	    Args:
   113	        config: Configuration object set in ``experiments_config.yaml``.
   114	    """
   115	    assert config.attack_success_computation.target_ids_to_test is not None, (
   116	        "Please specify target model IDs to compute attack success for in the config "
   117	        "by specifying `attack_success_computation.target_ids_to_test`."
   118	    )
   119	    target_ids = list(config.attack_success_computation.target_ids_to_test)
   120	    log(INFO, f"Computing attack success for target model IDs: {target_ids}...")
   121	    compute_attack_success_for_given_targets(
   122	        target_model_config=config.target_model,
   123	        target_ids=target_ids,
   124	        experiment_directory=Path(config.base_experiment_dir),
   125	        metaclassifier_model_name=config.metaclassifier.meta_classifier_model_name,
   126	    )
   127	
   128	
   129	if __name__ == "__main__":
   130	    main()

</shell_result>


Script executed:

#!/bin/bash
# Search for other callers of the function
rg -n "compute_attack_success_for_given_targets" ./examples/ensemble_attack/

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 0

stdout:

./examples/ensemble_attack/compute_attack_success.py:53:def compute_attack_success_for_given_targets(
./examples/ensemble_attack/compute_attack_success.py:121:    compute_attack_success_for_given_targets(

</shell_result>


Script executed:

#!/bin/bash
# Search entire codebase for other callers
rg -n "compute_attack_success_for_given_targets" --type py

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 0

stdout:

examples/ensemble_attack/compute_attack_success.py:53:def compute_attack_success_for_given_targets(
examples/ensemble_attack/compute_attack_success.py:121:    compute_attack_success_for_given_targets(

</shell_result>


Script executed:

#!/bin/bash
python3 << 'EOF'
import numpy as np

# Test 1: np.concatenate with empty list
print("Test 1: np.concatenate([])")
try:
    result = np.concatenate([])
    print(f"  Result: {result}")
except Exception as e:
    print(f"  Exception: {type(e).__name__}: {e}")

# Test 2: np.concatenate with single empty array
print("\nTest 2: np.concatenate([np.array([])])")
try:
    result = np.concatenate([np.array([])])
    print(f"  Result: {result}")
    print(f"  Result shape: {result.shape}")
except Exception as e:
    print(f"  Exception: {type(e).__name__}: {e}")

# Test 3: Check behavior with multiple empty arrays
print("\nTest 3: np.concatenate([np.array([]), np.array([])])")
try:
    result = np.concatenate([np.array([]), np.array([])])
    print(f"  Result: {result}")
    print(f"  Result shape: {result.shape}")
except Exception as e:
    print(f"  Exception: {type(e).__name__}: {e}")
EOF

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 1

stderr:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'numpy'

</shell_result>


Script executed:

#!/bin/bash
pip install numpy -q && python3 << 'EOF'
import numpy as np

# Test 1: np.concatenate with empty list
print("Test 1: np.concatenate([])")
try:
    result = np.concatenate([])
    print(f"  Result: {result}")
except Exception as e:
    print(f"  Exception: {type(e).__name__}: {e}")

# Test 2: np.concatenate with single empty array
print("\nTest 2: np.concatenate([np.array([])])")
try:
    result = np.concatenate([np.array([])])
    print(f"  Result: {result}")
    print(f"  Result shape: {result.shape}")
except Exception as e:
    print(f"  Exception: {type(e).__name__}: {e}")

# Test 3: Check behavior with multiple empty arrays
print("\nTest 3: np.concatenate([np.array([]), np.array([])])")
try:
    result = np.concatenate([np.array([]), np.array([])])
    print(f"  Result: {result}")
    print(f"  Result shape: {result.shape}")
except Exception as e:
    print(f"  Exception: {type(e).__name__}: {e}")
EOF

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 0

stdout:

Test 1: np.concatenate([])
  Exception: ValueError: need at least one array to concatenate

Test 2: np.concatenate([np.array([])])
  Result: []
  Result shape: (0,)

Test 3: np.concatenate([np.array([]), np.array([])])
  Result: []
  Result shape: (0,)

stderr:

  WARNING: The scripts f2py and numpy-config are installed in '/home/jailuser/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.

</shell_result>


Script executed:

#!/bin/bash
# Search for config files and documentation about target_ids_to_test
fd -e yaml -e yml | xargs grep -l "target_ids_to_test" 2>/dev/null | head -10

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 0

stdout:

examples/ensemble_attack/configs/experiment_config.yaml

</shell_result>


Script executed:

#!/bin/bash
cat -n examples/ensemble_attack/configs/experiment_config.yaml

Repository: VectorInstitute/midst-toolkit


<shell_result>
Repository: VectorInstitute/midst-toolkit
Exit code: 0

stdout:

     1	# Ensemble experiment configuration
     2	# This config can be used to run both the Ensemble attack training (``run_attack.py``) and testing phases (``tets_attack_model.py``).
     3	base_experiment_dir: examples/ensemble_attack/tabddpm_20k_experiment_data # Processed data, and experiment artifacts will be stored here
     4	base_data_config_dir: examples/ensemble_attack/data_configs # Training and data type configs are saved under this directory
     5	
     6	# Pipeline control
     7	pipeline:
     8	  run_data_processing: true # Set this to false if you have already saved the processed data
     9	  run_shadow_model_training: true # Set this to false if shadow models are already trained and saved
    10	  run_metaclassifier_training: true
    11	
    12	target_model: # This is only used for testing the attack on a real target model.
    13	  # This is for models trained on 20k data and generating 20k synthetic data
    14	  target_model_directory: /projects/midst-experiments/all_tabddpms/tabddpm_trained_with_20k/train/
    15	  target_model_id: 21  # Will be overridden per SLURM array task
    16	  target_model_name: tabddpm_${target_model.target_model_id}
    17	  target_synthetic_data_path: ${target_model.target_model_directory}/${target_model.target_model_name}/synthetic_data/20k/20k.csv
    18	  challenge_data_path: ${target_model.target_model_directory}/${target_model.target_model_name}/challenge_with_id.csv
    19	  challenge_label_path: ${target_model.target_model_directory}/${target_model.target_model_name}/challenge_label.csv
    20	
    21	  target_attack_artifact_dir: ${base_experiment_dir}/target_${target_model.target_model_id}_attack_artifacts/
    22	  attack_probabilities_result_path: ${target_model.target_attack_artifact_dir}/attack_model_${target_model.target_model_id}_proba
    23	  target_shadow_models_output_path: ${target_model.target_attack_artifact_dir}/tabddpm_${target_model.target_model_id}_shadows_dir
    24	
    25	
    26	# Data paths
    27	data_paths:
    28	  midst_data_path: /projects/midst-experiments/all_tabddpms # Used to collect the data
    29	  population_path: ${base_experiment_dir}/population_data  # Path where the collected population data will be stored
    30	  processed_attack_data_path: ${base_experiment_dir}/attack_data # Path where the processed attack real train and evaluation data is stored
    31	  attack_evaluation_result_path: ${base_experiment_dir}/evaluation_results # Path where the attack evaluation results will be stored
    32	
    33	model_paths:
    34	  metaclassifier_model_path: ${base_experiment_dir}/trained_models # Path where the trained metaclassifier model will be saved
    35	
    36	
    37	# Dataset specific information used for processing in this example
    38	data_processing_config:
    39	  population_attack_data_types_to_collect:
    40	        [
    41	          "tabddpm_trained_with_20k",
    42	        ]
    43	  challenge_attack_data_types_to_collect:
    44	        [
    45	          "tabddpm_trained_with_20k",
    46	        ]
    47	  population_splits: ["train"]  # Data splits to be collected for population data
    48	  challenge_splits: ["train"]  # Data splits to be collected for challenge points
    49	  # The column name in the data to be used for stratified splitting.
    50	  column_to_stratify: "trans_type"  # Attention: This value is not documented in the original codebase.
    51	  folder_ranges: #Specify folder ranges for any of the mentioned splits.
    52	    train: [[1, 20]] # Folders to be used for train data collection in the experiments
    53	  # File names in MIDST data directories.
    54	  single_table_train_data_file_name: "train_with_id.csv"
    55	  multi_table_train_data_file_name: "trans.csv"
    56	  challenge_data_file_name: "challenge_with_id.csv"
    57	  population_sample_size: 40000 # Population size is the total data that your attack has access to.
    58	  # In experiments, this is sampled out of all the collected training data in case the available data
    59	  # is more than this number. Note that, half of this data is actually used for training, the other half
    60	  # is used for evaluation. For example, with 40k population size, only 20k is used for training the attack model.
    61	  # TODO: make sure to consider this in experiments.
    62	
    63	# Training and data settings for shadow models (temporary, numbers subject to change)
    64	shadow_training:
    65	  # Data Config files path used for training a TabDDPM model
    66	  training_json_config_paths: # Config json files used for tabddpm training on the trans table
    67	    table_domain_file_path: ${base_data_config_dir}/trans_domain.json
    68	    dataset_meta_file_path: ${base_data_config_dir}/dataset_meta.json
    69	    tabddpm_training_config_path: ${base_data_config_dir}/trans.json
    70	  # Model training artifacts are saved under shadow_models_data_path/workspace_name/exp_name
    71	  # Also, training configs for each shadow model are created under shadow_models_data_path.
    72	  shadow_models_output_path: ${base_experiment_dir}/shadow_models_and_data
    73	  target_model_output_path: ${base_experiment_dir}/shadow_target_model_and_data
    74	  # Paths to final shadow models used for metaclassifier training (relative to shadow_models_output_path)
    75	  # These paths are a result of running the shadow model training pipeline, specifically the
    76	  # train_three_sets_of_shadow_models in shadow_model_training.py
    77	  # Each .pkl file contains the training data, trained model and training results for all shadow models in a list.
    78	  final_shadow_models_path: [
    79	          "${shadow_training.shadow_models_output_path}/initial_model_rmia_1/shadow_workspace/pre_trained_model/rmia_shadows.pkl",
    80	          "${shadow_training.shadow_models_output_path}/initial_model_rmia_2/shadow_workspace/pre_trained_model/rmia_shadows.pkl",
    81	          "${shadow_training.shadow_models_output_path}/shadow_model_rmia_third_set/shadow_workspace/trained_model/rmia_shadows_third_set.pkl",
    82	      ]
    83	  target_synthetic_data_path: ${shadow_training.target_model_output_path}/target_synthetic_data.csv
    84	  # Path to final shadow target's synthetic data (relative to target_model_output_path)
    85	  fine_tuning_config:
    86	    fine_tune_diffusion_iterations: 200000 # Original code: 200000
    87	    fine_tune_classifier_iterations: 20000 # Original code: 20000
    88	    pre_train_data_size: 60000 # Original code: 60000
    89	  number_of_points_to_synthesize: 20000 # Number of synthetic data samples to be generated by shadow models.
    90	  # Original code: 20000
    91	
    92	
    93	# Metaclassifier settings
    94	metaclassifier:
    95	  # Data types json file is used for xgboost model training.
    96	  data_types_file_path: ${base_data_config_dir}/data_types.json
    97	  model_type: "xgb"
    98	  # Model training parameters
    99	  num_optuna_trials: 100 # Original code: 100
   100	  num_kfolds: 5
   101	  use_gpu: false
   102	  # Temporary. Might remove having an epoch parameter.
   103	  epochs: 1
   104	  meta_classifier_model_name: ${metaclassifier.model_type}_metaclassifier_model
   105	
   106	attack_success_computation:
   107	  target_ids_to_test: [21,22,23] # List of target model IDs to compute the attack success for.
   108	
   109	# General settings
   110	random_seed: 42 # Set to null for no seed, or an integer for a fixed seed

</shell_result>


Guard against empty target_ids to avoid concatenation failure

The review comment is correct. Testing confirms that np.concatenate([]) raises ValueError: need at least one array to concatenate. If target_ids is empty, the for loop (line 73) doesn't execute, leaving predictions and targets as empty lists. When np.concatenate() is called on line 88-89, it will fail with a cryptic error. The entry validation in main() (lines 115-118) only checks if target_ids_to_test is not None, but doesn't validate that it's non-empty. Early validation with an explicit error message is clearer than relying on numpy's exception.

Suggested change:

 def compute_attack_success_for_given_targets(
     target_model_config: DictConfig,
     target_ids: list[int],
     experiment_directory: Path,
     metaclassifier_model_name: str,
 ) -> None:
     """
     Computes and saves the attack success metric given the test prediction probabilities
     of several attacked target models by concatenating the target models' targets and predictions.
     NOTE: This function does not compute the average success across all models but rather
     treats all predictions and labels together for metric computation.
 
     Args:
         target_model_config: Configuration object for target models set in ``experiments_config.yaml``.
         target_ids: List of target model IDs to compute the attack success for.
         experiment_directory: Path to the base experiment directory where results are saved.
         metaclassifier_model_name: Name of the metaclassifier model used in the attack.
     """
+    if not target_ids:
+        raise ValueError("target_ids must be a non-empty list of target model IDs.")
+
     predictions = []
     targets = []
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
predictions = []
targets = []
for target_id in target_ids:
# Override target model id in config as ``attack_probabilities_result_path`` and
# ``challenge_label_path`` are dependent on it and change in runtime.
target_model_config.target_model_id = target_id
# Load challenge labels and prediction probabilities
log(INFO, f"Loading challenge labels and prediction probabilities for target model ID {target_id}...")
test_target, test_prediction_probabilities = load_target_challenge_labels_and_probabilities(
metaclassifier_model_name=metaclassifier_model_name,
attack_results_path=Path(target_model_config.attack_probabilities_result_path),
challenge_label_path=Path(target_model_config.challenge_label_path),
)
predictions.append(test_prediction_probabilities)
targets.append(test_target)
# Flatten arrays
predictions = np.concatenate(predictions)
targets = np.concatenate(targets)
assert len(predictions) == len(targets), "Number of predictions must match number of targets."
if not target_ids:
raise ValueError("target_ids must be a non-empty list of target model IDs.")
predictions = []
targets = []
for target_id in target_ids:
# Override target model id in config as ``attack_probabilities_result_path`` and
# ``challenge_label_path`` are dependent on it and change in runtime.
target_model_config.target_model_id = target_id
# Load challenge labels and prediction probabilities
log(INFO, f"Loading challenge labels and prediction probabilities for target model ID {target_id}...")
test_target, test_prediction_probabilities = load_target_challenge_labels_and_probabilities(
metaclassifier_model_name=metaclassifier_model_name,
attack_results_path=Path(target_model_config.attack_probabilities_result_path),
challenge_label_path=Path(target_model_config.challenge_label_path),
)
predictions.append(test_prediction_probabilities)
targets.append(test_target)
# Flatten arrays
predictions = np.concatenate(predictions)
targets = np.concatenate(targets)
assert len(predictions) == len(targets), "Number of predictions must match number of targets."
🤖 Prompt for AI Agents
In examples/ensemble_attack/compute_attack_success.py around lines 71-91 (and
adjust main validation near lines 115-118), guard against an empty target_ids
list: before the loop, check if target_ids is empty and raise a clear ValueError
or log an error and exit (e.g., "target_ids must be a non-empty list") so
np.concatenate is never called on empty lists; additionally, strengthen the
validation in main() to ensure target_ids_to_test is not None and has at least
one element, returning or exiting with the same clear message if empty.

@fatemetkl fatemetkl merged commit 02e7610 into main Nov 25, 2025
6 of 7 checks passed
@fatemetkl fatemetkl deleted the ft/experiment-metric-calculation branch November 25, 2025 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants