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 51 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
87 changes: 87 additions & 0 deletions src/py/flwr/common/differential_privacy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Copyright 2024 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.
# ==============================================================================
"""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_arrays: NDArrays) -> float:
"""Compute the L2 norm of the flattened input."""
array_norms = [np.linalg.norm(array.flat) for array in input_arrays]
# pylint: disable=consider-using-generator
return float(np.sqrt(sum([norm**2 for norm in array_norms])))


def add_gaussian_noise_inplace(input_arrays: NDArrays, std_dev: float) -> None:
"""Add Gaussian noise to each element of the input arrays."""
for array in input_arrays:
array += np.random.normal(0, std_dev, array.shape)


def clip_inputs_inplace(input_arrays: NDArrays, clipping_norm: float) -> None:
"""Clip model update based on the clipping norm in-place.

FlatClip method of the paper: https://arxiv.org/abs/1710.06963
"""
input_norm = get_norm(input_arrays)
scaling_factor = min(1, clipping_norm / input_norm)
for array in input_arrays:
array *= scaling_factor


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

Paper: https://arxiv.org/abs/1710.06963
"""
return float((noise_multiplier * clipping_norm) / num_sampled_clients)


def compute_clip_model_update(
param1: NDArrays, param2: NDArrays, clipping_norm: float
) -> None:
"""Compute model update (param1 - param2) and clip it.

Then add the clipped value to param1."""
model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2)]
clip_inputs_inplace(model_update, clipping_norm)

for i, _ in enumerate(param2):
param1[i] = param2[i] + model_update[i]


def add_gaussian_noise_to_params(
model_params: Parameters,
noise_multiplier: float,
clipping_norm: float,
num_sampled_clients: int,
) -> Parameters:
"""Add gaussian noise to model parameters."""
model_params_ndarrays = parameters_to_ndarrays(model_params)
add_gaussian_noise_inplace(
model_params_ndarrays,
compute_stdv(noise_multiplier, clipping_norm, num_sampled_clients),
)
return ndarrays_to_parameters(model_params_ndarrays)
22 changes: 22 additions & 0 deletions src/py/flwr/common/differential_privacy_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2024 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.
# ==============================================================================
"""Constants for differential privacy."""

CLIENTS_DISCREPANCY_WARNING = (
"The number of clients returning parameters (%s)"
" differs from the number of sampled clients (%s)."
" This could impact the differential privacy guarantees,"
" potentially leading to privacy leakage or inadequate noise calibration."
)
138 changes: 138 additions & 0 deletions src/py/flwr/common/differential_privacy_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Copyright 2024 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 .differential_privacy import (
add_gaussian_noise_inplace,
clip_inputs_inplace,
compute_clip_model_update,
compute_stdv,
get_norm,
)


def test_add_gaussian_noise_inplace() -> None:
"""Test add_gaussian_noise_inplace function."""
# Prepare
update = [np.array([[1.0, 2.0], [3.0, 4.0]]), np.array([[5.0, 6.0], [7.0, 8.0]])]
std_dev = 0.1

# Execute
add_gaussian_noise_inplace(update, std_dev)

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

# Check that the values have been changed and are not equal to the original update
for layer in update:
assert not np.array_equal(
layer, [[1.0, 2.0], [3.0, 4.0]]
) and not np.array_equal(layer, [[5.0, 6.0], [7.0, 8.0]])

# Check that the noise has been added
for layer in update:
noise_added = (
layer - np.array([[1.0, 2.0], [3.0, 4.0]])
if np.array_equal(layer, [[1.0, 2.0], [3.0, 4.0]])
else layer - np.array([[5.0, 6.0], [7.0, 8.0]])
)
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_inplace() -> None:
"""Test clip_inputs_inplace 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

original_updates = [np.copy(update) for update in updates]

# Execute
clip_inputs_inplace(updates, clipping_norm)

# Assert
for updated, original_update in zip(updates, original_updates):
clip_norm = np.linalg.norm(original_update)
assert np.all(updated <= clip_norm) and np.all(updated >= -clip_norm)


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


def test_compute_clip_model_update() -> None:
"""Test compute_clip_model_update function."""
# Prepare
param1 = [
np.array([0.5, 1.5, 2.5]),
np.array([3.5, 4.5, 5.5]),
np.array([6.5, 7.5, 8.5]),
]
param2 = [
np.array([1.0, 2.0, 3.0]),
np.array([4.0, 5.0, 6.0]),
np.array([7.0, 8.0, 9.0]),
]
clipping_norm = 4

expected_result = [
np.array([0.5, 1.5, 2.5]),
np.array([3.5, 4.5, 5.5]),
np.array([6.5, 7.5, 8.5]),
]

# Execute
compute_clip_model_update(param1, param2, clipping_norm)

# Verify
for i, param in enumerate(param1):
np.testing.assert_array_almost_equal(param, expected_result[i])
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_fixed_clipping import DifferentialPrivacyServerSideFixedClipping
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",
"DifferentialPrivacyServerSideFixedClipping",
]
Loading