Exemplo n.º 1
0
    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')
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
    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')
Exemplo n.º 4
0
    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')
Exemplo n.º 5
0
    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')
Exemplo n.º 6
0
    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')
Exemplo n.º 7
0
    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')
Exemplo n.º 8
0
    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')
Exemplo n.º 9
0
    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')
Exemplo n.º 10
0
    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')
Exemplo n.º 11
0
    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
Exemplo n.º 12
0
    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')
Exemplo n.º 13
0
 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')
Exemplo n.º 14
0
    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)
Exemplo n.º 15
0
    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')
Exemplo n.º 16
0
    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
Exemplo n.º 17
0
    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
Exemplo n.º 18
0
    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')
Exemplo n.º 19
0
    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')
Exemplo n.º 20
0
    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)
Exemplo n.º 21
0
    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.