Add support for subsampled multi-Gaussian queries (composition of several Gaussian queries that may have different noise multipliers). This is used, for example, by QuantileAdaptiveClipSumQuery.
PiperOrigin-RevId: 402693872
This commit is contained in:
parent
98df2fed61
commit
977647a3bf
2 changed files with 96 additions and 6 deletions
|
@ -483,6 +483,42 @@ def _compute_rdp_sample_wor_gaussian_int(q, sigma, alpha):
|
||||||
return log_a
|
return log_a
|
||||||
|
|
||||||
|
|
||||||
|
def _effective_gaussian_noise_multiplier(event: dp_event.DpEvent):
|
||||||
|
"""Determines the effective noise multiplier of nested structure of Gaussians.
|
||||||
|
|
||||||
|
A series of Gaussian queries on the same data can be reexpressed as a single
|
||||||
|
query with pre- and post- processing. For details, see section 3 of
|
||||||
|
https://arxiv.org/pdf/1812.06210.pdf.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event: A `dp_event.DpEvent`. In order for conversion to be successful it
|
||||||
|
must consist of a single `dp_event.GaussianDpEvent`, or a nested structure
|
||||||
|
of `dp_event.ComposedDpEvent` and/or `dp_event.SelfComposedDpEvent`
|
||||||
|
bottoming out in `dp_event.GaussianDpEvent`s.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The noise multiplier of the equivalent `dp_event.GaussianDpEvent`, or None
|
||||||
|
if the input event was not a `dp_event.GaussianDpEvent` or a nested
|
||||||
|
structure of `dp_event.ComposedDpEvent` and/or
|
||||||
|
`dp_event.SelfComposedDpEvent` bottoming out in `dp_event.GaussianDpEvent`s.
|
||||||
|
"""
|
||||||
|
if isinstance(event, dp_event.GaussianDpEvent):
|
||||||
|
return event.noise_multiplier
|
||||||
|
elif isinstance(event, dp_event.ComposedDpEvent):
|
||||||
|
sum_sigma_inv_sq = 0
|
||||||
|
for e in event.events:
|
||||||
|
sigma = _effective_gaussian_noise_multiplier(e)
|
||||||
|
if sigma is None:
|
||||||
|
return None
|
||||||
|
sum_sigma_inv_sq += sigma**-2
|
||||||
|
return sum_sigma_inv_sq**-0.5
|
||||||
|
elif isinstance(event, dp_event.SelfComposedDpEvent):
|
||||||
|
sigma = _effective_gaussian_noise_multiplier(event.event)
|
||||||
|
return None if sigma is None else (event.count * sigma**-2)**-0.5
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class RdpAccountant(privacy_accountant.PrivacyAccountant):
|
class RdpAccountant(privacy_accountant.PrivacyAccountant):
|
||||||
"""Privacy accountant that uses Renyi differential privacy."""
|
"""Privacy accountant that uses Renyi differential privacy."""
|
||||||
|
|
||||||
|
@ -542,23 +578,29 @@ class RdpAccountant(privacy_accountant.PrivacyAccountant):
|
||||||
q=1.0, noise_multiplier=event.noise_multiplier, orders=self._orders)
|
q=1.0, noise_multiplier=event.noise_multiplier, orders=self._orders)
|
||||||
return True
|
return True
|
||||||
elif isinstance(event, dp_event.PoissonSampledDpEvent):
|
elif isinstance(event, dp_event.PoissonSampledDpEvent):
|
||||||
if (self._neighboring_relation is not NeighborRel.ADD_OR_REMOVE_ONE or
|
if self._neighboring_relation is not NeighborRel.ADD_OR_REMOVE_ONE:
|
||||||
not isinstance(event.event, dp_event.GaussianDpEvent)):
|
return False
|
||||||
|
gaussian_noise_multiplier = _effective_gaussian_noise_multiplier(
|
||||||
|
event.event)
|
||||||
|
if gaussian_noise_multiplier is None:
|
||||||
return False
|
return False
|
||||||
if do_compose:
|
if do_compose:
|
||||||
self._rdp += count * _compute_rdp_poisson_subsampled_gaussian(
|
self._rdp += count * _compute_rdp_poisson_subsampled_gaussian(
|
||||||
q=event.sampling_probability,
|
q=event.sampling_probability,
|
||||||
noise_multiplier=event.event.noise_multiplier,
|
noise_multiplier=gaussian_noise_multiplier,
|
||||||
orders=self._orders)
|
orders=self._orders)
|
||||||
return True
|
return True
|
||||||
elif isinstance(event, dp_event.SampledWithoutReplacementDpEvent):
|
elif isinstance(event, dp_event.SampledWithoutReplacementDpEvent):
|
||||||
if (self._neighboring_relation is not NeighborRel.REPLACE_ONE or
|
if self._neighboring_relation is not NeighborRel.REPLACE_ONE:
|
||||||
not isinstance(event.event, dp_event.GaussianDpEvent)):
|
return False
|
||||||
|
gaussian_noise_multiplier = _effective_gaussian_noise_multiplier(
|
||||||
|
event.event)
|
||||||
|
if gaussian_noise_multiplier is None:
|
||||||
return False
|
return False
|
||||||
if do_compose:
|
if do_compose:
|
||||||
self._rdp += count * _compute_rdp_sample_wor_gaussian(
|
self._rdp += count * _compute_rdp_sample_wor_gaussian(
|
||||||
q=event.sample_size / event.source_dataset_size,
|
q=event.sample_size / event.source_dataset_size,
|
||||||
noise_multiplier=event.event.noise_multiplier,
|
noise_multiplier=gaussian_noise_multiplier,
|
||||||
orders=self._orders)
|
orders=self._orders)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -94,11 +94,23 @@ class RdpPrivacyAccountantTest(privacy_accountant_test.PrivacyAccountantTest,
|
||||||
self.assertTrue(aor_accountant.supports(event))
|
self.assertTrue(aor_accountant.supports(event))
|
||||||
self.assertFalse(ro_accountant.supports(event))
|
self.assertFalse(ro_accountant.supports(event))
|
||||||
|
|
||||||
|
composed_gaussian = dp_event.ComposedDpEvent(
|
||||||
|
[dp_event.GaussianDpEvent(1.0),
|
||||||
|
dp_event.GaussianDpEvent(2.0)])
|
||||||
|
event = dp_event.PoissonSampledDpEvent(0.1, composed_gaussian)
|
||||||
|
self.assertTrue(aor_accountant.supports(event))
|
||||||
|
self.assertFalse(ro_accountant.supports(event))
|
||||||
|
|
||||||
event = dp_event.SampledWithoutReplacementDpEvent(
|
event = dp_event.SampledWithoutReplacementDpEvent(
|
||||||
1000, 10, dp_event.GaussianDpEvent(1.0))
|
1000, 10, dp_event.GaussianDpEvent(1.0))
|
||||||
self.assertFalse(aor_accountant.supports(event))
|
self.assertFalse(aor_accountant.supports(event))
|
||||||
self.assertTrue(ro_accountant.supports(event))
|
self.assertTrue(ro_accountant.supports(event))
|
||||||
|
|
||||||
|
event = dp_event.SampledWithoutReplacementDpEvent(1000, 10,
|
||||||
|
composed_gaussian)
|
||||||
|
self.assertFalse(aor_accountant.supports(event))
|
||||||
|
self.assertTrue(ro_accountant.supports(event))
|
||||||
|
|
||||||
event = dp_event.SampledWithReplacementDpEvent(
|
event = dp_event.SampledWithReplacementDpEvent(
|
||||||
1000, 10, dp_event.GaussianDpEvent(1.0))
|
1000, 10, dp_event.GaussianDpEvent(1.0))
|
||||||
self.assertFalse(aor_accountant.supports(event))
|
self.assertFalse(aor_accountant.supports(event))
|
||||||
|
@ -166,6 +178,42 @@ class RdpPrivacyAccountantTest(privacy_accountant_test.PrivacyAccountantTest,
|
||||||
accountant.compose(event)
|
accountant.compose(event)
|
||||||
self.assertAlmostEqual(accountant._rdp[0], alpha / (2 * sigma**2))
|
self.assertAlmostEqual(accountant._rdp[0], alpha / (2 * sigma**2))
|
||||||
|
|
||||||
|
def test_compute_rdp_multi_gaussian(self):
|
||||||
|
alpha = 3.14159
|
||||||
|
sigma1, sigma2 = 2.71828, 6.28319
|
||||||
|
|
||||||
|
rdp1 = alpha / (2 * sigma1**2)
|
||||||
|
rdp2 = alpha / (2 * sigma2**2)
|
||||||
|
rdp = rdp1 + rdp2
|
||||||
|
|
||||||
|
accountant = rdp_privacy_accountant.RdpAccountant(orders=[alpha])
|
||||||
|
accountant.compose(
|
||||||
|
dp_event.PoissonSampledDpEvent(
|
||||||
|
1.0,
|
||||||
|
dp_event.ComposedDpEvent([
|
||||||
|
dp_event.GaussianDpEvent(sigma1),
|
||||||
|
dp_event.GaussianDpEvent(sigma2)
|
||||||
|
])))
|
||||||
|
self.assertAlmostEqual(accountant._rdp[0], rdp)
|
||||||
|
|
||||||
|
def test_effective_gaussian_noise_multiplier(self):
|
||||||
|
np.random.seed(0xBAD5EED)
|
||||||
|
sigmas = np.random.uniform(size=(4,))
|
||||||
|
|
||||||
|
event = dp_event.ComposedDpEvent([
|
||||||
|
dp_event.GaussianDpEvent(sigmas[0]),
|
||||||
|
dp_event.SelfComposedDpEvent(dp_event.GaussianDpEvent(sigmas[1]), 3),
|
||||||
|
dp_event.ComposedDpEvent([
|
||||||
|
dp_event.GaussianDpEvent(sigmas[2]),
|
||||||
|
dp_event.GaussianDpEvent(sigmas[3])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
sigma = rdp_privacy_accountant._effective_gaussian_noise_multiplier(event)
|
||||||
|
multi_sigmas = list(sigmas) + [sigmas[1]] * 2
|
||||||
|
expected = sum(s**-2 for s in multi_sigmas)**-0.5
|
||||||
|
self.assertAlmostEqual(sigma, expected)
|
||||||
|
|
||||||
def test_compute_rdp_poisson_sampled_gaussian(self):
|
def test_compute_rdp_poisson_sampled_gaussian(self):
|
||||||
orders = [1.5, 2.5, 5, 50, 100, np.inf]
|
orders = [1.5, 2.5, 5, 50, 100, np.inf]
|
||||||
noise_multiplier = 2.5
|
noise_multiplier = 2.5
|
||||||
|
|
Loading…
Reference in a new issue