Example #1
0
  def testWeightedDistance(self):
    source_tensor = tf.constant([[1, 1], [2, 2], [0, 2], [5, 5]],
                                dtype='float32')
    target_tensor = tf.constant([[1, 1], [0, 2], [4, 4], [1, 4]],
                                dtype='float32')
    weights = tf.constant([[1], [0], [0.5], [0.5]], dtype='float32')

    l1_distance_config = configs.DistanceConfig('l1', sum_over_axis=-1)
    l1_distance_tensor = distances.pairwise_distance_wrapper(
        source_tensor, target_tensor, weights, l1_distance_config)
    l2_distance_config = configs.DistanceConfig('l2', sum_over_axis=-1)
    l2_distance_tensor = distances.pairwise_distance_wrapper(
        source_tensor, target_tensor, weights, l2_distance_config)
    with self.cached_session() as sess:
      l1_distance_value = sess.run(l1_distance_tensor)
      self.assertAllClose(l1_distance_value, 5.5 / 3)
      l2_distance_value = sess.run(l2_distance_tensor)
      self.assertAllClose(l2_distance_value, 18.5 / 3)
Example #2
0
  def testDistanceInvalidAxis(self):
    source_tensor = tf.constant(1.0, dtype='float32', shape=[4, 2])
    target_tensor = tf.constant(1.0, dtype='float32', shape=[4, 2])
    weights = tf.constant(1.0, dtype='float32', shape=[4, 2])

    distance_config = configs.DistanceConfig(sum_over_axis=2)
    with self.assertRaises(ValueError):
      distance_tensor = distances.pairwise_distance_wrapper(
          source_tensor, target_tensor, weights, distance_config)
      distance_tensor.eval()
Example #3
0
 def testL2Distance(self):
   source_tensor = tf.constant([[1, 1], [2, 2], [0, 2], [5, 5]],
                               dtype='float32')
   target_tensor = tf.constant([[1, 1], [0, 2], [4, 4], [1, 4]],
                               dtype='float32')
   distance_config = configs.DistanceConfig('l2', sum_over_axis=-1)
   distance_tensor = distances.pairwise_distance_wrapper(
       source_tensor, target_tensor, distance_config=distance_config)
   with self.cached_session() as sess:
     distance_value = sess.run(distance_tensor)
     self.assertAllClose(distance_value, 10.25)
Example #4
0
 def testCosineDistance(self):
   source_tensor = tf.constant([[1, 1], [1, 1], [3, 4], [-1, -1]],
                               dtype='float32')
   target_tensor = tf.constant([[1, 1], [5, 5], [4, 3], [1, 1]],
                               dtype='float32')
   distance_config = configs.DistanceConfig('cosine', sum_over_axis=-1)
   distance_tensor = distances.pairwise_distance_wrapper(
       source_tensor, target_tensor, distance_config=distance_config)
   with self.cached_session() as sess:
     distance_value = sess.run(distance_tensor)
     self.assertAllClose(distance_value,
                         0.51)  # sum([0.0, 1.0, 0.04, 2.0]) / 4
Example #5
0
  def testDistanceWithoutSumOverAxis(self):
    source_tensor = tf.constant([[1, 1], [2, 2], [0, 2], [5, 5]],
                                dtype='float32')
    target_tensor = tf.constant([[1, 1], [0, 2], [4, 4], [1, 4]],
                                dtype='float32')
    weights = tf.constant([[1], [0], [0.5], [0.5]], dtype='float32')

    distance_config = configs.DistanceConfig('l1')
    distance_tensor = distances.pairwise_distance_wrapper(
        source_tensor, target_tensor, weights, distance_config)
    with self.cached_session() as sess:
      distance_value = sess.run(distance_tensor)
      self.assertAllClose(distance_value, 5.5 / 6)
