Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Central DP (server-side fixed clipping) #2798

Merged
merged 53 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
6ba0507
Add central DP server side fixed clipping
mohammadnaseri Jan 15, 2024
e66e1ad
Add central DP server-side fixed clipping unit tests
mohammadnaseri Jan 15, 2024
a88f72e
Fix exceptin msg
mohammadnaseri Jan 15, 2024
91e5ef7
Merge branch 'main' into central-dp-server-side-fixed-clipping
mohammadnaseri Jan 25, 2024
16c1c41
Clean code
mohammadnaseri Jan 25, 2024
4b84265
Clean code
mohammadnaseri Jan 25, 2024
93d8d75
Fix tests
mohammadnaseri Jan 25, 2024
464c3e9
Fix
mohammadnaseri Jan 25, 2024
bade827
Fix test error
mohammadnaseri Jan 25, 2024
800eac6
Clean code
mohammadnaseri Jan 25, 2024
a257d7f
Clean code
mohammadnaseri Jan 26, 2024
d1d3ff5
Fix pylint error
mohammadnaseri Jan 26, 2024
d24de45
Fix pylint error
mohammadnaseri Jan 26, 2024
ba7a00c
Add minor comment
mohammadnaseri Jan 26, 2024
58d0e49
Merge branch 'main' into central-dp-server-side-fixed-clipping
mohammadnaseri Jan 29, 2024
6cb406c
Minor fix
mohammadnaseri Jan 29, 2024
27f4b63
Fix noise:
mohammadnaseri Jan 30, 2024
bacde18
Refactor
mohammadnaseri Jan 30, 2024
e70d9fe
Fix test
mohammadnaseri Jan 30, 2024
ce8b486
Address comments
mohammadnaseri Jan 31, 2024
a9d75bb
Address comments
mohammadnaseri Jan 31, 2024
e7ba313
Address comments
mohammadnaseri Jan 31, 2024
3efa81e
Fix errors
mohammadnaseri Jan 31, 2024
05944f4
Fix errors
mohammadnaseri Jan 31, 2024
b235336
Clean code
mohammadnaseri Feb 1, 2024
8cde98e
Fix errors
mohammadnaseri Feb 1, 2024
469dfca
Clean code
mohammadnaseri Feb 2, 2024
c49180b
Clean code
mohammadnaseri Feb 3, 2024
bbe5c5b
Improve
mohammadnaseri Feb 4, 2024
656f180
Remove function
mohammadnaseri Feb 7, 2024
55c8633
Merge branch 'main' into central-dp-server-side-fixed-clipping
mohammadnaseri Feb 8, 2024
46a6372
Add warning
mohammadnaseri Feb 9, 2024
510d6c6
Fix error
mohammadnaseri Feb 9, 2024
5eaade8
Minor
mohammadnaseri Feb 9, 2024
57396ac
Merge branch 'main' into central-dp-server-side-fixed-clipping
mohammadnaseri Feb 13, 2024
3368e95
Merge branch 'main' into central-dp-server-side-fixed-clipping
danieljanes Feb 14, 2024
eee4833
Update src/py/flwr/common/differential_privacy.py
mohammadnaseri Feb 14, 2024
d66e6c4
Update src/py/flwr/common/differential_privacy.py
mohammadnaseri Feb 14, 2024
8ac000a
Address comments
mohammadnaseri Feb 14, 2024
59ea1f2
minor
mohammadnaseri Feb 15, 2024
d31e934
Merge branch 'main' into central-dp-server-side-fixed-clipping
mohammadnaseri Feb 15, 2024
adbfd20
Add doc string example
mohammadnaseri Feb 15, 2024
aacfa0d
Use common logger
mohammadnaseri Feb 16, 2024
405aa0f
Use common logger
mohammadnaseri Feb 16, 2024
f1e0e69
Fix error
mohammadnaseri Feb 16, 2024
00e1b3e
minor
mohammadnaseri Feb 16, 2024
7d69476
fix copyright year
mohammadnaseri Feb 16, 2024
1515625
Merge branch 'main' into central-dp-server-side-fixed-clipping
danieljanes Feb 20, 2024
73be298
Address comments
mohammadnaseri Feb 20, 2024
1d430bd
Address comments
mohammadnaseri Feb 20, 2024
e95e161
Merge branch 'main' into central-dp-server-side-fixed-clipping
mohammadnaseri Feb 20, 2024
821fe7e
Update src/py/flwr/server/strategy/dp_fixed_clipping.py
danieljanes Feb 20, 2024
3faee74
Update src/py/flwr/common/differential_privacy_test.py
danieljanes Feb 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/py/flwr/common/differential_privacy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Utility functions for differential privacy."""

import numpy as np

from flwr.common import (
NDArrays,
Parameters,
ndarrays_to_parameters,
parameters_to_ndarrays,
)


def get_norm(input_array: NDArrays) -> float:
"""Compute the L2 norm of the flattened input."""
flattened_input = np.concatenate(
[np.asarray(sub_input).flatten() for sub_input in input_array]
)
return float(np.linalg.norm(flattened_input))


def add_gaussian_noise(input_array: NDArrays, std_dev: float) -> NDArrays:
"""Add noise to each element of the provided input from Gaussian (Normal)
distribution with respect to the passed standard deviation."""
noised_input = [
layer + np.random.normal(0, std_dev, layer.shape) for layer in input_array
]
return noised_input


def clip_inputs(input_array: NDArrays, clipping_norm: float) -> NDArrays:
"""Clip model update based on the clipping norm.

