From bc7fd3850d76876d1a69f2c4c3fce6d3608aa96f Mon Sep 17 00:00:00 2001 From: Tinsae Date: Sun, 7 Jun 2026 13:49:28 +0200 Subject: [PATCH] unlearning CR --- Tune.py | 17 +++++--- Util.py | 34 +++++++++++++++ architectures/Model.py | 4 +- reports/resnet50-finetunned.csv | 1 + unlearning/CertifiedRemoval.py | 77 +++++++++++++++++++++++++++++++++ unlearning/Strategy.py | 2 +- 6 files changed, 126 insertions(+), 9 deletions(-) create mode 100644 Util.py diff --git a/Tune.py b/Tune.py index e975f72..017991a 100644 --- a/Tune.py +++ b/Tune.py @@ -13,6 +13,8 @@ from IdentitySubset import IdentitySubset from architectures.Model import Model, Architecture from unlearning.LinearFiltration import LinearFiltration + +import Util # WeightFiltration, CertifiedRemoval # numbre of classes @@ -134,10 +136,11 @@ for i in range(0,CLASS_SIZE): print(f"Total test images for these {CLASS_SIZE} classes: {len(test_data)}") # Evaluate - model.evaluate( - loader = test_loader, - mode="finetunned" - ) + mode, accuracy, report_dict = model.evaluate( + loader = test_loader, + mode="finetunned" + ) + Util._log_to_csv(model=reloaded, mode = "finetuned", accuracy=accuracy, report_dict=report_dict, strategy="base") # test again reloaded = Model.create( @@ -169,7 +172,9 @@ for i in range(0,CLASS_SIZE): # 4. Final Performance Analysis print("\n--- Performance on Retained Classes") - reloaded.evaluate(loader=retain_test_loader, mode="retain") + mode, accuracy, report_dict = reloaded.evaluate(loader=retain_test_loader, mode="retain") + Util._log_to_csv(model=reloaded, mode = "retain", accuracy=accuracy, report_dict=report_dict, strategy="linearFiltration") print("\n--- Performance on Forgotten Class") - reloaded.evaluate(loader=forget_test_loader,mode="forget") \ No newline at end of file + mode, accuracy, report_dict = reloaded.evaluate(loader=forget_test_loader,mode="forget") + Util._log_to_csv(model=reloaded, mode = "forgotten", accuracy=accuracy, report_dict=report_dict, strategy="linearFiltration") \ No newline at end of file diff --git a/Util.py b/Util.py new file mode 100644 index 0000000..b2f4b0b --- /dev/null +++ b/Util.py @@ -0,0 +1,34 @@ + +from pathlib import Path +from architectures.Model import Model + +def _log_to_csv(model:Model, mode, accuracy, report_dict, strategy): + """Handles directory structures, file setups, and distinct CSV column formatting.""" + arch_name = model.__class__.__name__.lower() + save_dir = Path(f"reports/{strategy}") + save_dir.mkdir(parents=True, exist_ok=True) + csv_path = save_dir / f"{arch_name}-{mode}.csv" + + file_exists = csv_path.exists() + + headers = [ + "accuracy", + "macro_precision", "macro_recall", "macro_f1", + "weighted_precision", "weighted_recall", "weighted_f1" + ] + row = [ + f"{accuracy / 100.0:.4f}", + f"{report_dict['macro avg']['precision']:.4f}", + f"{report_dict['macro avg']['recall']:.4f}", + f"{report_dict['macro avg']['f1-score']:.4f}", + f"{report_dict['weighted avg']['precision']:.4f}", + f"{report_dict['weighted avg']['recall']:.4f}", + f"{report_dict['weighted avg']['f1-score']:.4f}" + ] + + with open(csv_path, "a") as f: + if not file_exists: + f.write(",".join(headers) + "\n") + f.write(",".join(row) + "\n") + + print(f">> Direct CSV metrics appended to {csv_path}") \ No newline at end of file diff --git a/architectures/Model.py b/architectures/Model.py index 2d5978e..29a4a3d 100644 --- a/architectures/Model.py +++ b/architectures/Model.py @@ -149,10 +149,10 @@ class Model(ABC): ) # 3. Delegate file tracking to isolated helper method - self._log_to_csv(mode, accuracy, classes, report_dict) + self._log_to_csv(mode, accuracy,report_dict) - def _log_to_csv(self, mode, accuracy, classes, report_dict): + def _log_to_csv(self, mode, accuracy, report_dict): """Handles directory structures, file setups, and distinct CSV column formatting.""" arch_name = self.__class__.__name__.lower() save_dir = Path("reports") diff --git a/reports/resnet50-finetunned.csv b/reports/resnet50-finetunned.csv index 0a2eab6..751fc10 100644 --- a/reports/resnet50-finetunned.csv +++ b/reports/resnet50-finetunned.csv @@ -19,3 +19,4 @@ accuracy,macro_precision,macro_recall,macro_f1,weighted_precision,weighted_recal 0.9667,0.9750,0.9667,0.9657,0.9750,0.9667,0.9657 0.9333,0.9500,0.9333,0.9314,0.9500,0.9333,0.9314 0.9000,0.9350,0.9000,0.8957,0.9350,0.9000,0.8957 +0.9000,0.9350,0.9000,0.9007,0.9350,0.9000,0.9007 diff --git a/unlearning/CertifiedRemoval.py b/unlearning/CertifiedRemoval.py index e69de29..6c27d1a 100644 --- a/unlearning/CertifiedRemoval.py +++ b/unlearning/CertifiedRemoval.py @@ -0,0 +1,77 @@ +import torch +import numpy as np +from scipy.optimize import minimize +from .Strategy import Strategy +import torch.nn as nn + +class CertifiedRemoval(Strategy): + """Implements Certified Removal for machine unlearning.""" + + def __init__(self, model, data, labels, removal_bound, epsilon): + super().__init__() + self.model = model + self.data = data + self.labels = labels + self.removal_bound = removal_bound + self.epsilon = epsilon + + def _run(self, model: nn.Module) -> nn.Module: + """Runs the certified removal algorithm.""" + # 1. Linear Model Creation + # This is a simplification for demonstration purposes. In a real implementation, + # you'd use more sophisticated methods to learn the parameters of the + # 'removal' model based on the example being removed. + + def linear_model(x): + return torch.dot(x, torch.tensor([1, 1])) # Simplified Linear Model + + # 2. Optimization for Parameter Adjustment + # Optimize the parameter values to minimize the loss while staying within bounds. + original_params = torch.tensor([0.0, 0.0]) # Initial parameters for linear model + + def objective_function(params): + new_model = linear_model #use same function as defined above + return torch.sum(((new_model(self.data[0]) - self.labels)**2)) + + result = minimize(objective_function, original_params, method='L-BFGS-B', bounds=[(-self.removal_bound, self.removal_bound)], options={'maxiter': 100}) + + if not result.success: + print("Warning: Optimization failed!") + print(result.message) + return model #Return original if optimization fails + + new_params = result.x + # 3. New Model Creation + + new_model = lambda x: torch.dot(x, new_params) + return new_model + + +if __name__ == '__main__': + # Example Usage - Synthetic Data for Demonstration + np.random.seed(42) # For reproducibility + n_samples = 100 + X = np.random.randn(n_samples, 2) + y = (X[:, 0] + X[:, 1] > 0).astype(int) + + # Create a simple linear model for demonstration + model = nn.Linear(2, 1) # Simple linear classifier - PyTorch Version + optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # Optimizer for training the linear model + + # Train a Linear Model + for _ in range(100): #training loop + optimizer.zero_grad() + predictions = model(X) + loss = torch.sum((predictions - y)**2) + loss.backward() + optimizer.step() + + # Define parameters for Certified Removal + removal_bound = 1.0 + epsilon = 0.1 + + # Create the CertifiedRemoval object with the trained model, data and labels + certified_removal_obj = CertifiedRemoval(model, X, y, removal_bound, epsilon) + + # Run Certified Removal + new_model = certified_removal_obj.apply(model) \ No newline at end of file diff --git a/unlearning/Strategy.py b/unlearning/Strategy.py index c41821d..99525a9 100644 --- a/unlearning/Strategy.py +++ b/unlearning/Strategy.py @@ -9,7 +9,7 @@ class Strategy: def __init__(self): # Dynamically set file name based on the class name (e.g., 'NormalizingLinearFiltration.txt') self.strategy_name = self.__class__.__name__ - self.log_file = f"reports/{self.strategy_name}_metrics.txt" + self.log_file = f"reports/{self.strategy_name}/metrics.txt" self._initialize_log_file() def _initialize_log_file(self):