diff --git a/openfl-tutorials/experimental/workflow/CrowdGuard/CrowdGuardClientValidation.py b/openfl-tutorials/experimental/workflow/CrowdGuard/CrowdGuardClientValidation.py index 1e8d5e2c59..40dc869773 100644 --- a/openfl-tutorials/experimental/workflow/CrowdGuard/CrowdGuardClientValidation.py +++ b/openfl-tutorials/experimental/workflow/CrowdGuard/CrowdGuardClientValidation.py @@ -377,7 +377,7 @@ def __prune_poisoned_models(num_layers, total_number_of_clients, own_client_inde ac_e = AgglomerativeClustering(n_clusters=2, distance_threshold=None, compute_full_tree=True, - affinity="euclidean", memory=None, + metric="euclidean", memory=None, connectivity=None, linkage='single', compute_distances=True).fit(cluster_input) diff --git a/openfl-tutorials/experimental/workflow/CrowdGuard/PoisoningAttackDemo.ipynb b/openfl-tutorials/experimental/workflow/CrowdGuard/PoisoningAttackDemo.ipynb index 56283d82b5..b4611a991f 100644 --- a/openfl-tutorials/experimental/workflow/CrowdGuard/PoisoningAttackDemo.ipynb +++ b/openfl-tutorials/experimental/workflow/CrowdGuard/PoisoningAttackDemo.ipynb @@ -430,9 +430,8 @@ " state_dicts = [model.state_dict() for model in models]\n", " state_dict = new_model.state_dict()\n", " for key in models[1].state_dict():\n", - " state_dict[key] = np.sum(\n", - " [state[key] for state in state_dicts], axis=0\n", - " ) / len(models)\n", + " state_dict[key] = torch.from_numpy(\n", + " np.average([state[key].numpy() for state in state_dicts], axis=0))\n", " new_model.load_state_dict(state_dict)\n", " return new_model\n", "\n", @@ -558,8 +557,7 @@ " exclude=[\"private\"],\n", " )\n", "\n", - " # @collaborator # Uncomment if you want ro run on CPU\n", - " @collaborator(num_gpus=1) # Assuming GPU(s) is available on the machine\n", + " @collaborator\n", " def train(self):\n", " self.collaborator_name = self.input\n", " print(20 * \"#\")\n", @@ -669,7 +667,7 @@ "\n", " ac_e = AgglomerativeClustering(n_clusters=2, distance_threshold=None,\n", " compute_full_tree=True,\n", - " affinity=\"euclidean\", memory=None, connectivity=None,\n", + " metric=\"euclidean\", memory=None, connectivity=None,\n", " linkage='single',\n", " compute_distances=True).fit(binary_votes)\n", " ac_e_labels: list = ac_e.labels_.tolist()\n", diff --git a/openfl-tutorials/experimental/workflow/CrowdGuard/cifar10_crowdguard.py b/openfl-tutorials/experimental/workflow/CrowdGuard/cifar10_crowdguard.py index bc147c1946..559d6f32f7 100644 --- a/openfl-tutorials/experimental/workflow/CrowdGuard/cifar10_crowdguard.py +++ b/openfl-tutorials/experimental/workflow/CrowdGuard/cifar10_crowdguard.py @@ -220,9 +220,8 @@ def FedAvg(models): # NOQA: N802 state_dicts = [model.state_dict() for model in models] state_dict = new_model.state_dict() for key in models[1].state_dict(): - state_dict[key] = np.sum( - [state[key] for state in state_dicts], axis=0 - ) / len(models) + state_dict[key] = torch.from_numpy( + np.average([state[key].numpy() for state in state_dicts], axis=0)) new_model.load_state_dict(state_dict) return new_model @@ -316,8 +315,7 @@ def start(self): exclude=["private"], ) - # @collaborator # Uncomment if you want ro run on CPU - @collaborator(num_gpus=1) # Assuming GPU(s) is available on the machine + @collaborator def train(self): self.collaborator_name = self.input print(20 * "#") @@ -428,7 +426,7 @@ def defend(self, inputs): ac_e = AgglomerativeClustering(n_clusters=2, distance_threshold=None, compute_full_tree=True, - affinity="euclidean", memory=None, connectivity=None, + metric="euclidean", memory=None, connectivity=None, linkage='single', compute_distances=True).fit(binary_votes) ac_e_labels: list = ac_e.labels_.tolist() diff --git a/openfl-tutorials/experimental/workflow/CrowdGuard/readme.md b/openfl-tutorials/experimental/workflow/CrowdGuard/readme.md index 2cf614ffcd..d252e93073 100644 --- a/openfl-tutorials/experimental/workflow/CrowdGuard/readme.md +++ b/openfl-tutorials/experimental/workflow/CrowdGuard/readme.md @@ -20,4 +20,29 @@ We implemented a simple scaling-based poisoning attack to demonstrate the effect For the local validation in CrowdGuard, each client uses its local dataset to obtain the hidden layer outputs for each local model. Then it calculates the Euclidean and Cosine Distance, before applying a PCA. Based on the first principal component, CrowdGuard employs several statistical tests to determine whether poisoned models remain and removes the poisoned models using clustering. This process is repeated until no more poisoned models are detected before sending the detected poisoned models to the server. On the server side, the votes of the individual clients are aggregated using a stacked-clustering scheme to prevent malicious clients from manipulating the aggregation process through manipulated votes. The client-side validation as well as the server-side operations, are executed with SGX to prevent privacy attacks. -[1] Rieger, P., Krauß, T., Miettinen, M., Dmitrienko, A., & Sadeghi, A. R. CrowdGuard: Federated Backdoor Detection in Federated Learning. NDSS 2024. \ No newline at end of file +[1] Rieger, P., Krauß, T., Miettinen, M., Dmitrienko, A., & Sadeghi, A. R. CrowdGuard: Federated Backdoor Detection in Federated Learning. NDSS 2024. + +## Running the CIFAR-10 demo script +The demo script requires a dedicated allocation of at least 18GB of RAM to run without issues. + +1) Create a Python virtual environment for better isolation +```shell +python -m venv venv +source venv/bin/activate +``` +2) Install OpenFL from the latest sources +```shell +git clone https://github.com/securefederatedai/openfl.git && cd openfl +pip install -e . +``` +3) Install the requirements for Workflow API +```shell +cd openfl-tutorials/experimental/workflow +pip install -r workflow_interface_requirements.txt +``` +4) Start the training script
+Note that the number of training rounds can be adjusted via the `--comm_round` parameter: +```shell +cd CrowdGuard +python cifar10_crowdguard.py --comm_round 5 +``` \ No newline at end of file