FlatClip method of the paper: https://arxiv.org/pdf/1710.06963.pdf
"""
input_norm = get_norm(input_array)
scaling_factor = min(1, clipping_norm / input_norm)
clipped_inputs: NDArrays = [layer * scaling_factor for layer in input_array]
return clipped_inputs


def add_noise_to_params(parameters: Parameters, stdv: float) -> Parameters:
"""Add Gaussian noise to model params."""
return ndarrays_to_parameters(
add_gaussian_noise(
parameters_to_ndarrays(parameters),
stdv,
)
)


def compute_stdv(
noise_multiplier: float, clipping_norm: float, num_sampled_clients: int
) -> float:
"""Compute standard deviation for noise addition.

paper: https://arxiv.org/pdf/1710.06963.pdf
"""
return float((noise_multiplier * clipping_norm) / num_sampled_clients)
133 changes: 133 additions & 0 deletions src/py/flwr/common/differential_privacy_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright 2020 Flower Labs GmbH. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""DP utility functions tests."""

import numpy as np

from flwr.common import Parameters, ndarrays_to_parameters, parameters_to_ndarrays

from .differential_privacy import (
add_gaussian_noise,
add_noise_to_params,
clip_inputs,
compute_stdv,
get_norm,
)


def test_add_gaussian_noise() -> None:
"""Test add_gaussian_noise function."""
# Prepare
update = [np.array([[1, 2], [3, 4]]), np.array([[5, 6], [7, 8]])]
std_dev = 0.1

# Execute
update_noised = add_gaussian_noise(update, std_dev)

# Assert
# Check that the shape of the result is the same as the input
for layer, layer_noised in zip(update, update_noised):
assert layer.shape == layer_noised.shape

# Check that the values have been changed and are not equal to the original update
for layer, layer_noised in zip(update, update_noised):
assert not np.array_equal(layer, layer_noised)

# Check that the noise has been added
for layer, layer_noised in zip(update, update_noised):
noise_added = layer_noised - layer
assert np.any(np.abs(noise_added) > 0)


def test_get_norm() -> None:
"""Test get_norm function."""
# Prepare
update = [np.array([[1, 2], [3, 4]]), np.array([[5, 6], [7, 8]])]

# Execute
result = get_norm(update)

expected = float(
np.linalg.norm(np.concatenate([sub_update.flatten() for sub_update in update]))
)

# Assert
assert expected == result


def test_clip_inputs() -> None:
"""Test clip_inputs function."""
# Prepare
updates = [
np.array([[1.5, -0.5], [2.0, -1.0]]),
np.array([0.5, -0.5]),
np.array([[-0.5, 1.5], [-1.0, 2.0]]),
np.array([-0.5, 0.5]),
]
clipping_norm = 1.5

# Execute
clipped_updates = clip_inputs(updates, clipping_norm)

# Assert
assert len(clipped_updates) == len(updates)

for clipped_update, original_update in zip(clipped_updates, updates):
clip_norm = np.linalg.norm(original_update)
assert np.all(clipped_update <= clip_norm) and np.all(
clipped_update >= -clip_norm
)


def test_add_noise_to_params() -> None:
"""Test add_noise_to_params function."""
# Prepare
parameters = ndarrays_to_parameters(
[np.array([[1, 2], [3, 4]]), np.array([[5, 6], [7, 8]])]
)
std_dev = 0.1

# Execute
noised_parameters = add_noise_to_params(parameters, std_dev)

original_params_list = parameters_to_ndarrays(parameters)
noised_params_list = parameters_to_ndarrays(noised_parameters)

# Assert
assert isinstance(noised_parameters, Parameters)

# Check the values have been changed and are not equal to the original parameters
for original_param, noised_param in zip(original_params_list, noised_params_list):
assert not np.array_equal(original_param, noised_param)

# Check that the noise has been added
for original_param, noised_param in zip(original_params_list, noised_params_list):
noise_added = noised_param - original_param
assert np.any(np.abs(noise_added) > 0)


def test_compute_stdv() -> None:
"""Test compute_stdv function."""
# Prepare
noise_multiplier = 1.0
clipping_norm = 0.5
num_sampled_clients = 10

# Execute
stdv = compute_stdv(noise_multiplier, clipping_norm, num_sampled_clients)

# Assert
expected_stdv = float((noise_multiplier * clipping_norm) / num_sampled_clients)
assert stdv == expected_stdv
2 changes: 2 additions & 0 deletions src/py/flwr/server/strategy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@


from .bulyan import Bulyan as Bulyan
from .dp_strategy_wrapper_fixed_clipping import DPStrategyWrapperServerSideFixedClipping
from .dpfedavg_adaptive import DPFedAvgAdaptive as DPFedAvgAdaptive
from .dpfedavg_fixed import DPFedAvgFixed as DPFedAvgFixed
from .fault_tolerant_fedavg import FaultTolerantFedAvg as FaultTolerantFedAvg
Expand Down Expand Up @@ -57,4 +58,5 @@
"DPFedAvgAdaptive",
"DPFedAvgFixed",
"Strategy",
"DPStrategyWrapperServerSideFixedClipping",
]
Loading
Loading