Пример #1
0
    def test_to_odict(self):
        d1 = {'b': 2, 'a': 1}
        odict1 = tensor_utils.to_odict(d1)
        self.assertIsInstance(odict1, collections.OrderedDict)
        self.assertCountEqual(d1, odict1)

        odict2 = tensor_utils.to_odict(odict1)
        self.assertEqual(odict1, odict2)

        with self.assertRaises(TypeError):
            tensor_utils.to_odict({1: 'a', 2: 'b'})
Пример #2
0
    def test_server_eager_mode(self, optimizer_fn, updated_val,
                               num_optimizer_vars):
        model_fn = lambda: model_examples.TrainableLinearRegression(feature_dim
                                                                    =2)

        server_state = optimizer_utils.server_init(model_fn, optimizer_fn, (),
                                                   ())
        train_vars = server_state.model.trainable
        self.assertAllClose(train_vars['a'].numpy(), np.array([[0.0], [0.0]]))
        self.assertEqual(train_vars['b'].numpy(), 0.0)
        self.assertEqual(server_state.model.non_trainable['c'].numpy(), 0.0)
        self.assertLen(server_state.optimizer_state, num_optimizer_vars)
        weights_delta = tensor_utils.to_odict({
            'a': tf.constant([[1.0], [0.0]]),
            'b': tf.constant(1.0)
        })
        server_state = optimizer_utils.server_update_model(
            server_state, weights_delta, model_fn, optimizer_fn)

        train_vars = server_state.model.trainable
        # For SGD: learning_Rate=0.1, update=[1.0, 0.0], initial model=[0.0, 0.0],
        # so updated_val=0.1
        self.assertAllClose(train_vars['a'].numpy(), [[updated_val], [0.0]])
        self.assertAllClose(train_vars['b'].numpy(), updated_val)
        self.assertEqual(server_state.model.non_trainable['c'].numpy(), 0.0)
Пример #3
0
def client_update(model, dataset, initial_weights):
    """Updates client model.

  Args:
    model: A `tff.learning.Model`.
    dataset: A 'tf.data.Dataset'.
    initial_weights: A `tff.learning.Model.weights` from server.

  Returns:
    A 'ClientOutput`.
  """
    tf.nest.map_structure(tf.assign, _get_weights(model), initial_weights)

    @tf.function
    def reduce_fn(num_examples_sum, batch):
        """Runs `tff.learning.Model.train_on_batch` on local client batch."""
        output = model.train_on_batch(batch)
        return num_examples_sum + tf.shape(output.predictions)[0]

    num_examples_sum = dataset.reduce(initial_state=tf.constant(0),
                                      reduce_func=reduce_fn)

    weights_delta = tf.nest.map_structure(tf.subtract,
                                          _get_weights(model).trainable,
                                          initial_weights.trainable)
    aggregated_outputs = model.report_local_outputs()

    weights_delta_weight = tf.cast(num_examples_sum, tf.float32)

    return ClientOutput(
        weights_delta, weights_delta_weight, aggregated_outputs,
        tensor_utils.to_odict({
            'num_examples': num_examples_sum,
        }))
