Esempio n. 1
0
  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
Esempio n. 2
0
    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
Esempio n. 3
0
  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
Esempio n. 4
0
    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
Esempio n. 5
0
    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)
Esempio n. 7
0
  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()
Esempio n. 8
0
    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))
Esempio n. 9
0
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)
Esempio n. 10
0
    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()
Esempio n. 11
0
    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))
Esempio n. 12
0
    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
Esempio n. 13
0
 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)
Esempio n. 15
0
    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)
Esempio n. 16
0
    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))
Esempio n. 17
0
    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)
Esempio n. 18
0
    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)
Esempio n. 19
0
    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)
Esempio n. 20
0
    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)
Esempio n. 21
0
 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
Esempio n. 22
0
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)
Esempio n. 23
0
  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)
Esempio n. 24
0
  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)
Esempio n. 25
0
    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
Esempio n. 26
0
    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
Esempio n. 27
0
    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)
Esempio n. 28
0
    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
Esempio n. 29
0
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)