def fast_k_subsample_upperbound(func, mm, prob, k): """ :param func: :param mm: :param prob: sample probability :param k: approximate term :return: k-term approximate upper bound in therorem 11 in ICML-19 """ def cgf(x): return (x - 1) * func(x) if np.isinf(func(mm)): return np.inf if mm == 1: return 0 #logBin = utils.get_binom_coeffs(mm) cur_k = np.minimum(k, mm - 1) if (2 * cur_k) >= mm: exact_term_1 = (mm - 1) * np.log(1 - prob) + np.log(mm * prob - prob + 1) exact_term_2 = [ np.log(scipy.special.comb(mm, l)) + (mm - l) * np.log(1 - prob) + l * np.log(prob) + cgf(l) for l in range(2, mm + 1) ] exact_term_2.append(exact_term_1) bound = utils.stable_logsumexp(exact_term_2) return bound s, mag1 = utils.stable_log_diff_exp(0, -func(mm - cur_k)) new_log_term_1 = np.log(1 - prob) * mm + mag1 new_log_term_2 = -func(mm - cur_k) + mm * utils.stable_logsumexp_two( np.log(1 - prob), np.log(prob) + func(mm - cur_k)) new_log_term_3 = [ np.log(scipy.special.comb(mm, l)) + (mm - l) * np.log(1 - prob) + l * np.log(prob) + utils.stable_log_diff_exp( (l - 1) * func(mm - cur_k), cgf(l))[1] for l in range(2, cur_k + 1) ] if len(new_log_term_3) > 0: new_log_term_3 = utils.stable_logsumexp(new_log_term_3) else: return utils.stable_logsumexp_two(new_log_term_1, new_log_term_2) new_log_term_4 = [ np.log(scipy.special.comb(mm, mm - l)) + (mm - l) * np.log(1 - prob) + l * np.log(prob) + utils.stable_log_diff_exp(cgf(l), (l - 1) * func(mm - cur_k))[1] for l in range(mm - cur_k + 1, mm + 1) ] new_log_term_4.append(new_log_term_1) new_log_term_4.append(new_log_term_2) new_log_term_4 = utils.stable_logsumexp(new_log_term_4) s, new_log_term_5 = utils.stable_log_diff_exp(new_log_term_4, new_log_term_3) new_bound = new_log_term_5 return new_bound
def fast_subsampled_cgf_upperbound(func, mm, prob, deltas_local): # evaulate the fast CGF bound for the subsampled mechanism # func evaluates the RDP of the base mechanism # mm is alpha. NOT lambda. return np.inf if np.isinf(func(mm)): return np.inf if mm == 1: return 0 secondterm = 2 * np.log(prob) + + np.log(mm) + np.log(mm - 1) - np.log(2) \ + np.mininum(np.log(4) + func(2.0) + np.log(1 - np.exp(-func(2.0))), func(2.0) + np.mininum(np.log(2), 2 * (eps_inf + np.log(1 - np.exp(-eps_inf))))) # secondterm = np.minimum(np.minimum((2) * np.log(np.exp(func(np.inf)) - 1) # + np.minimum(func(2), np.log(4)), # np.log(2) + func(2)), # np.log(4) + 0.5 * deltas_local[int(2 * np.floor(2 / 2.0)) - 1] # + 0.5 * deltas_local[int(2 * np.ceil(2 / 2.0)) - 1] # ) + 2 * np.log(prob) + np.log(mm) + np.log(mm - 1) - np.log(2) if mm == 2: return utils.stable_logsumexp([0, secondterm]) # approximate the remaining terms using a geometric series or binomial series log_exp_eps_minus_one = func(np.inf) + np.log(1 - np.exp(-func(np.inf))) if mm == 3: return utils.stable_logsumexp([ 0, secondterm, (3 * (np.log(prob) + np.log(mm)) + 2 * func(mm) + np.minumum(np.log(2), 3 * log_exp_eps_minus_one)) ]) logratio1 = np.log(prob) + np.log(mm) + func(mm) logratio2 = logratio1 + log_exp_eps_minus_one s, mag = utils.stable_log_diff_exp(1, logratio1) s, mag2 = utils.stable_log_diff_exp(1, (mm - 3) * logratio1) remaining_terms1 = (np.log(2) + 3 * (np.log(prob) + np.log(mm)) + 2 * func(mm) + mag2 - mag) s, mag = utils.stable_log_diff_exp(1, logratio2) s, mag2 = utils.stable_log_diff_exp(1, (mm - 3) * logratio2) remaining_terms2 = (3 * (np.log(prob) + np.log(mm) + log_exp_eps_minus_one) + 2 * func(mm) + mag2 - mag) return utils.stable_logsumexp( [0, secondterm, np.minimum(remaining_terms1, remaining_terms2)])
def rdp_int(x): if x == np.inf: return eps s, mag = utils.stable_log_diff_exp(eps,0) s, mag2 = utils.stable_log_diff_exp(eps2,0) s, mag3 = utils.stable_log_diff_exp(x*utils.stable_logsumexp_two(np.log(1-prob),np.log(prob)+eps), np.log(x) + np.log(prob) + mag) s, mag4 = utils.stable_log_diff_exp(mag3, np.log(1.0*x/2)+np.log(x-1)+2*np.log(prob) + np.log( np.exp(2*mag) - np.exp(np.min([mag,2*mag,mag2])))) return 1/(x-1)*mag4
def grad2_general(logx, u): s, mag = utils.stable_log_diff_exp( alpha * (u - logx), alpha * (np.log(1 - np.exp(u)) - np.log(1 - np.exp(logx)))) if alpha > 1: s, mag2 = utils.stable_log_diff_exp( (alpha - 1) * (u - logx), (alpha - 1) * (np.log(1 - np.exp(u)) - np.log(1 - np.exp(logx)))) return (np.log(1 - 1.0 / alpha)) + mag - mag2 else: # if alpha < 1 s, mag2 = utils.stable_log_diff_exp( (alpha - 1) * (np.log(1 - np.exp(u)) - np.log(1 - np.exp(logx))), (alpha - 1) * (u - logx)) return np.log(1.0 / alpha - 1) + mag - mag2
def grad2_KL(logx, u): mag1 = np.log(u - logx + np.log(1 - np.exp(logx)) - np.log(1 - np.exp(u))) s, mag2 = utils.stable_log_diff_exp( u - logx, np.log(1 - np.exp(u)) - np.log(1 - np.exp(logx))) return mag2 - mag1
def get_logdelta_ana_gaussian(sigma, eps): """ This function calculates the delta parameter for analytical gaussian mechanism given eps""" assert (eps >= 0) s, mag = utils.stable_log_diff_exp( norm.logcdf(0.5 / sigma - eps * sigma), eps + norm.logcdf(-0.5 / sigma - eps * sigma)) return mag
def RDP_pureDP(params, alpha): """ This function generically converts pure DP to Renyi DP. It implements Lemma 1.4 of Bun et al.'s CDP paper. With an additional cap at eps. :param params: pure DP parameter :param alpha: The order of the Renyi Divergence :return:Evaluation of the RDP's epsilon """ eps = params['eps'] # assert(eps>=0) # if alpha < 1: # # Pure DP needs to have identical support, thus - log(q(p>0)) = 0. # return 0 # else: # return np.minimum(eps,alpha*eps*eps/2) assert (alpha >= 0) if alpha == 1: # Calculate this by l'Hospital rule return eps * (math.cosh(eps) - 1) / math.sinh(eps) elif np.isinf(alpha): return eps elif alpha > 1: # in the proof of Lemma 4 of Bun et al. (2016) s, mag = utils.stable_log_diff_exp( utils.stable_log_sinh(alpha * eps), utils.stable_log_sinh((alpha - 1) * eps)) return (mag - utils.stable_log_sinh(eps)) / (alpha - 1) else: return min(alpha * eps * eps / 2, eps * (math.cosh(eps) - 1) / math.sinh(eps))
def grad1_general(logx, u): #return - grad1_general(np.log(1-np.exp(u)), np.log(1-np.exp(logx))) s, mag = utils.stable_log_diff_exp( alpha * (np.log(1 - np.exp(logx)) - np.log(1 - np.exp(u))), alpha * (logx - u)) if alpha > 1: s, mag1 = utils.stable_log_diff_exp( (alpha - 1) * (np.log(1 - np.exp(logx)) - np.log(1 - np.exp(u))), (alpha - 1) * (logx - u)) return np.log(alpha) - np.log(alpha - 1) + mag1 - mag else: s, mag1 = utils.stable_log_diff_exp( (alpha - 1) * (logx - u), (alpha - 1) * (np.log(1 - np.exp(logx)) - np.log(1 - np.exp(u)))) return np.log(alpha) - np.log(1 - alpha) + mag1 - mag
def general_upperbound(func, mm, prob): """ :param func: :param mm: alpha in RDP :param prob: sample probability :return: the upperbound in theorem 1 in 2019 ICML,could be applied for general case(including poisson distribution) k_approx = 100 k approximation is applied here """ def cgf(x): return (x - 1) * func(x) if np.isinf(func(mm)): return np.inf if mm == 1 or mm == 0: return 0 cur_k = np.minimum( 50, mm - 1 ) # choose small k-approx for general upperbound (here is 50) in case of scipy-accuracy log_term_1 = mm * np.log(1 - prob) #logBin = utils.get_binom_coeffs(mm) log_term_2 = np.log(3) - func(mm) + mm * utils.stable_logsumexp_two( np.log(1 - prob), np.log(prob) + func(mm)) neg_term_3 = [ np.log(scipy.special.comb(mm, l)) + np.log(3) + (mm - l) * np.log(1 - prob) + l * np.log(prob) + utils.stable_log_diff_exp((l - 1) * func(mm), cgf(l))[1] for l in range(3, cur_k + 1) ] neg_term_4 = np.log(mm * (mm - 1) / 2) + 2 * np.log(prob) + ( mm - 2) * np.log(1 - prob) + utils.stable_log_diff_exp( np.log(3) + func(mm), func(2))[1] neg_term_5 = np.log(2) + np.log(prob) + np.log(mm) + (mm - 1) * np.log(1 - prob) neg_term_6 = mm * np.log(1 - prob) + np.log(3) - func(mm) pos_term = utils.stable_logsumexp([log_term_1, log_term_2]) neg_term_3.append(neg_term_4) neg_term_3.append(neg_term_5) neg_term_3.append(neg_term_6) neg_term = utils.stable_logsumexp(neg_term_3) bound = utils.stable_log_diff_exp(pos_term, neg_term)[1] return bound
def approxdp(delta): logx = find_logx(delta) log_one_minus_f = fun1(logx) # log_neg_grad_l, log_neg_grad_h = fun2(logx) s, mag = utils.stable_log_diff_exp(log_one_minus_f, np.log(delta)) eps = mag - logx if eps < 0: return 0.0 else: return eps
def rdp(alpha): assert (alpha >= 0) if alpha == 1: # Calculate this by l'Hospital rule return eps * (math.cosh(eps) - 1) / math.sinh(eps) elif np.isinf(alpha): return eps elif alpha > 1: # in the proof of Lemma 4 of Bun et al. (2016) s, mag = utils.stable_log_diff_exp( utils.stable_log_sinh(alpha * eps), utils.stable_log_sinh((alpha - 1) * eps)) return (mag - utils.stable_log_sinh(eps)) / (alpha - 1) else: return min(alpha * eps * eps / 2, eps * (math.cosh(eps) - 1) / math.sinh(eps))
def fun(x): # the input the RDP's \alpha if x <= 1: return np.inf else: if naive: return np.log(1 / delta) / (x - 1) + rdp(x) bbghs = np.maximum( rdp(x) + np.log((x - 1) / x) - (np.log(delta) + np.log(x)) / (x - 1), 0) """ The following is for optimal conversion 1/(alpha -1 )log(e^{(alpha-1)*rdp -1}/(alpha*delta) +1 ) """ sign, term_1 = utils.stable_log_diff_exp( (x - 1) * rdp(x), 0) result = utils.stable_logsumexp_two( term_1 - np.log(x) - np.log(delta), 0) return min(result * 1.0 / (x - 1), bbghs)
def approxdp(delta): s, mag = utils.stable_log_diff_exp(eps, np.log(delta)) return mag
def grad1_KL(logx, u): mag1 = np.log(u - logx + np.log(1 - np.exp(logx)) - np.log(1 - np.exp(u))) s, mag2 = utils.stable_log_diff_exp( np.log(1 - np.exp(logx)) - np.log(1 - np.exp(u)), logx - u) return mag1 - mag2