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