示例#1
0
def _approx_ndcg_loss(labels,
                      logits,
                      weights=None,
                      reduction=tf.compat.v1.losses.Reduction.SUM,
                      name=None,
                      alpha=10.):
  """Computes ApproxNDCG loss.

  ApproxNDCG ["A general approximation framework for direct optimization of
  information retrieval measures" by Qin et al.] is a smooth approximation
  to NDCG. Its performance on datasets with graded relevance is competitive
  with other state-of-the-art algorithms [see "Revisiting Approximate Metric
  Optimization in the Age of Deep Neural Networks" by Bruch et al.].

  Args:
    labels: A `Tensor` of the same shape as `logits` representing graded
      relevance.
    logits: A `Tensor` with shape [batch_size, list_size]. Each value is the
      ranking score of the corresponding item.
    weights: A scalar, a `Tensor` with shape [batch_size, 1] for list-wise
      weights, or a `Tensor` with shape [batch_size, list_size] for item-wise
      weights. If None, the weight of a list in the mini-batch is set to the sum
      of the labels of the items in that list.
    reduction: One of `tf.losses.Reduction` except `NONE`. Describes how to
      reduce training loss over batch.
    name: A string used as the name for this loss.
    alpha: The exponent in the generalized sigmoid function.

  Returns:
    An op for the ApproxNDCG loss.
  """
  loss = losses_impl.ApproxNDCGLoss(name, params={'alpha': alpha})
  with tf.compat.v1.name_scope(loss.name, 'approx_ndcg_loss',
                               (labels, logits, weights)):
    return loss.compute(labels, logits, weights, reduction)
示例#2
0
 def __init__(self,
              reduction=tf.losses.Reduction.AUTO,
              name=None,
              lambda_weight=None):
   super(ApproxNDCGLoss, self).__init__(reduction, name)
   self._loss = losses_impl.ApproxNDCGLoss(
       name='{}_impl'.format(name), lambda_weight=lambda_weight)
