"git@developer.sourcefind.cn:chenpangpang/transformers.git" did not exist on "463226e2ee372ae48f473cd9f93917839f0901ff"
Unverified Commit 9120ae7d authored by Pavel Soriano's avatar Pavel Soriano Committed by GitHub
Browse files

Fixes NoneType exception when topk is larger than one coupled with a small...

Fixes NoneType exception when topk is larger than one coupled with a small context in the Question-Answering pipeline (#11628)

* added fix to decode function. added test to qa pipeline tests

* completed topk docstring

* fixed formatting with black

* applied style_doc to fix line length
parent dcb0e614
...@@ -177,7 +177,8 @@ class QuestionAnsweringPipeline(Pipeline): ...@@ -177,7 +177,8 @@ class QuestionAnsweringPipeline(Pipeline):
One or several context(s) associated with the question(s) (must be used in conjunction with the One or several context(s) associated with the question(s) (must be used in conjunction with the
:obj:`question` argument). :obj:`question` argument).
topk (:obj:`int`, `optional`, defaults to 1): topk (:obj:`int`, `optional`, defaults to 1):
The number of answers to return (will be chosen by order of likelihood). The number of answers to return (will be chosen by order of likelihood). Note that we return less than
topk answers if there are not enough options available within the context.
doc_stride (:obj:`int`, `optional`, defaults to 128): doc_stride (:obj:`int`, `optional`, defaults to 128):
If the context is too long to fit with the question for the model, it will be split in several chunks If the context is too long to fit with the question for the model, it will be split in several chunks
with some overlap. This argument controls the size of that overlap. with some overlap. This argument controls the size of that overlap.
...@@ -341,7 +342,9 @@ class QuestionAnsweringPipeline(Pipeline): ...@@ -341,7 +342,9 @@ class QuestionAnsweringPipeline(Pipeline):
# Mask CLS # Mask CLS
start_[0] = end_[0] = 0.0 start_[0] = end_[0] = 0.0
starts, ends, scores = self.decode(start_, end_, kwargs["topk"], kwargs["max_answer_len"]) starts, ends, scores = self.decode(
start_, end_, kwargs["topk"], kwargs["max_answer_len"], undesired_tokens
)
if not self.tokenizer.is_fast: if not self.tokenizer.is_fast:
char_to_word = np.array(example.char_to_word_offset) char_to_word = np.array(example.char_to_word_offset)
...@@ -403,7 +406,9 @@ class QuestionAnsweringPipeline(Pipeline): ...@@ -403,7 +406,9 @@ class QuestionAnsweringPipeline(Pipeline):
return all_answers[0] return all_answers[0]
return all_answers return all_answers
def decode(self, start: np.ndarray, end: np.ndarray, topk: int, max_answer_len: int) -> Tuple: def decode(
self, start: np.ndarray, end: np.ndarray, topk: int, max_answer_len: int, undesired_tokens: np.ndarray
) -> Tuple:
""" """
Take the output of any :obj:`ModelForQuestionAnswering` and will generate probabilities for each span to be the Take the output of any :obj:`ModelForQuestionAnswering` and will generate probabilities for each span to be the
actual answer. actual answer.
...@@ -417,6 +422,7 @@ class QuestionAnsweringPipeline(Pipeline): ...@@ -417,6 +422,7 @@ class QuestionAnsweringPipeline(Pipeline):
end (:obj:`np.ndarray`): Individual end probabilities for each token. end (:obj:`np.ndarray`): Individual end probabilities for each token.
topk (:obj:`int`): Indicates how many possible answer span(s) to extract from the model output. topk (:obj:`int`): Indicates how many possible answer span(s) to extract from the model output.
max_answer_len (:obj:`int`): Maximum size of the answer to extract from the model's output. max_answer_len (:obj:`int`): Maximum size of the answer to extract from the model's output.
undesired_tokens (:obj:`np.ndarray`): Mask determining tokens that can be part of the answer
""" """
# Ensure we have batch axis # Ensure we have batch axis
if start.ndim == 1: if start.ndim == 1:
...@@ -441,8 +447,13 @@ class QuestionAnsweringPipeline(Pipeline): ...@@ -441,8 +447,13 @@ class QuestionAnsweringPipeline(Pipeline):
idx = np.argpartition(-scores_flat, topk)[0:topk] idx = np.argpartition(-scores_flat, topk)[0:topk]
idx_sort = idx[np.argsort(-scores_flat[idx])] idx_sort = idx[np.argsort(-scores_flat[idx])]
start, end = np.unravel_index(idx_sort, candidates.shape)[1:] starts, ends = np.unravel_index(idx_sort, candidates.shape)[1:]
return start, end, candidates[0, start, end] desired_spans = np.isin(starts, undesired_tokens.nonzero()) & np.isin(ends, undesired_tokens.nonzero())
starts = starts[desired_spans]
ends = ends[desired_spans]
scores = candidates[0, starts, ends]
return starts, ends, scores
def span_to_answer(self, text: str, start: int, end: int) -> Dict[str, Union[str, int]]: def span_to_answer(self, text: str, start: int, end: int) -> Dict[str, Union[str, int]]:
""" """
......
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
import unittest import unittest
from transformers.data.processors.squad import SquadExample from transformers.data.processors.squad import SquadExample
from transformers.pipelines import Pipeline, QuestionAnsweringArgumentHandler from transformers.pipelines import Pipeline, QuestionAnsweringArgumentHandler, pipeline
from transformers.testing_utils import slow
from .test_pipelines_common import CustomInputPipelineCommonMixin from .test_pipelines_common import CustomInputPipelineCommonMixin
...@@ -50,6 +51,34 @@ class QAPipelineTests(CustomInputPipelineCommonMixin, unittest.TestCase): ...@@ -50,6 +51,34 @@ class QAPipelineTests(CustomInputPipelineCommonMixin, unittest.TestCase):
}, },
] ]
def get_pipelines(self):
question_answering_pipelines = [
pipeline(
task=self.pipeline_task,
model=model,
tokenizer=model,
framework="pt",
**self.pipeline_loading_kwargs,
)
for model in self.small_models
]
return question_answering_pipelines
@slow
def test_high_topk_small_context(self):
self.pipeline_running_kwargs.update({"topk": 20})
valid_inputs = [
{"question": "Where was HuggingFace founded ?", "context": "Paris"},
]
nlps = self.get_pipelines()
output_keys = {"score", "answer", "start", "end"}
for nlp in nlps:
result = nlp(valid_inputs, **self.pipeline_running_kwargs)
self.assertIsInstance(result, dict)
for key in output_keys:
self.assertIn(key, result)
def _test_pipeline(self, nlp: Pipeline): def _test_pipeline(self, nlp: Pipeline):
output_keys = {"score", "answer", "start", "end"} output_keys = {"score", "answer", "start", "end"}
valid_inputs = [ valid_inputs = [
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment