def f1_score(answer_ids, prediction_ids, vocab):
  """Compute F1 score between answer tokens and prediction tokens.

  Args:
    answer_ids: <int32> [batch_size, answer_length]
    prediction_ids: <int32> [batch_size, prediction_length]
    vocab: Instance of text_utils.Vocab.

  Returns:
    score: <float32> [batch_size] tensor of [0.0, 1.0].
  """
  # Order insensitive, so we just create a vocabulary sized bit tensor where
  # the vocabulary items that are not to be counted are masked out.
  vocab_size = len(vocab)
  remove_ids = list(_get_normalized_set(vocab))
  remove_mask = tf.expand_dims(tf.one_hot(remove_ids, vocab_size), 0)
  remove_mask = tf.reduce_sum(remove_mask, axis=1)
  remove_mask = tf.cast(tf.equal(remove_mask, 0), tf.float32)

  # [batch_size, vocab_size]
  answer_ids = tf.reduce_sum(tf.one_hot(answer_ids, vocab_size), axis=1)
  answer_ids *= remove_mask

  # [batch_size, vocab_size]
  prediction_ids = tf.reduce_sum(tf.one_hot(prediction_ids, vocab_size), axis=1)
  prediction_ids *= remove_mask

  # Compute multiset intersection, and count the size.
  intersection = tf.minimum(prediction_ids, answer_ids)
  intersection = tf.reduce_sum(intersection, axis=1)

  # Compute F1 score:
  #   Re(A, B) = |A \cap B| / |B|
  #   Pr(A, B) = |A \cap B| / |A|
  #   F1(A, B) = 2 * (Pr * Re) / (Pr + Re)
  recall = tf.div_no_nan(intersection, tf.reduce_sum(answer_ids, axis=1))
  precision = tf.div_no_nan(intersection, tf.reduce_sum(prediction_ids, axis=1))
  score = 2 * tf.div_no_nan(precision * recall, precision + recall)

  return score
Compute F1 score between answer tokens and prediction tokens.

  Args:
    answer_ids: <int32> [batch_size, answer_length]
    prediction_ids: <int32> [batch_size, prediction_length]
    vocab: Instance of text_utils.Vocab.

  Returns:
    score: <float32> [batch_size] tensor of [0.0, 1.0].
  
language/capwap/utils/reward_utils.py