Source code for flwr.server.strategy.dp_fixed_clipping
# 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.# =============================================================================="""Central differential privacy with fixed clipping.Papers: https://arxiv.org/abs/1712.07557, https://arxiv.org/abs/1710.06963"""fromloggingimportINFO,WARNINGfromtypingimportOptional,Unionfromflwr.commonimport(EvaluateIns,EvaluateRes,FitIns,FitRes,NDArrays,Parameters,Scalar,ndarrays_to_parameters,parameters_to_ndarrays,)fromflwr.common.differential_privacyimport(add_gaussian_noise_to_params,compute_clip_model_update,compute_stdv,)fromflwr.common.differential_privacy_constantsimport(CLIENTS_DISCREPANCY_WARNING,KEY_CLIPPING_NORM,)fromflwr.common.loggerimportlogfromflwr.server.client_managerimportClientManagerfromflwr.server.client_proxyimportClientProxyfromflwr.server.strategy.strategyimportStrategy
[docs]classDifferentialPrivacyServerSideFixedClipping(Strategy):"""Strategy wrapper for central DP with server-side fixed clipping. Parameters ---------- strategy : Strategy The strategy to which DP functionalities will be added by this wrapper. noise_multiplier : float The noise multiplier for the Gaussian mechanism for model updates. A value of 1.0 or higher is recommended for strong privacy. clipping_norm : float The value of the clipping norm. num_sampled_clients : int The number of clients that are sampled on each round. Examples -------- Create a strategy: >>> strategy = fl.server.strategy.FedAvg( ... ) Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping wrapper >>> dp_strategy = DifferentialPrivacyServerSideFixedClipping( >>> strategy, cfg.noise_multiplier, cfg.clipping_norm, cfg.num_sampled_clients >>> ) """# pylint: disable=too-many-arguments,too-many-instance-attributesdef__init__(self,strategy:Strategy,noise_multiplier:float,clipping_norm:float,num_sampled_clients:int,)->None:super().__init__()self.strategy=strategyifnoise_multiplier<0:raiseValueError("The noise multiplier should be a non-negative value.")ifclipping_norm<=0:raiseValueError("The clipping norm should be a positive value.")ifnum_sampled_clients<=0:raiseValueError("The number of sampled clients should be a positive value.")self.noise_multiplier=noise_multiplierself.clipping_norm=clipping_normself.num_sampled_clients=num_sampled_clientsself.current_round_params:NDArrays=[]def__repr__(self)->str:"""Compute a string representation of the strategy."""rep="Differential Privacy Strategy Wrapper (Server-Side Fixed Clipping)"returnrep
[docs]definitialize_parameters(self,client_manager:ClientManager)->Optional[Parameters]:"""Initialize global model parameters using given strategy."""returnself.strategy.initialize_parameters(client_manager)
[docs]defconfigure_fit(self,server_round:int,parameters:Parameters,client_manager:ClientManager)->list[tuple[ClientProxy,FitIns]]:"""Configure the next round of training."""self.current_round_params=parameters_to_ndarrays(parameters)returnself.strategy.configure_fit(server_round,parameters,client_manager)
[docs]defconfigure_evaluate(self,server_round:int,parameters:Parameters,client_manager:ClientManager)->list[tuple[ClientProxy,EvaluateIns]]:"""Configure the next round of evaluation."""returnself.strategy.configure_evaluate(server_round,parameters,client_manager)
[docs]defaggregate_fit(self,server_round:int,results:list[tuple[ClientProxy,FitRes]],failures:list[Union[tuple[ClientProxy,FitRes],BaseException]],)->tuple[Optional[Parameters],dict[str,Scalar]]:"""Compute the updates, clip, and pass them for aggregation. Afterward, add noise to the aggregated parameters. """iffailures:returnNone,{}iflen(results)!=self.num_sampled_clients:log(WARNING,CLIENTS_DISCREPANCY_WARNING,len(results),self.num_sampled_clients,)for_,resinresults:param=parameters_to_ndarrays(res.parameters)# Compute and clip updatecompute_clip_model_update(param,self.current_round_params,self.clipping_norm)log(INFO,"aggregate_fit: parameters are clipped by value: %.4f.",self.clipping_norm,)# Convert back to parametersres.parameters=ndarrays_to_parameters(param)# Pass the new parameters for aggregationaggregated_params,metrics=self.strategy.aggregate_fit(server_round,results,failures)# Add Gaussian noise to the aggregated parametersifaggregated_params:aggregated_params=add_gaussian_noise_to_params(aggregated_params,self.noise_multiplier,self.clipping_norm,self.num_sampled_clients,)log(INFO,"aggregate_fit: central DP noise with %.4f stdev added",compute_stdv(self.noise_multiplier,self.clipping_norm,self.num_sampled_clients),)returnaggregated_params,metrics
[docs]defaggregate_evaluate(self,server_round:int,results:list[tuple[ClientProxy,EvaluateRes]],failures:list[Union[tuple[ClientProxy,EvaluateRes],BaseException]],)->tuple[Optional[float],dict[str,Scalar]]:"""Aggregate evaluation losses using the given strategy."""returnself.strategy.aggregate_evaluate(server_round,results,failures)
[docs]defevaluate(self,server_round:int,parameters:Parameters)->Optional[tuple[float,dict[str,Scalar]]]:"""Evaluate model parameters using an evaluation function from the strategy."""returnself.strategy.evaluate(server_round,parameters)
[docs]classDifferentialPrivacyClientSideFixedClipping(Strategy):"""Strategy wrapper for central DP with client-side fixed clipping. Use `fixedclipping_mod` modifier at the client side. In comparison to `DifferentialPrivacyServerSideFixedClipping`, which performs clipping on the server-side, `DifferentialPrivacyClientSideFixedClipping` expects clipping to happen on the client-side, usually by using the built-in `fixedclipping_mod`. Parameters ---------- strategy : Strategy The strategy to which DP functionalities will be added by this wrapper. noise_multiplier : float The noise multiplier for the Gaussian mechanism for model updates. A value of 1.0 or higher is recommended for strong privacy. clipping_norm : float The value of the clipping norm. num_sampled_clients : int The number of clients that are sampled on each round. Examples -------- Create a strategy: >>> strategy = fl.server.strategy.FedAvg(...) Wrap the strategy with the `DifferentialPrivacyClientSideFixedClipping` wrapper: >>> dp_strategy = DifferentialPrivacyClientSideFixedClipping( >>> strategy, cfg.noise_multiplier, cfg.clipping_norm, cfg.num_sampled_clients >>> ) On the client, add the `fixedclipping_mod` to the client-side mods: >>> app = fl.client.ClientApp( >>> client_fn=client_fn, mods=[fixedclipping_mod] >>> ) """# pylint: disable=too-many-arguments,too-many-instance-attributesdef__init__(self,strategy:Strategy,noise_multiplier:float,clipping_norm:float,num_sampled_clients:int,)->None:super().__init__()self.strategy=strategyifnoise_multiplier<0:raiseValueError("The noise multiplier should be a non-negative value.")ifclipping_norm<=0:raiseValueError("The clipping threshold should be a positive value.")ifnum_sampled_clients<=0:raiseValueError("The number of sampled clients should be a positive value.")self.noise_multiplier=noise_multiplierself.clipping_norm=clipping_normself.num_sampled_clients=num_sampled_clientsdef__repr__(self)->str:"""Compute a string representation of the strategy."""rep="Differential Privacy Strategy Wrapper (Client-Side Fixed Clipping)"returnrep
[docs]definitialize_parameters(self,client_manager:ClientManager)->Optional[Parameters]:"""Initialize global model parameters using given strategy."""returnself.strategy.initialize_parameters(client_manager)
[docs]defconfigure_fit(self,server_round:int,parameters:Parameters,client_manager:ClientManager)->list[tuple[ClientProxy,FitIns]]:"""Configure the next round of training."""additional_config={KEY_CLIPPING_NORM:self.clipping_norm}inner_strategy_config_result=self.strategy.configure_fit(server_round,parameters,client_manager)for_,fit_insininner_strategy_config_result:fit_ins.config.update(additional_config)returninner_strategy_config_result
[docs]defconfigure_evaluate(self,server_round:int,parameters:Parameters,client_manager:ClientManager)->list[tuple[ClientProxy,EvaluateIns]]:"""Configure the next round of evaluation."""returnself.strategy.configure_evaluate(server_round,parameters,client_manager)
[docs]defaggregate_fit(self,server_round:int,results:list[tuple[ClientProxy,FitRes]],failures:list[Union[tuple[ClientProxy,FitRes],BaseException]],)->tuple[Optional[Parameters],dict[str,Scalar]]:"""Add noise to the aggregated parameters."""iffailures:returnNone,{}iflen(results)!=self.num_sampled_clients:log(WARNING,CLIENTS_DISCREPANCY_WARNING,len(results),self.num_sampled_clients,)# Pass the new parameters for aggregationaggregated_params,metrics=self.strategy.aggregate_fit(server_round,results,failures)# Add Gaussian noise to the aggregated parametersifaggregated_params:aggregated_params=add_gaussian_noise_to_params(aggregated_params,self.noise_multiplier,self.clipping_norm,self.num_sampled_clients,)log(INFO,"aggregate_fit: central DP noise with %.4f stdev added",compute_stdv(self.noise_multiplier,self.clipping_norm,self.num_sampled_clients),)returnaggregated_params,metrics
[docs]defaggregate_evaluate(self,server_round:int,results:list[tuple[ClientProxy,EvaluateRes]],failures:list[Union[tuple[ClientProxy,EvaluateRes],BaseException]],)->tuple[Optional[float],dict[str,Scalar]]:"""Aggregate evaluation losses using the given strategy."""returnself.strategy.aggregate_evaluate(server_round,results,failures)
[docs]defevaluate(self,server_round:int,parameters:Parameters)->Optional[tuple[float,dict[str,Scalar]]]:"""Evaluate model parameters using an evaluation function from the strategy."""returnself.strategy.evaluate(server_round,parameters)