Пример #4
0
    def __call__(self, dataset, initial_weights):
        # TODO(b/123898430): The control dependencies below have been inserted as a
        # temporary workaround. These control dependencies need to be removed, and
        # defuns and datasets supported together fully.
        model = self._model

        # TODO(b/113112108): Remove this temporary workaround and restore check for
        # `tf.data.Dataset` after subclassing the currently used custom data set
        # representation from it.
        if 'Dataset' not in str(type(dataset)):
            raise TypeError('Expected a data set, found {}.'.format(
                py_typecheck.type_string(type(dataset))))

        # TODO(b/120801384): We should initialize model.local_variables here.
        # Or, we may just need a convention that TFF initializes all variables
        # before invoking the TF function.

        # We must assign to a variable here in order to use control_dependencies.
        dummy_weights = nest.map_structure(tf.assign, model.weights,
                                           initial_weights)

        with tf.control_dependencies(list(dummy_weights.trainable.values())):

            def reduce_fn(dummy_state, batch):
                """Runs `tff.learning.Model.train_on_batch` on local client batch."""
                output = model.train_on_batch(batch)
                tf.assign_add(self._num_examples,
                              tf.shape(output.predictions)[0])
                return dummy_state

            # TODO(b/124477598): Remove dummy_output when b/121400757 fixed.
            dummy_output = dataset.reduce(initial_state=tf.constant(0.0),
                                          reduce_func=reduce_fn)

        with tf.control_dependencies([dummy_output]):
            weights_delta = nest.map_structure(tf.subtract,
                                               model.weights.trainable,
                                               initial_weights.trainable)
            aggregated_outputs = model.report_local_outputs()
            weights_delta_weight = self._client_weight_fn(aggregated_outputs)  # pylint:disable=not-callable

            # TODO(b/122071074): Consider moving this functionality into
            # tff.federated_average?
            weights_delta, has_non_finite_delta = (
                tensor_utils.zero_all_if_any_non_finite(weights_delta))
            weights_delta_weight = tf.cond(tf.equal(has_non_finite_delta, 0),
                                           lambda: weights_delta_weight,
                                           lambda: tf.constant(0))

            return optimizer_utils.ClientOutput(
                weights_delta, weights_delta_weight, aggregated_outputs,
                tensor_utils.to_odict({
                    'num_examples':
                    self._num_examples.value(),
                    'has_non_finite_delta':
                    has_non_finite_delta,
                    'workaround for b/121400757':
                    dummy_output,
                }))
Пример #5
0
    def __call__(self, dataset, initial_weights):
        # TODO(b/113112108): Remove this temporary workaround and restore check for
        # `tf.data.Dataset` after subclassing the currently used custom data set
        # representation from it.
        if 'Dataset' not in str(type(dataset)):
            raise TypeError('Expected a data set, found {}.'.format(
                py_typecheck.type_string(type(dataset))))

        model = self._model
        dummy_weights = nest.map_structure(tf.assign, model.weights,
                                           initial_weights)

        def reduce_fn(accumulated_grads, batch):
            """Runs forward_pass on batch."""
            with tf.contrib.eager.GradientTape() as tape:
                output = model.forward_pass(batch)

            with tf.control_dependencies(list(output)):
                flat_vars = nest.flatten(model.weights.trainable)
                grads = nest.pack_sequence_as(
                    accumulated_grads, tape.gradient(output.loss, flat_vars))

                if self._batch_weight_fn is not None:
                    batch_weight = self._batch_weight_fn(batch)
                else:
                    batch_weight = tf.cast(
                        tf.shape(output.predictions)[0], tf.float32)

            tf.assign_add(self._batch_weight_sum, batch_weight)
            return nest.map_structure(
                lambda accumulator, grad: accumulator + batch_weight * grad,
                accumulated_grads, grads)

        with tf.control_dependencies(list(dummy_weights.trainable.values())):
            self._grad_sum_vars = dataset.reduce(
                initial_state=self._grad_sum_vars, reduce_func=reduce_fn)

        with tf.control_dependencies(
            [tf.identity(v) for v in self._grad_sum_vars.values()]):
            # For SGD, the delta is just the negative of the average gradient:
            weights_delta = nest.map_structure(
                lambda gradient: -1.0 * gradient / self._batch_weight_sum,
                self._grad_sum_vars)
            weights_delta, has_non_finite_delta = (
                tensor_utils.zero_all_if_any_non_finite(weights_delta))
            weights_delta_weight = tf.cond(tf.equal(has_non_finite_delta, 0),
                                           lambda: self._batch_weight_sum,
                                           lambda: tf.constant(0.0))

            return optimizer_utils.ClientOutput(
                weights_delta, weights_delta_weight,
                model.report_local_outputs(),
                tensor_utils.to_odict({
                    'client_weight':
                    weights_delta_weight,
                    'has_non_finite_delta':
                    has_non_finite_delta,
                }))
