From 7ea2a4f88dd140cea439fd72c8d3757d5b2119d6 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Tue, 12 Mar 2024 14:26:29 +0000 Subject: [PATCH 01/31] Add dp secagg demo example --- examples/dp-secagg-demo/client.py | 51 ++++++++++++ examples/dp-secagg-demo/server_workflow.py | 63 +++++++++++++++ examples/dp-secagg-demo/task.py | 94 ++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 examples/dp-secagg-demo/client.py create mode 100644 examples/dp-secagg-demo/server_workflow.py create mode 100644 examples/dp-secagg-demo/task.py diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/client.py new file mode 100644 index 000000000000..ebbe977ecab1 --- /dev/null +++ b/examples/dp-secagg-demo/client.py @@ -0,0 +1,51 @@ +from flwr.client import ClientApp, NumPyClient + +from task import ( + Net, + DEVICE, + load_data, + get_weights, + set_weights, + train, + test, +) + + +# Load model and data (simple CNN, CIFAR-10) +net = Net().to(DEVICE) +trainloader, testloader = load_data() + + +# Define FlowerClient and client_fn +class FlowerClient(NumPyClient): + + def fit(self, parameters, config): + set_weights(net, parameters) + results = train(net, trainloader, testloader, epochs=1, device=DEVICE) + return get_weights(net), len(trainloader.dataset), results + + def evaluate(self, parameters, config): + set_weights(net, parameters) + loss, accuracy = test(net, testloader) + return loss, len(testloader.dataset), {"accuracy": accuracy} + + +def client_fn(cid: str): + """Create and return an instance of Flower `Client`.""" + return FlowerClient().to_client() + + +# Flower ClientApp +app = ClientApp( + client_fn=client_fn, +) + + +# Legacy mode +if __name__ == "__main__": + from flwr.client import start_client + + start_client( + server_address="127.0.0.1:8080", + client=FlowerClient().to_client(), + ) diff --git a/examples/dp-secagg-demo/server_workflow.py b/examples/dp-secagg-demo/server_workflow.py new file mode 100644 index 000000000000..6923010ecf7b --- /dev/null +++ b/examples/dp-secagg-demo/server_workflow.py @@ -0,0 +1,63 @@ +from typing import List, Tuple + +from task import Net, get_weights + +import flwr as fl +from flwr.common import Context, Metrics, ndarrays_to_parameters +from flwr.server import Driver, LegacyContext + + +# Define metric aggregation function +def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: + examples = [num_examples for num_examples, _ in metrics] + + # Multiply accuracy of each client by number of examples used + train_losses = [num_examples * m["train_loss"] for num_examples, m in metrics] + train_accuracies = [ + num_examples * m["train_accuracy"] for num_examples, m in metrics + ] + val_losses = [num_examples * m["val_loss"] for num_examples, m in metrics] + val_accuracies = [num_examples * m["val_accuracy"] for num_examples, m in metrics] + + # Aggregate and return custom metric (weighted average) + return { + "train_loss": sum(train_losses) / sum(examples), + "train_accuracy": sum(train_accuracies) / sum(examples), + "val_loss": sum(val_losses) / sum(examples), + "val_accuracy": sum(val_accuracies) / sum(examples), + } + + +# Initialize model parameters +ndarrays = get_weights(Net()) +parameters = ndarrays_to_parameters(ndarrays) + + +# Define strategy +strategy = fl.server.strategy.FedAvg( + fraction_fit=1.0, # Select all available clients + fraction_evaluate=0.0, # Disable evaluation + min_available_clients=2, + fit_metrics_aggregation_fn=weighted_average, + initial_parameters=parameters, +) + + +# Run via `flower-server-app server_workflow:app` +app = fl.server.ServerApp() + + +@app.main() +def main(driver: Driver, context: Context) -> None: + # Construct the LegacyContext + context = LegacyContext( + state=context.state, + config=fl.server.ServerConfig(num_rounds=3), + strategy=strategy, + ) + + # Create the workflow + workflow = fl.server.workflow.DefaultWorkflow() + + # Execute + workflow(driver, context) diff --git a/examples/dp-secagg-demo/task.py b/examples/dp-secagg-demo/task.py new file mode 100644 index 000000000000..240f290df320 --- /dev/null +++ b/examples/dp-secagg-demo/task.py @@ -0,0 +1,94 @@ +from collections import OrderedDict +from logging import INFO + +import torch +import torch.nn as nn +import torch.nn.functional as F +from flwr.common.logger import log +from torch.utils.data import DataLoader +from torchvision.datasets import CIFAR10 +from torchvision.transforms import Compose, Normalize, ToTensor + + +DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + + +class Net(nn.Module): + """Model (simple CNN adapted from 'PyTorch: A 60 Minute Blitz')""" + + def __init__(self) -> None: + super(Net, self).__init__() + self.conv1 = nn.Conv2d(3, 6, 5) + self.pool = nn.MaxPool2d(2, 2) + self.conv2 = nn.Conv2d(6, 16, 5) + self.fc1 = nn.Linear(16 * 5 * 5, 120) + self.fc2 = nn.Linear(120, 84) + self.fc3 = nn.Linear(84, 10) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.pool(F.relu(self.conv1(x))) + x = self.pool(F.relu(self.conv2(x))) + x = x.view(-1, 16 * 5 * 5) + x = F.relu(self.fc1(x)) + x = F.relu(self.fc2(x)) + return self.fc3(x) + + +def load_data(): + """Load CIFAR-10 (training and test set).""" + trf = Compose([ToTensor(), Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) + trainset = CIFAR10("./data", train=True, download=True, transform=trf) + testset = CIFAR10("./data", train=False, download=True, transform=trf) + return DataLoader(trainset, batch_size=32, shuffle=True), DataLoader(testset) + + +def train(net, trainloader, valloader, epochs, device): + """Train the model on the training set.""" + log(INFO, "Starting training...") + net.to(device) # move model to GPU if available + criterion = torch.nn.CrossEntropyLoss().to(device) + optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9) + net.train() + for _ in range(epochs): + for images, labels in trainloader: + images, labels = images.to(device), labels.to(device) + optimizer.zero_grad() + loss = criterion(net(images), labels) + loss.backward() + optimizer.step() + + train_loss, train_acc = test(net, trainloader) + val_loss, val_acc = test(net, valloader) + + results = { + "train_loss": train_loss, + "train_accuracy": train_acc, + "val_loss": val_loss, + "val_accuracy": val_acc, + } + return results + + +def test(net, testloader): + """Validate the model on the test set.""" + net.to(DEVICE) + criterion = torch.nn.CrossEntropyLoss() + correct, loss = 0, 0.0 + with torch.no_grad(): + for images, labels in testloader: + outputs = net(images.to(DEVICE)) + labels = labels.to(DEVICE) + loss += criterion(outputs, labels).item() + correct += (torch.max(outputs.data, 1)[1] == labels).sum().item() + accuracy = correct / len(testloader.dataset) + return loss, accuracy + + +def get_weights(net): + return [val.cpu().numpy() for _, val in net.state_dict().items()] + + +def set_weights(net, parameters): + params_dict = zip(net.state_dict().keys(), parameters) + state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict}) + net.load_state_dict(state_dict, strict=True) From 3f32370aa5cc5430eea81a1d91245baab49d8f81 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Tue, 12 Mar 2024 18:13:52 +0000 Subject: [PATCH 02/31] add sa --- examples/dp-secagg-demo/client.py | 27 +++++-------------- .../{server_workflow.py => server.py} | 12 ++++++--- 2 files changed, 15 insertions(+), 24 deletions(-) rename examples/dp-secagg-demo/{server_workflow.py => server.py} (84%) diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/client.py index ebbe977ecab1..ed69e6edebbc 100644 --- a/examples/dp-secagg-demo/client.py +++ b/examples/dp-secagg-demo/client.py @@ -1,15 +1,7 @@ -from flwr.client import ClientApp, NumPyClient - -from task import ( - Net, - DEVICE, - load_data, - get_weights, - set_weights, - train, - test, -) +from task import DEVICE, Net, get_weights, load_data, set_weights, test, train +from flwr.client import ClientApp, NumPyClient +from flwr.client.mod import secaggplus_mod # Load model and data (simple CNN, CIFAR-10) net = Net().to(DEVICE) @@ -38,14 +30,7 @@ def client_fn(cid: str): # Flower ClientApp app = ClientApp( client_fn=client_fn, + mods=[ + secaggplus_mod, + ], ) - - -# Legacy mode -if __name__ == "__main__": - from flwr.client import start_client - - start_client( - server_address="127.0.0.1:8080", - client=FlowerClient().to_client(), - ) diff --git a/examples/dp-secagg-demo/server_workflow.py b/examples/dp-secagg-demo/server.py similarity index 84% rename from examples/dp-secagg-demo/server_workflow.py rename to examples/dp-secagg-demo/server.py index 6923010ecf7b..dd4be383cf7f 100644 --- a/examples/dp-secagg-demo/server_workflow.py +++ b/examples/dp-secagg-demo/server.py @@ -5,6 +5,7 @@ import flwr as fl from flwr.common import Context, Metrics, ndarrays_to_parameters from flwr.server import Driver, LegacyContext +from flwr.server.workflow import SecAggPlusWorkflow # Define metric aggregation function @@ -35,9 +36,9 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Define strategy strategy = fl.server.strategy.FedAvg( - fraction_fit=1.0, # Select all available clients + fraction_fit=0.2, # Select 20% of all available clients fraction_evaluate=0.0, # Disable evaluation - min_available_clients=2, + min_available_clients=100, fit_metrics_aggregation_fn=weighted_average, initial_parameters=parameters, ) @@ -57,7 +58,12 @@ def main(driver: Driver, context: Context) -> None: ) # Create the workflow - workflow = fl.server.workflow.DefaultWorkflow() + workflow = fl.server.workflow.DefaultWorkflow( + fit_workflow=SecAggPlusWorkflow( + num_shares=7, + reconstruction_threshold=4, + ) + ) # Execute workflow(driver, context) From 1f1762777e37eb4e887b0dd96450e131b53ad1f0 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 15:38:00 +0000 Subject: [PATCH 03/31] Add clientside fixed clipping --- examples/dp-secagg-demo/client.py | 3 +++ examples/dp-secagg-demo/server.py | 14 +++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/client.py index ed69e6edebbc..2810d366984f 100644 --- a/examples/dp-secagg-demo/client.py +++ b/examples/dp-secagg-demo/client.py @@ -3,6 +3,8 @@ from flwr.client import ClientApp, NumPyClient from flwr.client.mod import secaggplus_mod +from flwr.client.mod.centraldp_mods import fixedclipping_mod + # Load model and data (simple CNN, CIFAR-10) net = Net().to(DEVICE) trainloader, testloader = load_data() @@ -32,5 +34,6 @@ def client_fn(cid: str): client_fn=client_fn, mods=[ secaggplus_mod, + fixedclipping_mod, ], ) diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index dd4be383cf7f..d57861549b1f 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -6,6 +6,7 @@ from flwr.common import Context, Metrics, ndarrays_to_parameters from flwr.server import Driver, LegacyContext from flwr.server.workflow import SecAggPlusWorkflow +from flwr.server.strategy.dp_fixed_clipping import DifferentialPrivacyClientSideFixedClipping # Define metric aggregation function @@ -36,13 +37,16 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Define strategy strategy = fl.server.strategy.FedAvg( - fraction_fit=0.2, # Select 20% of all available clients + fraction_fit=1.0, # Select 20% of all available clients fraction_evaluate=0.0, # Disable evaluation - min_available_clients=100, + min_fit_clients=3, + min_available_clients=3, fit_metrics_aggregation_fn=weighted_average, initial_parameters=parameters, ) +dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.5, 10, 3) + # Run via `flower-server-app server_workflow:app` app = fl.server.ServerApp() @@ -54,14 +58,14 @@ def main(driver: Driver, context: Context) -> None: context = LegacyContext( state=context.state, config=fl.server.ServerConfig(num_rounds=3), - strategy=strategy, + strategy=dp_strategy, ) # Create the workflow workflow = fl.server.workflow.DefaultWorkflow( fit_workflow=SecAggPlusWorkflow( - num_shares=7, - reconstruction_threshold=4, + num_shares=3, + reconstruction_threshold=2, ) ) From 8674db0f207c0bdf7b2d6f048a584ba48ab18090 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 15:56:20 +0000 Subject: [PATCH 04/31] Fix --- examples/dp-secagg-demo/client.py | 2 +- examples/dp-secagg-demo/server.py | 12 ++++++------ examples/dp-secagg-demo/task.py | 6 +++++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/client.py index 2810d366984f..1f8bf0fe6db0 100644 --- a/examples/dp-secagg-demo/client.py +++ b/examples/dp-secagg-demo/client.py @@ -15,7 +15,7 @@ class FlowerClient(NumPyClient): def fit(self, parameters, config): set_weights(net, parameters) - results = train(net, trainloader, testloader, epochs=1, device=DEVICE) + results = train(net, trainloader, testloader, iters=10, epochs=1, device=DEVICE) return get_weights(net), len(trainloader.dataset), results def evaluate(self, parameters, config): diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index d57861549b1f..acf20df87cf1 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -37,15 +37,15 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Define strategy strategy = fl.server.strategy.FedAvg( - fraction_fit=1.0, # Select 20% of all available clients + fraction_fit=0.2, # Select 20% of all available clients fraction_evaluate=0.0, # Disable evaluation - min_fit_clients=3, - min_available_clients=3, + min_fit_clients=20, + min_available_clients=100, fit_metrics_aggregation_fn=weighted_average, initial_parameters=parameters, ) -dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.5, 10, 3) +dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.5, 10, 20) # Run via `flower-server-app server_workflow:app` @@ -64,8 +64,8 @@ def main(driver: Driver, context: Context) -> None: # Create the workflow workflow = fl.server.workflow.DefaultWorkflow( fit_workflow=SecAggPlusWorkflow( - num_shares=3, - reconstruction_threshold=2, + num_shares=7, + reconstruction_threshold=4, ) ) diff --git a/examples/dp-secagg-demo/task.py b/examples/dp-secagg-demo/task.py index 240f290df320..157c1be12a0f 100644 --- a/examples/dp-secagg-demo/task.py +++ b/examples/dp-secagg-demo/task.py @@ -42,13 +42,14 @@ def load_data(): return DataLoader(trainset, batch_size=32, shuffle=True), DataLoader(testset) -def train(net, trainloader, valloader, epochs, device): +def train(net, trainloader, valloader, epochs, iters, device): """Train the model on the training set.""" log(INFO, "Starting training...") net.to(device) # move model to GPU if available criterion = torch.nn.CrossEntropyLoss().to(device) optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9) net.train() + iter_cnt = 0 for _ in range(epochs): for images, labels in trainloader: images, labels = images.to(device), labels.to(device) @@ -56,6 +57,9 @@ def train(net, trainloader, valloader, epochs, device): loss = criterion(net(images), labels) loss.backward() optimizer.step() + # iter_cnt += 1 + # if iter_cnt >= iters: + # return train_loss, train_acc = test(net, trainloader) val_loss, val_acc = test(net, valloader) From c38fc907454d330f4bc45b6f244b40a4db94dac7 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Wed, 13 Mar 2024 18:01:53 +0000 Subject: [PATCH 05/31] update --- examples/dp-secagg-demo/client.py | 16 ++++++++++------ examples/dp-secagg-demo/server.py | 8 ++++---- examples/dp-secagg-demo/task.py | 32 ++++++++++++++++++++----------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/client.py index 1f8bf0fe6db0..d901fb8d849b 100644 --- a/examples/dp-secagg-demo/client.py +++ b/examples/dp-secagg-demo/client.py @@ -7,26 +7,30 @@ # Load model and data (simple CNN, CIFAR-10) net = Net().to(DEVICE) -trainloader, testloader = load_data() # Define FlowerClient and client_fn class FlowerClient(NumPyClient): + + def __init__(self, trainloader, testloader) -> None: + self.trainloader = trainloader + self.testloader = testloader def fit(self, parameters, config): set_weights(net, parameters) - results = train(net, trainloader, testloader, iters=10, epochs=1, device=DEVICE) - return get_weights(net), len(trainloader.dataset), results + results = train(net, self.trainloader, self.testloader, epochs=1, device=DEVICE) + return get_weights(net), len(self.trainloader.dataset), results def evaluate(self, parameters, config): set_weights(net, parameters) - loss, accuracy = test(net, testloader) - return loss, len(testloader.dataset), {"accuracy": accuracy} + loss, accuracy = test(net, self.testloader) + return loss, len(self.testloader.dataset), {"accuracy": accuracy} def client_fn(cid: str): """Create and return an instance of Flower `Client`.""" - return FlowerClient().to_client() + trainloader, testloader = load_data(partition_id=int(cid)) + return FlowerClient(trainloader, testloader).to_client() # Flower ClientApp diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index acf20df87cf1..e9ebc3f7f982 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -37,15 +37,15 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Define strategy strategy = fl.server.strategy.FedAvg( - fraction_fit=0.2, # Select 20% of all available clients + fraction_fit=1.0, # Select 20% of all available clients fraction_evaluate=0.0, # Disable evaluation - min_fit_clients=20, - min_available_clients=100, + min_fit_clients=10, + min_available_clients=10, fit_metrics_aggregation_fn=weighted_average, initial_parameters=parameters, ) -dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.5, 10, 20) +dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.5, 10, 10) # Run via `flower-server-app server_workflow:app` diff --git a/examples/dp-secagg-demo/task.py b/examples/dp-secagg-demo/task.py index 157c1be12a0f..2154d20de730 100644 --- a/examples/dp-secagg-demo/task.py +++ b/examples/dp-secagg-demo/task.py @@ -1,5 +1,6 @@ from collections import OrderedDict from logging import INFO +from flwr_datasets import FederatedDataset import torch import torch.nn as nn @@ -34,22 +35,34 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: return self.fc3(x) -def load_data(): - """Load CIFAR-10 (training and test set).""" - trf = Compose([ToTensor(), Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) - trainset = CIFAR10("./data", train=True, download=True, transform=trf) - testset = CIFAR10("./data", train=False, download=True, transform=trf) - return DataLoader(trainset, batch_size=32, shuffle=True), DataLoader(testset) +def load_data(partition_id): + """Load partition CIFAR10 data.""" + fds = FederatedDataset(dataset="cifar10", partitioners={"train": 100}) + partition = fds.load_partition(partition_id) + # Divide data on each node: 80% train, 20% test + partition_train_test = partition.train_test_split(test_size=0.2) + pytorch_transforms = Compose( + [ToTensor(), Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))] + ) + def apply_transforms(batch): + """Apply transforms to the partition from FederatedDataset.""" + batch["img"] = [pytorch_transforms(img) for img in batch["img"]] + return batch -def train(net, trainloader, valloader, epochs, iters, device): + partition_train_test = partition_train_test.with_transform(apply_transforms) + trainloader = DataLoader(partition_train_test["train"], batch_size=32, shuffle=True) + testloader = DataLoader(partition_train_test["test"], batch_size=32) + return trainloader, testloader + + +def train(net, trainloader, valloader, epochs, device): """Train the model on the training set.""" log(INFO, "Starting training...") net.to(device) # move model to GPU if available criterion = torch.nn.CrossEntropyLoss().to(device) optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9) net.train() - iter_cnt = 0 for _ in range(epochs): for images, labels in trainloader: images, labels = images.to(device), labels.to(device) @@ -57,9 +70,6 @@ def train(net, trainloader, valloader, epochs, iters, device): loss = criterion(net(images), labels) loss.backward() optimizer.step() - # iter_cnt += 1 - # if iter_cnt >= iters: - # return train_loss, train_acc = test(net, trainloader) val_loss, val_acc = test(net, valloader) From 55739f889f8f9931a41879de65bc7e620245e226 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Wed, 13 Mar 2024 18:56:07 +0000 Subject: [PATCH 06/31] fix a bug causing data loading failure --- examples/dp-secagg-demo/client.py | 2 +- examples/dp-secagg-demo/server.py | 12 +++++++----- examples/dp-secagg-demo/task.py | 9 ++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/client.py index d901fb8d849b..a1a3b1467215 100644 --- a/examples/dp-secagg-demo/client.py +++ b/examples/dp-secagg-demo/client.py @@ -11,7 +11,7 @@ # Define FlowerClient and client_fn class FlowerClient(NumPyClient): - + def __init__(self, trainloader, testloader) -> None: self.trainloader = trainloader self.testloader = testloader diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index e9ebc3f7f982..54ea27c10cc5 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -6,7 +6,9 @@ from flwr.common import Context, Metrics, ndarrays_to_parameters from flwr.server import Driver, LegacyContext from flwr.server.workflow import SecAggPlusWorkflow -from flwr.server.strategy.dp_fixed_clipping import DifferentialPrivacyClientSideFixedClipping +from flwr.server.strategy.dp_fixed_clipping import ( + DifferentialPrivacyClientSideFixedClipping, +) # Define metric aggregation function @@ -37,15 +39,15 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Define strategy strategy = fl.server.strategy.FedAvg( - fraction_fit=1.0, # Select 20% of all available clients + fraction_fit=0.2, # Select 20% of all available clients fraction_evaluate=0.0, # Disable evaluation - min_fit_clients=10, - min_available_clients=10, + min_fit_clients=20, + min_available_clients=100, fit_metrics_aggregation_fn=weighted_average, initial_parameters=parameters, ) -dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.5, 10, 10) +dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.5, 10, 20) # Run via `flower-server-app server_workflow:app` diff --git a/examples/dp-secagg-demo/task.py b/examples/dp-secagg-demo/task.py index 2154d20de730..2fe68260c3f5 100644 --- a/examples/dp-secagg-demo/task.py +++ b/examples/dp-secagg-demo/task.py @@ -64,8 +64,9 @@ def train(net, trainloader, valloader, epochs, device): optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9) net.train() for _ in range(epochs): - for images, labels in trainloader: - images, labels = images.to(device), labels.to(device) + for batch in trainloader: + images = batch["img"].to(device) + labels = batch["label"].to(device) optimizer.zero_grad() loss = criterion(net(images), labels) loss.backward() @@ -89,7 +90,9 @@ def test(net, testloader): criterion = torch.nn.CrossEntropyLoss() correct, loss = 0, 0.0 with torch.no_grad(): - for images, labels in testloader: + for batch in testloader: + images = batch["img"].to(DEVICE) + labels = batch["label"].to(DEVICE) outputs = net(images.to(DEVICE)) labels = labels.to(DEVICE) loss += criterion(outputs, labels).item() From 0d3d0237f1470f39f3669ca4b1503dc2abf047a4 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Wed, 13 Mar 2024 20:57:08 +0000 Subject: [PATCH 07/31] update configs --- examples/dp-secagg-demo/server.py | 8 ++++---- examples/dp-secagg-demo/task.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index 54ea27c10cc5..1682cb2da86c 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -39,15 +39,15 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Define strategy strategy = fl.server.strategy.FedAvg( - fraction_fit=0.2, # Select 20% of all available clients + fraction_fit=1.0, # Select 20% of all available clients fraction_evaluate=0.0, # Disable evaluation - min_fit_clients=20, - min_available_clients=100, + min_fit_clients=40, + min_available_clients=40, fit_metrics_aggregation_fn=weighted_average, initial_parameters=parameters, ) -dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.5, 10, 20) +dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.1, 10, 40) # Run via `flower-server-app server_workflow:app` diff --git a/examples/dp-secagg-demo/task.py b/examples/dp-secagg-demo/task.py index 2fe68260c3f5..0743c9587b8b 100644 --- a/examples/dp-secagg-demo/task.py +++ b/examples/dp-secagg-demo/task.py @@ -37,7 +37,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: def load_data(partition_id): """Load partition CIFAR10 data.""" - fds = FederatedDataset(dataset="cifar10", partitioners={"train": 100}) + fds = FederatedDataset(dataset="cifar10", partitioners={"train": 40}) partition = fds.load_partition(partition_id) # Divide data on each node: 80% train, 20% test partition_train_test = partition.train_test_split(test_size=0.2) From 503212fc1938fcdfccf649ee3c276d592fa24c7c Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Wed, 13 Mar 2024 21:40:13 +0000 Subject: [PATCH 08/31] Create README.md --- examples/dp-secagg-demo/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/dp-secagg-demo/README.md diff --git a/examples/dp-secagg-demo/README.md b/examples/dp-secagg-demo/README.md new file mode 100644 index 000000000000..807cc392f60f --- /dev/null +++ b/examples/dp-secagg-demo/README.md @@ -0,0 +1,3 @@ +# DP + SecAgg Demo Example +This is a simple example which utilizes central differential privacy with client-side fixed clipping with secure aggregation. +Note: It is for small number of rounds and just for demo purpose. From 13acf884310199f389cf4b5f66751d92e9f5fb9e Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Wed, 13 Mar 2024 21:56:41 +0000 Subject: [PATCH 09/31] Update README.md --- examples/dp-secagg-demo/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/dp-secagg-demo/README.md b/examples/dp-secagg-demo/README.md index 807cc392f60f..872259dcf4c0 100644 --- a/examples/dp-secagg-demo/README.md +++ b/examples/dp-secagg-demo/README.md @@ -1,3 +1,4 @@ # DP + SecAgg Demo Example -This is a simple example which utilizes central differential privacy with client-side fixed clipping with secure aggregation. -Note: It is for small number of rounds and just for demo purpose. + +This is a simple example which utilizes central differential privacy with client-side fixed clipping with secure aggregation. +Note: It is for small number of rounds and just for demo purpose. From 9d1eadf72f75e94ecd3c1286ed094a525fc7b6da Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 22:22:24 +0000 Subject: [PATCH 10/31] Update --- examples/dp-secagg-demo/server.py | 13 ++++++++----- examples/dp-secagg-demo/task.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index 1682cb2da86c..c960b9bd6b91 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -39,15 +39,18 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Define strategy strategy = fl.server.strategy.FedAvg( - fraction_fit=1.0, # Select 20% of all available clients + fraction_fit=0.2, fraction_evaluate=0.0, # Disable evaluation - min_fit_clients=40, - min_available_clients=40, + min_fit_clients=20, + min_available_clients=20, fit_metrics_aggregation_fn=weighted_average, initial_parameters=parameters, ) -dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, 0.1, 10, 40) +dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, + noise_multiplier=0.1, + clipping_norm=10, + num_sampled_clients=20) # Run via `flower-server-app server_workflow:app` @@ -59,7 +62,7 @@ def main(driver: Driver, context: Context) -> None: # Construct the LegacyContext context = LegacyContext( state=context.state, - config=fl.server.ServerConfig(num_rounds=3), + config=fl.server.ServerConfig(num_rounds=10), strategy=dp_strategy, ) diff --git a/examples/dp-secagg-demo/task.py b/examples/dp-secagg-demo/task.py index 0743c9587b8b..2fe68260c3f5 100644 --- a/examples/dp-secagg-demo/task.py +++ b/examples/dp-secagg-demo/task.py @@ -37,7 +37,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: def load_data(partition_id): """Load partition CIFAR10 data.""" - fds = FederatedDataset(dataset="cifar10", partitioners={"train": 40}) + fds = FederatedDataset(dataset="cifar10", partitioners={"train": 100}) partition = fds.load_partition(partition_id) # Divide data on each node: 80% train, 20% test partition_train_test = partition.train_test_split(test_size=0.2) From 62a23481164aa859956606bfcd0cc25f8a346152 Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Wed, 13 Mar 2024 22:31:35 +0000 Subject: [PATCH 11/31] Update README.md --- examples/dp-secagg-demo/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/dp-secagg-demo/README.md b/examples/dp-secagg-demo/README.md index 872259dcf4c0..ffb3032fff8c 100644 --- a/examples/dp-secagg-demo/README.md +++ b/examples/dp-secagg-demo/README.md @@ -1,4 +1,11 @@ # DP + SecAgg Demo Example -This is a simple example which utilizes central differential privacy with client-side fixed clipping with secure aggregation. -Note: It is for small number of rounds and just for demo purpose. +This is a simple example that utilizes central differential privacy with client-side fixed clipping and secure aggregation. +Note: This example is designed for a small number of rounds and is intended for demonstration purposes. + +## Run + +The example uses the CIFAR-10 dataset with a total of 100 clients, with 20 clients sampled in each round. The hyperparameters for DP and SecAgg are specified in `server.py`. + +```shell +flower-simulation --server-app server:app --client-app client:app --num-supernodes 100 From 6986158b745c3a58ec197a5ac4bedb5320b943c9 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 16:52:48 +0000 Subject: [PATCH 12/31] Add logging to central dp wrappers --- .../server/strategy/dp_adaptive_clipping.py | 19 ++++++++++++++++++- .../flwr/server/strategy/dp_fixed_clipping.py | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/server/strategy/dp_adaptive_clipping.py b/src/py/flwr/server/strategy/dp_adaptive_clipping.py index d9422c791167..8627f16c5588 100644 --- a/src/py/flwr/server/strategy/dp_adaptive_clipping.py +++ b/src/py/flwr/server/strategy/dp_adaptive_clipping.py @@ -19,7 +19,7 @@ import math -from logging import WARNING +from logging import DEBUG, INFO, WARNING from typing import Dict, List, Optional, Tuple, Union import numpy as np @@ -39,6 +39,7 @@ adaptive_clip_inputs_inplace, add_gaussian_noise_to_params, compute_adaptive_noise_params, + compute_stdv, ) from flwr.common.differential_privacy_constants import ( CLIENTS_DISCREPANCY_WARNING, @@ -225,6 +226,14 @@ def aggregate_fit( self.clipping_norm, self.num_sampled_clients, ) + log(INFO, "aggregate_fit: central dp noise added to params.") + log( + DEBUG, + "aggregate_fit: central dp noise with standard deviation: %s added to params.", + compute_stdv( + self.noise_multiplier, self.clipping_norm, self.num_sampled_clients + ), + ) return aggregated_params, metrics @@ -408,6 +417,14 @@ def aggregate_fit( self.clipping_norm, self.num_sampled_clients, ) + log(INFO, "aggregate_fit: central dp noise added to params.") + log( + DEBUG, + "aggregate_fit: central dp noise with standard deviation: %s added to params.", + compute_stdv( + self.noise_multiplier, self.clipping_norm, self.num_sampled_clients + ), + ) return aggregated_params, metrics diff --git a/src/py/flwr/server/strategy/dp_fixed_clipping.py b/src/py/flwr/server/strategy/dp_fixed_clipping.py index 69930ce49c0b..d0a4701baf2f 100644 --- a/src/py/flwr/server/strategy/dp_fixed_clipping.py +++ b/src/py/flwr/server/strategy/dp_fixed_clipping.py @@ -18,7 +18,7 @@ """ -from logging import WARNING +from logging import DEBUG, INFO, WARNING from typing import Dict, List, Optional, Tuple, Union from flwr.common import ( @@ -35,6 +35,7 @@ from flwr.common.differential_privacy import ( add_gaussian_noise_to_params, compute_clip_model_update, + compute_stdv, ) from flwr.common.differential_privacy_constants import ( CLIENTS_DISCREPANCY_WARNING, @@ -171,6 +172,14 @@ def aggregate_fit( self.clipping_norm, self.num_sampled_clients, ) + log(INFO, "aggregate_fit: central dp noise added to params.") + log( + DEBUG, + "aggregate_fit: central dp noise with standard deviation: %s added to params.", + compute_stdv( + self.noise_multiplier, self.clipping_norm, self.num_sampled_clients + ), + ) return aggregated_params, metrics @@ -321,6 +330,14 @@ def aggregate_fit( self.clipping_norm, self.num_sampled_clients, ) + log(INFO, "aggregate_fit: central dp noise added to params.") + log( + DEBUG, + "aggregate_fit: central dp noise with standard deviation: %s added to params.", + compute_stdv( + self.noise_multiplier, self.clipping_norm, self.num_sampled_clients + ), + ) return aggregated_params, metrics def aggregate_evaluate( From ddd3f47a14c399b047fa7ff7c6830e669bb32417 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 19:33:37 +0000 Subject: [PATCH 13/31] Update --- src/py/flwr/server/strategy/dp_adaptive_clipping.py | 8 +++----- src/py/flwr/server/strategy/dp_fixed_clipping.py | 9 ++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/py/flwr/server/strategy/dp_adaptive_clipping.py b/src/py/flwr/server/strategy/dp_adaptive_clipping.py index 8627f16c5588..ec74ce0a94a0 100644 --- a/src/py/flwr/server/strategy/dp_adaptive_clipping.py +++ b/src/py/flwr/server/strategy/dp_adaptive_clipping.py @@ -19,7 +19,7 @@ import math -from logging import DEBUG, INFO, WARNING +from logging import INFO, WARNING from typing import Dict, List, Optional, Tuple, Union import numpy as np @@ -226,9 +226,8 @@ def aggregate_fit( self.clipping_norm, self.num_sampled_clients, ) - log(INFO, "aggregate_fit: central dp noise added to params.") log( - DEBUG, + INFO, "aggregate_fit: central dp noise with standard deviation: %s added to params.", compute_stdv( self.noise_multiplier, self.clipping_norm, self.num_sampled_clients @@ -417,9 +416,8 @@ def aggregate_fit( self.clipping_norm, self.num_sampled_clients, ) - log(INFO, "aggregate_fit: central dp noise added to params.") log( - DEBUG, + INFO, "aggregate_fit: central dp noise with standard deviation: %s added to params.", compute_stdv( self.noise_multiplier, self.clipping_norm, self.num_sampled_clients diff --git a/src/py/flwr/server/strategy/dp_fixed_clipping.py b/src/py/flwr/server/strategy/dp_fixed_clipping.py index d0a4701baf2f..b1f3691e27db 100644 --- a/src/py/flwr/server/strategy/dp_fixed_clipping.py +++ b/src/py/flwr/server/strategy/dp_fixed_clipping.py @@ -18,7 +18,7 @@ """ -from logging import DEBUG, INFO, WARNING +from logging import INFO, WARNING from typing import Dict, List, Optional, Tuple, Union from flwr.common import ( @@ -172,9 +172,9 @@ def aggregate_fit( self.clipping_norm, self.num_sampled_clients, ) - log(INFO, "aggregate_fit: central dp noise added to params.") + log( - DEBUG, + INFO, "aggregate_fit: central dp noise with standard deviation: %s added to params.", compute_stdv( self.noise_multiplier, self.clipping_norm, self.num_sampled_clients @@ -330,9 +330,8 @@ def aggregate_fit( self.clipping_norm, self.num_sampled_clients, ) - log(INFO, "aggregate_fit: central dp noise added to params.") log( - DEBUG, + INFO, "aggregate_fit: central dp noise with standard deviation: %s added to params.", compute_stdv( self.noise_multiplier, self.clipping_norm, self.num_sampled_clients From f3b122b4f4e56027cbc77b775afd198d733bc64f Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 20:30:55 +0000 Subject: [PATCH 14/31] Update --- src/py/flwr/server/strategy/dp_adaptive_clipping.py | 4 ++-- src/py/flwr/server/strategy/dp_fixed_clipping.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/py/flwr/server/strategy/dp_adaptive_clipping.py b/src/py/flwr/server/strategy/dp_adaptive_clipping.py index ec74ce0a94a0..9a1116ea4458 100644 --- a/src/py/flwr/server/strategy/dp_adaptive_clipping.py +++ b/src/py/flwr/server/strategy/dp_adaptive_clipping.py @@ -228,7 +228,7 @@ def aggregate_fit( ) log( INFO, - "aggregate_fit: central dp noise with standard deviation: %s added to params.", + "aggregate_fit: central DP noise with standard deviation: %s added to parameters.", compute_stdv( self.noise_multiplier, self.clipping_norm, self.num_sampled_clients ), @@ -418,7 +418,7 @@ def aggregate_fit( ) log( INFO, - "aggregate_fit: central dp noise with standard deviation: %s added to params.", + "aggregate_fit: central DP noise with standard deviation: %s added to parameters.", compute_stdv( self.noise_multiplier, self.clipping_norm, self.num_sampled_clients ), diff --git a/src/py/flwr/server/strategy/dp_fixed_clipping.py b/src/py/flwr/server/strategy/dp_fixed_clipping.py index b1f3691e27db..6714841e2fec 100644 --- a/src/py/flwr/server/strategy/dp_fixed_clipping.py +++ b/src/py/flwr/server/strategy/dp_fixed_clipping.py @@ -175,7 +175,7 @@ def aggregate_fit( log( INFO, - "aggregate_fit: central dp noise with standard deviation: %s added to params.", + "aggregate_fit: central DP noise with standard deviation: %s added to parameters.", compute_stdv( self.noise_multiplier, self.clipping_norm, self.num_sampled_clients ), @@ -332,7 +332,7 @@ def aggregate_fit( ) log( INFO, - "aggregate_fit: central dp noise with standard deviation: %s added to params.", + "aggregate_fit: central DP noise with standard deviation: %s added to parameters.", compute_stdv( self.noise_multiplier, self.clipping_norm, self.num_sampled_clients ), From 5af8720fb829360a9c06b25f8dbad777f1dcdf7a Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 20:44:38 +0000 Subject: [PATCH 15/31] Update --- src/py/flwr/client/mod/centraldp_mods.py | 5 +++++ src/py/flwr/server/strategy/dp_adaptive_clipping.py | 6 ++++++ src/py/flwr/server/strategy/dp_fixed_clipping.py | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/src/py/flwr/client/mod/centraldp_mods.py b/src/py/flwr/client/mod/centraldp_mods.py index 0c0134e0f876..6a3816ff0a0c 100644 --- a/src/py/flwr/client/mod/centraldp_mods.py +++ b/src/py/flwr/client/mod/centraldp_mods.py @@ -15,6 +15,8 @@ """Clipping modifiers for central DP with client-side clipping.""" +from logging import INFO + from flwr.client.typing import ClientAppCallable from flwr.common import ndarrays_to_parameters, parameters_to_ndarrays from flwr.common import recordset_compat as compat @@ -25,6 +27,7 @@ compute_clip_model_update, ) from flwr.common.differential_privacy_constants import KEY_CLIPPING_NORM, KEY_NORM_BIT +from flwr.common.logger import log from flwr.common.message import Message @@ -79,6 +82,8 @@ def fixedclipping_mod( clipping_norm, ) + log(INFO, "fixedclipping_mod: parameters are clipped by value: $s.", clipping_norm) + fit_res.parameters = ndarrays_to_parameters(client_to_server_params) out_msg.content = compat.fitres_to_recordset(fit_res, keep_input=True) return out_msg diff --git a/src/py/flwr/server/strategy/dp_adaptive_clipping.py b/src/py/flwr/server/strategy/dp_adaptive_clipping.py index 9a1116ea4458..9facace42bf1 100644 --- a/src/py/flwr/server/strategy/dp_adaptive_clipping.py +++ b/src/py/flwr/server/strategy/dp_adaptive_clipping.py @@ -198,6 +198,12 @@ def aggregate_fit( norm_bit = adaptive_clip_inputs_inplace(model_update, self.clipping_norm) norm_bit_set_count += norm_bit + log( + INFO, + "aggregate_fit: parameters are clipped by value: $s.", + self.clipping_norm, + ) + for i, _ in enumerate(self.current_round_params): param[i] = self.current_round_params[i] + model_update[i] # Convert back to parameters diff --git a/src/py/flwr/server/strategy/dp_fixed_clipping.py b/src/py/flwr/server/strategy/dp_fixed_clipping.py index 6714841e2fec..b4066a54746f 100644 --- a/src/py/flwr/server/strategy/dp_fixed_clipping.py +++ b/src/py/flwr/server/strategy/dp_fixed_clipping.py @@ -156,6 +156,11 @@ def aggregate_fit( compute_clip_model_update( param, self.current_round_params, self.clipping_norm ) + log( + INFO, + "aggregate_fit: parameters are clipped by value: $s.", + self.clipping_norm, + ) # Convert back to parameters res.parameters = ndarrays_to_parameters(param) From 6369c8ed105b4480a703795efc29863e49f73ead Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 20:50:49 +0000 Subject: [PATCH 16/31] Update --- src/py/flwr/client/mod/centraldp_mods.py | 5 +++++ src/py/flwr/client/mod/localdp_mod.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/py/flwr/client/mod/centraldp_mods.py b/src/py/flwr/client/mod/centraldp_mods.py index 6a3816ff0a0c..2aed8354a6cb 100644 --- a/src/py/flwr/client/mod/centraldp_mods.py +++ b/src/py/flwr/client/mod/centraldp_mods.py @@ -144,6 +144,11 @@ def adaptiveclipping_mod( server_to_client_params, clipping_norm, ) + log( + INFO, + "adaptiveclipping_mod: parameters are clipped by value: $s.", + clipping_norm, + ) fit_res.parameters = ndarrays_to_parameters(client_to_server_params) diff --git a/src/py/flwr/client/mod/localdp_mod.py b/src/py/flwr/client/mod/localdp_mod.py index 5f62c9e44800..e41ca9cc1544 100644 --- a/src/py/flwr/client/mod/localdp_mod.py +++ b/src/py/flwr/client/mod/localdp_mod.py @@ -15,6 +15,10 @@ """Local DP modifier.""" +from logging import INFO + +import numpy as np + from flwr.client.typing import ClientAppCallable from flwr.common import ndarrays_to_parameters, parameters_to_ndarrays from flwr.common import recordset_compat as compat @@ -24,6 +28,7 @@ add_localdp_gaussian_noise_to_params, compute_clip_model_update, ) +from flwr.common.logger import log from flwr.common.message import Message @@ -122,6 +127,9 @@ def __call__( server_to_client_params, self.clipping_norm, ) + log( + INFO, "LocalDpMod: parameters are clipped by value: $s.", self.clipping_norm + ) fit_res.parameters = ndarrays_to_parameters(client_to_server_params) @@ -129,6 +137,12 @@ def __call__( add_localdp_gaussian_noise_to_params( fit_res.parameters, self.sensitivity, self.epsilon, self.delta ) + log( + INFO, + "LocalDpMod: local DP noise with " + "standard deviation: %s added to parameters.", + self.sensitivity * np.sqrt(2 * np.log(1.25 / self.delta)) / self.epsilon, + ) out_msg.content = compat.fitres_to_recordset(fit_res, keep_input=True) return out_msg From b0fa4203dc55c0be458b5d099bf4d4a31da5d669 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 21:03:53 +0000 Subject: [PATCH 17/31] Update --- src/py/flwr/client/mod/centraldp_mods.py | 4 ++-- src/py/flwr/client/mod/localdp_mod.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/py/flwr/client/mod/centraldp_mods.py b/src/py/flwr/client/mod/centraldp_mods.py index 2aed8354a6cb..4f4a595e8d9c 100644 --- a/src/py/flwr/client/mod/centraldp_mods.py +++ b/src/py/flwr/client/mod/centraldp_mods.py @@ -82,7 +82,7 @@ def fixedclipping_mod( clipping_norm, ) - log(INFO, "fixedclipping_mod: parameters are clipped by value: $s.", clipping_norm) + log(INFO, "fixedclipping_mod: parameters are clipped by value: %s.", clipping_norm) fit_res.parameters = ndarrays_to_parameters(client_to_server_params) out_msg.content = compat.fitres_to_recordset(fit_res, keep_input=True) @@ -146,7 +146,7 @@ def adaptiveclipping_mod( ) log( INFO, - "adaptiveclipping_mod: parameters are clipped by value: $s.", + "adaptiveclipping_mod: parameters are clipped by value: %s.", clipping_norm, ) diff --git a/src/py/flwr/client/mod/localdp_mod.py b/src/py/flwr/client/mod/localdp_mod.py index e41ca9cc1544..3b0311a612b9 100644 --- a/src/py/flwr/client/mod/localdp_mod.py +++ b/src/py/flwr/client/mod/localdp_mod.py @@ -128,7 +128,7 @@ def __call__( self.clipping_norm, ) log( - INFO, "LocalDpMod: parameters are clipped by value: $s.", self.clipping_norm + INFO, "LocalDpMod: parameters are clipped by value: %s.", self.clipping_norm ) fit_res.parameters = ndarrays_to_parameters(client_to_server_params) From 920075433f1b8fc5f29a3781d880c7e35fec6531 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 21:06:03 +0000 Subject: [PATCH 18/31] Update --- src/py/flwr/server/strategy/dp_adaptive_clipping.py | 2 +- src/py/flwr/server/strategy/dp_fixed_clipping.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/server/strategy/dp_adaptive_clipping.py b/src/py/flwr/server/strategy/dp_adaptive_clipping.py index 9facace42bf1..c3c3761f9dc7 100644 --- a/src/py/flwr/server/strategy/dp_adaptive_clipping.py +++ b/src/py/flwr/server/strategy/dp_adaptive_clipping.py @@ -200,7 +200,7 @@ def aggregate_fit( log( INFO, - "aggregate_fit: parameters are clipped by value: $s.", + "aggregate_fit: parameters are clipped by value: %s.", self.clipping_norm, ) diff --git a/src/py/flwr/server/strategy/dp_fixed_clipping.py b/src/py/flwr/server/strategy/dp_fixed_clipping.py index b4066a54746f..c670c26e4977 100644 --- a/src/py/flwr/server/strategy/dp_fixed_clipping.py +++ b/src/py/flwr/server/strategy/dp_fixed_clipping.py @@ -158,7 +158,7 @@ def aggregate_fit( ) log( INFO, - "aggregate_fit: parameters are clipped by value: $s.", + "aggregate_fit: parameters are clipped by value: %s.", self.clipping_norm, ) # Convert back to parameters From 4dfdbd1686d67377602e6b16244f7ea9de68eb71 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 22:33:20 +0000 Subject: [PATCH 19/31] Update --- examples/dp-secagg-demo/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index c960b9bd6b91..90ac79b382d8 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -40,7 +40,7 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Define strategy strategy = fl.server.strategy.FedAvg( fraction_fit=0.2, - fraction_evaluate=0.0, # Disable evaluation + fraction_evaluate=0.0, # Disable evaluation for demo purpose min_fit_clients=20, min_available_clients=20, fit_metrics_aggregation_fn=weighted_average, From 50b785d572eefd187c37d1ae1a338332aef12f22 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Wed, 13 Mar 2024 23:13:04 +0000 Subject: [PATCH 20/31] Update --- examples/dp-secagg-demo/client.py | 1 - examples/dp-secagg-demo/server.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/client.py index a1a3b1467215..6a6114b5e16c 100644 --- a/examples/dp-secagg-demo/client.py +++ b/examples/dp-secagg-demo/client.py @@ -11,7 +11,6 @@ # Define FlowerClient and client_fn class FlowerClient(NumPyClient): - def __init__(self, trainloader, testloader) -> None: self.trainloader = trainloader self.testloader = testloader diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index 90ac79b382d8..ff7d81b7ecd4 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -47,10 +47,9 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: initial_parameters=parameters, ) -dp_strategy = DifferentialPrivacyClientSideFixedClipping(strategy, - noise_multiplier=0.1, - clipping_norm=10, - num_sampled_clients=20) +dp_strategy = DifferentialPrivacyClientSideFixedClipping( + strategy, noise_multiplier=0.1, clipping_norm=10, num_sampled_clients=20 +) # Run via `flower-server-app server_workflow:app` From 578d6b56350a11b1c2076bbcb500df0cdb7ad997 Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Wed, 13 Mar 2024 23:23:35 +0000 Subject: [PATCH 21/31] Update README.md --- examples/dp-secagg-demo/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/dp-secagg-demo/README.md b/examples/dp-secagg-demo/README.md index ffb3032fff8c..d5d5069222a7 100644 --- a/examples/dp-secagg-demo/README.md +++ b/examples/dp-secagg-demo/README.md @@ -9,3 +9,4 @@ The example uses the CIFAR-10 dataset with a total of 100 clients, with 20 clien ```shell flower-simulation --server-app server:app --client-app client:app --num-supernodes 100 +``` From b9273c711c052949ed49efda909555bf05d5e253 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 14 Mar 2024 09:11:59 +0000 Subject: [PATCH 22/31] use MNIST and Adam --- examples/dp-secagg-demo/server.py | 2 +- examples/dp-secagg-demo/task.py | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index ff7d81b7ecd4..47411b7b62fe 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -61,7 +61,7 @@ def main(driver: Driver, context: Context) -> None: # Construct the LegacyContext context = LegacyContext( state=context.state, - config=fl.server.ServerConfig(num_rounds=10), + config=fl.server.ServerConfig(num_rounds=3), strategy=dp_strategy, ) diff --git a/examples/dp-secagg-demo/task.py b/examples/dp-secagg-demo/task.py index 2fe68260c3f5..fa17fe608a9c 100644 --- a/examples/dp-secagg-demo/task.py +++ b/examples/dp-secagg-demo/task.py @@ -7,7 +7,6 @@ import torch.nn.functional as F from flwr.common.logger import log from torch.utils.data import DataLoader -from torchvision.datasets import CIFAR10 from torchvision.transforms import Compose, Normalize, ToTensor @@ -15,11 +14,11 @@ class Net(nn.Module): - """Model (simple CNN adapted from 'PyTorch: A 60 Minute Blitz')""" + """Model.""" def __init__(self) -> None: super(Net, self).__init__() - self.conv1 = nn.Conv2d(3, 6, 5) + self.conv1 = nn.Conv2d(1, 6, 3, padding=1) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16 * 5 * 5, 120) @@ -27,9 +26,10 @@ def __init__(self) -> None: self.fc3 = nn.Linear(84, 10) def forward(self, x: torch.Tensor) -> torch.Tensor: + batch_size = x.size(0) x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) - x = x.view(-1, 16 * 5 * 5) + x = x.view(batch_size, -1) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) return self.fc3(x) @@ -37,17 +37,15 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: def load_data(partition_id): """Load partition CIFAR10 data.""" - fds = FederatedDataset(dataset="cifar10", partitioners={"train": 100}) + fds = FederatedDataset(dataset="mnist", partitioners={"train": 100}) partition = fds.load_partition(partition_id) # Divide data on each node: 80% train, 20% test partition_train_test = partition.train_test_split(test_size=0.2) - pytorch_transforms = Compose( - [ToTensor(), Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))] - ) + pytorch_transforms = Compose([ToTensor(), Normalize((0.5,), (0.5,))]) def apply_transforms(batch): """Apply transforms to the partition from FederatedDataset.""" - batch["img"] = [pytorch_transforms(img) for img in batch["img"]] + batch["image"] = [pytorch_transforms(img) for img in batch["image"]] return batch partition_train_test = partition_train_test.with_transform(apply_transforms) @@ -61,11 +59,11 @@ def train(net, trainloader, valloader, epochs, device): log(INFO, "Starting training...") net.to(device) # move model to GPU if available criterion = torch.nn.CrossEntropyLoss().to(device) - optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9) + optimizer = torch.optim.Adam(net.parameters()) net.train() for _ in range(epochs): for batch in trainloader: - images = batch["img"].to(device) + images = batch["image"].to(device) labels = batch["label"].to(device) optimizer.zero_grad() loss = criterion(net(images), labels) @@ -91,7 +89,7 @@ def test(net, testloader): correct, loss = 0, 0.0 with torch.no_grad(): for batch in testloader: - images = batch["img"].to(DEVICE) + images = batch["image"].to(DEVICE) labels = batch["label"].to(DEVICE) outputs = net(images.to(DEVICE)) labels = labels.to(DEVICE) From abe665b2d348c505e471fa3621322e7237a3c7ba Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Thu, 14 Mar 2024 16:01:58 +0000 Subject: [PATCH 23/31] Change noise multiplier --- examples/dp-secagg-demo/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index 47411b7b62fe..c1b9d57d993c 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -48,7 +48,7 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: ) dp_strategy = DifferentialPrivacyClientSideFixedClipping( - strategy, noise_multiplier=0.1, clipping_norm=10, num_sampled_clients=20 + strategy, noise_multiplier=0.2, clipping_norm=10, num_sampled_clients=20 ) From 53f52f39489bf706deac0cd76778619003f7c31a Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Thu, 14 Mar 2024 16:25:53 +0000 Subject: [PATCH 24/31] Organize imports, update comments --- examples/dp-secagg-demo/client.py | 7 +++---- examples/dp-secagg-demo/server.py | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/client.py index 6a6114b5e16c..d5213707c920 100644 --- a/examples/dp-secagg-demo/client.py +++ b/examples/dp-secagg-demo/client.py @@ -1,9 +1,8 @@ -from task import DEVICE, Net, get_weights, load_data, set_weights, test, train - from flwr.client import ClientApp, NumPyClient -from flwr.client.mod import secaggplus_mod +from flwr.client.mod import fixedclipping_mod, secaggplus_mod + +from task import DEVICE, Net, get_weights, load_data, set_weights, test, train -from flwr.client.mod.centraldp_mods import fixedclipping_mod # Load model and data (simple CNN, CIFAR-10) net = Net().to(DEVICE) diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/server.py index 47411b7b62fe..51276265a4f8 100644 --- a/examples/dp-secagg-demo/server.py +++ b/examples/dp-secagg-demo/server.py @@ -1,14 +1,14 @@ from typing import List, Tuple -from task import Net, get_weights - -import flwr as fl +from flwr.server import Driver, LegacyContext, ServerApp, ServerConfig from flwr.common import Context, Metrics, ndarrays_to_parameters -from flwr.server import Driver, LegacyContext -from flwr.server.workflow import SecAggPlusWorkflow -from flwr.server.strategy.dp_fixed_clipping import ( +from flwr.server.strategy import ( DifferentialPrivacyClientSideFixedClipping, + FedAvg, ) +from flwr.server.workflow import DefaultWorkflow, SecAggPlusWorkflow + +from task import Net, get_weights # Define metric aggregation function @@ -37,8 +37,8 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: parameters = ndarrays_to_parameters(ndarrays) -# Define strategy -strategy = fl.server.strategy.FedAvg( +# Define core strategy +strategy = FedAvg( fraction_fit=0.2, fraction_evaluate=0.0, # Disable evaluation for demo purpose min_fit_clients=20, @@ -47,13 +47,13 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: initial_parameters=parameters, ) -dp_strategy = DifferentialPrivacyClientSideFixedClipping( +# Wrap the core strategy +strategy = DifferentialPrivacyClientSideFixedClipping( strategy, noise_multiplier=0.1, clipping_norm=10, num_sampled_clients=20 ) -# Run via `flower-server-app server_workflow:app` -app = fl.server.ServerApp() +app = ServerApp() @app.main() @@ -61,12 +61,12 @@ def main(driver: Driver, context: Context) -> None: # Construct the LegacyContext context = LegacyContext( state=context.state, - config=fl.server.ServerConfig(num_rounds=3), - strategy=dp_strategy, + config=ServerConfig(num_rounds=3), + strategy=strategy, ) - # Create the workflow - workflow = fl.server.workflow.DefaultWorkflow( + # Create the train/evaluate workflow + workflow = DefaultWorkflow( fit_workflow=SecAggPlusWorkflow( num_shares=7, reconstruction_threshold=4, From 23ec20ebaba6dbe3756bcaf441f61fb07625ed3c Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Thu, 14 Mar 2024 17:50:07 +0000 Subject: [PATCH 25/31] Move code into subdir --- examples/dp-secagg-demo/fl_dp_sa/__init__.py | 1 + examples/dp-secagg-demo/{ => fl_dp_sa}/client.py | 0 examples/dp-secagg-demo/{ => fl_dp_sa}/server.py | 0 examples/dp-secagg-demo/{ => fl_dp_sa}/task.py | 0 4 files changed, 1 insertion(+) create mode 100644 examples/dp-secagg-demo/fl_dp_sa/__init__.py rename examples/dp-secagg-demo/{ => fl_dp_sa}/client.py (100%) rename examples/dp-secagg-demo/{ => fl_dp_sa}/server.py (100%) rename examples/dp-secagg-demo/{ => fl_dp_sa}/task.py (100%) diff --git a/examples/dp-secagg-demo/fl_dp_sa/__init__.py b/examples/dp-secagg-demo/fl_dp_sa/__init__.py new file mode 100644 index 000000000000..dd38275b20df --- /dev/null +++ b/examples/dp-secagg-demo/fl_dp_sa/__init__.py @@ -0,0 +1 @@ +"""fl_dp_secaggplus.""" diff --git a/examples/dp-secagg-demo/client.py b/examples/dp-secagg-demo/fl_dp_sa/client.py similarity index 100% rename from examples/dp-secagg-demo/client.py rename to examples/dp-secagg-demo/fl_dp_sa/client.py diff --git a/examples/dp-secagg-demo/server.py b/examples/dp-secagg-demo/fl_dp_sa/server.py similarity index 100% rename from examples/dp-secagg-demo/server.py rename to examples/dp-secagg-demo/fl_dp_sa/server.py diff --git a/examples/dp-secagg-demo/task.py b/examples/dp-secagg-demo/fl_dp_sa/task.py similarity index 100% rename from examples/dp-secagg-demo/task.py rename to examples/dp-secagg-demo/fl_dp_sa/task.py From 77f21edbf9758d1f2f910b85f637e37fe6741c67 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Thu, 14 Mar 2024 17:53:08 +0000 Subject: [PATCH 26/31] Update imports --- examples/dp-secagg-demo/fl_dp_sa/__init__.py | 2 +- examples/dp-secagg-demo/fl_dp_sa/client.py | 4 +++- examples/dp-secagg-demo/fl_dp_sa/server.py | 4 +++- examples/dp-secagg-demo/fl_dp_sa/task.py | 2 ++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/dp-secagg-demo/fl_dp_sa/__init__.py b/examples/dp-secagg-demo/fl_dp_sa/__init__.py index dd38275b20df..741260348ab8 100644 --- a/examples/dp-secagg-demo/fl_dp_sa/__init__.py +++ b/examples/dp-secagg-demo/fl_dp_sa/__init__.py @@ -1 +1 @@ -"""fl_dp_secaggplus.""" +"""fl_dp_sa: A Flower / PyTorch app.""" diff --git a/examples/dp-secagg-demo/fl_dp_sa/client.py b/examples/dp-secagg-demo/fl_dp_sa/client.py index d5213707c920..104264158833 100644 --- a/examples/dp-secagg-demo/fl_dp_sa/client.py +++ b/examples/dp-secagg-demo/fl_dp_sa/client.py @@ -1,7 +1,9 @@ +"""fl_dp_sa: A Flower / PyTorch app.""" + from flwr.client import ClientApp, NumPyClient from flwr.client.mod import fixedclipping_mod, secaggplus_mod -from task import DEVICE, Net, get_weights, load_data, set_weights, test, train +from fl_dp_sa.task import DEVICE, Net, get_weights, load_data, set_weights, test, train # Load model and data (simple CNN, CIFAR-10) diff --git a/examples/dp-secagg-demo/fl_dp_sa/server.py b/examples/dp-secagg-demo/fl_dp_sa/server.py index 602028e76035..58b397f6eb90 100644 --- a/examples/dp-secagg-demo/fl_dp_sa/server.py +++ b/examples/dp-secagg-demo/fl_dp_sa/server.py @@ -1,3 +1,5 @@ +"""fl_dp_sa: A Flower / PyTorch app.""" + from typing import List, Tuple from flwr.server import Driver, LegacyContext, ServerApp, ServerConfig @@ -8,7 +10,7 @@ ) from flwr.server.workflow import DefaultWorkflow, SecAggPlusWorkflow -from task import Net, get_weights +from fl_dp_sa.task import Net, get_weights # Define metric aggregation function diff --git a/examples/dp-secagg-demo/fl_dp_sa/task.py b/examples/dp-secagg-demo/fl_dp_sa/task.py index fa17fe608a9c..d091d0b974ea 100644 --- a/examples/dp-secagg-demo/fl_dp_sa/task.py +++ b/examples/dp-secagg-demo/fl_dp_sa/task.py @@ -1,3 +1,5 @@ +"""fl_dp_sa: A Flower / PyTorch app.""" + from collections import OrderedDict from logging import INFO from flwr_datasets import FederatedDataset From 2c52ac7b39c277628dc725293c160c9deef7d3b1 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Thu, 14 Mar 2024 17:56:29 +0000 Subject: [PATCH 27/31] Add flower.toml/pyproject.toml/requirements.txt --- examples/dp-secagg-demo/flower.toml | 13 +++++++++++++ examples/dp-secagg-demo/pyproject.toml | 21 +++++++++++++++++++++ examples/dp-secagg-demo/requirements.txt | 4 ++++ 3 files changed, 38 insertions(+) create mode 100644 examples/dp-secagg-demo/flower.toml create mode 100644 examples/dp-secagg-demo/pyproject.toml create mode 100644 examples/dp-secagg-demo/requirements.txt diff --git a/examples/dp-secagg-demo/flower.toml b/examples/dp-secagg-demo/flower.toml new file mode 100644 index 000000000000..ea2e98206791 --- /dev/null +++ b/examples/dp-secagg-demo/flower.toml @@ -0,0 +1,13 @@ +[project] +name = "fl_dp_sa" +version = "1.0.0" +description = "" +license = "Apache-2.0" +authors = [ + "The Flower Authors ", +] +readme = "README.md" + +[flower.components] +serverapp = "fl_dp_sa.server:app" +clientapp = "fl_dp_sa.client:app" diff --git a/examples/dp-secagg-demo/pyproject.toml b/examples/dp-secagg-demo/pyproject.toml new file mode 100644 index 000000000000..131e744c5b39 --- /dev/null +++ b/examples/dp-secagg-demo/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["poetry-core>=1.4.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "fl_dp_sa" +version = "1.0.0" +description = "" +license = "Apache-2.0" +authors = [ + "The Flower Authors ", +] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.9" +# Mandatory dependencies +flwr-nightly = { version = "1.8.0.dev20240313", extras = ["simulation"] } +flwr-datasets = { version = "0.0.2", extras = ["vision"] } +torch = "2.2.1" +torchvision = "0.17.1" diff --git a/examples/dp-secagg-demo/requirements.txt b/examples/dp-secagg-demo/requirements.txt new file mode 100644 index 000000000000..ddb8a814447b --- /dev/null +++ b/examples/dp-secagg-demo/requirements.txt @@ -0,0 +1,4 @@ +flwr-nightly[simulation]==1.8.0.dev20240313 +flwr-datasets[vision]==0.0.2 +torch==2.2.1 +torchvision==0.17.1 From 7c3d1b318bffa3a00a3ff335e181da2727d8c25e Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Thu, 14 Mar 2024 18:03:04 +0000 Subject: [PATCH 28/31] Update README --- examples/dp-secagg-demo/README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/dp-secagg-demo/README.md b/examples/dp-secagg-demo/README.md index d5d5069222a7..99a0a7e50980 100644 --- a/examples/dp-secagg-demo/README.md +++ b/examples/dp-secagg-demo/README.md @@ -1,12 +1,22 @@ -# DP + SecAgg Demo Example +# fl_dp_sa This is a simple example that utilizes central differential privacy with client-side fixed clipping and secure aggregation. Note: This example is designed for a small number of rounds and is intended for demonstration purposes. +## Install dependencies + +```bash +# Using pip +pip install . + +# Or using Poetry +poetry install +``` + ## Run The example uses the CIFAR-10 dataset with a total of 100 clients, with 20 clients sampled in each round. The hyperparameters for DP and SecAgg are specified in `server.py`. ```shell -flower-simulation --server-app server:app --client-app client:app --num-supernodes 100 +flower-simulation --server-app fl_dp_sa.server:app --client-app fl_dp_sa.client:app --num-supernodes 100 ``` From ac31041c72b2484a58ae131f4f31fd45647f923d Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Thu, 14 Mar 2024 23:42:58 +0000 Subject: [PATCH 29/31] Rename example --- examples/{dp-secagg-demo => fl-dp-sa}/README.md | 0 examples/{dp-secagg-demo => fl-dp-sa}/fl_dp_sa/__init__.py | 0 examples/{dp-secagg-demo => fl-dp-sa}/fl_dp_sa/client.py | 0 examples/{dp-secagg-demo => fl-dp-sa}/fl_dp_sa/server.py | 0 examples/{dp-secagg-demo => fl-dp-sa}/fl_dp_sa/task.py | 0 examples/{dp-secagg-demo => fl-dp-sa}/flower.toml | 0 examples/{dp-secagg-demo => fl-dp-sa}/pyproject.toml | 0 examples/{dp-secagg-demo => fl-dp-sa}/requirements.txt | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename examples/{dp-secagg-demo => fl-dp-sa}/README.md (100%) rename examples/{dp-secagg-demo => fl-dp-sa}/fl_dp_sa/__init__.py (100%) rename examples/{dp-secagg-demo => fl-dp-sa}/fl_dp_sa/client.py (100%) rename examples/{dp-secagg-demo => fl-dp-sa}/fl_dp_sa/server.py (100%) rename examples/{dp-secagg-demo => fl-dp-sa}/fl_dp_sa/task.py (100%) rename examples/{dp-secagg-demo => fl-dp-sa}/flower.toml (100%) rename examples/{dp-secagg-demo => fl-dp-sa}/pyproject.toml (100%) rename examples/{dp-secagg-demo => fl-dp-sa}/requirements.txt (100%) diff --git a/examples/dp-secagg-demo/README.md b/examples/fl-dp-sa/README.md similarity index 100% rename from examples/dp-secagg-demo/README.md rename to examples/fl-dp-sa/README.md diff --git a/examples/dp-secagg-demo/fl_dp_sa/__init__.py b/examples/fl-dp-sa/fl_dp_sa/__init__.py similarity index 100% rename from examples/dp-secagg-demo/fl_dp_sa/__init__.py rename to examples/fl-dp-sa/fl_dp_sa/__init__.py diff --git a/examples/dp-secagg-demo/fl_dp_sa/client.py b/examples/fl-dp-sa/fl_dp_sa/client.py similarity index 100% rename from examples/dp-secagg-demo/fl_dp_sa/client.py rename to examples/fl-dp-sa/fl_dp_sa/client.py diff --git a/examples/dp-secagg-demo/fl_dp_sa/server.py b/examples/fl-dp-sa/fl_dp_sa/server.py similarity index 100% rename from examples/dp-secagg-demo/fl_dp_sa/server.py rename to examples/fl-dp-sa/fl_dp_sa/server.py diff --git a/examples/dp-secagg-demo/fl_dp_sa/task.py b/examples/fl-dp-sa/fl_dp_sa/task.py similarity index 100% rename from examples/dp-secagg-demo/fl_dp_sa/task.py rename to examples/fl-dp-sa/fl_dp_sa/task.py diff --git a/examples/dp-secagg-demo/flower.toml b/examples/fl-dp-sa/flower.toml similarity index 100% rename from examples/dp-secagg-demo/flower.toml rename to examples/fl-dp-sa/flower.toml diff --git a/examples/dp-secagg-demo/pyproject.toml b/examples/fl-dp-sa/pyproject.toml similarity index 100% rename from examples/dp-secagg-demo/pyproject.toml rename to examples/fl-dp-sa/pyproject.toml diff --git a/examples/dp-secagg-demo/requirements.txt b/examples/fl-dp-sa/requirements.txt similarity index 100% rename from examples/dp-secagg-demo/requirements.txt rename to examples/fl-dp-sa/requirements.txt From 3e1a121bc04ea6ae78cbeebcadd54e6b5764723d Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Thu, 14 Mar 2024 23:45:34 +0000 Subject: [PATCH 30/31] Update pyproject.toml --- examples/fl-dp-sa/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/fl-dp-sa/pyproject.toml b/examples/fl-dp-sa/pyproject.toml index 131e744c5b39..d30fa4675e34 100644 --- a/examples/fl-dp-sa/pyproject.toml +++ b/examples/fl-dp-sa/pyproject.toml @@ -3,8 +3,8 @@ requires = ["poetry-core>=1.4.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] -name = "fl_dp_sa" -version = "1.0.0" +name = "fl-dp-sa" +version = "0.1.0" description = "" license = "Apache-2.0" authors = [ From 3ffe3f5dd1dec68f8839c1fd2ee63e863ee7eb9a Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Fri, 15 Mar 2024 00:20:37 +0000 Subject: [PATCH 31/31] Remove log --- examples/fl-dp-sa/fl_dp_sa/server.py | 5 +---- examples/fl-dp-sa/fl_dp_sa/task.py | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/fl-dp-sa/fl_dp_sa/server.py b/examples/fl-dp-sa/fl_dp_sa/server.py index 58b397f6eb90..f7da75997e98 100644 --- a/examples/fl-dp-sa/fl_dp_sa/server.py +++ b/examples/fl-dp-sa/fl_dp_sa/server.py @@ -39,7 +39,7 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: parameters = ndarrays_to_parameters(ndarrays) -# Define core strategy +# Define strategy strategy = FedAvg( fraction_fit=0.2, fraction_evaluate=0.0, # Disable evaluation for demo purpose @@ -48,9 +48,6 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: fit_metrics_aggregation_fn=weighted_average, initial_parameters=parameters, ) - - -# Wrap the core strategy strategy = DifferentialPrivacyClientSideFixedClipping( strategy, noise_multiplier=0.2, clipping_norm=10, num_sampled_clients=20 ) diff --git a/examples/fl-dp-sa/fl_dp_sa/task.py b/examples/fl-dp-sa/fl_dp_sa/task.py index d091d0b974ea..3d506263d5a3 100644 --- a/examples/fl-dp-sa/fl_dp_sa/task.py +++ b/examples/fl-dp-sa/fl_dp_sa/task.py @@ -58,7 +58,6 @@ def apply_transforms(batch): def train(net, trainloader, valloader, epochs, device): """Train the model on the training set.""" - log(INFO, "Starting training...") net.to(device) # move model to GPU if available criterion = torch.nn.CrossEntropyLoss().to(device) optimizer = torch.optim.Adam(net.parameters())