Source code for secretflow.security.privacy.mechanism.tensorflow.mechanism_fl

# Copyright 2022 Ant Group Co., Ltd.
#
# 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
#
#      https://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.

import tensorflow as tf
from typing import List
import numpy as np

from secretflow.security.privacy.accounting.rdp_accountant import (
    get_rdp,
    get_privacy_spent_rdp,
)


[docs]class GaussianModelDP: """global model differential privacy perturbation using gaussian noise"""
[docs] def __init__( self, noise_multiplier: float, num_clients: int, num_updates: int = None, l2_norm_clip: float = 1.0, delta: float = None, is_secure_generator: bool = False, is_clip_each_layer: bool = False, ) -> None: """ Args: epnoise_multipliers: Epsilon for pure DP. num_clients: Number of all clients. num_updates: Number of Clients that participate in the update. l2_norm_clip: The clipping norm to apply to the parameters or gradients. is_secure_generator: whether use the secure generator to generate noise. is_clip_prelayer: The 2norm of each layer is dynamically assigned. """ self.noise_multiplier = noise_multiplier self.l2_norm_clip = l2_norm_clip self.num_clients = num_clients self.num_updates = num_clients if num_updates is None else num_updates self.delta = delta if delta is not None else min(1 / num_clients**2, 1e-5) self.is_secure_generator = is_secure_generator self.is_clip_each_layer = is_clip_each_layer
def __call__(self, inputs: List): """Add gaussion dp on model parameters or gradients. Args: inputs: Model parameters. """ assert inputs, 'the inputs of GaussianModelDP should not be empty!' model_weights_noised = [] gradient_norm_all = tf.linalg.global_norm(inputs) if self.is_clip_each_layer: for i in range(len(inputs)): # clipping gradient_norm = tf.linalg.global_norm([inputs[i]]) gradient_clipped = inputs[i] * min( 1, (self.l2_norm_clip / np.sqrt(gradient_norm * gradient_norm_all)), ) # add noise if self.is_secure_generator: import secretflow.security.privacy._lib.random as random noise = random.secure_normal_real( 0, self.noise_multiplier * self.l2_norm_clip, size=inputs[i].shape, ) else: noise = tf.random.normal( inputs[i].shape, stddev=self.noise_multiplier * self.l2_norm_clip, ) model_weights_noised.append( tf.add(gradient_clipped, noise / np.sqrt(self.num_updates)) ) else: scale = min(1, self.l2_norm_clip / gradient_norm_all) for i in range(len(inputs)): # clipping gradient_clipped = inputs[i] * scale # add noise if self.is_secure_generator: import secretflow.security.privacy._lib.random as random noise = random.secure_normal_real( 0, self.noise_multiplier * self.l2_norm_clip, size=inputs[i].shape, ) else: noise = tf.random.normal( inputs[i].shape, stddev=self.noise_multiplier * self.l2_norm_clip, ) model_weights_noised.append( np.add(gradient_clipped, noise / np.sqrt(self.num_updates)) ) return model_weights_noised
[docs] def privacy_spent_rdp(self, step: int, orders: List = None): """Get accountant using RDP. Args: step: The dp current step of model training or prediction. orders: An array (or a scalar) of RDP orders. """ if orders is None: # order \in [2,128] empirically orders = [1 + x / 10.0 for x in range(1, 100)] + list(range(12, 64)) # optional value # orders = ( # [1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 3.0, 3.5, 4.0, 4.5] # + list(range(5, 64)) # + [128, 256, 512] # ) q = self.num_updates / self.num_clients rdp = get_rdp(q, self.noise_multiplier, step, orders) eps, _, opt_order = get_privacy_spent_rdp(orders, rdp, target_delta=self.delta) return eps, self.delta, opt_order