# Copyright 2022 Ant Group Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import numpy as np
from .log_utils import log_alpha
"""Implements privacy accounting for Rényi Differential Privacy.
"""
[docs]def rdp_core(q: float, noise_multiplier: float, alpha: float):
"""Comnpute RDP of the Sampled Gaussian mechanism at order alpha.
Args:
q: The sampling rate.
noise_multiplier: The noise_multiplier used for calculating the std of the additive Gaussian noise.
alpha: The order at which RDP is cald
Returns:
RDP at alpha, can be np.inf.
"""
if q == 0:
return 0
if q == 1:
return alpha / (2 * noise_multiplier**2)
if np.isinf(alpha):
return np.inf
return log_alpha(q, noise_multiplier, alpha) / (alpha - 1)
[docs]def get_rdp(q: float, noise_multiplier: float, steps: int, orders):
"""Calculate RDP of the Sampled Gaussian Mechanism.
Args:
q: The sampling rate.
noise_multiplier: The ratio of the standard deviation of the Gaussian noise
to the l2-sensitivity of the function to which it is added
steps: The number of steps.
orders: An array (or a scalar) of RDP orders.
Returns:
The RDPs at all orders. Can be `np.inf`.
"""
if np.isscalar(orders):
rdp = rdp_core(q, noise_multiplier, orders)
else:
rdp = np.array([rdp_core(q, noise_multiplier, order) for order in orders])
return rdp * steps
[docs]def cal_delta(orders, rdp, eps: float):
"""Calculate delta given a list of RDP values and target epsilon.
Args:
orders: An array (or a scalar) of orders.
rdp: A list (or a scalar) of RDP guarantees.
eps: The target epsilon.
Returns:
Pair of (delta, optimal_order).
Raises:
ValueError: If input is malformed.
"""
orders_vec = np.atleast_1d(orders)
rdp_vec = np.atleast_1d(rdp)
if eps < 0:
raise ValueError("Value of privacy loss bound epsilon must be >=0.")
if len(orders_vec) != len(rdp_vec):
raise ValueError("Input lists must have the same length.")
logdeltas = []
for (a, r) in zip(orders_vec, rdp_vec):
if a < 1:
raise ValueError("Renyi divergence order must be >=1.")
if r < 0:
raise ValueError("Renyi divergence must be >=0.")
logdelta = 0.5 * math.log1p(-math.exp(-r))
if a > 1.01:
rdp_bound = (a - 1) * (r - eps + math.log1p(-1 / a)) - math.log(a)
logdelta = min(logdelta, rdp_bound)
logdeltas.append(logdelta)
idx_opt = np.argmin(logdeltas)
return min(math.exp(logdeltas[idx_opt]), 1.0), orders_vec[idx_opt]
[docs]def cal_eps(orders, rdp, delta: float):
"""Calculate epsilon given a list of RDP values and target delta.
Args:
orders: An array (or a scalar) of orders.
rdp: A list (or a scalar) of RDP guarantees.
delta: The target delta.
Returns:
Pair of (eps, optimal_order).
Raises:
ValueError: If input is malformed.
"""
orders_vec = np.atleast_1d(orders)
rdp_vec = np.atleast_1d(rdp)
if delta <= 0:
raise ValueError("Privacy failure probability bound delta must be >0.")
if len(orders_vec) != len(rdp_vec):
raise ValueError("Input lists must have the same length.")
eps_vec = []
for (a, r) in zip(orders_vec, rdp_vec):
if a < 1:
raise ValueError("Renyi divergence order must be >=1.")
if r < 0:
raise ValueError("Renyi divergence must be >=0.")
if delta**2 + math.expm1(-r) >= 0:
eps = 0
elif a > 1.01:
eps = r + math.log1p(-1 / a) - math.log(delta * a) / (a - 1)
else:
eps = np.inf
eps_vec.append(eps)
idx_opt = np.argmin(eps_vec)
return max(0, eps_vec[idx_opt]), orders_vec[idx_opt]
[docs]def get_privacy_spent_rdp(
orders, rdp, target_eps: float = None, target_delta: float = None
):
"""Calculates delta (or eps) for given eps (or delta) from RDP values.
Args:
orders: An array (or a scalar) of RDP orders.
rdp: An array of RDP values. Must be of the same length as the orders
list.
target_eps: If not `None`, the epsilon for which we cal the
corresponding delta.
target_delta: If not `None`, the delta for which we cal the
corresponding epsilon. Exactly one of `target_eps` and
`target_delta` must be `None`.
Returns:
A tuple of epsilon, delta, and the optimal order.
Raises:
ValueError: If target_eps and target_delta are messed up.
"""
if target_eps is None and target_delta is None:
raise ValueError("Exactly one out of eps and delta must be None. (Both are).")
if target_eps is not None and target_delta is not None:
raise ValueError("Exactly one out of eps and delta must be None. (None is).")
if target_eps is not None:
delta, opt_order = cal_delta(orders, rdp, target_eps)
return target_eps, delta, opt_order
else:
eps, opt_order = cal_eps(orders, rdp, target_delta)
return eps, target_delta, opt_order