Questions or feedback?

Privacy Filters#

You can convert any odometer into a measurement by setting an upper bound on the privacy loss. The following example converts the fully adaptive composition odometer into a privacy filter that rejects any query that would cause the privacy loss to exceed 2.0:

>>> odom_fully_adaptive_comp = (
...     dp.c.make_fully_adaptive_composition(
...         input_domain=dp.vector_domain(
...             dp.atom_domain(T=int)
...         ),
...         input_metric=dp.symmetric_distance(),
...         output_measure=dp.max_divergence(),
...     )
... )
>>> meas_fully_adaptive_comp = dp.c.make_privacy_filter(
...     odom_fully_adaptive_comp,
...     d_in=1,
...     d_out=2.0,
... )
meas_fully_adaptive_comp <- make_privacy_filter(
  odom_fully_adaptive_comp,
  d_in = 1L,
  d_out = 2.0
)

Privacy filters are measurements, meaning that they can be passed into make_composition, adaptive composition queryables, or into other combinators. However, they have the added benefit of not needing to specify privacy-loss parameters ahead-of-time. When the privacy filter (meas_fully_adaptive_comp) is invoked, it still returns an odometer queryable, but this time the queryable will limit the overall privacy loss.

>>> int_dataset = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> qbl_fully_adaptive_comp = meas_fully_adaptive_comp(
...     int_dataset
... )
int_dataset <- c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L)
qbl_fully_adaptive_comp <- meas_fully_adaptive_comp(arg = int_dataset)

Similarly as before, we now interactively submit queries to estimate the sum and count:

>>> input_space = (
...     dp.vector_domain(dp.atom_domain(T=int)),
...     dp.symmetric_distance(),
... )
>>> meas_count = (
...     input_space
...     >> dp.t.then_count()
...     >> dp.m.then_laplace(scale=1.0)
... )
>>> meas_sum = (
...     input_space
...     >> dp.t.then_clamp((0, 10))
...     >> dp.t.then_sum()
...     >> dp.m.then_laplace(scale=5.0)
... )
>>> print("dp count:", qbl_fully_adaptive_comp(meas_count))
dp count: ...
>>> print("dp count:", qbl_fully_adaptive_comp(meas_count))
dp count: ...
qbl_fully_adaptive_comp(query = meas_count)
# 11
qbl_fully_adaptive_comp(query = meas_count)
# 9

Now that we have submitted two queries, we can see that the privacy loss has increased commensurately:

>>> qbl_fully_adaptive_comp.privacy_loss(1)
2.0
qbl_fully_adaptive_comp(d_in = 1L)
# 2.0

Since the privacy loss is capped at 2.0, any more queries will be rejected:

>>> print("dp count:", qbl_fully_adaptive_comp(meas_count))
Traceback (most recent call last):
...
opendp.mod.OpenDPException:
  FailedFunction("filter is now exhausted: pending privacy loss (3.0) would exceed privacy budget (2.0)")
tryCatch(
  qbl_fully_adaptive_comp(query = meas_count),
  error = print
)
# [FailedFunction] : insufficient privacy budget: 3.0 > 2.0