コード例 #1
0
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
コード例 #2
0
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
コード例 #3
0
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
コード例 #4
0
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
コード例 #5
0
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