def __init__(self, params, name='SubsampleGaussian'): Mechanism.__init__(self) self.name = name self.params = { 'prob': params['prob'], 'sigma': params['sigma'], 'coeff': params['coeff'] } # create such a mechanism as in previously subsample = transformer_zoo.AmplificationBySampling( ) # by default this is using poisson sampling mech = GaussianMechanism(sigma=params['sigma']) # Create subsampled Gaussian mechanism SubsampledGaussian_mech = subsample(mech, params['prob'], improved_bound_flag=True) # Now run this for niter iterations compose = transformer_zoo.Composition() mech = compose([SubsampledGaussian_mech], [params['coeff']]) # Now we get it and let's extract the RDP function and assign it to the current mech being constructed rdp_total = mech.RenyiDP self.propagate_updates(rdp_total, type_of_update='RDP')
def __init__(self, sigma, name='Gaussian', RDP_off=False, approxDP_off=False, fdp_off=True, use_basic_RDP_to_approxDP_conversion=False, use_fDP_based_RDP_to_approxDP_conversion=False): # the sigma parameter is the std of the noise divide by the l2 sensitivity Mechanism.__init__(self) self.name = name # When composing self.params = {'sigma': sigma} # This will be useful for the Calibrator # TODO: should a generic unspecified mechanism have a name and a param dictionary? self.delta0 = 0 if not RDP_off: new_rdp = lambda x: rdp_bank.RDP_gaussian({'sigma': sigma}, x) if use_fDP_based_RDP_to_approxDP_conversion: # This setting is slightly more complex, which involves converting RDP to fDP, # then to eps-delta-DP via the duality self.propagate_updates(new_rdp, 'RDP', fDP_based_conversion=True) elif use_basic_RDP_to_approxDP_conversion: self.propagate_updates(new_rdp, 'RDP', BBGHS_conversion=False) else: # This is the default setting with fast computation of RDP to approx-DP self.propagate_updates(new_rdp, 'RDP') if not approxDP_off: # Direct implementation of approxDP new_approxdp = lambda x: dp_bank.get_eps_ana_gaussian(sigma, x) self.propagate_updates(new_approxdp,'approxDP_func') if not fdp_off: # Direct implementation of fDP fun1 = lambda x: fdp_bank.log_one_minus_fdp_gaussian({'sigma': sigma}, x) fun2 = lambda x: fdp_bank.log_neg_fdp_grad_gaussian({'sigma': sigma}, x) self.propagate_updates([fun1,fun2],'fDP_and_grad_log') # overwrite the fdp computation with the direct computation self.fdp = lambda x: fdp_bank.fDP_gaussian({'sigma': sigma}, x)
def __init__(self, sigma, gamma, name='Subsample_Gaussian_phi', lower_bound = False, upper_bound=False): """ sigma: the std of the noise divide by the l2 sensitivity. gamma: the sampling probability. lower_bound: if the lower_bound is True, the privacy cost (delta(epsilon) or delta(epsilon)) is a valid lower bound of the true privacy guarantee besides negligible errors induced by trunction. upper_bound: if the upper_bound is True, the privacy cost (delta(epsilon) or delta(epsilon)) is a valid upper bound of the true privacy guarantee besides negligible errors induced by trunction. """ Mechanism.__init__(self) self.name = name # When composing self.params = {'sigma': sigma,'gamma':gamma} self.delta0 = 0 if lower_bound: # log_phi_p denotes the approximated phi-function of the privacy loss R.V. log(p/q). # log_phi_q denotes the approximated phi-function of the privacy loss R.V. log(q/p). self.log_phi_p = lambda x: phi_bank.phi_subsample_gaussian_p(self.params, x, phi_min = True) self.log_phi_q = lambda x: phi_bank.phi_subsample_gaussian_q(self.params, x, phi_min = True) elif upper_bound: self.log_phi_p = lambda x: phi_bank.phi_subsample_gaussian_p(self.params, x, phi_max=True) self.log_phi_q = lambda x: phi_bank.phi_subsample_gaussian_q(self.params, x, phi_max=True) else: # The following phi_p and phi_q is for Double quadrature method. # Double quadrature method approximates phi-function using Gaussian quadrature directly. self.log_phi_p = lambda x: phi_bank.phi_subsample_gaussian_p(self.params, x) self.log_phi_q = lambda x: phi_bank.phi_subsample_gaussian_q(self.params, x) self.propagate_updates((self.log_phi_p, self.log_phi_q), 'log_phi')
def __init__(self, params, name='GaussianSVT'): Mechanism.__init__(self) self.name = name self.params = {'b': params['b'], 'k': params['k'], 'c': params['c']} new_rdp = lambda x: rdp_bank.RDP_svt_laplace(self.params, x) self.propagate_updates(new_rdp, 'RDP')
def __init__(self, b=None, name='Laplace', RDP_off=False, phi_off=True): """ b: the ratio of the scale parameter and L1 sensitivity. RDP_off: if False, then we characterize the mechanism using RDP. fdp_off: if False, then we characterize the mechanism using fdp. phi_off: if False, then we characterize the mechanism using phi-function. """ Mechanism.__init__(self) self.name = name self.params = {'b': b} # This will be useful for the Calibrator self.delta0 = 0 if not phi_off: log_phi_p = lambda x: phi_bank.phi_laplace(self.params, x) log_phi_q = lambda x: phi_bank.phi_laplace(self.params, x) self.log_phi_p = log_phi_p self.log_phi_q = log_phi_q self.propagate_updates((log_phi_p, log_phi_q), 'log_phi') if not RDP_off: new_rdp = lambda x: rdp_bank.RDP_laplace({'b': b}, x) self.propagate_updates(new_rdp, 'RDP')
def __init__(self, eps, name='PureDP'): # the eps parameter is the pure DP parameter of this mechanism Mechanism.__init__(self) self.name = name # Used for generating new names when composing self.params = {'eps': eps} # self.propagate_updates(eps, 'pureDP')
def __init__(self,params,name='NoisyScreen'): Mechanism.__init__(self) self.name=name self.params={'logp':params['logp'],'logq':params['logq']} # create such a mechanism as in previously new_rdp = lambda x: rdp_bank.RDP_noisy_screen({'logp': params['logp'], 'logq': params['logq']}, x) self.propagate_updates(new_rdp, 'RDP')
def __init__(self,rho,xi=0,name='zCDP_mech'): Mechanism.__init__(self) self.name = name self.params = {'rho':rho,'xi':xi} new_rdp = lambda x: rdp_bank.RDP_zCDP(self.params, x) self.propagate_updates(new_rdp,'RDP')
def __init__(self, p=None, name='Randresponse'): Mechanism.__init__(self) self.name = name self.params = {'p': p} # This will be useful for the Calibrator self.delta0 = 0 if p is not None: new_rdp = lambda x: rdp_bank.RDP_randresponse({'p': p}, x) self.propagate_updates(new_rdp, 'RDP')
def __init__(self, b=None, name='Laplace'): Mechanism.__init__(self) self.name = name self.params = {'b': b} # This will be useful for the Calibrator self.delta0 = 0 if b is not None: new_rdp = lambda x: rdp_bank.RDP_laplace({'b': b}, x) self.propagate_updates(new_rdp, 'RDP')
def compose(self, mechanism_list, coeff_list): """ In the composition, we keep track of two lists of characteristic functions (Phi(t) and Phi'(t))with respect to the privacy loss R.V. log(p/q) and log(q/p). For most basic mechanisms (e.g., Gaussian mechanism, Lapalce mechanism), their phi(t) and Phi'(t) are the same. For some advanced mechanisms (e.g., SubsampleGaussian mechanism), their characteristic functions are not symmetric. """ newmech = Mechanism() # update the functions: log_phi_p and log_phi_q def new_log_phi_p(x): return sum([c * mech.log_phi_p(x) for (mech, c) in zip(mechanism_list, coeff_list)]) def new_log_phi_q(x): return sum([c * mech.log_phi_q(x) for (mech, c) in zip(mechanism_list, coeff_list)]) newmech.exactPhi = False # For mechanism with an exact phi-function, it admits both upper and lower bound phi-functions. # The phi-functions of mechanisms that are being composed shall be all (upper_bound / exact_phi) or (lower_bound / exact_phi. newmech.log_phi_p = lambda x: new_log_phi_p(x) newmech.log_phi_q = lambda x: new_log_phi_q(x) newmech.propagate_updates((new_log_phi_p, new_log_phi_q), 'log_phi') # Other book keeping newmech.name = self.update_name(mechanism_list, coeff_list) # keep track of all parameters of the composed mechanisms newmech.params = self.update_params(mechanism_list) return newmech
def __init__(self, params=None,approxDP_off=False, name='StageWiseMechanism'): # the sigma parameter is the std of the noise divide by the l2 sensitivity Mechanism.__init__(self) self.name = name # When composing self.params = {'sigma': params['sigma'], 'k':params['k'], 'c':params['c']} self.delta0 = 0 if not approxDP_off: # Direct implementation of approxDP new_approxdp = lambda x: dp_bank.eps_generalized_gaussian(x, **params) self.propagate_updates(new_approxdp, 'approxDP_func')
def __init__(self,params,name='GaussianSVT', rdp_c_1=True): Mechanism.__init__(self) self.name=name if rdp_c_1 == True: self.name = name + 'c_1' self.params = {'sigma': params['sigma'], 'k': params['k'], 'margin':params['margin']} new_rdp = lambda x: rdp_bank.RDP_gaussian_svt_c1(self.params, x) else: self.name = name + 'c>1' self.params = {'sigma':params['sigma'],'k':params['k'], 'c':params['c']} new_rdp = lambda x: rdp_bank.RDP_gaussian_svt_cgreater1(self.params, x) self.propagate_updates(new_rdp, 'RDP')
def __init__(self, sigma=None, name='Gaussian'): # the sigma parameter is the std of the noise divide by the l2 sensitivity Mechanism.__init__(self) self.name = name # When composing self.params = {'sigma': sigma} # This will be useful for the Calibrator self.delta0 = 0 if sigma is not None: new_rdp = lambda x: rdp_bank.RDP_gaussian({'sigma': sigma}, x) self.propagate_updates(new_rdp, 'RDP') # Overwrite the approxDP and fDP with their direct computation self.approxDP = lambda x: dp_bank.get_eps_ana_gaussian(sigma, x) self.fDP = lambda x: fdp_bank.fDP_gaussian({'sigma': sigma}, x)
def __init__(self, params, name='SubsampleGaussian'): Mechanism.__init__(self) self.name = name self.params = {'sigma': params['sigma'], 'coeff': params['coeff']} # create such a mechanism as in previously mech = GaussianMechanism(sigma=params['sigma']) # Now run this for coeff iterations compose = transformer_zoo.Composition() mech = compose([mech], [params['coeff']]) # Now we get it and let's extract the RDP function and assign it to the current mech being constructed rdp_total = mech.RenyiDP self.propagate_updates(rdp_total, type_of_update='RDP')
def compose(self, mechanism_list, coeff_list, RDP_compose_only=True, BBGHS_conversion=True, fDP_based_conversion=False): # Make sure that the mechanism has a unique list # for example, if there are two Gaussian mechanism with two different sigmas, call it # Gaussian1, and Gaussian2 # if RDP_compose_only is true, we use only RDP composition. newmech = Mechanism() # update the functions def newrdp(x): return sum([c * mech.RenyiDP(x) for (mech, c) in zip(mechanism_list, coeff_list)]) newmech.propagate_updates(newrdp, 'RDP', BBGHS_conversion= BBGHS_conversion, fDP_based_conversion=fDP_based_conversion) # TODO: the fDP_based_conversion sometimes fails due to undefined RDP with alpha < 1 newmech.eps_pureDP = sum([c * mech.eps_pureDP for (mech, c) in zip(mechanism_list, coeff_list)]) newmech.delta0 = max([mech.delta0 for (mech, c) in zip(mechanism_list, coeff_list)]) if not RDP_compose_only: # Also do KOV-composition while optimizing over \delta parameter # TODO: Also implement the KOV-composition here and propagate the updates # TODO: How do we generically compose eps(delta) functions? # TODO: How do we generically compose approximate RDP functions # TODO: How do we generically compose fDP? (efficiently) pass # Other book keeping newmech.name = self.update_name(mechanism_list, coeff_list) # keep track of all parameters of the composed mechanisms newmech.params = self.update_params(mechanism_list) return newmech
def compose(self, mechanism_list, comp_list): """ In the composition, we keep track of two lists of characteristic functions (Phi(t) and Phi'(t))with respect to the privacy loss R.V. log(p/q) and log(q/p). For most basic mechanisms (e.g., Gaussian mechanism, Laplace mechanism), their phi(t) and Phi'(t) are the same. For some advanced mechanisms (e.g., SubsampleGaussian mechanism), their characteristic functions are not symmetric. """ newmech = Mechanism() def new_log_phi_p(x): return sum([c * mech.log_phi_p(x) for (mech, c) in zip(mechanism_list, comp_list)]) def new_log_phi_q(x): return sum([c * mech.log_phi_q(x) for (mech, c) in zip(mechanism_list, comp_list)]) # Define a composed mechanism newmech.log_phi_p = lambda x: new_log_phi_p(x) newmech.log_phi_q = lambda x: new_log_phi_q(x) newmech.propagate_updates((newmech.log_phi_p, newmech.log_phi_q), 'log_phi', n_quad=self.n_quad) # Other book keeping newmech.name = self.update_name(mechanism_list, comp_list) # keep track of all parameters of the composed mechanisms newmech.params = self.update_params(mechanism_list) return newmech
def __init__(self, p=None, RDP_off=False, phi_off=True, name='Randresponse'): """ p: the Bernoulli probability p of outputting the truth. """ Mechanism.__init__(self) self.name = name self.params = {'p': p} self.delta0 = 0 if not RDP_off: new_rdp = lambda x: rdp_bank.RDP_randresponse({'p': p}, x) self.propagate_updates(new_rdp, 'RDP') approxDP = lambda x: dp_bank.get_eps_randresp_optimal(p, x) self.propagate_updates(approxDP, 'approxDP_func') if not phi_off: log_phi = lambda x: phi_bank.phi_rr_p({'p': p, 'q':1-p}, x) self.log_phi_p = self.log_phi_q = log_phi self.propagate_updates((self.log_phi_p, self.log_phi_q), 'log_phi')
def __init__(self, prob, sigma, niter, PoissonSampling=True, name='NoisySGD'): Mechanism.__init__(self) self.name = name self.params = {'prob': prob, 'sigma': sigma, 'niter': niter, 'PoissonSampling': PoissonSampling} # create such a mechanism as in previously subsample = transformer_zoo.AmplificationBySampling(PoissonSampling=PoissonSampling) # by default this is using poisson sampling mech = ExactGaussianMechanism(sigma=sigma) prob = 0.01 # Create subsampled Gaussian mechanism SubsampledGaussian_mech = subsample(mech, prob, improved_bound_flag=True) # for Gaussian mechanism the improved bound always applies # Now run this for niter iterations compose = transformer_zoo.Composition() mech = compose([SubsampledGaussian_mech], [niter]) # Now we get it and let's extract the RDP function and assign it to the current mech being constructed rdp_total = mech.RenyiDP self.propagate_updates(rdp_total, type_of_update='RDP')
def __init__(self, sigma, name='Gaussian', RDP_off=False, approxDP_off=False, fdp_off=True, use_basic_RDP_to_approxDP_conversion=False, use_fDP_based_RDP_to_approxDP_conversion=False, phi_off=True): """ sigma: the std of the noise divide by the l2 sensitivity. coeff: the number of composition RDP_off: if False, then we characterize the mechanism using RDP. fdp_off: if False, then we characterize the mechanism using fdp. phi_off: if False, then we characterize the mechanism using phi-function. """ Mechanism.__init__(self) self.name = name # When composing self.params = {'sigma': sigma} # This will be useful for the Calibrator # TODO: should a generic unspecified mechanism have a name and a param dictionary? self.delta0 = 0 if not phi_off: """ Apply phi function to analyze Gaussian mechanism. the CDF of privacy loss R.V. is computed using an integration (see details in cdf_bank) through Levy Theorem. If self.exactPhi = True, the algorithm provides an exact characterization. """ self.exactPhi = True log_phi = lambda x: phi_bank.phi_gaussian({'sigma': sigma}, x) self.log_phi_p = self.log_phi_q = log_phi # self.cdf tracks the cdf of log(p/q) and the cdf of log(q/p). self.propagate_updates((log_phi, log_phi), 'log_phi') """ Moreover, we know the closed-form expression of the CDF of the privacy loss RV privacy loss RV distribution l=log(p/q) ~ N(1/2\sigma^2, 1/sigma^2) We can also use the following closed-form cdf directly. """ #sigma = sigma*1.0/np.sqrt(coeff) #mean = 1.0 / (2.0 * sigma ** 2) #std = 1.0 / (sigma) #cdf = lambda x: norm.cdf((x - mean) / std) #self.propagate_updates(cdf, 'cdf', take_log=True) if not RDP_off: new_rdp = lambda x: rdp_bank.RDP_gaussian({'sigma': sigma}, x) if use_fDP_based_RDP_to_approxDP_conversion: # This setting is slightly more complex, which involves converting RDP to fDP, # then to eps-delta-DP via the duality self.propagate_updates(new_rdp, 'RDP', fDP_based_conversion=True) elif use_basic_RDP_to_approxDP_conversion: self.propagate_updates(new_rdp, 'RDP', BBGHS_conversion=False) else: # This is the default setting with fast computation of RDP to approx-DP self.propagate_updates(new_rdp, 'RDP') if not approxDP_off: # Direct implementation of approxDP new_approxdp = lambda x: dp_bank.get_eps_ana_gaussian(sigma, x) self.propagate_updates(new_approxdp,'approxDP_func') if not fdp_off: # Direct implementation of fDP fun1 = lambda x: fdp_bank.log_one_minus_fdp_gaussian({'sigma': sigma}, x) fun2 = lambda x: fdp_bank.log_neg_fdp_grad_gaussian({'sigma': sigma}, x) self.propagate_updates([fun1,fun2],'fDP_and_grad_log') # overwrite the fdp computation with the direct computation self.fdp = lambda x: fdp_bank.fDP_gaussian({'sigma': sigma}, x)
def amplify(self, mechanism, prob, improved_bound_flag=False): # If you know that your mechanism # - (for PoissonSampling) satisfies the the conditions in Theorem 8 of http://proceedings.mlr.press/v97/zhu19c/zhu19c.pdf # - or (for subsampling) satisfies the conditions of Theorem 27 of https://arxiv.org/pdf/1808.00087.pdf # then you may switch general_bound_flag to False to get a tighter bound. # Else, for all mechanisms with RDP bounds, the general upper bounds are used by default. newmech = Mechanism() # privacy amplification via approx-dp # Amplification of RDP # propagate to approxDP as well. if self.PoissonSampling: assert not mechanism.replace_one, "mechanism's replace_one notion of DP is " \ "incompatible with Privacy Amplification " \ "by Poisson sampling" # check that the input mechanism uses the standard add-or-remove notion of DP. # If not, there actually isn't a way to convert it from replace-one notation, # unless a "dummy" user exists in the space. newmech.replace_one = False else: # if we want subsampled DP assert mechanism.replace_one, "mechanism's add-remove notion of DP is " \ "incompatible with Privacy Amplification " \ "by subsampling without replacements" # TODO: implement a transformer that convert add/remove to replace_one notion of DP. newmech.replace_one = True if prob == 0: new_approxDP = lambda delta:0 else: new_approxDP = lambda delta: np.log(1 + prob*(np.exp(mechanism.approxDP(delta/prob))-1)) newmech.approxDP = new_approxDP acct = rdp_acct.anaRDPacct() if self.PoissonSampling: if improved_bound_flag: acct.compose_poisson_subsampled_mechanisms(mechanism.RenyiDP,prob) else: acct.compose_poisson_subsampled_mechanisms1(mechanism.RenyiDP,prob) else: # subsampling if improved_bound_flag: acct.compose_subsampled_mechanism(mechanism.RenyiDP, prob, improved_bound_flag=True) else: acct.compose_subsampled_mechanism(mechanism.RenyiDP, prob) acct.build_zeroth_oracle() new_rdp = acct.evalRDP newmech.propagate_updates(new_rdp,'RDP') #TODO: Implement the amplification of f-DP # propagate to approxDP, or simply get the f-DP from approximate-DP. # book keeping key = self.name + '_' + str(prob) num = 0 newname = self.name # the following handles the case when key is already in the params while key in mechanism.params: num = num+1 newname = self.name+str(num) key = newname + '_' + str(prob) newmech.name = newname +':'+mechanism.name newmech.params = mechanism.params new_params = {newname:prob} newmech.params.update(new_params) return newmech # TODO: implement other transformers: # - amplification by shuffling # - parallel composition # - group composition # - private selection of private candidates # - amplification by overwhelmingly large-probability event.