Пример #6
0
    def __call__(self, dataset, initial_weights):
        # N.B. When not in eager mode, this code must be wrapped as a defun
        # as it uses program-order semantics to avoid adding many explicit
        # control dependencies.
        model = self._model
        py_typecheck.check_type(dataset, tf.data.Dataset)

        nest.map_structure(tf.assign, model.weights, initial_weights)

        @tf.contrib.eager.function(autograph=False)
        def reduce_fn(dummy_state, batch):
            """Runs forward_pass on batch."""
            with tf.contrib.eager.GradientTape() as tape:
                output = model.forward_pass(batch)

            flat_vars = nest.flatten(model.weights.trainable)
            grads = nest.pack_sequence_as(
                self._grad_sum_vars, tape.gradient(output.loss, flat_vars))

            if self._batch_weight_fn is not None:
                batch_weight = self._batch_weight_fn(batch)
            else:
                batch_weight = tf.cast(
                    tf.shape(output.predictions)[0], tf.float32)

            tf.assign_add(self._batch_weight_sum, batch_weight)
            nest.map_structure(
                lambda v, g:  # pylint:disable=g-long-lambda
                tf.assign_add(v, batch_weight * g),
                self._grad_sum_vars,
                grads)

            return dummy_state

        # TODO(b/121400757): Remove dummy_output when bug fixed.
        dummy_output = dataset.reduce(initial_state=tf.constant(0.0),
                                      reduce_func=reduce_fn)

        # For SGD, the delta is just the negative of the average gradient:
        # TODO(b/109733734): Might be better to send the weighted grad sums
        # and the denominator separately?
        weights_delta = nest.map_structure(
            lambda g: -1.0 * g / self._batch_weight_sum, self._grad_sum_vars)
        weights_delta, has_non_finite_delta = (
            tensor_utils.zero_all_if_any_non_finite(weights_delta))
        weights_delta_weight = tf.cond(tf.equal(has_non_finite_delta, 0),
                                       lambda: self._batch_weight_sum,
                                       lambda: tf.constant(0.0))

        return optimizer_utils.ClientOutput(
            weights_delta, weights_delta_weight, model.report_local_outputs(),
            tensor_utils.to_odict({
                'client_weight': weights_delta_weight,
                'has_non_finite_delta': has_non_finite_delta,
                'workaround for b/121400757': dummy_output,
            }))
Пример #7
0
    def __call__(self, dataset, initial_weights):
        # TODO(b/113112108): Remove this temporary workaround and restore check for
        # `tf.data.Dataset` after subclassing the currently used custom data set
        # representation from it.
        if 'Dataset' not in str(type(dataset)):
            raise TypeError('Expected a data set, found {}.'.format(
                py_typecheck.type_string(type(dataset))))

        model = self._model
        tf.nest.map_structure(lambda a, b: a.assign(b), model.weights,
                              initial_weights)

        @tf.function
        def reduce_fn(num_examples_sum, batch):
            """Runs `tff.learning.Model.train_on_batch` on local client batch."""
            output = model.train_on_batch(batch)
            if output.num_examples is None:
                return num_examples_sum + tf.shape(output.predictions)[0]
            else:
                return num_examples_sum + output.num_examples

        num_examples_sum = dataset.reduce(initial_state=tf.constant(0),
                                          reduce_func=reduce_fn)

        weights_delta = tf.nest.map_structure(tf.subtract,
                                              model.weights.trainable,
                                              initial_weights.trainable)
        aggregated_outputs = model.report_local_outputs()

        # TODO(b/122071074): Consider moving this functionality into
        # tff.federated_mean?
        weights_delta, has_non_finite_delta = (
            tensor_utils.zero_all_if_any_non_finite(weights_delta))
        if self._client_weight_fn is None:
            weights_delta_weight = tf.cast(num_examples_sum, tf.float32)
        else:
            weights_delta_weight = self._client_weight_fn(aggregated_outputs)
        # Zero out the weight if there are any non-finite values.
        if has_non_finite_delta > 0:
            weights_delta_weight = tf.constant(0.0)

        return optimizer_utils.ClientOutput(
            weights_delta, weights_delta_weight, aggregated_outputs,
            tensor_utils.to_odict({
                'num_examples': num_examples_sum,
                'has_non_finite_delta': has_non_finite_delta,
            }))
