def _dp_factory( config: DifferentialPrivacyConfig ) -> dp_factory.DifferentiallyPrivateFactory: """Creates DifferentiallyPrivateFactory based on config settings.""" if isinstance(config.clipping, FixedClippingConfig): stddev = config.clipping.clip * config.noise_multiplier query = tfp.GaussianAverageQuery(l2_norm_clip=config.clipping.clip, sum_stddev=stddev, denominator=config.clients_per_round) elif isinstance(config.clipping, AdaptiveClippingConfig): query = tfp.QuantileAdaptiveClipAverageQuery( initial_l2_norm_clip=config.clipping.initial_clip, noise_multiplier=config.noise_multiplier, denominator=config.clients_per_round, target_unclipped_quantile=config.clipping.target_quantile, learning_rate=config.clipping.learning_rate, clipped_count_stddev=config.clipped_count_stddev, expected_num_records=config.clients_per_round, geometric_update=True) else: raise TypeError( f'config.clipping is not a supported type of ClippingConfig. Found ' f'type {type(config.clipping)}.') return dp_factory.DifferentiallyPrivateFactory(query)
def test_simple_sum(self): agg_factory = dp_factory.DifferentiallyPrivateFactory(_test_dp_query) value_type = computation_types.to_type(tf.float32) process = agg_factory.create(value_type) # The test query has clip 1.0 and no noise, so this computes clipped sum. state = process.initialize() client_data = [0.5, 1.0, 1.5] output = process.next(state, client_data) self.assertAllClose(2.5, output.result)
def test_inner_sum(self): agg_factory = dp_factory.DifferentiallyPrivateFactory( _test_dp_query, _test_inner_agg_factory) value_type = computation_types.to_type(tf.float32) process = agg_factory.create(value_type) # The test query has clip 1.0 and no noise, so this computes clipped sum. # Inner agg adds another 1.0 (post-clipping). state = process.initialize() self.assertAllEqual(0, state[1]) client_data = [0.5, 1.0, 1.5] output = process.next(state, client_data) self.assertAllEqual(1, output.state[1]) self.assertAllClose(3.5, output.result) self.assertAllEqual(test_utils.MEASUREMENT_CONSTANT, output.measurements['record_agg_process'])
def to_factory(self) -> dp_factory.DifferentiallyPrivateFactory: """Creates factory based on config settings.""" if self._clipping.is_fixed: stddev = self._clipping.clip * self._noise_multiplier query = tfp.GaussianAverageQuery( l2_norm_clip=self._clipping.clip, sum_stddev=stddev, denominator=self._clients_per_round) else: query = tfp.QuantileAdaptiveClipAverageQuery( initial_l2_norm_clip=self._clipping.clip.initial_estimate, noise_multiplier=self._noise_multiplier, denominator=self._clients_per_round, target_unclipped_quantile=self._clipping.clip.target_quantile, learning_rate=self._clipping.clip.learning_rate, clipped_count_stddev=self._clipped_count_stddev, expected_num_records=self._clients_per_round, geometric_update=True) return dp_factory.DifferentiallyPrivateFactory(query)
def test_structure_sum(self): agg_factory = dp_factory.DifferentiallyPrivateFactory(_test_dp_query) value_type = computation_types.to_type([tf.float32, tf.float32]) process = agg_factory.create_unweighted(value_type) # The test query has clip 1.0 and no noise, so this computes clipped sum. state = process.initialize() # pyformat: disable client_data = [ [0.1, 0.2], # not clipped (norm < 1) [5 / 13, 12 / 13], # not clipped (norm == 1) [3.0, 4.0] # clipped to 3/5, 4/5 ] output = process.next(state, client_data) expected_result = [0.1 + 5 / 13 + 3 / 5, 0.2 + 12 / 13 + 4 / 5] # pyformat: enable self.assertAllClose(expected_result, output.result)
def test_type_properties(self, value_type, inner_agg_factory): agg_factory = dp_factory.DifferentiallyPrivateFactory( _test_dp_query, inner_agg_factory) self.assertIsInstance(agg_factory, factory.UnweightedAggregationFactory) value_type = computation_types.to_type(value_type) process = agg_factory.create_unweighted(value_type) self.assertIsInstance(process, aggregation_process.AggregationProcess) query_state = _test_dp_query.initial_global_state() query_state_type = type_conversions.type_from_tensors(query_state) query_metrics_type = type_conversions.type_from_tensors( _test_dp_query.derive_metrics(query_state)) inner_state_type = tf.int32 if inner_agg_factory else () server_state_type = computation_types.at_server( (query_state_type, inner_state_type)) expected_initialize_type = computation_types.FunctionType( parameter=None, result=server_state_type) self.assertTrue( process.initialize.type_signature.is_equivalent_to( expected_initialize_type)) inner_measurements_type = tf.int32 if inner_agg_factory else () expected_measurements_type = computation_types.at_server( collections.OrderedDict( query_metrics=query_metrics_type, record_agg_process=inner_measurements_type)) expected_next_type = computation_types.FunctionType( parameter=collections.OrderedDict( state=server_state_type, value=computation_types.at_clients(value_type)), result=measured_process.MeasuredProcessOutput( state=server_state_type, result=computation_types.at_server(value_type), measurements=expected_measurements_type)) self.assertTrue( process.next.type_signature.is_equivalent_to(expected_next_type))
def test_adaptive_query(self): query = tfp.QuantileAdaptiveClipSumQuery(initial_l2_norm_clip=1.0, noise_multiplier=0.0, target_unclipped_quantile=1.0, learning_rate=1.0, clipped_count_stddev=0.0, expected_num_records=3.0, geometric_update=False) agg_factory = dp_factory.DifferentiallyPrivateFactory(query) value_type = computation_types.to_type(tf.float32) process = agg_factory.create_unweighted(value_type) state = process.initialize() client_data = [0.5, 1.5, 2.0] # Two clipped on first round. expected_result = 0.5 + 1.0 + 1.0 output = process.next(state, client_data) self.assertAllClose(expected_result, output.result) # Clip is increased by 2/3 to 5/3. expected_result = 0.5 + 1.5 + 5 / 3 output = process.next(output.state, client_data) self.assertAllClose(expected_result, output.result)
def test_incorrect_value_type_raises(self, bad_value_type): agg_factory = dp_factory.DifferentiallyPrivateFactory(_test_dp_query) with self.assertRaises(TypeError): agg_factory.create(bad_value_type)
def test_init_non_agg_factory_raises(self): with self.assertRaises(TypeError): dp_factory.DifferentiallyPrivateFactory(_test_dp_query, 'not an agg factory')