def __call__(self, predictions: torch.Tensor, gold_labels: torch.Tensor, mask: Optional[torch.Tensor] = None): """ Parameters ---------- predictions : ``torch.Tensor``, required. A tensor of predictions of shape (batch_size, sequence_length, num_classes). gold_labels : ``torch.Tensor``, required. A tensor of integer class label of shape (batch_size, sequence_length). It must be the same shape as the ``predictions`` tensor without the ``num_classes`` dimension. mask: ``torch.Tensor``, optional (default = None). A masking tensor the same size as ``gold_labels``. """ if mask is None: mask = ones_like(gold_labels) # If you actually passed in Variables here instead of Tensors, this will be a huge memory # leak, because it will prevent garbage collection for the computation graph. We'll ensure # that we're using tensors here first. if isinstance(predictions, Variable): predictions = predictions.data.cpu() if isinstance(gold_labels, Variable): gold_labels = gold_labels.data.cpu() if isinstance(mask, Variable): mask = mask.data.cpu() num_classes = predictions.size(-1) if (gold_labels >= num_classes).any(): raise ConfigurationError( "A gold label passed to SpanBasedF1Measure contains an " "id >= {}, the number of classes.".format(num_classes)) sequence_lengths = get_lengths_from_binary_sequence_mask(mask) argmax_predictions = predictions.max(-1)[1].float() # Iterate over timesteps in batch. batch_size = gold_labels.size(0) for i in range(batch_size): sequence_prediction = argmax_predictions[i, :] sequence_gold_label = gold_labels[i, :] length = sequence_lengths[i] prediction_spans = self._extract_spans( sequence_prediction[:length].tolist()) gold_spans = self._extract_spans( sequence_gold_label[:length].tolist()) for span in prediction_spans: if span in gold_spans: self._true_positives[span[1]] += 1 gold_spans.remove(span) else: self._false_positives[span[1]] += 1 # These spans weren't predicted. for span in gold_spans: self._false_negatives[span[1]] += 1
def __call__(self, predictions: torch.Tensor, gold_labels: torch.Tensor, mask: Optional[torch.Tensor] = None): """ Parameters ---------- predictions : ``torch.Tensor``, required. A tensor of predictions of shape (batch_size, ..., num_classes). gold_labels : ``torch.Tensor``, required. A tensor of integer class label of shape (batch_size, ...). It must be the same shape as the ``predictions`` tensor without the ``num_classes`` dimension. mask: ``torch.Tensor``, optional (default = None). A masking tensor the same size as ``gold_labels``. """ # Get the data from the Variables. predictions, gold_labels, mask = self.unwrap_to_tensors( predictions, gold_labels, mask) num_classes = predictions.size(-1) if (gold_labels >= num_classes).any(): raise ConfigurationError( "A gold label passed to F1Measure contains an id >= {}, " "the number of classes.".format(num_classes)) if mask is None: mask = ones_like(gold_labels) mask = mask.float() gold_labels = gold_labels.float() positive_label_mask = gold_labels.eq(self._positive_label).float() negative_label_mask = 1.0 - positive_label_mask argmax_predictions = predictions.topk(1, -1)[1].float().squeeze(-1) # True Negatives: correct non-positive predictions. correct_null_predictions = (argmax_predictions != self._positive_label ).float() * negative_label_mask self._true_negatives += (correct_null_predictions.float() * mask).sum() # True Positives: correct positively labeled predictions. correct_non_null_predictions = ( argmax_predictions == self._positive_label).float() * positive_label_mask self._true_positives += (correct_non_null_predictions * mask).sum() # False Negatives: incorrect negatively labeled predictions. incorrect_null_predictions = ( argmax_predictions != self._positive_label).float() * positive_label_mask self._false_negatives += (incorrect_null_predictions * mask).sum() # False Positives: incorrect positively labeled predictions incorrect_non_null_predictions = ( argmax_predictions == self._positive_label).float() * negative_label_mask self._false_positives += (incorrect_non_null_predictions * mask).sum()
def __call__(self, predictions: torch.Tensor, gold_labels: torch.Tensor, mask: Optional[torch.Tensor] = None): """ Parameters ---------- predictions : ``torch.Tensor``, required. A tensor of predictions of shape (batch_size, sequence_length, num_classes). gold_labels : ``torch.Tensor``, required. A tensor of integer class label of shape (batch_size, sequence_length). It must be the same shape as the ``predictions`` tensor without the ``num_classes`` dimension. mask: ``torch.Tensor``, optional (default = None). A masking tensor the same size as ``gold_labels``. """ if mask is None: mask = ones_like(gold_labels) # Get the data from the Variables. predictions, gold_labels, mask = self.unwrap_to_tensors( predictions, gold_labels, mask) num_classes = predictions.size(-1) if (gold_labels >= num_classes).any(): raise ConfigurationError( "A gold label passed to SpanBasedF1Measure contains an " "id >= {}, the number of classes.".format(num_classes)) sequence_lengths = get_lengths_from_binary_sequence_mask(mask) argmax_predictions = predictions.max(-1)[1].float() # Iterate over timesteps in batch. batch_size = gold_labels.size(0) for i in range(batch_size): sequence_prediction = argmax_predictions[i, :] sequence_gold_label = gold_labels[i, :] length = sequence_lengths[i] prediction_spans = self._extract_spans( sequence_prediction[:length].tolist()) gold_spans = self._extract_spans( sequence_gold_label[:length].tolist()) for span in prediction_spans: if span in gold_spans: self._true_positives[span[1]] += 1 gold_spans.remove(span) else: self._false_positives[span[1]] += 1 # These spans weren't predicted. for span in gold_spans: self._false_negatives[span[1]] += 1
def __call__(self, predictions: torch.Tensor, gold_labels: torch.Tensor, mask: Optional[torch.Tensor] = None): """ Parameters ---------- predictions : ``torch.Tensor``, required. A tensor of predictions of shape (batch_size, ..., num_classes). gold_labels : ``torch.Tensor``, required. A tensor of integer class label of shape (batch_size, ...). It must be the same shape as the ``predictions`` tensor without the ``num_classes`` dimension. mask: ``torch.Tensor``, optional (default = None). A masking tensor the same size as ``gold_labels``. """ # Get the data from the Variables. predictions, gold_labels, mask = self.unwrap_to_tensors(predictions, gold_labels, mask) num_classes = predictions.size(-1) if (gold_labels >= num_classes).any(): raise ConfigurationError("A gold label passed to F1Measure contains an id >= {}, " "the number of classes.".format(num_classes)) if mask is None: mask = ones_like(gold_labels) mask = mask.float() gold_labels = gold_labels.float() positive_label_mask = gold_labels.eq(self._positive_label).float() negative_label_mask = 1.0 - positive_label_mask argmax_predictions = predictions.max(-1)[1].float().squeeze(-1) # True Negatives: correct non-positive predictions. correct_null_predictions = (argmax_predictions != self._positive_label).float() * negative_label_mask self._true_negatives += (correct_null_predictions.float() * mask).sum() # True Positives: correct positively labeled predictions. correct_non_null_predictions = (argmax_predictions == self._positive_label).float() * positive_label_mask self._true_positives += (correct_non_null_predictions * mask).sum() # False Negatives: incorrect negatively labeled predictions. incorrect_null_predictions = (argmax_predictions != self._positive_label).float() * positive_label_mask self._false_negatives += (incorrect_null_predictions * mask).sum() # False Positives: incorrect positively labeled predictions incorrect_non_null_predictions = (argmax_predictions == self._positive_label).float() * negative_label_mask self._false_positives += (incorrect_non_null_predictions * mask).sum()
def forward( self, sequence_tensor: torch.FloatTensor, span_indices: torch.LongTensor, sequence_mask: torch.LongTensor = None, span_indices_mask: torch.LongTensor = None) -> torch.FloatTensor: # Both of shape (batch_size, sequence_length, embedding_size / 2) forward_sequence, backward_sequence = sequence_tensor.split(int( self._input_dim / 2), dim=-1) forward_sequence = forward_sequence.contiguous() backward_sequence = backward_sequence.contiguous() # shape (batch_size, num_spans) span_starts, span_ends = [ index.squeeze(-1) for index in span_indices.split(1, dim=-1) ] if span_indices_mask is not None: span_starts = span_starts * span_indices_mask span_ends = span_ends * span_indices_mask # We want `exclusive` span starts, so we remove 1 from the forward span starts # as the AllenNLP ``SpanField`` is inclusive. # shape (batch_size, num_spans) exclusive_span_starts = span_starts - 1 # shape (batch_size, num_spans, 1) start_sentinel_mask = ( exclusive_span_starts == -1).long().unsqueeze(-1) # We want `exclusive` span ends for the backward direction # (so that the `start` of the span in that direction is exlusive), so # we add 1 to the span ends as the AllenNLP ``SpanField`` is inclusive. exclusive_span_ends = span_ends + 1 if sequence_mask is not None: # shape (batch_size) sequence_lengths = util.get_lengths_from_binary_sequence_mask( sequence_mask) else: # shape (batch_size), filled with the sequence length size of the sequence_tensor. sequence_lengths = util.ones_like( sequence_tensor[:, 0, 0]).long() * sequence_tensor.size(1) # shape (batch_size, num_spans, 1) end_sentinel_mask = (exclusive_span_ends == sequence_lengths.unsqueeze( -1)).long().unsqueeze(-1) # As we added 1 to the span_ends to make them exclusive, which might have caused indices # equal to the sequence_length to become out of bounds, we multiply by the inverse of the # end_sentinel mask to erase these indices (as we will replace them anyway in the block below). # The same argument follows for the exclusive span start indices. exclusive_span_ends = exclusive_span_ends * ( 1 - end_sentinel_mask.squeeze(-1)) exclusive_span_starts = exclusive_span_starts * ( 1 - start_sentinel_mask.squeeze(-1)) # We'll check the indices here at runtime, because it's difficult to debug # if this goes wrong and it's tricky to get right. if (exclusive_span_starts < 0).any() or ( exclusive_span_ends > sequence_lengths.unsqueeze(-1)).any(): raise ValueError( f"Adjusted span indices must lie inside the length of the sequence tensor, " f"but found: exclusive_span_starts: {exclusive_span_starts}, " f"exclusive_span_ends: {exclusive_span_ends} for a sequence tensor with lengths " f"{sequence_lengths}.") # Forward Direction: start indices are exclusive. Shape (batch_size, num_spans, input_size / 2) forward_start_embeddings = util.batched_index_select( forward_sequence, exclusive_span_starts) # Forward Direction: end indices are inclusive, so we can just use span_ends. # Shape (batch_size, num_spans, input_size / 2) forward_end_embeddings = util.batched_index_select( forward_sequence, span_ends) # Backward Direction: The backward start embeddings use the `forward` end # indices, because we are going backwards. # Shape (batch_size, num_spans, input_size / 2) backward_start_embeddings = util.batched_index_select( backward_sequence, exclusive_span_ends) # Backward Direction: The backward end embeddings use the `forward` start # indices, because we are going backwards. # Shape (batch_size, num_spans, input_size / 2) backward_end_embeddings = util.batched_index_select( backward_sequence, span_starts) if self._use_sentinels: # If we're using sentinels, we need to replace all the elements which were # outside the dimensions of the sequence_tensor with either the start sentinel, # or the end sentinel. float_end_sentinel_mask = end_sentinel_mask.float() float_start_sentinel_mask = start_sentinel_mask.float() forward_start_embeddings = forward_start_embeddings * (1 - float_start_sentinel_mask) \ + float_start_sentinel_mask * self._start_sentinel backward_start_embeddings = backward_start_embeddings * (1 - float_end_sentinel_mask) \ + float_end_sentinel_mask * self._end_sentinel # Now we combine the forward and backward spans in the manner specified by the # respective combinations and concatenate these representations. # Shape (batch_size, num_spans, forward_combination_dim) forward_spans = util.combine_tensors( self._forward_combination, [forward_start_embeddings, forward_end_embeddings]) # Shape (batch_size, num_spans, backward_combination_dim) backward_spans = util.combine_tensors( self._backward_combination, [backward_start_embeddings, backward_end_embeddings]) # Shape (batch_size, num_spans, forward_combination_dim + backward_combination_dim) span_embeddings = torch.cat([forward_spans, backward_spans], -1) if self._span_width_embedding is not None: # Embed the span widths and concatenate to the rest of the representations. if self._bucket_widths: span_widths = util.bucket_values( span_ends - span_starts, num_total_buckets=self._num_width_embeddings) else: span_widths = span_ends - span_starts span_width_embeddings = self._span_width_embedding(span_widths) return torch.cat([span_embeddings, span_width_embeddings], -1) if span_indices_mask is not None: return span_embeddings * span_indices_mask.float().unsqueeze(-1) return span_embeddings
def forward(self, sequence_tensor: torch.FloatTensor, span_indices: torch.LongTensor, sequence_mask: torch.LongTensor = None, span_indices_mask: torch.LongTensor = None) -> torch.FloatTensor: # Both of shape (batch_size, sequence_length, embedding_size / 2) forward_sequence, backward_sequence = sequence_tensor.split(int(self._input_dim / 2), dim=-1) forward_sequence = forward_sequence.contiguous() backward_sequence = backward_sequence.contiguous() # shape (batch_size, num_spans) span_starts, span_ends = [index.squeeze(-1) for index in span_indices.split(1, dim=-1)] if span_indices_mask is not None: span_starts = span_starts * span_indices_mask span_ends = span_ends * span_indices_mask # We want `exclusive` span starts, so we remove 1 from the forward span starts # as the AllenNLP ``SpanField`` is inclusive. # shape (batch_size, num_spans) exclusive_span_starts = span_starts - 1 # shape (batch_size, num_spans, 1) start_sentinel_mask = (exclusive_span_starts == -1).long().unsqueeze(-1) # We want `exclusive` span ends for the backward direction # (so that the `start` of the span in that direction is exlusive), so # we add 1 to the span ends as the AllenNLP ``SpanField`` is inclusive. exclusive_span_ends = span_ends + 1 if sequence_mask is not None: # shape (batch_size) sequence_lengths = util.get_lengths_from_binary_sequence_mask(sequence_mask) else: # shape (batch_size), filled with the sequence length size of the sequence_tensor. sequence_lengths = util.ones_like(sequence_tensor[:, 0, 0]).long() * sequence_tensor.size(1) # shape (batch_size, num_spans, 1) end_sentinel_mask = (exclusive_span_ends == sequence_lengths.unsqueeze(-1)).long().unsqueeze(-1) # As we added 1 to the span_ends to make them exclusive, which might have caused indices # equal to the sequence_length to become out of bounds, we multiply by the inverse of the # end_sentinel mask to erase these indices (as we will replace them anyway in the block below). # The same argument follows for the exclusive span start indices. exclusive_span_ends = exclusive_span_ends * (1 - end_sentinel_mask.squeeze(-1)) exclusive_span_starts = exclusive_span_starts * (1 - start_sentinel_mask.squeeze(-1)) # We'll check the indices here at runtime, because it's difficult to debug # if this goes wrong and it's tricky to get right. if (exclusive_span_starts < 0).any() or (exclusive_span_ends > sequence_lengths.unsqueeze(-1)).any(): raise ValueError(f"Adjusted span indices must lie inside the length of the sequence tensor, " f"but found: exclusive_span_starts: {exclusive_span_starts}, " f"exclusive_span_ends: {exclusive_span_ends} for a sequence tensor with lengths " f"{sequence_lengths}.") # Forward Direction: start indices are exclusive. Shape (batch_size, num_spans, input_size / 2) forward_start_embeddings = util.batched_index_select(forward_sequence, exclusive_span_starts) # Forward Direction: end indices are inclusive, so we can just use span_ends. # Shape (batch_size, num_spans, input_size / 2) forward_end_embeddings = util.batched_index_select(forward_sequence, span_ends) # Backward Direction: The backward start embeddings use the `forward` end # indices, because we are going backwards. # Shape (batch_size, num_spans, input_size / 2) backward_start_embeddings = util.batched_index_select(backward_sequence, exclusive_span_ends) # Backward Direction: The backward end embeddings use the `forward` start # indices, because we are going backwards. # Shape (batch_size, num_spans, input_size / 2) backward_end_embeddings = util.batched_index_select(backward_sequence, span_starts) if self._use_sentinels: # If we're using sentinels, we need to replace all the elements which were # outside the dimensions of the sequence_tensor with either the start sentinel, # or the end sentinel. float_end_sentinel_mask = end_sentinel_mask.float() float_start_sentinel_mask = start_sentinel_mask.float() forward_start_embeddings = forward_start_embeddings * (1 - float_start_sentinel_mask) \ + float_start_sentinel_mask * self._start_sentinel backward_start_embeddings = backward_start_embeddings * (1 - float_end_sentinel_mask) \ + float_end_sentinel_mask * self._end_sentinel # Now we combine the forward and backward spans in the manner specified by the # respective combinations and concatenate these representations. # Shape (batch_size, num_spans, forward_combination_dim) forward_spans = util.combine_tensors(self._forward_combination, [forward_start_embeddings, forward_end_embeddings]) # Shape (batch_size, num_spans, backward_combination_dim) backward_spans = util.combine_tensors(self._backward_combination, [backward_start_embeddings, backward_end_embeddings]) # Shape (batch_size, num_spans, forward_combination_dim + backward_combination_dim) span_embeddings = torch.cat([forward_spans, backward_spans], -1) if self._span_width_embedding is not None: # Embed the span widths and concatenate to the rest of the representations. if self._bucket_widths: span_widths = util.bucket_values(span_ends - span_starts, num_total_buckets=self._num_width_embeddings) else: span_widths = span_ends - span_starts span_width_embeddings = self._span_width_embedding(span_widths) return torch.cat([span_embeddings, span_width_embeddings], -1) if span_indices_mask is not None: return span_embeddings * span_indices_mask.float().unsqueeze(-1) return span_embeddings
def __call__(self, predictions: torch.Tensor, gold_labels: torch.Tensor, mask: Optional[torch.Tensor] = None, prediction_map: Optional[torch.Tensor] = None): """ Parameters ---------- predictions : ``torch.Tensor``, required. A tensor of predictions of shape (batch_size, sequence_length, num_classes). gold_labels : ``torch.Tensor``, required. A tensor of integer class label of shape (batch_size, sequence_length). It must be the same shape as the ``predictions`` tensor without the ``num_classes`` dimension. mask: ``torch.Tensor``, optional (default = None). A masking tensor the same size as ``gold_labels``. prediction_map: ``torch.Tensor``, optional (default = None). A tensor of size (batch_size, num_classes) which provides a mapping from the index of predictions to the indices of the label vocabulary. If provided, the output label at each timestep will be ``vocabulary.get_index_to_token_vocabulary(prediction_map[batch, argmax(predictions[batch, t]))``, rather than simply ``vocabulary.get_index_to_token_vocabulary(argmax(predictions[batch, t]))``. This is useful in cases where each Instance in the dataset is associated with a different possible subset of labels from a large label-space (IE FrameNet, where each frame has a different set of possible roles associated with it). """ if mask is None: mask = ones_like(gold_labels) # Get the data from the Variables. predictions, gold_labels, mask, prediction_map = self.unwrap_to_tensors( predictions, gold_labels, mask, prediction_map) num_classes = predictions.size(-1) if (gold_labels >= num_classes).any(): raise ConfigurationError( "A gold label passed to SpanBasedF1Measure contains an " "id >= {}, the number of classes.".format(num_classes)) sequence_lengths = get_lengths_from_binary_sequence_mask(mask) argmax_predictions = predictions.max(-1)[1] if prediction_map is not None: argmax_predictions = torch.gather(prediction_map, 1, argmax_predictions) gold_labels = torch.gather(prediction_map, 1, gold_labels.long()) argmax_predictions = argmax_predictions.float() # Iterate over timesteps in batch. batch_size = gold_labels.size(0) for i in range(batch_size): sequence_prediction = argmax_predictions[i, :] sequence_gold_label = gold_labels[i, :] length = sequence_lengths[i] if length == 0: # It is possible to call this metric with sequences which are # completely padded. These contribute nothing, so we skip these rows. continue predicted_string_labels = [ self._label_vocabulary[label_id] for label_id in sequence_prediction[:length].tolist() ] gold_string_labels = [ self._label_vocabulary[label_id] for label_id in sequence_gold_label[:length].tolist() ] predicted_spans = bio_tags_to_spans(predicted_string_labels, self._ignore_classes) gold_spans = bio_tags_to_spans(gold_string_labels, self._ignore_classes) predicted_spans = self._handle_continued_spans(predicted_spans) gold_spans = self._handle_continued_spans(gold_spans) for span in predicted_spans: if span in gold_spans: self._true_positives[span[0]] += 1 gold_spans.remove(span) else: self._false_positives[span[0]] += 1 # These spans weren't predicted. for span in gold_spans: self._false_negatives[span[0]] += 1
def __call__(self, predictions: torch.Tensor, gold_labels: torch.Tensor, mask: Optional[torch.Tensor] = None, frames: List[str] = None, target_indices: torch.Tensor = None): """ Parameters ---------- num_classes: ``int``, required. predictions : ``torch.Tensor``, required. A tensor of predictions of shape (batch_size, sequence_length, max_span_width). gold_labels : ``torch.Tensor``, required. A tensor of integer class label of shape (batch_size, sequence_length, max_span_width). It must be the same shape as the ``predictions`` tensor. mask: ``torch.Tensor``, optional (default = None). A masking tensor of shape (batch_size, sequence_length). """ if (gold_labels >= self.num_classes).any(): raise ConfigurationError( "A gold label passed to NonBioSpanBasedF1Measure contains an " "id >= {}, the number of classes.".format(self.num_classes)) if mask is None: mask = ones_like(gold_labels) # Get the data from the Variables. predictions, gold_labels, mask = self.unwrap_to_tensors( predictions, gold_labels, mask) sequence_lengths = get_lengths_from_binary_sequence_mask(mask) argmax_predictions = predictions.float() # Iterate over timesteps in batch. batch_size = gold_labels.size(0) for i in range(batch_size): gold_sequence = gold_labels[i, :] predicted_sequence = argmax_predictions[i, :] length = sequence_lengths[i] if length == 0: # It is possible to call this metric with sequences which are # completely padded. These contribute nothing, so we skip these rows. continue gold_spans = self._extract_spans(gold_sequence[:length].tolist(), merge=True) predicted_spans = self._extract_spans( predicted_sequence[:length].tolist(), merge=True) self._gold_spans.append(gold_spans) self._predicted_spans.append(predicted_spans) frame = None if frames is not None: frame = frames[i] # FN is not to be evaluated for empty gold annotations. if not gold_spans and frame: continue self._get_labeled_evaluation(gold_spans, predicted_spans, frame) self._get_unlabeled_evaluation(gold_spans, predicted_spans, frame) self._get_partial_match_evaluation(gold_spans, predicted_spans, frame) self._get_width_wise_labeled_evaluation(gold_spans, predicted_spans, frame) if target_indices is not None: target_index = target_indices[i][0].data[0] self._get_distance_wise_labeled_evaluation( gold_spans, predicted_spans, target_index, frame)
def __call__(self, predictions: torch.Tensor, gold_labels: torch.Tensor, mask: Optional[torch.Tensor] = None): """ Parameters ---------- predictions : ``torch.Tensor``, required. A tensor of predictions of shape (batch_size, ..., num_classes). gold_labels : ``torch.Tensor``, required. A tensor of integer class label of shape (batch_size, ...). It must be the same shape as the ``predictions`` tensor without the ``num_classes`` dimension. mask: ``torch.Tensor``, optional (default = None). A masking tensor the same size as ``gold_labels``. """ # If you actually passed in Variables here instead of Tensors, this will be a huge memory # leak, because it will prevent garbage collection for the computation graph. We'll ensure # that we're using tensors here first. if isinstance(predictions, Variable): predictions = predictions.data if isinstance(gold_labels, Variable): gold_labels = gold_labels.data if isinstance(mask, Variable): mask = mask.data num_classes = predictions.size(-1) if (gold_labels >= num_classes).any(): raise ConfigurationError( "A gold label passed to F1Measure contains an id >= {}, " "the number of classes.".format(num_classes)) if mask is None: mask = ones_like(gold_labels) mask = mask.float() gold_labels = gold_labels.float() positive_label_mask = gold_labels.eq(self._positive_label).float() negative_label_mask = 1.0 - positive_label_mask argmax_predictions = predictions.topk(1, -1)[1].float().squeeze(-1) # True Negatives: correct non-positive predictions. correct_null_predictions = (argmax_predictions != self._positive_label ).float() * negative_label_mask self._true_negatives += (correct_null_predictions.float() * mask).sum() # True Positives: correct positively labeled predictions. correct_non_null_predictions = ( argmax_predictions == self._positive_label).float() * positive_label_mask self._true_positives += (correct_non_null_predictions * mask).sum() # False Negatives: incorrect negatively labeled predictions. incorrect_null_predictions = ( argmax_predictions != self._positive_label).float() * positive_label_mask self._false_negatives += (incorrect_null_predictions * mask).sum() # False Positives: incorrect positively labeled predictions incorrect_non_null_predictions = ( argmax_predictions == self._positive_label).float() * negative_label_mask self._false_positives += (incorrect_non_null_predictions * mask).sum()
def __call__(self, predictions: torch.Tensor, gold_labels: torch.Tensor, mask: Optional[torch.Tensor] = None, prediction_map: Optional[torch.Tensor] = None): """ Parameters ---------- predictions : ``torch.Tensor``, required. A tensor of predictions of shape (batch_size, sequence_length, num_classes). gold_labels : ``torch.Tensor``, required. A tensor of integer class label of shape (batch_size, sequence_length). It must be the same shape as the ``predictions`` tensor without the ``num_classes`` dimension. mask: ``torch.Tensor``, optional (default = None). A masking tensor the same size as ``gold_labels``. prediction_map: ``torch.Tensor``, optional (default = None). A tensor of size (batch_size, num_classes) which provides a mapping from the index of predictions to the indices of the label vocabulary. If provided, the output label at each timestep will be ``vocabulary.get_index_to_token_vocabulary(prediction_map[batch, argmax(predictions[batch, t]))``, rather than simply ``vocabulary.get_index_to_token_vocabulary(argmax(predictions[batch, t]))``. This is useful in cases where each Instance in the dataset is associated with a different possible subset of labels from a large label-space (IE FrameNet, where each frame has a different set of possible roles associated with it). """ if mask is None: mask = ones_like(gold_labels) # Get the data from the Variables. predictions, gold_labels, mask, prediction_map = self.unwrap_to_tensors(predictions, gold_labels, mask, prediction_map) num_classes = predictions.size(-1) if (gold_labels >= num_classes).any(): raise ConfigurationError("A gold label passed to SpanBasedF1Measure contains an " "id >= {}, the number of classes.".format(num_classes)) sequence_lengths = get_lengths_from_binary_sequence_mask(mask) argmax_predictions = predictions.max(-1)[1] if prediction_map is not None: argmax_predictions = torch.gather(prediction_map, 1, argmax_predictions) gold_labels = torch.gather(prediction_map, 1, gold_labels.long()) argmax_predictions = argmax_predictions.float() # Iterate over timesteps in batch. batch_size = gold_labels.size(0) for i in range(batch_size): sequence_prediction = argmax_predictions[i, :] sequence_gold_label = gold_labels[i, :] length = sequence_lengths[i] if length == 0: # It is possible to call this metric with sequences which are # completely padded. These contribute nothing, so we skip these rows. continue predicted_string_labels = [self._label_vocabulary[label_id] for label_id in sequence_prediction[:length].tolist()] gold_string_labels = [self._label_vocabulary[label_id] for label_id in sequence_gold_label[:length].tolist()] predicted_spans = bio_tags_to_spans(predicted_string_labels, self._ignore_classes) gold_spans = bio_tags_to_spans(gold_string_labels, self._ignore_classes) predicted_spans = self._handle_continued_spans(predicted_spans) gold_spans = self._handle_continued_spans(gold_spans) for span in predicted_spans: if span in gold_spans: self._true_positives[span[0]] += 1 gold_spans.remove(span) else: self._false_positives[span[0]] += 1 # These spans weren't predicted. for span in gold_spans: self._false_negatives[span[0]] += 1