Example #6
0
  def testDistanceReductionMean(self):
    source_tensor = tf.constant([[1, 1], [2, 2], [0, 2], [5, 5]],
                                dtype='float32')
    target_tensor = tf.constant([[1, 1], [0, 2], [4, 4], [1, 4]],
                                dtype='float32')
    weights = tf.constant([[1], [0], [0.5], [0.5]], dtype='float32')

    distance_mean_config = configs.DistanceConfig(
        'l1', tf.compat.v1.losses.Reduction.MEAN, sum_over_axis=-1)
    distance_mean_tensor = distances.pairwise_distance_wrapper(
        source_tensor, target_tensor, weights, distance_mean_config)
    with self.cached_session() as sess:
      distance_mean_value = sess.run(distance_mean_tensor)
      self.assertAllClose(distance_mean_value, 5.5 / 2.0)
Example #7
0
 def testJensenShannonDistance(self):
   source_tensor = np.array([[1, 0, 0], [0.1, 0.2, 0.7]], dtype='float32')
   target_tensor = np.array([[1, 0, 0], [0.1, 0.9, 0]], dtype='float32')
   expected_tensor = np.sum(self._jsd_func(source_tensor, target_tensor), -1)
   expected_value = np.mean(expected_tensor)
   distance_config = configs.DistanceConfig(
       'jensen_shannon_divergence', sum_over_axis=-1)
   distance_tensor = distances.pairwise_distance_wrapper(
       tf.constant(source_tensor),
       tf.constant(target_tensor),
       distance_config=distance_config)
   with self.cached_session() as sess:
     distance_value = sess.run(distance_tensor)
     self.assertAllClose(distance_value, expected_value)
Example #8
0
  def testDistanceWithTransformButNoSumOverAxis(self):
    source = np.array([[1, 1], [2, 2], [0, 2], [10, -10]], dtype='float32')
    target = np.array([[0, 0], [0, 2], [1, 3], [3, 3]], dtype='float32')

    distance_config = configs.DistanceConfig(
        distance_type='l1',
        reduction=tf.compat.v1.losses.Reduction.NONE,
        transform_fn='softmax')
    distance_tensor = distances.pairwise_distance_wrapper(
        tf.constant(source),
        tf.constant(target),
        distance_config=distance_config)

    expected_distance = np.abs(
        self._softmax_func(source) - self._softmax_func(target))
    with self.cached_session() as sess:
      distance = sess.run(distance_tensor)
      self.assertAllClose(distance, expected_distance)
Example #9
0
  def testKLDistanceFromLogit(self):
    source = np.array([[1, 2, 3], [1, -1, 2]], dtype='float32')
    target = np.array([[1, 2, 3], [1, 0, -1]], dtype='float32')

    expected_value = np.mean(
        np.sum(
            self._kl_func(
                self._softmax_func(source), self._softmax_func(target)), -1))

    distance_config = configs.DistanceConfig(
        'kl_divergence', transform_fn='softmax', sum_over_axis=-1)
    distance_tensor = distances.pairwise_distance_wrapper(
        tf.constant(source),
        tf.constant(target),
        distance_config=distance_config)
    with self.cached_session() as sess:
      distance_value = sess.run(distance_tensor)
      self.assertAllClose(distance_value, expected_value)
    def call(self, inputs, weights=None):
        """Replicates sources and computes pairwise distance.

    Args:
      inputs: Symbolic inputs. Should be (sources, targets) if `weights` is
        non-symbolic. Otherwise, should be (sources, targets, weights).
      weights: If target weights are not symbolic, `weights` should be passed as
        a separate argument. In this case, `inputs` should have length 2.

    Returns:
      Pairwise distance tensor.
    """
        if weights is None:
            sources, targets, weights = inputs
        else:
            sources, targets = inputs

        return distances.pairwise_distance_wrapper(
            sources=self._replicate_sources(sources, targets),
            targets=targets,
            weights=weights,
            distance_config=self._distance_config)
