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