Пример #8
0
    def test_server_graph_mode(self):
        optimizer_fn = lambda: gradient_descent.SGD(learning_rate=0.1)
        model_fn = lambda: model_examples.TrainableLinearRegression(feature_dim
                                                                    =2)

        # Explicitly entering a graph as a default enables graph-mode.
        with tf.Graph().as_default() as g:
            server_state_op = optimizer_utils.server_init(
                model_fn, optimizer_fn, (), ())
            init_op = tf.group(tf.global_variables_initializer(),
                               tf.local_variables_initializer())
            g.finalize()
            with self.session() as sess:
                sess.run(init_op)
                server_state = sess.run(server_state_op)
        train_vars = server_state.model.trainable
        self.assertAllClose(train_vars['a'], [[0.0], [0.0]])
        self.assertEqual(train_vars['b'], 0.0)
        self.assertEqual(server_state.model.non_trainable['c'], 0.0)
        self.assertEqual(server_state.optimizer_state, [0.0])

        with tf.Graph().as_default() as g:
            # N.B. Must use a fresh graph so variable names are the same.
            weights_delta = tensor_utils.to_odict({
                'a':
                tf.constant([[1.0], [0.0]]),
                'b':
                tf.constant(2.0)
            })
            update_op = optimizer_utils.server_update_model(
                server_state, weights_delta, model_fn, optimizer_fn)
            init_op = tf.group(tf.global_variables_initializer(),
                               tf.local_variables_initializer())
            g.finalize()
            with self.session() as sess:
                sess.run(init_op)
                server_state = sess.run(update_op)
        train_vars = server_state.model.trainable
        # learning_Rate=0.1, update is [1.0, 0.0], initial model is [0.0, 0.0].
        self.assertAllClose(train_vars['a'], [[0.1], [0.0]])
        self.assertAllClose(train_vars['b'], 0.2)
        self.assertEqual(server_state.model.non_trainable['c'], 0.0)
Пример #9
0
    def __call__(self, dataset, initial_weights):
        model = self._model

        # TODO(b/113112108): Remove this temporary workaround and restore check for
        # `tf.data.Dataset` after subclassing the currently used custom data set
        # representation from it.
        if 'Dataset' not in str(type(dataset)):
            raise TypeError('Expected a data set, found {}.'.format(
                py_typecheck.type_string(type(dataset))))

        tf.nest.map_structure(lambda a, b: a.assign(b), model.weights,
                              initial_weights)
        flat_trainable_weights = tuple(tf.nest.flatten(
            model.weights.trainable))

        @tf.function
        def reduce_fn(state, batch):
            """Runs forward_pass on batch and sums the weighted gradients."""
            flat_accumulated_grads, batch_weight_sum = state

            with tf.GradientTape() as tape:
                output = model.forward_pass(batch)
            flat_grads = tape.gradient(output.loss, flat_trainable_weights)

            if self._batch_weight_fn is not None:
                batch_weight = self._batch_weight_fn(batch)
            else:
                batch_weight = tf.cast(
                    tf.shape(output.predictions)[0], tf.float32)

            flat_accumulated_grads = tuple(
                accumulator + batch_weight * grad for accumulator, grad in zip(
                    flat_accumulated_grads, flat_grads))

            # The TF team is aware of an optimization in the reduce state to avoid
            # doubling the number of required variables here (e.g. keeping two copies
            # of all gradients). If you're looking to optimize memory usage this might
            # be a place to look.
            return (flat_accumulated_grads, batch_weight_sum + batch_weight)

        def _zero_initial_state():
            """Create a tuple of (tuple of gradient accumulators, batch weight sum)."""
            return (tuple(tf.zeros_like(w)
                          for w in flat_trainable_weights), tf.constant(0.0))

        flat_grad_sums, batch_weight_sum = self._dataset_reduce_fn(
            reduce_fn=reduce_fn,
            dataset=dataset,
            initial_state_fn=_zero_initial_state)
        grad_sums = tf.nest.pack_sequence_as(model.weights.trainable,
                                             flat_grad_sums)

        # For SGD, the delta is just the negative of the average gradient:
        weights_delta = tf.nest.map_structure(
            lambda gradient: -1.0 * gradient / batch_weight_sum, grad_sums)
        weights_delta, has_non_finite_delta = (
            tensor_utils.zero_all_if_any_non_finite(weights_delta))
        if has_non_finite_delta > 0:
            weights_delta_weight = tf.constant(0.0)
        else:
            weights_delta_weight = batch_weight_sum
        return optimizer_utils.ClientOutput(
            weights_delta, weights_delta_weight, model.report_local_outputs(),
            tensor_utils.to_odict({
                'client_weight': weights_delta_weight,
                'has_non_finite_delta': has_non_finite_delta,
            }))
