blob: 9c10ce3037fc99e2301e70326d0fd51030f5bace [file] [log] [blame]
# Copyright 2020 Google LLC.
#
# 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
#
# http://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.
"""Tests for privacy_loss_mechanism."""
import math
import unittest
from absl.testing import parameterized
from scipy import stats
from dp_accounting import common
from dp_accounting import privacy_loss_mechanism
from dp_accounting import test_util
class LaplacePrivacyLossTest(parameterized.TestCase):
@parameterized.parameters((1, 1, -0.1, 1), (1, 1, 2, -1), (1, 1, 0.3, 0.4),
(4, 4, -0.4, 1), (5, 5, 7, -1), (7, 7, 2.1, 0.4))
def test_laplace_privacy_loss(self, parameter, sensitivity, x,
expected_privacy_loss):
pl = privacy_loss_mechanism.LaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
self.assertAlmostEqual(expected_privacy_loss, pl.privacy_loss(x))
@parameterized.parameters(
(1, 1, 1, 0), (1, 1, -1, math.inf), (1, 1, 0.4, 0.3), (4, 4, 1, 0),
(5, 5, -1, math.inf), (7, 7, 0.4, 2.1), (1, 1, 2, -math.inf),
(3, 1, 3.1, -math.inf), (4, 4, 1.1, -math.inf))
def test_laplace_inverse_privacy_loss(self, parameter, sensitivity,
privacy_loss, expected_x):
pl = privacy_loss_mechanism.LaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
self.assertAlmostEqual(expected_x, pl.inverse_privacy_loss(privacy_loss))
@parameterized.parameters((1, 1, 0, 1, {
1: 0.5,
-1: 0.18393972
}), (3, 3, 0, 3, {
1: 0.5,
-1: 0.18393972
}), (1, 2, 0, 2, {
2: 0.5,
-2: 0.06766764
}), (4, 8, 0, 8, {
2: 0.5,
-2: 0.06766764
}))
def test_laplace_privacy_loss_tail(self, parameter, sensitivity,
expected_lower_x_truncation,
expected_upper_x_truncation,
expected_tail_probability_mass_function):
pl = privacy_loss_mechanism.LaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
tail_pld = pl.privacy_loss_tail()
self.assertAlmostEqual(expected_lower_x_truncation,
tail_pld.lower_x_truncation)
self.assertAlmostEqual(expected_upper_x_truncation,
tail_pld.upper_x_truncation)
test_util.dictionary_almost_equal(self,
expected_tail_probability_mass_function,
tail_pld.tail_probability_mass_function)
@parameterized.parameters((1, 1, 0, 1), (1, 1, 0.1, 1), (2, 1, 0.01, 2),
(1, 3, 0.01, 0.33333333))
def test_laplace_from_privacy_parameters(self, sensitivity, epsilon, delta,
expected_parameter):
pl = privacy_loss_mechanism.LaplacePrivacyLoss.from_privacy_guarantee(
common.DifferentialPrivacyParameters(epsilon, delta),
sensitivity)
self.assertAlmostEqual(expected_parameter, pl.parameter)
@parameterized.parameters((1, 1, 1, 0), (3, 3, 1, 0), (2, 4, 2, 0),
(2, 4, 0.5, 0.52763345), (1, 1, 0, 0.39346934),
(2, 2, 0, 0.39346934), (1, 1, -2, 0.86466472))
def test_laplace_get_delta_for_epsilon(
self, parameter, sensitivity, epsilon, expected_delta):
pl = privacy_loss_mechanism.LaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
self.assertAlmostEqual(expected_delta, pl.get_delta_for_epsilon(epsilon))
class GaussianPrivacyLossTest(parameterized.TestCase):
@parameterized.parameters((1, 1, 5, -4.5), (1, 1, -3, 3.5), (1, 2, 3, -4),
(4, 4, 20, -4.5), (5, 5, -15, 3.5), (7, 14, 21, -4))
def test_gaussian_privacy_loss(self, standard_deviation, sensitivity, x,
expected_privacy_loss):
pl = privacy_loss_mechanism.GaussianPrivacyLoss(
standard_deviation,
sensitivity=sensitivity)
self.assertAlmostEqual(expected_privacy_loss, pl.privacy_loss(x))
@parameterized.parameters((1, 1, -4.5, 5), (1, 1, 3.5, -3), (1, 2, -4, 3),
(4, 4, -4.5, 20), (5, 5, 3.5, -15), (7, 14, -4, 21))
def test_gaussian_inverse_privacy_loss(self, standard_deviation, sensitivity,
privacy_loss, expected_x):
pl = privacy_loss_mechanism.GaussianPrivacyLoss(
standard_deviation,
sensitivity=sensitivity)
self.assertAlmostEqual(expected_x, pl.inverse_privacy_loss(privacy_loss))
@parameterized.parameters((1, 1, -1, 1, True, {
math.inf: 0.15865525,
-0.5: 0.15865525
}), (3, 3, -3, 3, True, {
math.inf: 0.15865525,
-0.5: 0.15865525
}), (1, 2, -1, 1, True, {
math.inf: 0.15865525,
0: 0.15865525
}), (4, 8, -4, 4, True, {
math.inf: 0.15865525,
0: 0.15865525
}), (1, 1, -1, 1, False, {
1.5: 0.15865525,
}), (3, 3, -3, 3, False, {
1.5: 0.15865525,
}), (1, 2, -1, 1, False, {
4.0: 0.15865525,
}), (4, 8, -4, 4, False, {
4.0: 0.15865525,
}))
def test_gaussian_privacy_loss_tail(self, standard_deviation, sensitivity,
expected_lower_x_truncation,
expected_upper_x_truncation,
pessimistic_estimate,
expected_tail_probability_mass_function):
pl = privacy_loss_mechanism.GaussianPrivacyLoss(
standard_deviation,
sensitivity=sensitivity,
pessimistic_estimate=pessimistic_estimate,
log_mass_truncation_bound=math.log(2) + stats.norm.logcdf(-1))
tail_pld = pl.privacy_loss_tail()
self.assertAlmostEqual(expected_lower_x_truncation,
tail_pld.lower_x_truncation)
self.assertAlmostEqual(expected_upper_x_truncation,
tail_pld.upper_x_truncation)
test_util.dictionary_almost_equal(self,
expected_tail_probability_mass_function,
tail_pld.tail_probability_mass_function)
@parameterized.parameters((0, 1), (-10, 2), (4, 0), (2, -1))
def test_gaussian_value_errors(self, standard_deviation, sensitivity):
with self.assertRaises(ValueError):
privacy_loss_mechanism.GaussianPrivacyLoss(
standard_deviation, sensitivity=sensitivity)
@parameterized.parameters((1, 1, 0.12693674, 1), (2, 1, 0.12693674, 2),
(3, 1, 0.78760074, 1), (6, 1, 0.78760074, 2),
(1, 2, 0.02092364, 1), (5, 2, 0.02092364, 5),
(1, 16, 1e-5, 0.344), (2, 16, 1e-5, 0.688))
def test_gaussian_from_privacy_parameters(self, sensitivity, epsilon, delta,
expected_standard_deviation):
pl = privacy_loss_mechanism.GaussianPrivacyLoss.from_privacy_guarantee(
common.DifferentialPrivacyParameters(epsilon, delta),
sensitivity)
self.assertAlmostEqual(expected_standard_deviation, pl.standard_deviation,
3)
@parameterized.parameters((1, 1, 1, 0.12693674), (2, 2, 1, 0.12693674),
(1, 3, 1, 0.78760074), (2, 6, 1, 0.78760074),
(1, 1, 2, 0.02092364), (5, 5, 2, 0.02092364))
def test_gaussian_get_delta_for_epsilon(
self, standard_deviation, sensitivity, epsilon, expected_delta):
pl = privacy_loss_mechanism.GaussianPrivacyLoss(
standard_deviation,
sensitivity=sensitivity)
self.assertAlmostEqual(expected_delta, pl.get_delta_for_epsilon(epsilon))
class DiscreteLaplacePrivacyLossDistributionTest(parameterized.TestCase):
@parameterized.parameters((1, 1, 0, 1), (1, 1, 1, -1), (0.3, 2, 0, 0.6),
(0.3, 2, 1, 0), (0.3, 2, 2, -0.6))
def test_discrete_laplace_privacy_loss(self, parameter, sensitivity, x,
expected_privacy_loss):
pl = privacy_loss_mechanism.DiscreteLaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
self.assertAlmostEqual(expected_privacy_loss, pl.privacy_loss(x))
@parameterized.parameters((1, 1, 0.4), (2, 7, -1.1))
def test_discrete_laplace_privacy_loss_value_errors(
self, parameter, sensitivity, x):
pl = privacy_loss_mechanism.DiscreteLaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
with self.assertRaises(ValueError):
pl.privacy_loss(x)
@parameterized.parameters((1, 1, 1.1, -math.inf), (1, 1, 0.9, 0),
(1, 1, -1, math.inf), (0.3, 2, 0.7, -math.inf),
(0.3, 2, 0.2, 0), (0.3, 2, 0, 1),
(0.3, 2, -0.6, math.inf))
def test_discrete_laplace_inverse_privacy_loss(self, parameter, sensitivity,
privacy_loss, expected_x):
pl = privacy_loss_mechanism.DiscreteLaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
self.assertAlmostEqual(expected_x, pl.inverse_privacy_loss(privacy_loss))
@parameterized.parameters((1, 1, 1, 0, {
1: 0.73105858,
-1: 0.26894142
}), (0.3, 2, 1, 1, {
0.6: 0.57444252,
-0.6: 0.31526074
}))
def test_discrete_laplace_privacy_loss_tail(
self, parameter, sensitivity, expected_lower_x_truncation,
expected_upper_x_truncation, expected_tail_probability_mass_function):
pl = privacy_loss_mechanism.DiscreteLaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
tail_pld = pl.privacy_loss_tail()
self.assertAlmostEqual(expected_lower_x_truncation,
tail_pld.lower_x_truncation)
self.assertAlmostEqual(expected_upper_x_truncation,
tail_pld.upper_x_truncation)
test_util.dictionary_almost_equal(self,
expected_tail_probability_mass_function,
tail_pld.tail_probability_mass_function)
@parameterized.parameters((-3, 1), (2, 0.5), (2.0, -1))
def test_discrete_laplace_value_errors(self, parameter, sensitivity):
with self.assertRaises(ValueError):
privacy_loss_mechanism.DiscreteLaplacePrivacyLoss(
parameter,
sensitivity=sensitivity)
@parameterized.parameters((1, 1, 0, 1), (1, 1, 0.1, 1), (2, 1, 0.01, 0.5),
(1, 3, 0.01, 3))
def test_discrete_laplace_from_privacy_parameters(self, sensitivity, epsilon,
delta, expected_parameter):
pl = (privacy_loss_mechanism.DiscreteLaplacePrivacyLoss
.from_privacy_guarantee(
common.DifferentialPrivacyParameters(
epsilon, delta),
sensitivity))
self.assertAlmostEqual(expected_parameter, pl.parameter)
@parameterized.parameters((1, 1, 1, 0), (0.333333, 3, 1, 0), (0.5, 4, 2, 0),
(0.5, 4, 0.5, 0.54202002), (0.5, 4, 1, 0.39346934),
(0.5, 4, -0.5, 0.72222110))
def test_discrete_laplace_get_delta_for_epsilon(
self, parameter, sensitivity, epsilon, expected_delta):
pl = privacy_loss_mechanism.DiscreteLaplacePrivacyLoss(
parameter, sensitivity=sensitivity)
self.assertAlmostEqual(expected_delta, pl.get_delta_for_epsilon(epsilon))
class DiscreteGaussianPrivacyLossTest(parameterized.TestCase):
@parameterized.parameters((1, 1, 5, -4.5), (1, 1, -3, 3.5), (1, 2, 3, -4),
(4, 4, 20, -4.5), (5, 5, -15, 3.5), (7, 14, 21, -4),
(1, 1, -12, math.inf))
def test_discrete_gaussian_privacy_loss(self, sigma, sensitivity,
x, expected_privacy_loss):
pl = privacy_loss_mechanism.DiscreteGaussianPrivacyLoss(
sigma, sensitivity=sensitivity)
self.assertAlmostEqual(expected_privacy_loss, pl.privacy_loss(x))
@parameterized.parameters((1, 1, -4.5, 5), (1, 1, 3.5, -3), (1, 2, -4, 3),
(4, 4, -4.51, 20), (5, 5, 3.49, -15),
(7, 14, -4, 21))
def test_discrete_gaussian_inverse_privacy_loss(self, sigma, sensitivity,
privacy_loss, expected_x):
pl = privacy_loss_mechanism.DiscreteGaussianPrivacyLoss(
sigma, sensitivity=sensitivity)
self.assertAlmostEqual(expected_x, pl.inverse_privacy_loss(privacy_loss))
@parameterized.parameters((1, 1, 2, -1, 2, {
math.inf: 0.05448868
}), (1, 2, 2, 0, 2, {
math.inf: 0.29869003
}))
def test_discrete_gaussian_privacy_loss_tail(
self, sigma, sensitivity, truncation_bound, expected_lower_x_truncation,
expected_upper_x_truncation, expected_tail_probability_mass_function):
pl = privacy_loss_mechanism.DiscreteGaussianPrivacyLoss(
sigma, sensitivity=sensitivity, truncation_bound=truncation_bound)
tail_pld = pl.privacy_loss_tail()
self.assertAlmostEqual(expected_lower_x_truncation,
tail_pld.lower_x_truncation)
self.assertAlmostEqual(expected_upper_x_truncation,
tail_pld.upper_x_truncation)
test_util.dictionary_almost_equal(self,
expected_tail_probability_mass_function,
tail_pld.tail_probability_mass_function)
@parameterized.parameters((1, 1, 1, {
-1.5: 0,
-1: 0.27406862,
0: 0.7259314,
1: 1,
1.5: 1
}), (3, 2, 2, {
-2.1: 0,
-2: 0.17820326,
-1: 0.38872553,
0: 0.61127447,
1: 0.82179674,
2: 1,
2.7: 1
}))
def test_discrete_gaussian_noise_cdf(self, sigma, sensitivity,
truncation_bound, x_to_cdf_value):
pl = privacy_loss_mechanism.DiscreteGaussianPrivacyLoss(
sigma, sensitivity=sensitivity, truncation_bound=truncation_bound)
for x, cdf_value in x_to_cdf_value.items():
self.assertAlmostEqual(cdf_value, pl.noise_cdf(x))
@parameterized.parameters((1, 1, 1, 0.7403629), (3, 2, 2, 1.3589226))
def test_discrete_gaussian_std(self, sigma, sensitivity, truncation_bound,
expected_std):
pl = privacy_loss_mechanism.DiscreteGaussianPrivacyLoss(
sigma, sensitivity=sensitivity, truncation_bound=truncation_bound)
self.assertAlmostEqual(expected_std, pl.standard_deviation())
@parameterized.parameters(
(1, 1, 0.12693674, 1.041), (2, 1, 0.12693674, 1.972),
(3, 1, 0.78760074, 0.993), (6, 1, 0.78760074, 2.014),
(1, 2, 0.02092364, 1.038), (5, 2, 0.02092364, 5.008),
(1, 16, 1e-5, 0.306), (2, 16, 1e-5, 0.703))
def test_discrete_gaussian_from_privacy_parameters(self, sensitivity, epsilon,
delta, expected_sigma):
pl = (
privacy_loss_mechanism.DiscreteGaussianPrivacyLoss
.from_privacy_guarantee(
common.DifferentialPrivacyParameters(epsilon, delta), sensitivity))
self.assertAlmostEqual(expected_sigma, pl._sigma, 3)
if __name__ == '__main__':
unittest.main()