# Respuestas a preguntas sobre documentos

Document Question Answering, también conocido como Document Visual Question Answering, es una tarea que consiste en proporcionar respuestas a preguntas formuladas sobre imágenes de documentos. La entrada de los modelos que admiten esta tarea suele ser una combinación de una imagen y una pregunta, y la salida es una respuesta expresada en lenguaje natural. Estos modelos utilizan múltiples modalidades, incluido el texto, las posiciones de las palabras (bounding boxes) y la propia imagen.

Esta guía ilustra cómo:

- Ajusta [LayoutLMv2](../model_doc/layoutlmv2) con el [conjunto de datos DocVQA](https://huggingface.co/datasets/nielsr/docvqa_1200_examples_donut).
- Usa tu modelo afinado para la inferencia.

Para ver todas las arquitecturas y puntos de control compatibles con esta tarea, recomendamos consultar la [página de la tarea](https://huggingface.co/tasks/image-to-text)

LayoutLMv2 resuelve la tarea de respuesta a preguntas en documentos añadiendo una cabeza de respuesta a preguntas sobre los estados ocultos finales de los tokens, para predecir las posiciones de los tokens de inicio y fin de la respuesta. En otras palabras, el problema se trata como una respuesta a preguntas extractiva: dado el contexto, se extrae qué información responde a la pregunta. El contexto proviene de la salida de un motor de OCR, en este caso Tesseract de Google.

Antes de comenzar, asegúrate de tener instaladas todas las bibliotecas necesarias. LayoutLMv2 depende de detectron2, torchvision y tesseract.

```bash
pip install -q transformers datasets
```

```bash
pip install 'git+https://github.com/facebookresearch/detectron2.git'
pip install torchvision
```

```bash
sudo apt install tesseract-ocr
pip install -q pytesseract
```

Una vez que hayas instalado todas las dependencias, reinicia tu entorno de ejecución.

Te animamos a compartir tu modelo con la comunidad. Inicia sesión en tu cuenta de Hugging Face para subirlo al 🤗 Hub. Cuando se te solicite, introduce tu token para iniciar sesión:

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

Definamos algunas variables globales.

```py
>>> model_checkpoint = "microsoft/layoutlmv2-base-uncased"
>>> batch_size = 4
```

## Cargar los datos

En esta guía utilizamos una pequeña muestra de DocVQA preprocesado que puedes encontrar en 🤗 Hub. Si deseas utilizar el conjunto de datos completo de DocVQA, puedes registrarte y descargarlo en [la página principal de DocVQA](https://rrc.cvc.uab.es/?ch=17). Si lo haces, para continuar con esta guía consulta [cómo cargar archivos en un conjunto de datos de 🤗](https://huggingface.co/docs/datasets/loading#local-and-remote-files).

```py
>>> from datasets import load_dataset

>>> dataset = load_dataset("nielsr/docvqa_1200_examples")
>>> dataset
DatasetDict({
    train: Dataset({
        features: ['id', 'image', 'query', 'answers', 'words', 'bounding_boxes', 'answer'],
        num_rows: 1000
    })
    test: Dataset({
        features: ['id', 'image', 'query', 'answers', 'words', 'bounding_boxes', 'answer'],
        num_rows: 200
    })
})
```

Como puede ver, el conjunto de datos ya está dividido en conjuntos de entrenamiento y prueba. Examine un ejemplo aleatorio para familiarizarse con las características.

```py
>>> dataset["train"].features
```

Estos son los campos individuales y lo que representan:

* `id`: el id del ejemplo
* `image`: un objeto PIL.Image.Image que contiene la imagen del documento
* `query`: la cadena de texto de la pregunta - pregunta formulada en lenguaje natural, en varios idiomas
* `answers`: una lista de respuestas correctas proporcionadas por anotadores humanos
* `words` y `bounding_boxes`: los resultados de OCR, que no utilizaremos aquí
* `answer`: una respuesta que coincide con un modelo diferente, que no utilizaremos aquí

Dejemos solo las preguntas en inglés y descartemos la característica `answer`, que parece contener predicciones de otro modelo. También tomaremos la primera de las respuestas del conjunto proporcionado por los anotadores. Alternativamente, puedes muestrearla aleatoriamente.

```py
>>> updated_dataset = dataset.map(lambda example: {"question": example["query"]["en"]}, remove_columns=["query"])
>>> updated_dataset = updated_dataset.map(
...     lambda example: {"answer": example["answers"][0]}, remove_columns=["answer", "answers"]
... )
```

Tenga en cuenta que el checkpoint de LayoutLMv2 que utilizamos en esta guía se ha entrenado con `max_position_embeddings = 512` (puede encontrar esta información en el [archivo `config.json` del checkpoint](https://huggingface.co/microsoft/layoutlmv2-base-uncased/blob/main/config.json#L18)). Podemos truncar los ejemplos, pero para evitar la situación en la que la respuesta podría estar al final de un documento extenso y acabar truncada, aquí eliminaremos los pocos ejemplos en los que es probable que el embedding termine siendo más largo que 512. Si la mayoría de los documentos de su conjunto de datos son largos, puede implementar una estrategia de ventana deslizante; consulte [este cuaderno](https://github.com/huggingface/notebooks/blob/main/examples/question_answering.ipynb) para obtener más detalles.

```py
>>> updated_dataset = updated_dataset.filter(lambda x: len(x["words"]) + len(x["question"].split()) < 512)
```

En este punto, también eliminemos las características de OCR de este conjunto de datos. Estas son el resultado de un OCR para el ajuste fino de un modelo diferente. Si quisiéramos utilizarlas, aún requerirían algún procesamiento, ya que no cumplen con los requisitos de entrada del modelo que utilizamos en esta guía. En su lugar, podemos usar [LayoutLMv2Processor](/docs/transformers/main/es/model_doc/layoutlmv2#transformers.LayoutLMv2Processor) sobre los datos originales tanto para el OCR como para la tokenización. De esta forma, obtendremos las entradas que coinciden con la entrada esperada por el modelo. Si deseas procesar las imágenes manualmente, consulta la [documentación del modelo `LayoutLMv2`](../model_doc/layoutlmv2) para conocer el formato de entrada que espera el modelo.

```py
>>> updated_dataset = updated_dataset.remove_columns("words")
>>> updated_dataset = updated_dataset.remove_columns("bounding_boxes")
```

Por último, la exploración de los datos no estará completa si no echamos un vistazo a un ejemplo de imagen.

```py
>>> updated_dataset["train"][11]["image"]
```

     
 

## Preprocesar los datos

La tarea de Document Question Answering es una tarea multimodal, por lo que debes asegurarte de que las entradas de cada modalidad se preprocesen según las expectativas del modelo. Comencemos cargando el [LayoutLMv2Processor](/docs/transformers/main/es/model_doc/layoutlmv2#transformers.LayoutLMv2Processor), que internamente combina un procesador de imágenes capaz de manejar datos de imagen y un tokenizador que puede codificar datos de texto.

```py
>>> from transformers import AutoProcessor

>>> processor = AutoProcessor.from_pretrained(model_checkpoint)
```

### Preprocesamiento de imágenes de documentos

Primero, preparemos las imágenes de los documentos para el modelo con la ayuda del `image_processor` del procesador. De forma predeterminada, el procesador de imágenes redimensiona las imágenes a 224x224, se asegura de que tengan el orden correcto de los canales de color y aplica OCR con tesseract para obtener palabras y cajas delimitadoras normalizadas. En este tutorial, todos estos valores predeterminados son exactamente lo que necesitamos. Escriba una función que aplique el procesamiento de imágenes predeterminado a un lote de imágenes y devuelva los resultados del OCR.

```py
>>> image_processor = processor.image_processor

>>> def get_ocr_words_and_boxes(examples):
...     images = [image.convert("RGB") for image in examples["image"]]
...     encoded_inputs = image_processor(images)

...     examples["image"] = encoded_inputs.pixel_values
...     examples["words"] = encoded_inputs.words
...     examples["boxes"] = encoded_inputs.boxes

...     return examples
```

Para aplicar este preprocesamiento a todo el conjunto de datos de forma rápida, use [map](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.map).

```py
>>> dataset_with_ocr = updated_dataset.map(get_ocr_words_and_boxes, batched=True, batch_size=2)
```

### Preprocesamiento de datos de texto

Una vez que hemos aplicado OCR a las imágenes, necesitamos codificar la parte de texto del conjunto de datos para prepararlo para el modelo. Esto implica convertir las palabras y las cajas que obtuvimos en el paso anterior en `input_ids`, `attention_mask`, `token_type_ids` y `bbox` a nivel de token. Para preprocesar el texto, necesitaremos el `tokenizer` del procesador.

```py
>>> tokenizer = processor.tokenizer
```

Además del preprocesamiento mencionado anteriormente, también es necesario añadir las etiquetas para el modelo. En los modelos `xxxForQuestionAnswering` de 🤗 Transformers, las etiquetas consisten en `start_positions` y `end_positions`, que indican qué token se encuentra al inicio y cuál al final de la respuesta.

Empecemos por ahí. Define una función auxiliar que pueda encontrar una sublista (la respuesta dividida en palabras) dentro de una lista más grande (la lista de palabras).

This function will take two lists as input, `words_list` and `answer_list`. It will then iterate over the `words_list` and check if the current word in the `words_list` (words_list[i]) is equal to the first word of answer_list (answer_list[0]) and if the sublist of `words_list` starting from the current word and of the same length as `answer_list` is equal `to answer_list`. If this condition is true, it means that a match has been found, and the function will record the match, its starting index (idx), and its ending index (idx + len(answer_list) - 1). If more than one match was found, the function will return only the first one. If no match is found, the function returns (`None`, 0, and 0).

```py
>>> def subfinder(words_list, answer_list):
...     matches = []
...     start_indices = []
...     end_indices = []
...     for idx, i in enumerate(range(len(words_list))):
...         if words_list[i] == answer_list[0] and words_list[i : i + len(answer_list)] == answer_list:
...             matches.append(answer_list)
...             start_indices.append(idx)
...             end_indices.append(idx + len(answer_list) - 1)
...     if matches:
...         return matches[0], start_indices[0], end_indices[0]
...     else:
...         return None, 0, 0
```

Para ilustrar cómo esta función encuentra la posición de la respuesta, utilicémosla en un ejemplo:

```py
>>> example = dataset_with_ocr["train"][1]
>>> words = [word.lower() for word in example["words"]]
>>> match, word_idx_start, word_idx_end = subfinder(words, example["answer"].lower().split())
>>> print("Question: ", example["question"])
>>> print("Words:", words)
>>> print("Answer: ", example["answer"])
>>> print("start_index", word_idx_start)
>>> print("end_index", word_idx_end)
Question:  Who is in  cc in this letter?
Words: ['wie', 'baw', 'brown', '&', 'williamson', 'tobacco', 'corporation', 'research', '&', 'development', 'internal', 'correspondence', 'to:', 'r.', 'h.', 'honeycutt', 'ce:', 't.f.', 'riehl', 'from:', '.', 'c.j.', 'cook', 'date:', 'may', '8,', '1995', 'subject:', 'review', 'of', 'existing', 'brainstorming', 'ideas/483', 'the', 'major', 'function', 'of', 'the', 'product', 'innovation', 'graup', 'is', 'to', 'develop', 'marketable', 'nove!', 'products', 'that', 'would', 'be', 'profitable', 'to', 'manufacture', 'and', 'sell.', 'novel', 'is', 'defined', 'as:', 'of', 'a', 'new', 'kind,', 'or', 'different', 'from', 'anything', 'seen', 'or', 'known', 'before.', 'innovation', 'is', 'defined', 'as:', 'something', 'new', 'or', 'different', 'introduced;', 'act', 'of', 'innovating;', 'introduction', 'of', 'new', 'things', 'or', 'methods.', 'the', 'products', 'may', 'incorporate', 'the', 'latest', 'technologies,', 'materials', 'and', 'know-how', 'available', 'to', 'give', 'then', 'a', 'unique', 'taste', 'or', 'look.', 'the', 'first', 'task', 'of', 'the', 'product', 'innovation', 'group', 'was', 'to', 'assemble,', 'review', 'and', 'categorize', 'a', 'list', 'of', 'existing', 'brainstorming', 'ideas.', 'ideas', 'were', 'grouped', 'into', 'two', 'major', 'categories', 'labeled', 'appearance', 'and', 'taste/aroma.', 'these', 'categories', 'are', 'used', 'for', 'novel', 'products', 'that', 'may', 'differ', 'from', 'a', 'visual', 'and/or', 'taste/aroma', 'point', 'of', 'view', 'compared', 'to', 'canventional', 'cigarettes.', 'other', 'categories', 'include', 'a', 'combination', 'of', 'the', 'above,', 'filters,', 'packaging', 'and', 'brand', 'extensions.', 'appearance', 'this', 'category', 'is', 'used', 'for', 'novel', 'cigarette', 'constructions', 'that', 'yield', 'visually', 'different', 'products', 'with', 'minimal', 'changes', 'in', 'smoke', 'chemistry', 'two', 'cigarettes', 'in', 'cne.', 'emulti-plug', 'te', 'build', 'yaur', 'awn', 'cigarette.', 'eswitchable', 'menthol', 'or', 'non', 'menthol', 'cigarette.', '*cigarettes', 'with', 'interspaced', 'perforations', 'to', 'enable', 'smoker', 'to', 'separate', 'unburned', 'section', 'for', 'future', 'smoking.', '«short', 'cigarette,', 'tobacco', 'section', '30', 'mm.', '«extremely', 'fast', 'buming', 'cigarette.', '«novel', 'cigarette', 'constructions', 'that', 'permit', 'a', 'significant', 'reduction', 'iretobacco', 'weight', 'while', 'maintaining', 'smoking', 'mechanics', 'and', 'visual', 'characteristics.', 'higher', 'basis', 'weight', 'paper:', 'potential', 'reduction', 'in', 'tobacco', 'weight.', '«more', 'rigid', 'tobacco', 'column;', 'stiffing', 'agent', 'for', 'tobacco;', 'e.g.', 'starch', '*colored', 'tow', 'and', 'cigarette', 'papers;', 'seasonal', 'promotions,', 'e.g.', 'pastel', 'colored', 'cigarettes', 'for', 'easter', 'or', 'in', 'an', 'ebony', 'and', 'ivory', 'brand', 'containing', 'a', 'mixture', 'of', 'all', 'black', '(black', 'paper', 'and', 'tow)', 'and', 'ail', 'white', 'cigarettes.', '499150498']
Answer:  T.F. Riehl
start_index 17
end_index 18
```

Sin embargo, una vez que los ejemplos se codifican, se verán así:

```py
>>> encoding = tokenizer(example["question"], example["words"], example["boxes"])
>>> tokenizer.decode(encoding["input_ids"])
[CLS] who is in cc in this letter? [SEP] wie baw brown & williamson tobacco corporation research & development ...
```

Tendremos que encontrar la posición de la respuesta en la entrada codificada.

* `token_type_ids` nos indica qué tokens forman parte de la pregunta y cuáles forman parte de las palabras del documento.
* `tokenizer.cls_token_id` servirá para encontrar el token especial al inicio de la entrada.
* `word_ids` servirá para hacer coincidir la respuesta encontrada en las `words` originales con la misma respuesta en la entrada codificada completa y determinar
la posición de inicio/fin de la respuesta en la entrada codificada.

Con eso en mente, creemos una función para codificar un lote de ejemplos del conjunto de datos:

```py
>>> def encode_dataset(examples, max_length=512):
...     questions = examples["question"]
...     words = examples["words"]
...     boxes = examples["boxes"]
...     answers = examples["answer"]

...     # encode the batch of examples and initialize the start_positions and end_positions
...     encoding = tokenizer(questions, words, boxes, max_length=max_length, padding="max_length", truncation=True)
...     start_positions = []
...     end_positions = []

...     # loop through the examples in the batch
...     for i in range(len(questions)):
...         cls_index = encoding["input_ids"][i].index(tokenizer.cls_token_id)

...         # find the position of the answer in example's words
...         words_example = [word.lower() for word in words[i]]
...         answer = answers[i]
...         match, word_idx_start, word_idx_end = subfinder(words_example, answer.lower().split())

...         if match:
...             # if match is found, use `token_type_ids` to find where words start in the encoding
...             token_type_ids = encoding["token_type_ids"][i]
...             token_start_index = 0
...             while token_type_ids[token_start_index] != 1:
...                 token_start_index += 1

...             token_end_index = len(encoding["input_ids"][i]) - 1
...             while token_type_ids[token_end_index] != 1:
...                 token_end_index -= 1

...             word_ids = encoding.word_ids(i)[token_start_index : token_end_index + 1]
...             start_position = cls_index
...             end_position = cls_index

...             # loop over word_ids and increase `token_start_index` until it matches the answer position in words
...             # once it matches, save the `token_start_index` as the `start_position` of the answer in the encoding
...             for id in word_ids:
...                 if id == word_idx_start:
...                     start_position = token_start_index
...                 else:
...                     token_start_index += 1

...             # similarly loop over `word_ids` starting from the end to find the `end_position` of the answer
...             for id in word_ids[::-1]:
...                 if id == word_idx_end:
...                     end_position = token_end_index
...                 else:
...                     token_end_index -= 1

...             start_positions.append(start_position)
...             end_positions.append(end_position)

...         else:
...             start_positions.append(cls_index)
...             end_positions.append(cls_index)

...     encoding["image"] = examples["image"]
...     encoding["start_positions"] = start_positions
...     encoding["end_positions"] = end_positions

...     return encoding
```

Ahora que tenemos esta función de preprocesamiento, podemos codificar el conjunto de datos completo:

```py
>>> encoded_train_dataset = dataset_with_ocr["train"].map(
...     encode_dataset, batched=True, batch_size=2, remove_columns=dataset_with_ocr["train"].column_names
... )
>>> encoded_test_dataset = dataset_with_ocr["test"].map(
...     encode_dataset, batched=True, batch_size=2, remove_columns=dataset_with_ocr["test"].column_names
... )
```

Veamos cómo son las características del conjunto de datos codificado:

```py
>>> encoded_train_dataset.features
{'image': Sequence(feature=Sequence(feature=Sequence(feature=Value(dtype='uint8', id=None), length=-1, id=None), length=-1, id=None), length=-1, id=None),
 'input_ids': Sequence(feature=Value(dtype='int32', id=None), length=-1, id=None),
 'token_type_ids': Sequence(feature=Value(dtype='int8', id=None), length=-1, id=None),
 'attention_mask': Sequence(feature=Value(dtype='int8', id=None), length=-1, id=None),
 'bbox': Sequence(feature=Sequence(feature=Value(dtype='int64', id=None), length=-1, id=None), length=-1, id=None),
 'start_positions': Value(dtype='int64', id=None),
 'end_positions': Value(dtype='int64', id=None)}
```

## Evaluación

La evaluación para la respuesta de preguntas sobre documentos requiere una cantidad significativa de posprocesamiento. Para no ocupar demasiado su tiempo, esta guía omite el paso de evaluación. El [Trainer](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer) sigue calculando la pérdida de evaluación durante el entrenamiento, por lo que no queda completamente a ciegas sobre el rendimiento de su modelo. La respuesta de preguntas extractiva se evalúa típicamente mediante F1/exact match. Si desea implementarlo usted mismo, consulte el [capítulo de respuesta de preguntas](https://huggingface.co/course/chapter7/7?fw=pt#postprocessing) del curso de Hugging Face para obtener inspiración.

## Entrenar

¡Felicidades! Has superado con éxito la parte más difícil de esta guía y ahora estás listo para entrenar tu propio modelo. El entrenamiento implica los siguientes pasos:

* Cargue el modelo con [AutoModelForDocumentQuestionAnswering](/docs/transformers/main/es/model_doc/auto#transformers.AutoModelForDocumentQuestionAnswering) utilizando el mismo punto de control que en el preprocesamiento.
* Define los hiperparámetros de entrenamiento en [TrainingArguments](/docs/transformers/main/es/main_classes/trainer#transformers.TrainingArguments).
* Defina una función para agrupar ejemplos en lotes; aquí, [DefaultDataCollator](/docs/transformers/main/es/main_classes/data_collator#transformers.DefaultDataCollator) será suficiente.
* Pasa los argumentos de entrenamiento a [Trainer](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer) junto con el modelo, el conjunto de datos y el data collator.
* Llama a [train()](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer.train) para ajustar tu modelo.

```py
>>> from transformers import AutoModelForDocumentQuestionAnswering

>>> model = AutoModelForDocumentQuestionAnswering.from_pretrained(model_checkpoint)
```

En [TrainingArguments](/docs/transformers/main/es/main_classes/trainer#transformers.TrainingArguments), usa `output_dir` para especificar dónde guardar tu modelo y configura los hiperparámetros a tu conveniencia. Si deseas compartir tu modelo con la comunidad, establece `push_to_hub` en `True` (debes iniciar sesión en Hugging Face para subir tu modelo). En este caso, `output_dir` también será el nombre del repositorio donde se subirá el punto de control de tu modelo.

```py
>>> from transformers import TrainingArguments

>>> # REPLACE THIS WITH YOUR REPO ID
>>> repo_id = "MariaK/layoutlmv2-base-uncased_finetuned_docvqa"

>>> training_args = TrainingArguments(
...     output_dir=repo_id,
...     per_device_train_batch_size=4,
...     num_train_epochs=20,
...     save_steps=200,
...     logging_steps=50,
...     eval_strategy="steps",
...     learning_rate=5e-5,
...     save_total_limit=2,
...     remove_unused_columns=False,
...     push_to_hub=True,
... )
```

Defina un collator de datos simple para agrupar ejemplos en lotes.

```py
>>> from transformers import DefaultDataCollator

>>> data_collator = DefaultDataCollator()
```

Por último, reúne todo y llama a [train()](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer.train):

```py
>>> from transformers import Trainer

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     data_collator=data_collator,
...     train_dataset=encoded_train_dataset,
...     eval_dataset=encoded_test_dataset,
...     processing_class=processor,
... )

>>> trainer.train()
```

Para añadir el modelo final a 🤗 Hub, crea una tarjeta de modelo y llama a `push_to_hub`:

```py
>>> trainer.create_model_card()
>>> trainer.push_to_hub()
```

## Inferencia

Ahora que has ajustado un modelo de LayoutLMv2 y lo has subido al 🤗 Hub, puedes usarlo para inferencia. La forma más sencilla de probar tu modelo ajustado para inferencia es utilizarlo en un [Pipeline](/docs/transformers/main/es/main_classes/pipelines#transformers.Pipeline).

Veamos un ejemplo:

```py
>>> example = dataset["test"][2]
>>> question = example["query"]["en"]
>>> image = example["image"]
>>> print(question)
>>> print(example["answers"])
'Who is ‘presiding’ TRRF GENERAL SESSION (PART 1)?'
['TRRF Vice President', 'lee a. waller']
```

A continuación, instancie un pipeline para la respuesta a preguntas sobre documentos con su modelo y pase la combinación de imagen + pregunta a este.

```py
>>> from transformers import pipeline

>>> qa_pipeline = pipeline("document-question-answering", model="MariaK/layoutlmv2-base-uncased_finetuned_docvqa")
>>> qa_pipeline(image, question)
[{'score': 0.9949808120727539,
  'answer': 'Lee A. Waller',
  'start': 55,
  'end': 57}]
```

También puede replicar manualmente los resultados de la pipeline si lo desea:

1. Toma una imagen y una pregunta, y prepáralas para el modelo usando el procesador de tu modelo.
2. Pase el resultado o el preprocesamiento a través del modelo.
3. El modelo devuelve `start_logits` y `end_logits`, que indican qué token se encuentra al inicio de la respuesta y
qué token está al final de la respuesta. Ambos tienen forma (batch_size, sequence_length).
4. Aplica un argmax en la última dimensión de `start_logits` y `end_logits` para obtener el `start_idx` y el `end_idx` predichos.
5. Decodifica la respuesta con el tokenizador.

```py
>>> import torch
>>> from transformers import AutoProcessor
>>> from transformers import AutoModelForDocumentQuestionAnswering

>>> processor = AutoProcessor.from_pretrained("MariaK/layoutlmv2-base-uncased_finetuned_docvqa")
>>> model = AutoModelForDocumentQuestionAnswering.from_pretrained("MariaK/layoutlmv2-base-uncased_finetuned_docvqa")

>>> with torch.no_grad():
...     encoding = processor(image.convert("RGB"), question, return_tensors="pt")
...     outputs = model(**encoding)
...     start_logits = outputs.start_logits
...     end_logits = outputs.end_logits
...     predicted_start_idx = start_logits.argmax(-1).item()
...     predicted_end_idx = end_logits.argmax(-1).item()

>>> processor.tokenizer.decode(encoding.input_ids.squeeze()[predicted_start_idx : predicted_end_idx + 1])
'lee a. waller'
```