Пример #10
0
 def __new__(cls, trainable, non_trainable):
   return super(ModelWeights,
                cls).__new__(cls, tensor_utils.to_odict(trainable),
                             tensor_utils.to_odict(non_trainable))
Пример #11
0
  def __call__(self, model, optimizer, benign_dataset, malicious_dataset,
               client_is_malicious, initial_weights):
    """Updates client model with client potentially being malicious.

    Args:
      model: A `tff.learning.Model`.
      optimizer: A 'tf.keras.optimizers.Optimizer'.
      benign_dataset: A 'tf.data.Dataset' consisting of benign dataset.
      malicious_dataset: A 'tf.data.Dataset' consisting of malicious dataset.
      client_is_malicious: A 'tf.bool' showing whether the client is malicious.
      initial_weights: A `tff.learning.Model.weights` from server.

    Returns:
      A 'ClientOutput`.
    """
    model_weights = _get_weights(model)

    @tf.function
    def clip_by_norm(gradient, norm):
      """Clip the gradient by its l2 norm."""
      norm = tf.cast(norm, tf.float32)
      delta_norm = _get_norm(gradient)

      if delta_norm < norm:
        return gradient
      else:
        delta_mul_factor = tf.math.divide_no_nan(norm, delta_norm)
        return tf.nest.map_structure(lambda g: g * delta_mul_factor, gradient)

    @tf.function
    def project_weights(weights, initial_weights, norm):
      """Project the weight onto l2 ball around initial_weights with radius norm."""
      weights_delta = tf.nest.map_structure(lambda a, b: a - b, weights,
                                            initial_weights)

      return tf.nest.map_structure(tf.add, clip_by_norm(weights_delta, norm),
                                   initial_weights)

    @tf.function
    def reduce_fn(num_examples_sum, batch):
      """Runs `tff.learning.Model.train_on_batch` on local client batch."""
      with tf.GradientTape() as tape:
        output = model.forward_pass(batch)
      gradients = tape.gradient(output.loss, model.trainable_variables)
      optimizer.apply_gradients(zip(gradients, model.trainable_variables))
      return num_examples_sum + tf.shape(output.predictions)[0]

    @tf.function
    def compute_benign_update():
      """compute benign update sent back to the server."""
      tf.nest.map_structure(lambda a, b: a.assign(b), model_weights,
                            initial_weights)

      num_examples_sum = benign_dataset.reduce(
          initial_state=tf.constant(0), reduce_func=reduce_fn)

      weights_delta_benign = tf.nest.map_structure(lambda a, b: a - b,
                                                   model_weights.trainable,
                                                   initial_weights.trainable)

      aggregated_outputs = model.report_local_outputs()

      return weights_delta_benign, aggregated_outputs, num_examples_sum

    @tf.function
    def compute_malicious_update():
      """compute malicious update sent back to the server."""

      _, aggregated_outputs, num_examples_sum = compute_benign_update()

      tf.nest.map_structure(lambda a, b: a.assign(b), model_weights,
                            initial_weights)

      for _ in range(self.round_num):
        benign_dataset.reduce(
            initial_state=tf.constant(0), reduce_func=reduce_fn)
        malicious_dataset.reduce(
            initial_state=tf.constant(0), reduce_func=reduce_fn)

        tf.nest.map_structure(
            lambda a, b: a.assign(b), model_weights.trainable,
            project_weights(model_weights.trainable, initial_weights.trainable,
                            tf.cast(self.norm_bound, tf.float32)))

      weights_delta_malicious = tf.nest.map_structure(lambda a, b: a - b,
                                                      model_weights.trainable,
                                                      initial_weights.trainable)
      weights_delta = tf.nest.map_structure(
          lambda update: self.boost_factor * update, weights_delta_malicious)

      return weights_delta, aggregated_outputs, num_examples_sum

    if client_is_malicious:
      result = compute_malicious_update()
    else:
      result = compute_benign_update()
    weights_delta, aggregated_outputs, num_examples_sum = result

    weights_delta_weight = tf.cast(num_examples_sum, tf.float32)
    weight_norm = _get_norm(weights_delta)

    return ClientOutput(
        weights_delta, weights_delta_weight, aggregated_outputs,
        tensor_utils.to_odict({
            'num_examples': num_examples_sum,
            'weight_norm': weight_norm,
        }))
