DpEventBuilder tracks the order of events, instead of just maintaining a multiset.

Existing approaches to accounting are generally agnostic to the order of composition, even when the composition is adaptive. But in principle it is possible for an accountant to require such information, so we had better not throw it away.

Note that `ComposedDpEvent` is now treated like any other `DpEvent`, not taken apart and the components added separately as it was. The reason for this is that a common pattern may be to compose a series of `ComposedDpEvent`s that have identical substructure. We want the `DpEventBuilder` to represent this as a single `SelfComposedDpEvent`, not a linearly-growing `ComposedDpEvent`.

PiperOrigin-RevId: 398359519
This commit is contained in:
Galen Andrew 2021-09-22 16:37:21 -07:00 committed by A. Unique TensorFlower
parent 67a7096d52
commit 39c75f62af
2 changed files with 32 additions and 31 deletions

View file

@ -13,8 +13,6 @@
# limitations under the License.
"""Builder class for ComposedDpEvent."""
import collections
from tensorflow_privacy.privacy.analysis import dp_event
@ -28,7 +26,8 @@ class DpEventBuilder(object):
"""
def __init__(self):
self._events = collections.OrderedDict()
# A list of (event, count) pairs.
self._event_counts = []
self._composed_event = None
def compose(self, event: dp_event.DpEvent, count: int = 1):
@ -46,33 +45,32 @@ class DpEventBuilder(object):
if count < 1:
raise ValueError(f'`count` must be positive. Found {count}.')
if isinstance(event, dp_event.ComposedDpEvent):
for composed_event in event.events:
self.compose(composed_event, count)
if isinstance(event, dp_event.NoOpDpEvent):
return
elif isinstance(event, dp_event.SelfComposedDpEvent):
self.compose(event.event, count * event.count)
elif isinstance(event, dp_event.NoOpDpEvent):
return
else:
current_count = self._events.get(event, 0)
self._events[event] = current_count + count
if self._event_counts and self._event_counts[-1][0] == event:
new_event_count = (event, self._event_counts[-1][1] + count)
self._event_counts[-1] = new_event_count
else:
self._event_counts.append((event, count))
self._composed_event = None
def build(self) -> dp_event.DpEvent:
"""Builds and returns the composed DpEvent represented by the builder."""
if not self._composed_event:
self_composed_events = []
for event, count in self._events.items():
events = []
for event, count in self._event_counts:
if count == 1:
self_composed_events.append(event)
events.append(event)
else:
self_composed_events.append(
dp_event.SelfComposedDpEvent(event, count))
if not self_composed_events:
events.append(dp_event.SelfComposedDpEvent(event, count))
if not events:
self._composed_event = dp_event.NoOpDpEvent()
elif len(self_composed_events) == 1:
self._composed_event = self_composed_events[0]
elif len(events) == 1:
self._composed_event = events[0]
else:
self._composed_event = dp_event.ComposedDpEvent(self_composed_events)
self._composed_event = dp_event.ComposedDpEvent(events)
return self._composed_event

View file

@ -20,8 +20,6 @@ from tensorflow_privacy.privacy.analysis import dp_event_builder
_gaussian_event = dp_event.GaussianDpEvent(1.0)
_poisson_event = dp_event.PoissonSampledDpEvent(_gaussian_event, 0.1)
_self_composed_event = dp_event.SelfComposedDpEvent(_gaussian_event, 3)
_composed_event = dp_event.ComposedDpEvent(
[_self_composed_event, _poisson_event])
class DpEventBuilderTest(absltest.TestCase):
@ -50,22 +48,27 @@ class DpEventBuilderTest(absltest.TestCase):
def test_compose_heterogenous(self):
builder = dp_event_builder.DpEventBuilder()
builder.compose(_poisson_event)
builder.compose(_gaussian_event)
builder.compose(_poisson_event)
builder.compose(_gaussian_event, 2)
self.assertEqual(_composed_event, builder.build())
builder.compose(_poisson_event)
expected_event = dp_event.ComposedDpEvent(
[_poisson_event, _self_composed_event, _poisson_event])
self.assertEqual(expected_event, builder.build())
def test_compose_complex(self):
def test_compose_composed(self):
builder = dp_event_builder.DpEventBuilder()
builder.compose(_gaussian_event, 2)
builder.compose(_composed_event)
composed_event = dp_event.ComposedDpEvent(
[_gaussian_event, _poisson_event, _self_composed_event])
builder.compose(_gaussian_event)
builder.compose(composed_event)
builder.compose(composed_event, 2)
builder.compose(_poisson_event)
builder.compose(_poisson_event)
builder.compose(_composed_event, 2)
expected_event = dp_event.ComposedDpEvent([
dp_event.SelfComposedDpEvent(_gaussian_event, 11),
dp_event.SelfComposedDpEvent(_poisson_event, 4)
])
_gaussian_event,
dp_event.SelfComposedDpEvent(composed_event, 3),
dp_event.SelfComposedDpEvent(_poisson_event, 2)])
self.assertEqual(expected_event, builder.build())