def _build_quantile_estimation_process(initial_estimate, target_quantile,
                                       learning_rate):
    return quantile_estimation.PrivateQuantileEstimationProcess(
        tfp.NoPrivacyQuantileEstimatorQuery(initial_estimate=initial_estimate,
                                            target_quantile=target_quantile,
                                            learning_rate=learning_rate,
                                            geometric_update=True))
    def no_noise(cls, initial_estimate: float, target_quantile: float,
                 learning_rate: float):
        """No-noise estimator for value at quantile.

    Estimates value `C` at `q`'th quantile of input distribution. `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.

    Returns:
      A `PrivateQuantileEstimationProcess`.
    """
        _check_float_positive(initial_estimate, 'initial_estimate')
        _check_float_probability(target_quantile, 'target_quantile')
        _check_float_positive(learning_rate, 'learning_rate')

        return cls(
            tfp.NoPrivacyQuantileEstimatorQuery(
                initial_estimate=initial_estimate,
                target_quantile=target_quantile,
                learning_rate=learning_rate,
                geometric_update=True))
Example #3
0
    def test_adaptation(self, geometric_update):
        initial_estimate = 3.14159
        target_quantile = 0.61803
        learning_rate = 2.71828

        quantile_estimator_query = tfp.NoPrivacyQuantileEstimatorQuery(
            initial_estimate=initial_estimate,
            target_quantile=target_quantile,
            learning_rate=learning_rate,
            geometric_update=geometric_update)

        process = QEProcess(quantile_estimator_query)

        state = process.initialize()
        self.assertAllClose(process.report(state), initial_estimate)

        # Run on two records greater than estimate.
        state = process.next(state,
                             [initial_estimate + 1, initial_estimate + 2])

        if geometric_update:
            expected_estimate = (initial_estimate *
                                 np.exp(learning_rate * target_quantile))
        else:
            expected_estimate = initial_estimate + learning_rate * target_quantile

        self.assertAllClose(process.report(state), expected_estimate)
 def to_quantile_estimation_process(
     self) -> quantile_estimation.PrivateQuantileEstimationProcess:
   return quantile_estimation.PrivateQuantileEstimationProcess(
       tfp.NoPrivacyQuantileEstimatorQuery(
           initial_estimate=self._initial_estimate,
           target_quantile=self._target_quantile,
           learning_rate=self._learning_rate,
           geometric_update=True))
Example #5
0
def _make_quantile_estimation_process(initial_estimate: float,
                                      target_quantile: float,
                                      learning_rate: float):
    return quantile_estimation.PrivateQuantileEstimationProcess(
        tensorflow_privacy.NoPrivacyQuantileEstimatorQuery(
            initial_estimate=initial_estimate,
            target_quantile=target_quantile,
            learning_rate=learning_rate,
            geometric_update=True))
Example #6
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))
Example #7
0
    def test_bad_aggregation_factory(self):
        quantile_estimator_query = tfp.NoPrivacyQuantileEstimatorQuery(
            initial_estimate=1.0,
            target_quantile=0.5,
            learning_rate=1.0,
            geometric_update=True)

        with self.assertRaises(TypeError):
            QEProcess(quantile_estimator_query=quantile_estimator_query,
                      record_aggregation_factory=
                      "I'm not a record_aggregation_factory.")
  def test_process_type_signature(self, private):
    if private:
      quantile_estimator_query = tfp.QuantileEstimatorQuery(
          initial_estimate=1.0,
          target_quantile=0.5,
          learning_rate=1.0,
          below_estimate_stddev=0.5,
          expected_num_records=100,
          geometric_update=True)
    else:
      quantile_estimator_query = tfp.NoPrivacyQuantileEstimatorQuery(
          initial_estimate=1.0,
          target_quantile=0.5,
          learning_rate=1.0,
          geometric_update=True)

    process = quantile_estimation.PrivateQuantileEstimationProcess(
        quantile_estimator_query)

    query_state = quantile_estimator_query.initial_global_state()
    sum_process_state = ()

    server_state_type = computation_types.FederatedType(
        type_conversions.type_from_tensors((query_state, sum_process_state)),
        placements.SERVER)

    self.assertEqual(
        computation_types.FunctionType(
            parameter=None, result=server_state_type),
        process.initialize.type_signature)

    estimate_type = computation_types.FederatedType(tf.float32,
                                                    placements.SERVER)

    self.assertEqual(
        computation_types.FunctionType(
            parameter=server_state_type, result=estimate_type),
        process.report.type_signature)

    client_value_type = computation_types.FederatedType(tf.float32,
                                                        placements.CLIENTS)
    self.assertTrue(
        process.next.type_signature.is_equivalent_to(
            computation_types.FunctionType(
                parameter=collections.OrderedDict(
                    state=server_state_type, value=client_value_type),
                result=server_state_type)))
