def get_FF1_substitution(sigma, q, nx, L): """ For a single Gaussian Mechanism, compute the FFT approximation points for the privacy loss distribution, under the substitution adjacency definition. :param sigma: The effective noise applied :param q: The sample probability :param nx: The number of approximation points :param L: The clipping length of the approximation :return: The FFT approximation points """ try: filename = f"sub_{sigma}_{q}_{nx}_{L}.p" saved_result_flag, result, fp = grab_pickled_accountant_results(filename) except (AttributeError, EOFError, ImportError, IndexError, pickle.UnpicklingError): logger.error("Error reading accountant Pickle!") if saved_result_flag: return result # Evaluate the PLD distribution, # This is the case of substitution relation (subsection 5.2) half = int(nx / 2) dx = 2.0 * L / nx # discretisation interval \Delta x x = np.linspace(-L, L - dx, nx, dtype=np.complex128) # grid for the numerical integration c = q * np.exp(-1 / (2 * sigma ** 2)) ey = np.exp(x) term1 = (-(1 - q) * (1 - ey) + np.sqrt((1 - q) ** 2 * (1 - ey) ** 2 + 4 * c ** 2 * ey)) / (2 * c) term1 = np.maximum(term1, 1e-16) Linvx = (sigma ** 2) * np.log(term1) sq = np.sqrt((1 - q) ** 2 * (1 - ey) ** 2 + 4 * c ** 2 * ey) nom1 = 4 * c ** 2 * ey - 2 * (1 - q) ** 2 * ey * (1 - ey) term1 = nom1 / (2 * sq) nom2 = term1 + (1 - q) * ey nom2 = nom2 * (sq + (1 - q) * (1 - ey)) dLinvx = sigma ** 2 * nom2 / (4 * c ** 2 * ey) ALinvx = (1 / np.sqrt(2 * np.pi * sigma ** 2)) * ((1 - q) * np.exp(-Linvx * Linvx / (2 * sigma ** 2)) + q * np.exp(-(Linvx - 1) * (Linvx - 1) / (2 * sigma ** 2))) fx = np.real(ALinvx * dLinvx) # Flip fx, i.e. fx <- D(fx), the matrix D = [0 I;I 0] temp = np.copy(fx[half:]) fx[half:] = np.copy(fx[:half]) fx[:half] = temp FF1 = np.fft.fft(fx * dx) try: os.makedirs(os.path.dirname(fp), exist_ok=True) with open(fp, 'wb+') as dump: pickle.dump(FF1, dump) except (FileNotFoundError, pickle.PickleError, pickle.PicklingError): logger.error("Error with saving accountant pickle") return FF1
def get_FF1_add_remove(sigma, q, nx, L): """ For a single Gaussian Mechanism, compute the FFT approximation points for the privacy loss distribution, under the addition/removal adjacency definition. :param sigma: The effective noise applied :param q: The sample probability :param nx: The number of approximation points :param L: The clipping length of the approximation :return: The FFT approximation points """ try: filename = f"add_remove_{sigma}_{q}_{nx}_{L}.p" saved_result_flag, result, fp = grab_pickled_accountant_results(filename) except (AttributeError, EOFError, ImportError, IndexError, pickle.UnpicklingError): logger.error("Error reading accountant Pickle!") if saved_result_flag: return result # Evaluate the PLD distribution, # This is the case of substitution relation (subsection 5.1) if q == 1.0: q = 1 - 1E-5 half = int(nx / 2) dx = 2.0 * L / nx # discretisation interval \Delta x x = np.linspace(-L, L - dx, nx, dtype=np.complex128) # grid for the numerical integration ii = int(np.floor(float(nx * (L + np.log(1 - q)) / (2 * L)))) # Evaluate the PLD distribution, # The case of remove/add relation (Subsection 5.1) Linvx = (sigma ** 2) * np.log((np.exp(x[ii + 1:]) - (1 - q)) / q) + 0.5 ALinvx = (1 / np.sqrt(2 * np.pi * sigma ** 2)) * ((1 - q) * np.exp(-Linvx * Linvx / (2 * sigma ** 2)) + q * np.exp(-(Linvx - 1) * (Linvx - 1) / (2 * sigma ** 2))) dLinvx = (sigma ** 2 * np.exp(x[ii + 1:])) / (np.exp(x[ii + 1:]) - (1 - q)) fx = np.zeros(nx) fx[ii + 1:] = np.real(ALinvx * dLinvx) # Flip fx, i.e. fx <- D(fx), the matrix D = [0 I;I 0] temp = np.copy(fx[half:]) fx[half:] = np.copy(fx[:half]) fx[:half] = temp FF1 = np.fft.fft(fx * dx) try: os.makedirs(os.path.dirname(fp), exist_ok=True) with open(fp, 'wb+') as dump: pickle.dump(FF1, dump) except (FileNotFoundError, pickle.PickleError, pickle.PicklingError): logger.error("Error with saving accountant pickle") return FF1
def generate_log_moments(q, noise_scale, max_lambda): # these moments are a function of q, noise_scale and max_lambda try: filename = f"MA_{q}_{noise_scale}_{max_lambda}.p" saved_result_flag, result, fp = grab_pickled_accountant_results( filename) except (AttributeError, EOFError, ImportError, IndexError, pickle.UnpicklingError): logger.error("Error reading accountant Pickle!") if saved_result_flag: return result # generate pdfs which are to be integrated numerically pdf1 = lambda x: pdf_gauss(x, noise_scale, mp.mpf(0)) pdf2 = lambda x: (1 - q) * pdf_gauss(x, noise_scale, mp.mpf(0)) + \ q * pdf_gauss(x, noise_scale, mp.mpf(1)) # placeholder for alpha_M(lambda) for each iteration alpha_M_lambda = np.zeros(max_lambda) for lambda_val in range(1, max_lambda + 1): # it isn't defined which dataset is D and which is D' - thus consider both and take the maximum I1_func, I2_func = get_I1_I2_lambda(lambda_val, pdf1, pdf2) I1_val = integral_inf_mp(I1_func) I2_val = integral_inf_mp(I2_func) if I1_val > I2_val: alpha_M_lambda[lambda_val - 1] = to_np_float_64(mp.log(I1_val)) else: alpha_M_lambda[lambda_val - 1] = to_np_float_64(mp.log(I2_val)) try: os.makedirs(os.path.dirname(fp), exist_ok=True) with open(fp, 'wb+') as dump: pickle.dump(alpha_M_lambda, dump) except (FileNotFoundError, pickle.PickleError, pickle.PicklingError): logger.error("Error with saving accountant pickle") return alpha_M_lambda
def get_eps_substitution(effective_z_t, q_t, target_delta=1e-6, nx=1E6, L=20.0, F_prod=None): """ Computes the approximation of the exact privacy as per https://arxiv.org/abs/1906.03049 for a fixed epsilon, for a list of given mechanisms applied. Considers neighbouring sets as those with the substitution property. :param effective_z_t: 1D numpy array of the effective noises applied in the mechanisms. :param q_t: 1D numpy array of the selection probabilities of the data used in the mechanisms. :param target_delta: The target delta to aim for. :param nx: The number of discretisation points to use. :param L: The range truncation parameter :param F_prod: Specify a previous F_prod for computing online accountancy. If none, assume not online. :return: (epsilon, delta) privacy bound. """ nx = int(nx) half = int(nx / 2) tol_newton = 1e-10 # set this to, e.g., 0.01*target_delta dx = 2.0 * L / nx # discretisation interval \Delta x x = np.linspace(-L, L - dx, nx, dtype=np.complex128) # grid for the numerical integration # Initial value \epsilon_0 eps_0 = 0 fx_table = [] if F_prod is None: F_prod = np.ones(x.size) ncomp = effective_z_t.size if (q_t.size != ncomp): logger.error('The arrays for q and sigma are of different size!') return float('inf') for ij in range(ncomp): sigma = effective_z_t[ij] q = q_t[ij] FF1 = get_FF1_substitution(sigma, q, nx, L) F_prod = F_prod * FF1 exp_e = 1 - np.exp(eps_0 - x) # first jj for which 1-exp(eps_0-x)>0, # i.e. start of the integral domain jj = int(np.floor(float(nx * (L + np.real(eps_0)) / (2 * L)))) # Compute the inverse DFT cfx = np.fft.ifft((F_prod / dx)) # Flip again, i.e. cfx <- D(cfx), D = [0 I;I 0] temp = np.copy(cfx[half:]) cfx[half:] = cfx[:half] cfx[:half] = temp # Evaluate \delta(eps_0) and \delta'(eps_0) exp_e = 1 - np.exp(eps_0 - x) dexp_e = -np.exp(eps_0 - x) integrand = exp_e * cfx integrand2 = dexp_e * cfx sum_int = np.sum(integrand[jj + 1:]) sum_int2 = np.sum(integrand2[jj + 1:]) delta_temp = sum_int * dx derivative = sum_int2 * dx # Here tol is the stopping criterion for Newton's iteration # e.g., 0.1*delta value or 0.01*delta value (relative error small enough) while np.abs(delta_temp - target_delta) > tol_newton: # print('Residual of the Newton iteration: ' + str(np.abs(delta_temp - target_delta))) # Update epsilon eps_0 = eps_0 - (delta_temp - target_delta) / derivative if (eps_0 < -L or eps_0 > L): break # Integrands and integral domain exp_e = 1 - np.exp(eps_0 - x) dexp_e = -np.exp(eps_0 - x) # first kk for which 1-exp(eps_0-x)>0, # i.e. start of the integral domain kk = int(np.floor(float(nx * (L + np.real(eps_0)) / (2 * L)))) integrand = exp_e * cfx sum_int = np.sum(integrand[kk + 1:]) delta_temp = sum_int * dx # Evaluate \delta(eps_0) and \delta'(eps_0) integrand = exp_e * cfx integrand2 = dexp_e * cfx sum_int = np.sum(integrand[kk + 1:]) sum_int2 = np.sum(integrand2[kk + 1:]) delta_temp = sum_int * dx derivative = sum_int2 * dx if (np.real(eps_0) < -L or np.real(eps_0) > L): logger.error('Epsilon out of [-L,L] window, please check the parameters.') return (float('inf'), float('inf')), F_prod else: # print('Bounded DP-epsilon after ' + str(int(ncomp)) + ' compositions defined by sigma and q arrays: ' + str( # np.real(eps_0)) + ' (delta=' + str(target_delta) + ')') return (np.real(eps_0), target_delta), F_prod
def get_delta_substitution(effective_z_t, q_t, target_eps=1.0, nx=1E6, L=20.0, F_prod=None): """ Computes the approximation of the exact privacy as per https://arxiv.org/abs/1906.03049 for a fixed epsilon, for a list of given mechanisms applied. Considers neighbouring sets as those with the substitution property. :param effective_z_t: 1D numpy array of the effective noises applied in the mechanisms. :param q_t: 1D numpy array of the selection probabilities of the data used in the mechanisms. :param target_eps: The target epsilon to aim for. :param nx: The number of discretisation points to use. :param L: The range truncation parameter :param F_prod: Specify a previous F_prod for computing online accountancy. If none, assume not online. :return: (epsilon, delta) privacy bound. """ nx = int(nx) half = int(nx / 2) tol_newton = 1e-10 # set this to, e.g., 0.01*target_delta dx = 2.0 * L / nx # discretisation interval \Delta x x = np.linspace(-L, L - dx, nx, dtype=np.complex128) # grid for the numerical integration fx_table = [] if F_prod is None: F_prod = np.ones(x.size) ncomp = effective_z_t.size if (q_t.size != ncomp): logger.error('The arrays for q and sigma are of different size!') return float('inf') for ij in range(ncomp): # Change from original: cache results for speedup on similar requests sigma = effective_z_t[ij] q = q_t[ij] FF1 = get_FF1_substitution(sigma, q, nx, L) F_prod = F_prod * FF1 # first jj for which 1-exp(target_eps-x)>0, # i.e. start of the integral domain jj = int(np.floor(float(nx * (L + np.real(target_eps)) / (2 * L)))) # Compute the inverse DFT cfx = np.fft.ifft((F_prod / dx)) # Flip again, i.e. cfx <- D(cfx), D = [0 I;I 0] temp = np.copy(cfx[half:]) cfx[half:] = cfx[:half] cfx[:half] = temp # Evaluate \delta(target_eps) and \delta'(target_eps) exp_e = 1 - np.exp(target_eps - x) integrand = exp_e * cfx sum_int = np.sum(integrand[jj + 1:]) delta = sum_int * dx # print('Bounded DP-delta after ' + str(int(ncomp)) + ' compositions defined by sigma and q arrays:' + str(np.real(delta)) + ' (epsilon=' + str(target_eps) + ')') # Change from original: return signature consistent across methods to give epsilon and delta. return (target_eps, np.real(delta)), F_prod