Пример #12
0
  def __call__(self, model, optimizer, benign_dataset, malicious_dataset,
               client_type, initial_weights):
    """Updates client model with client potentially being malicious.

    Args:
      model: A `tff.learning.Model`.
      optimizer: A 'tf.keras.optimizers.Optimizer'.
      benign_dataset: A 'tf.data.Dataset' consisting of benign dataset.
      malicious_dataset: A 'tf.data.Dataset' consisting of malicious dataset.
      client_type: A 'tf.bool' indicating whether the client is malicious; iff
        `True` the client will construct its update using `malicious_dataset`,
        otherwise will construct the update using `benign_dataset`.
      initial_weights: A `tff.learning.Model.weights` from server.

    Returns:
      A 'ClientOutput`.
    """
    model_weights = _get_weights(model)

    @tf.function
    def reduce_fn(num_examples_sum, batch):
      """Runs `tff.learning.Model.train_on_batch` on local client batch."""
      with tf.GradientTape() as tape:
        output = model.forward_pass(batch)
      gradients = tape.gradient(output.loss, model.trainable_variables)
      optimizer.apply_gradients(zip(gradients, model.trainable_variables))
      return num_examples_sum + tf.shape(output.predictions)[0]

    @tf.function
    def compute_benign_update():
      """compute benign update sent back to the server."""
      tf.nest.map_structure(lambda a, b: a.assign(b), model_weights,
                            initial_weights)

      num_examples_sum = benign_dataset.reduce(
          initial_state=tf.constant(0), reduce_func=reduce_fn)

      weights_delta_benign = tf.nest.map_structure(lambda a, b: a - b,
                                                   model_weights.trainable,
                                                   initial_weights.trainable)

      aggregated_outputs = model.report_local_outputs()

      return weights_delta_benign, aggregated_outputs, num_examples_sum

    @tf.function
    def compute_malicious_update():
      """compute malicious update sent back to the server."""

      weights_delta_benign, aggregated_outputs, num_examples_sum \
          = compute_benign_update()

      tf.nest.map_structure(lambda a, b: a.assign(b), model_weights,
                            initial_weights)

      malicious_dataset.reduce(
          initial_state=tf.constant(0), reduce_func=reduce_fn)

      weights_delta_malicious = tf.nest.map_structure(lambda a, b: a - b,
                                                      model_weights.trainable,
                                                      initial_weights.trainable)

      weights_delta = tf.nest.map_structure(
          tf.add, weights_delta_benign,
          tf.nest.map_structure(lambda delta: delta * self.boost_factor,
                                weights_delta_malicious))

      return weights_delta, aggregated_outputs, num_examples_sum

    result = tf.cond(
        tf.equal(client_type, True), compute_malicious_update,
        compute_benign_update)
    weights_delta, aggregated_outputs, num_examples_sum = result

    weights_delta_weight = tf.cast(num_examples_sum, tf.float32)

    weight_norm = _get_norm(weights_delta)

    return ClientOutput(
        weights_delta, weights_delta_weight, aggregated_outputs,
        tensor_utils.to_odict({
            'num_examples': num_examples_sum,
            'weight_norm': weight_norm,
        }))