示例#3
0
    def test_approx_ndcg_loss(self):
        with tf.Graph().as_default():
            scores = [[1.4, -2.8, -0.4], [0., 1.8, 10.2], [1., 1.2, -3.2]]
            # ranks= [[1,    3,    2],   [2,  1,   3],    [2,  1,    3]]
            labels = [[0., 2., 1.], [1., 0., -1.], [0., 0., 0.]]
            weights = [[2.], [1.], [1.]]
            example_weights = [[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]
            norm_weights = []
            for weight, label in zip(example_weights, labels):
                sum_label = sum(max(0, l) for l in label)
                norm_weights.append(
                    sum(w * max(0, l) for w, l in zip(weight, label)) /
                    sum_label if sum_label else 0)
            reduction = tf.compat.v1.losses.Reduction.SUM

            with self.cached_session():
                loss_fn = losses_impl.ApproxNDCGLoss(name=None,
                                                     temperature=0.1)
                self.assertAlmostEqual(
                    loss_fn.compute(labels, scores, None, reduction).eval(),
                    -((1 / (3 / ln(2) + 1 / ln(3))) *
                      (3 / ln(4) + 1 / ln(3)) + ln(2) * (1 / ln(3))),
                    places=5)
                self.assertAlmostEqual(
                    loss_fn.compute(labels, scores, weights, reduction).eval(),
                    -(2 * (1 / (3 / ln(2) + 1 / ln(3))) *
                      (3 / ln(4) + 1 / ln(3)) + 1 * ln(2) * (1 / ln(3))),
                    places=5)
                self.assertAlmostEqual(
                    loss_fn.compute(labels, scores, example_weights,
                                    reduction).eval(),
                    -(norm_weights[0] * (1 / (3 / ln(2) + 1 / ln(3))) *
                      (3 / ln(4) + 1 / ln(3)) + norm_weights[1] * ln(2) *
                      (1 / ln(3))),
                    places=5)
示例#4
0
 def __init__(self,
              reduction=tf.losses.Reduction.AUTO,
              name=None,
              lambda_weight=None,
              temperature=0.1,
              ragged=False):
     super().__init__(reduction, name, lambda_weight, temperature, ragged)
     self._loss = losses_impl.ApproxNDCGLoss(
         name='{}_impl'.format(name) if name else None,
         lambda_weight=lambda_weight,
         temperature=temperature,
         ragged=ragged)
示例#5
0
    def test_listwise_compute_per_list(self):
        with tf.Graph().as_default():
            scores = [[1., 3., 2.], [1., 2., 3.]]
            labels = [[0., 0., 1.], [0., 0., 2.]]
            per_item_weights = [[2., 3., 4.], [1., 1., 1.]]

            with self.cached_session():
                # ApproxNDCGLoss is chosen as an arbitrary listwise loss to test the
                # `compute_per_list` behavior.
                loss_fn = losses_impl.ApproxNDCGLoss(name=None)
                losses, weights = loss_fn.compute_per_list(
                    labels, scores, per_item_weights)
                losses, weights = losses.eval(), weights.eval()

            self.assertAllClose(losses, [-0.63093, -0.796248])
            self.assertAllClose(weights, [4., 1.])
示例#6
0
def make_loss_metric_fn(loss_key,
                        weights_feature_name=None,
                        lambda_weight=None,
                        name=None):
  """Factory method to create a metric based on a loss.

  Args:
    loss_key: A key in `RankingLossKey`.
    weights_feature_name: A `string` specifying the name of the weights feature
      in `features` dict.
    lambda_weight: A `_LambdaWeight` object.
    name: A `string` used as the name for this metric.

  Returns:
    A metric fn with the following Args:
    * `labels`: A `Tensor` of the same shape as `predictions` representing
    graded relevance.
    * `predictions`: A `Tensor` with shape [batch_size, list_size]. Each value
    is the ranking score of the corresponding example.
    * `features`: A dict of `Tensor`s that contains all features.
  """

  metric_dict = {
      RankingLossKey.PAIRWISE_HINGE_LOSS:
          losses_impl.PairwiseHingeLoss(name, lambda_weight=lambda_weight),
      RankingLossKey.PAIRWISE_LOGISTIC_LOSS:
          losses_impl.PairwiseLogisticLoss(name, lambda_weight=lambda_weight),
      RankingLossKey.PAIRWISE_SOFT_ZERO_ONE_LOSS:
          losses_impl.PairwiseSoftZeroOneLoss(
              name, lambda_weight=lambda_weight),
      RankingLossKey.SOFTMAX_LOSS:
          losses_impl.SoftmaxLoss(name, lambda_weight=lambda_weight),
      RankingLossKey.SIGMOID_CROSS_ENTROPY_LOSS:
          losses_impl.SigmoidCrossEntropyLoss(name),
      RankingLossKey.MEAN_SQUARED_LOSS:
          losses_impl.MeanSquaredLoss(name),
      RankingLossKey.LIST_MLE_LOSS:
          losses_impl.ListMLELoss(name, lambda_weight=lambda_weight),
      RankingLossKey.APPROX_NDCG_LOSS:
          losses_impl.ApproxNDCGLoss(name),
      RankingLossKey.APPROX_MRR_LOSS:
          losses_impl.ApproxMRRLoss(name),
      RankingLossKey.GUMBEL_APPROX_NDCG_LOSS: losses_impl.ApproxNDCGLoss(name),
  }

  def _get_weights(features):
    """Get weights tensor from features and reshape it to 2-D if necessary."""
    weights = None
    if weights_feature_name:
      weights = tf.convert_to_tensor(value=features[weights_feature_name])
      # Convert weights to a 2-D Tensor.
      weights = utils.reshape_to_2d(weights)
    return weights

  def metric_fn(labels, predictions, features):
    """Defines the metric fn."""
    weights = _get_weights(features)
    loss = metric_dict.get(loss_key, None)
    if loss is None:
      raise ValueError('loss_key {} not supported.'.format(loss_key))
    return loss.eval_metric(labels, predictions, weights)

  return metric_fn