Example #9
0
    def test_process_type_signature(self, value_template):
        value_type = type_conversions.type_from_tensors(value_template)
        mean_process = adaptive_zeroing.build_adaptive_zeroing_mean_process(
            value_type=value_type,
            initial_quantile_estimate=100.0,
            target_quantile=0.99,
            multiplier=2.0,
            increment=1.0,
            learning_rate=1.0,
            norm_order=np.inf)

        dummy_quantile_query = tensorflow_privacy.NoPrivacyQuantileEstimatorQuery(
            50.0, 0.99, 1.0, True)
        quantile_query_state = dummy_quantile_query.initial_global_state()
        server_state_type = computation_types.FederatedType(
            type_conversions.type_from_tensors(quantile_query_state),
            placements.SERVER)

        self.assertEqual(
            mean_process.initialize.type_signature,
            computation_types.FunctionType(parameter=None,
                                           result=server_state_type))

        client_value_type = computation_types.FederatedType(
            value_type, placements.CLIENTS)
        client_value_weight_type = computation_types.FederatedType(
            tf.float32, placements.CLIENTS)
        server_result_type = computation_types.FederatedType(
            value_type, placements.SERVER)
        server_metrics_type = computation_types.FederatedType(
            adaptive_zeroing.AdaptiveZeroingMetrics(
                zeroing_threshold=tf.float32, num_zeroed=tf.int32),
            placements.SERVER)
        self.assertTrue(
            mean_process.next.type_signature.is_equivalent_to(
                computation_types.FunctionType(
                    parameter=collections.OrderedDict(
                        global_state=server_state_type,
                        value=client_value_type,
                        weight=client_value_weight_type),
                    result=collections.OrderedDict(
                        state=server_state_type,
                        result=server_result_type,
                        measurements=server_metrics_type,
                    ))))
    def no_noise(cls,
                 initial_estimate: float,
                 target_quantile: float,
                 learning_rate: float,
                 multiplier: float = 1.0,
                 increment: float = 0.0):
        """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.

    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')

        quantile = cls(
            tfp.NoPrivacyQuantileEstimatorQuery(
                initial_estimate=initial_estimate,
                target_quantile=target_quantile,
                learning_rate=learning_rate,
                geometric_update=True))
        if multiplier == 1.0 and increment == 0.0:
            return quantile
        else:
            return quantile.map(_affine_transform(multiplier, increment))
def build_adaptive_zeroing_mean_process(
    value_type: ValueType,
    initial_threshold: float,
    target_quantile: float,
    multiplier: float,
    learning_rate: float,
    norm_order: float,
):
    """Builds `tff.templates.MeasuredProcess` for averaging with adaptive zeroing.

  The returned `MeasuredProcess` averages values after zeroing out any values
  whose norm is greater than C * r where C is adapted to approximate the q'th
  quantile of the distribution of value norms. Its next function has the
  following type signature:

  ({state_type}@SERVER,{value_type}@CLIENTS,{float32}@CLIENTS> ->
   <state={state_type}@SERVER,result={value_type}@SERVER,
   measurements=AdaptiveZeroingMetrics@SERVER>)

  Args:
    value_type: The type of values to be averaged by the `MeasuredProcess`. Can
      be a `tff.TensorType` or a nested structure of `tff.StructType` that
      bottoms out in `tff.TensorType`.
    initial_threshold: The initial value of C * r. Values with norm greater than
      this will be zeroed out.
    target_quantile: The target quantile q. The adaptive process ensures that C
      will approximate the q'th quantile of the distribution of value norms.
    multiplier: The multiplier r of the quantile estimate C.
    learning_rate: The learning rate l for the adaptive process. If the observed
      fraction of values whose norm is less than C on a given round is p, then C
      will be updated according to C *= exp(l * (q - p)). It follows that the
      maximum possible update is multiplying or dividing by a factor of exp(l).
    norm_order: The order of the norm. May be 1, 2, or np.inf.

  Returns:
    A `MeasuredProcess` implementing averaging values with adaptive zeroing with
    the type signature described above.
  """
    # Actually value_type can be any nested structure of StructType bottoming
    # out in TensorType, but we'll just verify this much here.
    py_typecheck.check_type(
        value_type,
        (computation_types.TensorType, computation_types.StructType))

    if isinstance(value_type, computation_types.StructType):
        if not value_type:
            raise ValueError("value_type cannot be empty.")

    initial_quantile_estimate = initial_threshold / multiplier

    quantile_query = tensorflow_privacy.NoPrivacyQuantileEstimatorQuery(
        initial_estimate=initial_quantile_estimate,
        target_quantile=target_quantile,
        learning_rate=learning_rate,
        geometric_update=True)

    assert isinstance(quantile_query, tensorflow_privacy.SumAggregationDPQuery)

    @computations.tf_computation
    def initial_state_fn():
        return quantile_query.initial_global_state()

    @computations.federated_computation()
    def initial_state_comp():
        return intrinsics.federated_eval(initial_state_fn, placements.SERVER)

    global_state_type = initial_state_fn.type_signature.result

    @computations.tf_computation(global_state_type)
    def derive_sample_params(global_state):
        return quantile_query.derive_sample_params(global_state)

    @computations.tf_computation(derive_sample_params.type_signature.result,
                                 value_type, tf.float32)
    def preprocess_value(params, value, weight):
        vectors = tree.map_structure(lambda v: tf.reshape(v, [-1]), value)
        norm = tf.norm(tf.concat(tree.flatten(vectors), axis=0),
                       ord=norm_order)
        quantile_record = quantile_query.preprocess_record(params, norm)

        threshold = params.current_estimate * multiplier
        too_large = (norm > threshold)
        adj_weight = tf.cond(too_large, lambda: tf.constant(0.0),
                             lambda: weight)

        weighted_value = tree.map_structure(
            lambda v: tf.math.multiply_no_nan(v, adj_weight), value)

        too_large = tf.cast(too_large, tf.int32)
        return weighted_value, adj_weight, quantile_record, too_large

    quantile_record_type = preprocess_value.type_signature.result[2]

    @computations.tf_computation(quantile_record_type, global_state_type)
    def next_quantile(quantile_sum, global_state):
        new_estimate, new_global_state = quantile_query.get_noised_result(
            quantile_sum, global_state)
        new_threshold = new_estimate * multiplier
        return new_threshold, new_global_state

    @computations.tf_computation(value_type, tf.float32)
    def divide_no_nan(value_sum, total_weight):
        return tree.map_structure(
            lambda v: tf.math.divide_no_nan(v, total_weight), value_sum)

    @computations.federated_computation(
        initial_state_comp.type_signature.result,
        computation_types.FederatedType(value_type, placements.CLIENTS),
        computation_types.FederatedType(tf.float32, placements.CLIENTS))
    def next_fn(global_state, value, weight):
        sample_params = intrinsics.federated_broadcast(
            intrinsics.federated_map(derive_sample_params, global_state))
        weighted_value, adj_weight, quantile_record, too_large = (
            intrinsics.federated_map(preprocess_value,
                                     (sample_params, value, weight)))

        value_sum = intrinsics.federated_sum(weighted_value)
        total_weight = intrinsics.federated_sum(adj_weight)
        quantile_sum = intrinsics.federated_sum(quantile_record)
        num_zeroed = intrinsics.federated_sum(too_large)

        mean_value = intrinsics.federated_map(divide_no_nan,
                                              (value_sum, total_weight))

        new_threshold, new_global_state = intrinsics.federated_map(
            next_quantile, (quantile_sum, global_state))

        measurements = intrinsics.federated_zip(
            AdaptiveZeroingMetrics(new_threshold, num_zeroed))

        return collections.OrderedDict(state=new_global_state,
                                       result=mean_value,
                                       measurements=measurements)

    return measured_process.MeasuredProcess(initialize_fn=initial_state_comp,
                                            next_fn=next_fn)