Example #11
0
 def loss_fn(embedding, perturbed_embedding):
     return distances.pairwise_distance_wrapper(
         sources=embedding,
         targets=perturbed_embedding,
         distance_config=virtual_adv_config.distance_config)
    def graph_reg_model_fn(features, labels, mode, params=None, config=None):
        """The graph-regularized model function.

    Args:
      features: This is the first item returned from the `input_fn` passed to
        `train`, `evaluate`, and `predict`. This should be a dictionary
        containing sample features as well as corresponding neighbor features
        and neighbor weights.
      labels: This is the second item returned from the `input_fn` passed to
        `train`, `evaluate`, and `predict`. This should be a single `Tensor` or
        `dict` of same (for multi-head models). If mode is
        `tf.estimator.ModeKeys.PREDICT`, `labels=None` will be passed. If the
        `model_fn`'s signature does not accept `mode`, the `model_fn` must still
        be able to handle `labels=None`.
      mode: Optional. Specifies if this is training, evaluation, or prediction.
        See `tf.estimator.ModeKeys`.
      params: Optional `dict` of hyperparameters. Will receive what is passed to
        Estimator in the `params` parameter. This allows users to configure
        Estimators from hyper parameter tuning.
      config: Optional `tf.estimator.RunConfig` object. Will receive what is
        passed to Estimator as its `config` parameter, or a default value.
        Allows setting up things in the `model_fn` based on configuration such
        as `num_ps_replicas`, or `model_dir`. Unused currently.

    Returns:
      A `tf.estimator.EstimatorSpec` with graph regularization.
    """
        # Parameters 'params' and 'config' are optional. If they are not passed,
        # then it is possible for base_model_fn not to accept these arguments.
        # See documentation for tf.estimator.Estimator for additional context.
        kwargs = {'mode': mode}
        embedding_fn_kwargs = dict()
        if 'params' in base_model_fn_args:
            kwargs['params'] = params
            embedding_fn_kwargs['params'] = params
        if 'config' in base_model_fn_args:
            kwargs['config'] = config

        # Uses the same variable scope for calculating the original objective and
        # the graph regularization loss term.
        with tf.compat.v1.variable_scope(tf.compat.v1.get_variable_scope(),
                                         reuse=tf.compat.v1.AUTO_REUSE,
                                         auxiliary_name_scope=False):
            nbr_features = dict()
            nbr_weights = None
            if mode == tf.estimator.ModeKeys.TRAIN:
                # Extract sample features, neighbor features, and neighbor weights if we
                # are in training mode.
                sample_features, nbr_features, nbr_weights = (
                    utils.unpack_neighbor_features(
                        features, graph_reg_config.neighbor_config))
            else:
                # Otherwise, we strip out all neighbor features and use just the
                # sample's features.
                sample_features = utils.strip_neighbor_features(
                    features, graph_reg_config.neighbor_config)

            base_spec = base_model_fn(sample_features, labels, **kwargs)

            has_nbr_inputs = nbr_weights is not None and nbr_features

            # Graph regularization happens only if all the following conditions are
            # satisfied:
            # - the mode is training
            # - neighbor inputs exist
            # - the graph regularization multiplier is greater than zero.
            # So, return early if any of these conditions is false.
            if (not has_nbr_inputs or mode != tf.estimator.ModeKeys.TRAIN
                    or graph_reg_config.multiplier <= 0):
                return base_spec

            # Compute sample embeddings.
            sample_embeddings = embedding_fn(sample_features, mode,
                                             **embedding_fn_kwargs)

            # Compute the embeddings of the neighbors.
            nbr_embeddings = embedding_fn(nbr_features, mode,
                                          **embedding_fn_kwargs)

            replicated_sample_embeddings = utils.replicate_embeddings(
                sample_embeddings,
                graph_reg_config.neighbor_config.max_neighbors)

            # Compute the distance between the sample embeddings and each of their
            # corresponding neighbor embeddings.
            graph_loss = distances.pairwise_distance_wrapper(
                replicated_sample_embeddings,
                nbr_embeddings,
                weights=nbr_weights,
                distance_config=graph_reg_config.distance_config)
            scaled_graph_loss = graph_reg_config.multiplier * graph_loss
            tf.compat.v1.summary.scalar('loss/scaled_graph_loss',
                                        scaled_graph_loss)

            supervised_loss = base_spec.loss
            tf.compat.v1.summary.scalar('loss/supervised_loss',
                                        supervised_loss)

            total_loss = supervised_loss + scaled_graph_loss

            if not optimizer_fn:
                # Default to Adagrad optimizer, the same as the canned DNNEstimator.
                optimizer = tf.compat.v1.train.AdagradOptimizer(
                    learning_rate=0.05)
            else:
                optimizer = optimizer_fn()
            train_op = optimizer.minimize(
                loss=total_loss,
                global_step=tf.compat.v1.train.get_global_step())
            update_ops = tf.compat.v1.get_collection(
                tf.compat.v1.GraphKeys.UPDATE_OPS)
            if update_ops:
                train_op = tf.group(train_op, *update_ops)

        return base_spec._replace(loss=total_loss, train_op=train_op)
  def graph_reg_model_fn(features, labels, mode, params=None, config=None):
    """The graph-regularized model function.

    Args:
      features: This is the first item returned from the `input_fn` passed to
        `train`, `evaluate`, and `predict`. This should be a dictionary
        containing sample features as well as corresponding neighbor features
        and neighbor weights.
      labels: This is the second item returned from the `input_fn` passed to
        `train`, `evaluate`, and `predict`. This should be a single `Tensor` or
        `dict` of same (for multi-head models). If mode is
        `tf.estimator.ModeKeys.PREDICT`, `labels=None` will be passed. If the
        `model_fn`'s signature does not accept `mode`, the `model_fn` must still
        be able to handle `labels=None`.
      mode: Optional. Specifies if this is training, evaluation, or prediction.
        See `tf.estimator.ModeKeys`.
      params: Optional `dict` of hyperparameters. Will receive what is passed to
        Estimator in the `params` parameter. This allows users to configure
        Estimators from hyper parameter tuning.
      config: Optional `tf.estimator.RunConfig` object. Will receive what is
        passed to Estimator as its `config` parameter, or a default value.
        Allows setting up things in the `model_fn` based on configuration such
        as `num_ps_replicas`, or `model_dir`. Unused currently.

    Returns:
      A `tf.EstimatorSpec` whose loss incorporates graph-based regularization.
    """

    # Uses the same variable scope for calculating the original objective and
    # the graph regularization loss term.
    with tf.compat.v1.variable_scope(
        tf.compat.v1.get_variable_scope(),
        reuse=tf.compat.v1.AUTO_REUSE,
        auxiliary_name_scope=False):
      # Extract sample features, neighbor features, and neighbor weights.
      sample_features, nbr_features, nbr_weights = (
          utils.unpack_neighbor_features(features,
                                         graph_reg_config.neighbor_config))

      # If no 'params' is passed, then it is possible for base_model_fn not to
      # accept a 'params' argument. See documentation for tf.estimator.Estimator
      # for additional context.
      if params:
        base_spec = base_model_fn(sample_features, labels, mode, params, config)
      else:
        base_spec = base_model_fn(sample_features, labels, mode, config)

      has_nbr_inputs = nbr_weights is not None and nbr_features

      # Graph regularization happens only if all the following conditions are
      # satisfied:
      # - the mode is training
      # - neighbor inputs exist
      # - the graph regularization multiplier is greater than zero.
      # So, return early if any of these conditions is false.
      if (not has_nbr_inputs or mode != tf.estimator.ModeKeys.TRAIN or
          graph_reg_config.multiplier <= 0):
        return base_spec

      # Compute sample embeddings.
      sample_embeddings = embedding_fn(sample_features, mode)

      # Compute the embeddings of the neighbors.
      nbr_embeddings = embedding_fn(nbr_features, mode)

      replicated_sample_embeddings = utils.replicate_embeddings(
          sample_embeddings, graph_reg_config.neighbor_config.max_neighbors)

      # Compute the distance between the sample embeddings and each of their
      # corresponding neighbor embeddings.
      graph_loss = distances.pairwise_distance_wrapper(
          replicated_sample_embeddings,
          nbr_embeddings,
          weights=nbr_weights,
          distance_config=graph_reg_config.distance_config)
      total_loss = base_spec.loss + graph_reg_config.multiplier * graph_loss

      if not optimizer_fn:
        # Default to Adagrad optimizer, the same as the canned DNNEstimator.
        optimizer = tf.train.AdagradOptimizer(learning_rate=0.05)
      else:
        optimizer = optimizer_fn()
      final_train_op = optimizer.minimize(
          loss=total_loss, global_step=tf.compat.v1.train.get_global_step())

    return base_spec._replace(loss=total_loss, train_op=final_train_op)