def __init__( self, value_sum_factory: Optional[factory.UnweightedAggregationFactory] = None, count_sum_factory: Optional[factory.UnweightedAggregationFactory] = None): """Initializes `UnweightedMeanFactory`. Args: value_sum_factory: An optional `tff.aggregators.UnweightedAggregationFactory` responsible for summation of values. If not specified, `tff.aggregators.SumFactory` is used. count_sum_factory: An optional `tff.aggregators.UnweightedAggregationFactory` responsible for summation of ones to determine the cardinality of the `CLIENTS` placement. If not specified, `tff.aggregators.SumFactory` is used. Raises: TypeError: If provided `value_sum_factory` or `count_sum_factory` is not an instance of `tff.aggregators.UnweightedAggregationFactory`. """ if value_sum_factory is None: value_sum_factory = sum_factory.SumFactory() py_typecheck.check_type(value_sum_factory, factory.UnweightedAggregationFactory) self._value_sum_factory = value_sum_factory if count_sum_factory is None: count_sum_factory = sum_factory.SumFactory() py_typecheck.check_type(count_sum_factory, factory.UnweightedAggregationFactory) self._count_sum_factory = count_sum_factory
def __init__( self, value_sum_factory: Optional[factory.AggregationProcessFactory] = None, weight_sum_factory: Optional[ factory.AggregationProcessFactory] = None): """Initializes `MeanFactory`. Args: value_sum_factory: An optional `tff.aggregators.AggregationProcessFactory` responsible for summation of weighted values. If not specified, `tff.aggregators.SumFactory` is used. weight_sum_factory: An optional `tff.aggregators.AggregationProcessFactory` responsible for summation of weights. If not specified, `tff.aggregators.SumFactory` is used. Raises: TypeError: If provided `value_sum_factory` or `weight_sum_factory` is not an instance of `tff.aggregators.AggregationProcessFactory`. """ if value_sum_factory is None: value_sum_factory = sum_factory.SumFactory() py_typecheck.check_type(value_sum_factory, factory.AggregationProcessFactory) self._value_sum_factory = value_sum_factory if weight_sum_factory is None: weight_sum_factory = sum_factory.SumFactory() py_typecheck.check_type(weight_sum_factory, factory.AggregationProcessFactory) self._weight_sum_factory = weight_sum_factory
def __init__( self, value_sum_factory: Optional[factory.AggregationProcessFactory] = None, weight_sum_factory: Optional[factory.AggregationProcessFactory] = None, no_nan_division: Optional[bool] = False): """Initializes `MeanFactory`. Args: value_sum_factory: An optional `tff.aggregators.AggregationProcessFactory` responsible for summation of weighted values. If not specified, `tff.aggregators.SumFactory` is used. weight_sum_factory: An optional `tff.aggregators.AggregationProcessFactory` responsible for summation of weights. If not specified, `tff.aggregators.SumFactory` is used. no_nan_division: A bool. If True, the computed mean is 0 if sum of weights is equal to 0. Raises: TypeError: If provided `value_sum_factory` or `weight_sum_factory` is not an instance of `tff.aggregators.AggregationProcessFactory`. """ if value_sum_factory is None: value_sum_factory = sum_factory.SumFactory() py_typecheck.check_type(value_sum_factory, factory.AggregationProcessFactory) self._value_sum_factory = value_sum_factory if weight_sum_factory is None: weight_sum_factory = sum_factory.SumFactory() py_typecheck.check_type(weight_sum_factory, factory.AggregationProcessFactory) self._weight_sum_factory = weight_sum_factory py_typecheck.check_type(no_nan_division, bool) self._no_nan_division = no_nan_division
def __init__( self, *, capacity: int, string_max_length: int, repetitions: int, seed: int = 0, sketch_agg_factory: Optional[ factory.UnweightedAggregationFactory] = None, value_tensor_agg_factory: Optional[ factory.UnweightedAggregationFactory] = None, ) -> None: """Initializes IbltFactory. Args: capacity: The capacity of the IBLT sketch. Must be positive. string_max_length: The maximum length of a string in the IBLT. Must be positive. repetitions: The number of repetitions in IBLT data structure (must be >= 3). Must be at least `3`. seed: An integer seed for hash functions. Defaults to 0. sketch_agg_factory: (Optional) A `UnweightedAggregationFactory` specifying the value aggregation to sum IBLT sketches. Defaults to `tff.aggregators.SumFactory`. If `sketch_agg_factory` is set to a `tff.aggregators.SecureSumFactory`, then the `upper_bound_threshold` should be at least 2 ** 32 - 1. value_tensor_agg_factory: (Optional) A `UnweightedAggregationFactory` specifying the value aggregation to sum value tensors. Defaults to `tff.aggregators.SumFactory`. Note that when using `sketch_agg_factory` is set to a `tff.aggregators.SecureSumFactory`, the value to be summed might be clipped depends on the choices of `upper_bound_threshold` and `lower_bound_threshold` parameters in `SecureSumFactory`. Raises: ValueError: if parameters don't meet expectations. """ if capacity < 1: raise ValueError('capacity should be at least 1, got ' f'{capacity}') if string_max_length < 1: raise ValueError('string_max_length should be at least 1, got ' f'{string_max_length}') if repetitions < 3: raise ValueError( f'repetitions should be at least 3, got {repetitions}') self._sketch_agg_factory = sum_factory.SumFactory( ) if sketch_agg_factory is None else sketch_agg_factory self._value_tensor_agg_factory = sum_factory.SumFactory( ) if value_tensor_agg_factory is None else value_tensor_agg_factory self._capacity = capacity self._string_max_length = string_max_length self._repetitions = repetitions self._seed = seed
def __init__(self, clipping_norm: Union[float, estimation_process.EstimationProcess], zeroing_norm_fn: computation_base.Computation, inner_agg_factory: factory.AggregationProcessFactory): """Initializes a ZeroingClippingFactory. Args: clipping_norm: Either a float (for fixed norm) or an `EstimationProcess` (for adaptive norm) that specifies the norm over which values should be clipped. If an `EstimationProcess` is passed, value norms will be passed to the process and its `report` function will be used as the clipping norm. zeroing_norm_fn: A `tff.Computation` to apply to the clipping norm to produce the zeroing norm. inner_agg_factory: A factory specifying the type of aggregation to be done after zeroing and clipping. """ py_typecheck.check_type(inner_agg_factory, factory.AggregationProcessFactory) self._inner_agg_factory = inner_agg_factory py_typecheck.check_type(clipping_norm, (float, estimation_process.EstimationProcess)) if isinstance(clipping_norm, float): clipping_norm = _constant_process(clipping_norm) _check_norm_process(clipping_norm, 'clipping_norm') self._clipping_norm_process = clipping_norm py_typecheck.check_type(zeroing_norm_fn, computation_base.Computation) zeroing_norm_arg_type = zeroing_norm_fn.type_signature.parameter norm_type = clipping_norm.report.type_signature.result.member if not zeroing_norm_arg_type.is_assignable_from(norm_type): raise TypeError( f'Argument of `zeroing_norm_fn` must be assignable from result of ' f'`clipping_norm`, but `clipping_norm` outputs {norm_type}\n ' f'and the argument of `zeroing_norm_fn` is {zeroing_norm_arg_type}.' ) zeroing_norm_result_type = zeroing_norm_fn.type_signature.result float_type = computation_types.to_type(NORM_TF_TYPE) if not float_type.is_assignable_from(zeroing_norm_result_type): raise TypeError( f'Result of `zeroing_norm_fn` must be assignable to ' f'NORM_TF_TYPE but found {zeroing_norm_result_type}.') self._zeroing_norm_fn = zeroing_norm_fn # The aggregation factories that will be used to count the number of zeroed # and clipped values at each iteration. For now we are just creating them # here, but soon we will make this customizable to allow DP measurements. self._clipped_count_agg_factory = sum_factory.SumFactory() self._zeroed_count_agg_factory = sum_factory.SumFactory()
def test_iterative_process_fails_with_dp_agg_and_none_client_weighting(self): def loss_fn(): return tf.keras.losses.SparseCategoricalCrossentropy() def metrics_fn(): return [ NumExamplesCounter(), NumBatchesCounter(), tf.keras.metrics.SparseCategoricalAccuracy() ] # No values should be changed, but working with inf directly zeroes out all # updates. Preferring very large value, but one that can be handled in # multiplication/division gaussian_sum_query = tfp.GaussianSumQuery(l2_norm_clip=1e10, stddev=0) dp_sum_factory = differential_privacy.DifferentiallyPrivateFactory( query=gaussian_sum_query, record_aggregation_factory=sum_factory.SumFactory()) dp_mean_factory = _DPMean(dp_sum_factory) with self.assertRaisesRegex(ValueError, 'unweighted aggregator'): training_process.build_training_process( MnistModel, loss_fn=loss_fn, metrics_fn=metrics_fn, server_optimizer_fn=_get_keras_optimizer_fn(0.01), client_optimizer_fn=_get_keras_optimizer_fn(0.001), reconstruction_optimizer_fn=_get_keras_optimizer_fn(0.0), aggregation_factory=dp_mean_factory, client_weighting=None, dataset_split_fn=reconstruction_utils.simple_dataset_split_fn)
def __init__(self, zeroing_norm: Union[float, estimation_process.EstimationProcess], inner_agg_factory: _InnerFactoryType, norm_order: float = 2.0): """Initializes a ZeroingFactory. Args: zeroing_norm: Either a float (for fixed norm) or an `EstimationProcess` (for adaptive norm) that specifies the norm over which values should be zeroed. If an `EstimationProcess` is passed, value norms will be passed to the process and its `report` function will be used as the zeroing norm. inner_agg_factory: A factory specifying the type of aggregation to be done after zeroing. norm_order: A float for the order of the norm. Must be 1, 2, or np.inf. """ py_typecheck.check_type(inner_agg_factory, _InnerFactoryType.__args__) self._inner_agg_factory = inner_agg_factory py_typecheck.check_type(zeroing_norm, (float, estimation_process.EstimationProcess)) if isinstance(zeroing_norm, float): zeroing_norm = _constant_process(zeroing_norm) _check_norm_process(zeroing_norm, 'zeroing_norm') self._zeroing_norm_process = zeroing_norm py_typecheck.check_type(norm_order, float) if norm_order not in [1.0, 2.0, np.inf]: raise ValueError('norm_order must be 1.0, 2.0 or np.inf.') self._norm_order = norm_order # The aggregation factory that will be used to count the number of zeroed # values at each iteration. For now we are just creating it here, but soon # we will make this customizable to allow DP measurements. self._zeroed_count_agg_factory = sum_factory.SumFactory()
def test_clip_type_properties_with_clipped_count_agg_factory( self, value_type): factory = robust.clipping_factory( clipping_norm=1.0, inner_agg_factory=sum_factory.SumFactory(), clipped_count_sum_factory=aggregator_test_utils.SumPlusOneFactory( )) value_type = computation_types.to_type(value_type) process = factory.create(value_type) self.assertIsInstance(process, aggregation_process.AggregationProcess) server_state_type = computation_types.at_server( collections.OrderedDict(clipping_norm=(), inner_agg=(), clipped_count_agg=tf.int32)) expected_initialize_type = computation_types.FunctionType( parameter=None, result=server_state_type) self.assertTrue( process.initialize.type_signature.is_equivalent_to( expected_initialize_type)) expected_measurements_type = computation_types.at_server( collections.OrderedDict(clipping=(), clipping_norm=robust.NORM_TF_TYPE, clipped_count=robust.COUNT_TF_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 _discretization_sum(scale_factor=2, stochastic=False, beta=0, prior_norm_bound=None): return discretization.DiscretizationFactory(sum_factory.SumFactory(), scale_factor, stochastic, beta, prior_norm_bound)
def __init__(self, clipping_norm: Union[float, estimation_process.EstimationProcess], inner_agg_factory: _InnerFactoryType): """Initializes `ClippingFactory`. Args: clipping_norm: Either a float (for fixed norm) or an `EstimationProcess` (for adaptive norm) that specifies the norm over which the values should be clipped. inner_agg_factory: A factory specifying the type of aggregation to be done after clipping. """ py_typecheck.check_type(inner_agg_factory, _InnerFactoryType.__args__) self._inner_agg_factory = inner_agg_factory py_typecheck.check_type(clipping_norm, (float, estimation_process.EstimationProcess)) if isinstance(clipping_norm, float): clipping_norm = _constant_process(clipping_norm) _check_norm_process(clipping_norm, 'clipping_norm') self._clipping_norm_process = clipping_norm # The aggregation factory that will be used to count the number of clipped # values at each iteration. For now we are just creating it here, but soon # we will make this customizable to allow DP measurements. self._clipped_count_agg_factory = sum_factory.SumFactory()
def test_type_properties(self, value_type): sum_f = sum_factory.SumFactory() self.assertIsInstance(sum_f, factory.UnweightedAggregationFactory) value_type = computation_types.to_type(value_type) process = sum_f.create(value_type) self.assertIsInstance(process, aggregation_process.AggregationProcess) param_value_type = computation_types.FederatedType( value_type, placements.CLIENTS) result_value_type = computation_types.FederatedType( value_type, placements.SERVER) expected_state_type = computation_types.FederatedType( (), placements.SERVER) expected_measurements_type = computation_types.FederatedType( (), placements.SERVER) expected_initialize_type = computation_types.FunctionType( parameter=None, result=expected_state_type) self.assertTrue( process.initialize.type_signature.is_equivalent_to( expected_initialize_type)) expected_next_type = computation_types.FunctionType( parameter=collections.OrderedDict(state=expected_state_type, value=param_value_type), result=measured_process.MeasuredProcessOutput( expected_state_type, result_value_type, expected_measurements_type)) self.assertTrue( process.next.type_signature.is_equivalent_to(expected_next_type))
def __init__(self, query: tfp.DPQuery, record_aggregation_factory: Optional[ factory.UnweightedAggregationFactory] = None): """Initializes `DifferentiallyPrivateFactory`. Args: query: A `tfp.SumAggregationDPQuery` to perform private estimation. record_aggregation_factory: A `tff.aggregators.UnweightedAggregationFactory` to aggregate values after preprocessing by the `query`. If `None`, defaults to `tff.aggregators.SumFactory`. The provided factory is assumed to implement a sum, and to have the property that it does not increase the sensitivity of the query - typically this means that it should not increase the l2 norm of the records when aggregating. Raises: TypeError: If `query` is not an instance of `tfp.SumAggregationDPQuery` or `record_aggregation_factory` is not an instance of `tff.aggregators.UnweightedAggregationFactory`. """ py_typecheck.check_type(query, tfp.SumAggregationDPQuery) self._query = query if record_aggregation_factory is None: record_aggregation_factory = sum_factory.SumFactory() py_typecheck.check_type(record_aggregation_factory, factory.UnweightedAggregationFactory) self._record_aggregation_factory = record_aggregation_factory
def test_unweighted_aggregator_raises(self): bad_aggregator = sum_factory.SumFactory().create(FLOAT_TYPE) with self.assertRaisesRegex(TypeError, 'weighted'): composers.compose_learning_process(test_init_model_weights_fn, test_distributor(), test_client_work(), bad_aggregator, test_finalizer())
def test_construction(self, weighted): aggregation_factory = (mean.MeanFactory() if weighted else sum_factory.SumFactory()) iterative_process = optimizer_utils.build_model_delta_optimizer_process( model_fn=model_examples.LinearRegression, model_to_client_delta_fn=DummyClientDeltaFn, server_optimizer_fn=tf.keras.optimizers.SGD, model_update_aggregation_factory=aggregation_factory) if weighted: aggregate_state = collections.OrderedDict(value_sum_process=(), weight_sum_process=()) aggregate_metrics = collections.OrderedDict(mean_value=(), mean_weight=()) else: aggregate_state = () aggregate_metrics = () server_state_type = computation_types.FederatedType( optimizer_utils.ServerState(model=model_utils.ModelWeights( trainable=[ computation_types.TensorType(tf.float32, [2, 1]), computation_types.TensorType(tf.float32) ], non_trainable=[computation_types.TensorType(tf.float32)]), optimizer_state=[tf.int64], delta_aggregate_state=aggregate_state, model_broadcast_state=()), placements.SERVER) self.assert_types_equivalent( computation_types.FunctionType(parameter=None, result=server_state_type), iterative_process.initialize.type_signature) dataset_type = computation_types.FederatedType( computation_types.SequenceType( collections.OrderedDict( x=computation_types.TensorType(tf.float32, [None, 2]), y=computation_types.TensorType(tf.float32, [None, 1]))), placements.CLIENTS) metrics_type = computation_types.FederatedType( collections.OrderedDict( broadcast=(), aggregation=aggregate_metrics, train=collections.OrderedDict( loss=computation_types.TensorType(tf.float32), num_examples=computation_types.TensorType(tf.int32)), stat=collections.OrderedDict( num_examples=computation_types.TensorType(tf.float32))), placements.SERVER) self.assert_types_equivalent( computation_types.FunctionType(parameter=collections.OrderedDict( server_state=server_state_type, federated_dataset=dataset_type, ), result=(server_state_type, metrics_type)), iterative_process.next.type_signature)
def test_raises_bad_measurement_fn(self): unweighted_factory = sum_factory.SumFactory() with self.assertRaisesRegex(ValueError, 'single parameter'): measurements.add_measurements(unweighted_factory, _get_weighted_min) weighted_factory = mean.MeanFactory() with self.assertRaisesRegex(ValueError, 'two parameters'): measurements.add_measurements(weighted_factory, _get_min)
def no_noise(cls, initial_estimate: float, target_quantile: float, learning_rate: float, multiplier: float = 1.0, increment: float = 0.0, secure_estimation: bool = False): """No-noise estimator for affine function of value at quantile. Estimates value `C` at `q`'th quantile of input distribution and reports `rC + i` for multiplier `r` and increment `i`. The quantile `C` is estimated using the geometric method described in Thakkar et al. 2019, "Differentially Private Learning with Adaptive Clipping" (https://arxiv.org/abs/1905.03871) without noise added. Note that this estimator does not add noise, so it does not guarantee differential privacy. It is useful for estimating quantiles in contexts where rigorous privacy guarantees are not needed. Args: initial_estimate: The initial estimate of `C`. target_quantile: The quantile `q` to which `C` will be adapted. learning_rate: The learning rate for the adaptive algorithm. multiplier: The multiplier `r` of the affine transform. increment: The increment `i` of the affine transform. secure_estimation: Whether to perform the aggregation for estimation using `tff.aggregators.SumFactory` (if `False`; default) or `tff.aggregators.SecureSumFactory` (if `True`). Returns: An `EstimationProcess` whose `report` function returns `rC + i`. """ _check_float_positive(initial_estimate, 'initial_estimate') _check_float_probability(target_quantile, 'target_quantile') _check_float_positive(learning_rate, 'learning_rate') _check_float_positive(multiplier, 'multiplier') _check_float_nonnegative(increment, 'increment') if secure_estimation: # NoPrivacyQuantileEstimatorQuery aggregates +/-0.5 values as the record, # and a constant 1.0 as weights for the average, thus the bound of 1.0. record_aggregation_factory = secure.SecureSumFactory(1.0) else: record_aggregation_factory = sum_factory.SumFactory() quantile = cls( tfp.NoPrivacyQuantileEstimatorQuery( initial_estimate=initial_estimate, target_quantile=target_quantile, learning_rate=learning_rate, geometric_update=True), record_aggregation_factory) if multiplier == 1.0 and increment == 0.0: return quantile else: return quantile.map(_affine_transform(multiplier, increment))
def test_unweighted(self): factory = sum_factory.SumFactory() factory = measurements.add_measurements(factory, _get_min) process = factory.create(_float_type) state = process.initialize() client_data = [1.0, 2.0, 3.0] output = process.next(state, client_data) self.assertAllClose(6.0, output.result) self.assertDictEqual(collections.OrderedDict(min_value=1.0), output.measurements)
def test_unweighted_struct(self): factory = sum_factory.SumFactory() factory = measurements.add_measurements(factory, _get_min_norm) process = factory.create(_struct_type) state = process.initialize() client_data = [_make_struct(x) for x in [1.0, 2.0, 3.0]] output = process.next(state, client_data) self.assertAllClose(_make_struct(6.0), output.result) self.assertDictEqual(collections.OrderedDict(min_norm=2.0), output.measurements)
def test_sum_structure(self): sum_f = sum_factory.SumFactory() value_type = computation_types.to_type(((tf.float32, (2, )), tf.int32)) process = sum_f.create(value_type) state = process.initialize() self.assertEqual((), state) client_data = [((1.0, 2.0), 3), ((2.0, 5.0), 4), ((3.0, 0.0), 5)] output = process.next(state, client_data) self.assertEqual((), output.state) self.assertAllClose(((6.0, 7.0), 12), output.result) self.assertEqual((), output.measurements)
def test_sum_scalar(self): sum_f = sum_factory.SumFactory() value_type = computation_types.to_type(tf.float32) process = sum_f.create(value_type) state = process.initialize() self.assertEqual((), state) client_data = [1.0, 2.0, 3.0] output = process.next(state, client_data) self.assertEqual((), output.state) self.assertAllClose(6.0, output.result) self.assertEqual((), output.measurements)
def __init__( self, inner_agg_factory: Optional[factory.UnweightedAggregationFactory] = None, num_repeats: int = 1): if inner_agg_factory is None: inner_agg_factory = sum_factory.SumFactory() if not isinstance(inner_agg_factory, factory.UnweightedAggregationFactory): raise TypeError( 'Provided `inner_agg_factory` must be an ' f'UnweightedAggregationFactory. Found {type(inner_agg_factory)}.') if not isinstance(num_repeats, int) or num_repeats < 1: raise ValueError('`num_repeats` should be a positive integer. ' f'Found {num_repeats}.') self._inner_agg_factory = inner_agg_factory self._num_repeats = num_repeats
def create_central_hierarchical_histogram_factory( stddev: float = 0.0, arity: int = 2, max_records_per_user: int = 10, secure_sum: bool = False): """Creates aggregator for hierarchical histograms with differential privacy. Args: stddev: The standard deviation of noise added to each node of the central tree. arity: The branching factor of the tree. max_records_per_user: The maximum of records each user can upload in their local histogram. secure_sum: A boolean deciding whether to use secure aggregation. Defaults to `False`. Returns: `tff.aggregators.UnWeightedAggregationFactory`. Raises: `ValueError`: If 'stddev < 0', `arity < 2`, `max_records_per_user < 1` or `inner_agg_factory` is illegal. """ if stddev < 0: raise ValueError(f"Standard deviation should be greater than zero." f"stddev={stddev} is given.") if arity < 2: raise ValueError(f"Arity should be at least 2." f"arity={arity} is given.") if max_records_per_user < 1: raise ValueError( f"Maximum records per user should be at least 1." f"max_records_per_user={max_records_per_user} is given.") central_tree_agg_query = tfp.privacy.dp_query.tree_aggregation_query.CentralTreeSumQuery( stddev=stddev, arity=arity, l1_bound=max_records_per_user) if secure_sum: inner_agg_factory = secure.SecureSumFactory( upper_bound_threshold=float(max_records_per_user), lower_bound_threshold=0.) else: inner_agg_factory = sum_factory.SumFactory() return differential_privacy.DifferentiallyPrivateFactory( central_tree_agg_query, inner_agg_factory)
def test_wrapped_aggregator_independent_of_weights(self): aggregator = factory_utils.as_weighted_aggregator( sum_factory.SumFactory()).create(_TEST_VALUE_TYPE, _TEST_WEIGHT_TYPE) test_data = [(1.0, 2.0), (3.0, 4.0), (0.0, 5.0)] uniform_weights = [1.0, 1.0, 1.0] state = aggregator.initialize() uniform_output = aggregator.next(state, test_data, uniform_weights) for seed in range(0, 30, 3): random_weights = [ tf.random.stateless_uniform((), [seed + i, 0]) for i in range(3) ] random_output = aggregator.next(state, test_data, random_weights) self.assertAllEqual(uniform_output.state, random_output.state) self.assertAllEqual(uniform_output.result, random_output.result) self.assertAllEqual(uniform_output.measurements, random_output.measurements)
def test_wrapped_aggregator_same_as_unweighted_aggregator(self): unweighted_factory = sum_factory.SumFactory() wrapped_factory = factory_utils.as_weighted_aggregator(unweighted_factory) unweighted_aggregator = unweighted_factory.create(_TEST_VALUE_TYPE) weighted_aggregator = wrapped_factory.create(_TEST_VALUE_TYPE, _TEST_WEIGHT_TYPE) test_data = [(1.0, 2.0), (3.0, 4.0), (0.0, 5.0)] test_weights = [1.0, 1.0, 1.0] unweighted_output = unweighted_aggregator.next( unweighted_aggregator.initialize(), test_data) weighted_output = weighted_aggregator.next(weighted_aggregator.initialize(), test_data, test_weights) self.assertAllEqual(unweighted_output.state, weighted_output.state) self.assertAllEqual(unweighted_output.result, weighted_output.result) self.assertAllEqual(unweighted_output.measurements, weighted_output.measurements)
def __init__(self, clip_range_lower: int, clip_range_upper: int, inner_agg_factory: Optional[ factory.UnweightedAggregationFactory] = None): """Initializes a `ModularClippingSumFactory` instance. Args: clip_range_lower: A Python integer specifying the inclusive lower modular clipping range. clip_range_upper: A Python integer specifying the exclusive upper modular clipping range. inner_agg_factory: (Optional) A `UnweightedAggregationFactory` specifying the value aggregation to be wrapped by modular clipping. Defaults to `tff.aggregators.SumFactory`. Raises: TypeError: If `clip_range_lower` or `clip_range_upper` are not integers. TypeError: If `inner_agg_factory` isn't an `UnweightedAggregationFactory`. ValueError: If `clip_range_lower` or `clip_range_upper` have invalid values. """ _check_is_integer(clip_range_lower, 'clip_range_lower') _check_is_integer(clip_range_upper, 'clip_range_upper') _check_less_than_equal(clip_range_lower, clip_range_upper, 'clip_range_lower', 'clip_range_upper') _check_clip_range_overflow(clip_range_lower, clip_range_upper) if inner_agg_factory is None: inner_agg_factory = sum_factory.SumFactory() else: _check_is_unweighted_aggregation_factory(inner_agg_factory, 'inner_agg_factory') if clip_range_lower > clip_range_upper: raise ValueError('`clip_range_lower` should not be larger than ' f'`clip_range_upper`, got {clip_range_lower} and ' f'{clip_range_upper}') self._clip_range_lower = clip_range_lower self._clip_range_upper = clip_range_upper self._inner_agg_factory = inner_agg_factory
def __init__(self, clip_mechanism: str = 'sub-sampling', max_records_per_user: int = 10, inner_agg_factory: Optional[ factory.UnweightedAggregationFactory] = None, cast_to_float: bool = False): """Initializes a `HistogramClippingSumFactory` instance. Args: clip_mechanism: A `str` representing the clipping mechanism. Currently supported mechanisms are - 'sub-sampling': (Default) Uniformly sample up to `max_records_per_user` records without replacement from the client dataset. - 'distinct': Uniquify client dataset and uniformly sample up to `max_records_per_user` records without replacement from it. max_records_per_user: An `int` representing the maximum of records each user can include in their local histogram. Defaults to 10. inner_agg_factory: (Optional) An `UnweightedAggregationFactory` specifying the value aggregation to be wrapped by `HistogramClippingSumFactory`. Defaults to `tff.aggregators.SumFactory`. cast_to_float: A boolean specifying the data type of the clipped histogram. If set to `False` (default), tensor with the same integer dtype will be passed to `inner_agg_factory`. If set to `True`, the clipped histogram will be casted to `tf.float32` before being passed to `inner_agg_factory`. Raises: TypeError: If arguments have the wrong type(s). ValueError: If arguments have invalid value(s). """ _check_membership(clip_mechanism, CLIP_MECHANISMS, 'clip_mechanism') _check_positive(max_records_per_user, 'max_records_per_user') self._clip_mechanism = clip_mechanism self._max_records_per_user = max_records_per_user if inner_agg_factory is None: self._inner_agg_factory = sum_factory.SumFactory() else: self._inner_agg_factory = inner_agg_factory self._cast_to_float = cast_to_float
def __init__( self, quantile_estimator_query: tfp.QuantileEstimatorQuery, record_aggregation_factory: factory.UnweightedAggregationFactory = None ): """Initializes `PrivateQuantileEstimationProcess`. Args: quantile_estimator_query: A `tfp.QuantileEstimatorQuery` for estimating quantiles with differential privacy. record_aggregation_factory: A `tff.aggregators.UnweightedAggregationFactory` to aggregate counts of values below the current estimate. If `None`, defaults to `tff.aggregators.SumFactory`. """ py_typecheck.check_type(quantile_estimator_query, tfp.QuantileEstimatorQuery) if record_aggregation_factory is None: record_aggregation_factory = sum_factory.SumFactory() else: py_typecheck.check_type(record_aggregation_factory, factory.UnweightedAggregationFactory) # 1. Define tf_computations. initial_state_fn = computations.tf_computation( quantile_estimator_query.initial_global_state) quantile_state_type = initial_state_fn.type_signature.result derive_sample_params = computations.tf_computation( quantile_estimator_query.derive_sample_params, quantile_state_type) get_quantile_record = computations.tf_computation( quantile_estimator_query.preprocess_record, derive_sample_params.type_signature.result, tf.float32) quantile_record_type = get_quantile_record.type_signature.result get_noised_result = computations.tf_computation( quantile_estimator_query.get_noised_result, quantile_record_type, quantile_state_type) quantile_agg_process = record_aggregation_factory.create( quantile_record_type) # 2. Define federated_computations. @computations.federated_computation() def init_fn(): return intrinsics.federated_zip( (intrinsics.federated_eval(initial_state_fn, placements.SERVER), quantile_agg_process.initialize())) @computations.federated_computation(init_fn.type_signature.result, computation_types.FederatedType( tf.float32, placements.CLIENTS)) def next_fn(state, value): quantile_query_state, agg_state = state params = intrinsics.federated_broadcast( intrinsics.federated_map(derive_sample_params, quantile_query_state)) quantile_record = intrinsics.federated_map(get_quantile_record, (params, value)) (new_agg_state, agg_result, agg_measurements) = quantile_agg_process.next( agg_state, quantile_record) # We expect the quantile record aggregation process to be something simple # like basic sum, so we won't surface its measurements. del agg_measurements _, new_quantile_query_state = intrinsics.federated_map( get_noised_result, (agg_result, quantile_query_state)) return intrinsics.federated_zip( (new_quantile_query_state, new_agg_state)) report_fn = computations.federated_computation( lambda state: state[0].current_estimate, init_fn.type_signature.result) super().__init__(init_fn, next_fn, report_fn)
def __init__(self, clip_range_lower: int, clip_range_upper: int, inner_agg_factory: Optional[ factory.UnweightedAggregationFactory] = None, estimate_stddev: Optional[bool] = False): """Initializes a `ModularClippingSumFactory` instance. Args: clip_range_lower: A Python integer specifying the inclusive lower modular clipping range. clip_range_upper: A Python integer specifying the exclusive upper modular clipping range. inner_agg_factory: (Optional) A `UnweightedAggregationFactory` specifying the value aggregation to be wrapped by modular clipping. Defaults to `tff.aggregators.SumFactory`. estimate_stddev: (Optional) Whether to report the estimated standard deviation of the aggregated and modular-clipped values in the measurements. The estimation procedure assumes that the input client values (and thus the aggregate) are (approximately) normally distributed and centered at the midpoint of `clip_lower` and `clip_upper`. Defaults to `False`. Raises: TypeError: If `clip_range_lower` or `clip_range_upper` are not integers. TypeError: If `inner_agg_factory` isn't an `UnweightedAggregationFactory`. TypeError: If `estimate_stddev` is not a bool. ValueError: If `clip_range_lower` or `clip_range_upper` have invalid values. """ if inner_agg_factory is None: inner_agg_factory = sum_factory.SumFactory() elif not isinstance(inner_agg_factory, factory.UnweightedAggregationFactory): raise TypeError('`inner_agg_factory` must have type ' '`UnweightedAggregationFactory`. ' f'Found {type(inner_agg_factory)}.') if not (isinstance(clip_range_lower, int) and isinstance(clip_range_upper, int)): raise TypeError( '`clip_range_lower` and `clip_range_upper` must be ' f'Python `int`; got {repr(clip_range_lower)} with type ' f'{type(clip_range_lower)} and {repr(clip_range_upper)} ' f'with type {type(clip_range_upper)}, respectively.') if clip_range_lower > clip_range_upper: raise ValueError('`clip_range_lower` should not be larger than ' f'`clip_range_upper`, got {clip_range_lower} and ' f'{clip_range_upper}') if (clip_range_upper > tf.int32.max or clip_range_lower < tf.int32.min or clip_range_upper - clip_range_lower > tf.int32.max): raise ValueError( '`clip_range_lower` and `clip_range_upper` should be ' 'set such that the range of the modulus do not overflow ' f'tf.int32. Found clip_range_lower={clip_range_lower} ' f'and clip_range_upper={clip_range_upper} respectively.') if not isinstance(estimate_stddev, bool): raise TypeError(f'{estimate_stddev} must be a bool. ' f'Found {repr(estimate_stddev)}.') self._clip_range_lower = clip_range_lower self._clip_range_upper = clip_range_upper self._inner_agg_factory = inner_agg_factory self._estimate_stddev = estimate_stddev
def _concat_sum(): return concat.concat_factory(sum_factory.SumFactory())
def _zeroed_sum(clip=2.0, norm_order=2.0): return clipping_factory.ZeroingFactory(clip, sum_factory.SumFactory(), norm_order)