Transformers documentation

Resposta a perguntas visuais

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Resposta a perguntas visuais

Visual Question Answering (VQA) é a tarefa de responder a perguntas abertas com base em uma imagem. A entrada para modelos que suportam essa tarefa é tipicamente uma combinação de uma imagem e uma pergunta, e a saída é uma resposta expressa em linguagem natural.

Alguns exemplos notáveis de casos de uso para VQA incluem:

  • Aplicações de acessibilidade para pessoas com deficiência visual.
  • Educação: formular perguntas sobre materiais visuais apresentados em aulas ou livros didáticos. O VQA também pode ser utilizado em exposições de museus interativas ou em locais históricos.
  • Atendimento ao cliente e comércio eletrônico: a VQA pode aprimorar a experiência do usuário, permitindo que os clientes façam perguntas sobre os produtos.
  • Recuperação de imagens: modelos de VQA podem ser usados para recuperar imagens com características específicas. Por exemplo, o usuário pode perguntar “Há um cachorro?” para encontrar todas as imagens com cachorros a partir de um conjunto de imagens.

Neste guia, você aprenderá como:

  • Ajuste fino de um modelo de VQA de classificação, especificamente ViLT, no conjunto de dados Graphcore/vqa.
  • Use seu ViLT ajustado para inferência.
  • Execute inferência de VQA zero-shot com um modelo generativo, como o BLIP-2.

Fine-tuning do ViLT

O modelo ViLT incorpora embeddings de texto em um Vision Transformer (ViT), permitindo um design minimalista para o pré-treinamento de visão e linguagem (VLP). Este modelo pode ser usado para várias tarefas de nível inferior. Para a tarefa de VQA, uma cabeça de classificador é colocada no topo (uma camada linear sobre o estado oculto final do token [CLS]) e inicializada aleatoriamente. O Visual Question Answering é, portanto, tratado como um problema de classificação.

Modelos mais recentes, como BLIP, BLIP-2 e InstructBLIP, tratam a VQA como uma tarefa generativa. Mais adiante neste guia, ilustramos como usá-los para inferência de VQA zero-shot.

Antes de começar, verifique se todas as bibliotecas necessárias estão instaladas.

pip install -q transformers datasets

Incentivamos você a compartilhar seu modelo com a comunidade. Faça login na sua conta do Hugging Face para enviá-lo ao 🤗 Hub. Quando solicitado, insira seu token para fazer login:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

Vamos definir o checkpoint do modelo como uma variável global.

>>> model_checkpoint = "dandelin/vilt-b32-mlm"

Carregar os dados

Para fins de ilustração, neste guia usamos uma amostra muito pequena do conjunto de dados de resposta a perguntas visuais anotadas Graphcore/vqa. Você pode encontrar o conjunto de dados completo no 🤗 Hub.

Como alternativa ao conjunto de dados Graphcore/vqa, você pode baixar os mesmos dados manualmente da página oficial do conjunto de dados VQA. Se você preferir seguir o tutorial com seus próprios dados, consulte o guia Criar um conjunto de dados de imagens na documentação do 🤗 Datasets.

Vamos carregar os primeiros 200 exemplos do split de validação e explorar os recursos do dataset:

>>> from datasets import load_dataset

>>> dataset = load_dataset("Graphcore/vqa", split="validation[:200]")
>>> dataset
Dataset({
    features: ['question', 'question_type', 'question_id', 'image_id', 'answer_type', 'label'],
    num_rows: 200
})

Vamos examinar um exemplo para entender os recursos do conjunto de dados:

>>> dataset[0]
{'question': 'Where is he looking?',
 'question_type': 'none of the above',
 'question_id': 262148000,
 'image_id': '/root/.cache/huggingface/datasets/downloads/extracted/ca733e0e000fb2d7a09fbcc94dbfe7b5a30750681d0e965f8e0a23b1c2f98c75/val2014/COCO_val2014_000000262148.jpg',
 'answer_type': 'other',
 'label': {'ids': ['at table', 'down', 'skateboard', 'table'],
  'weights': [0.30000001192092896,
   1.0,
   0.30000001192092896,
   0.30000001192092896]}}

Os recursos relevantes para a tarefa incluem:

  • question: a pergunta a ser respondida a partir da imagem
  • image_id: o caminho da imagem à qual a pergunta se refere
  • label: as anotações

Podemos remover o restante dos recursos, pois eles não serão necessários:

>>> dataset = dataset.remove_columns(['question_type', 'question_id', 'answer_type'])

Como você pode ver, o recurso label contém várias respostas para a mesma pergunta (chamadas de ids aqui), coletadas por diferentes anotadores humanos. Isso ocorre porque a resposta a uma pergunta pode ser subjetiva. Neste caso, a pergunta é “onde ele está olhando?”. Algumas pessoas anotaram isso como “para baixo”, outras como “na mesa”, outra como “skateboard”, etc.

Dê uma olhada na imagem e considere qual resposta você daria:

>>> from PIL import Image

>>> image = Image.open(dataset[0]['image_id'])
>>> image
VQA Image Example

Devido à ambiguidade das perguntas e das respostas, conjuntos de dados como esse são tratados como um problema de classificação multirótulo (já que múltiplas respostas podem ser válidas). Além disso, em vez de simplesmente criar um vetor codificado em one-hot, cria-se uma codificação suave, com base no número de vezes que uma determinada resposta apareceu nas anotações.

Por exemplo, no exemplo acima, como a resposta “down” é selecionada com muito mais frequência do que as outras respostas, ela tem uma pontuação (chamada de weight no conjunto de dados) de 1.0, e as demais respostas têm pontuações < 1.0.

Para instanciar o modelo posteriormente com uma cabeça de classificação adequada, vamos criar dois dicionários: um que mapeia o nome do rótulo para um inteiro e vice-versa:

>>> import itertools

>>> labels = [item['ids'] for item in dataset['label']]
>>> flattened_labels = list(itertools.chain(*labels))
>>> unique_labels = list(set(flattened_labels))

>>> label2id = {label: idx for idx, label in enumerate(unique_labels)}
>>> id2label = {idx: label for label, idx in label2id.items()}

Agora que temos os mapeamentos, podemos substituir as respostas em texto pelos seus ids e achatar o conjunto de dados para um pré-processamento posterior mais conveniente.

>>> def replace_ids(inputs):
...   inputs["label"]["ids"] = [label2id[x] for x in inputs["label"]["ids"]]
...   return inputs


>>> dataset = dataset.map(replace_ids)
>>> flat_dataset = dataset.flatten()
>>> flat_dataset.features
{'question': Value(dtype='string', id=None),
 'image_id': Value(dtype='string', id=None),
 'label.ids': Sequence(feature=Value(dtype='int64', id=None), length=-1, id=None),
 'label.weights': Sequence(feature=Value(dtype='float64', id=None), length=-1, id=None)}

Pré-processamento de dados

O próximo passo é carregar um processador ViLT para preparar os dados de imagem e texto para o modelo. ViltProcessor encapsula um tokenizador BERT e um processador de imagem ViLT em um único processador conveniente:

>>> from transformers import ViltProcessor

>>> processor = ViltProcessor.from_pretrained(model_checkpoint)

Para pré-processar os dados, precisamos codificar as imagens e as perguntas usando o ViltProcessor. O processador usará o BertTokenizerFast para tokenizar o texto e criar input_ids, attention_mask e token_type_ids para os dados de texto. No que diz respeito às imagens, o processador aproveitará o ViltImageProcessor para redimensionar e normalizar a imagem, e criar pixel_values e pixel_mask.

Todos esses passos de pré-processamento são executados internamente; basta chamar o processor. No entanto, ainda precisamos preparar os rótulos-alvo. Nessa representação, cada elemento corresponde a uma resposta possível (rótulo). Para as respostas corretas, o elemento contém a respectiva pontuação (peso), enquanto os elementos restantes são definidos como zero.

A função a seguir aplica o processor às imagens e às perguntas e formata os rótulos conforme descrito acima:

>>> import torch

>>> def preprocess_data(examples):
...     image_paths = examples['image_id']
...     images = [Image.open(image_path) for image_path in image_paths]
...     texts = examples['question']

...     encoding = processor(images, texts, padding="max_length", truncation=True, return_tensors="pt")

...     for k, v in encoding.items():
...           encoding[k] = v.squeeze()

...     targets = []

...     for labels, scores in zip(examples['label.ids'], examples['label.weights']):
...         target = torch.zeros(len(id2label))

...         for label, score in zip(labels, scores):
...             target[label] = score

...         targets.append(target)

...     encoding["labels"] = targets

...     return encoding

Para aplicar a função de pré-processamento em todo o conjunto de dados, use a função map do 🤗 Datasets. Você pode acelerar o map definindo batched=True para processar vários elementos do conjunto de dados de uma vez. Neste ponto, sinta-se à vontade para remover as colunas de que você não precisa.

>>> processed_dataset = flat_dataset.map(preprocess_data, batched=True, remove_columns=['question','question_type',  'question_id', 'image_id', 'answer_type', 'label.ids', 'label.weights'])
>>> processed_dataset
Dataset({
    features: ['input_ids', 'token_type_ids', 'attention_mask', 'pixel_values', 'pixel_mask', 'labels'],
    num_rows: 200
})

Como etapa final, crie um lote de exemplos usando DefaultDataCollator:

>>> from transformers import DefaultDataCollator

>>> data_collator = DefaultDataCollator()

Treinar o modelo

Agora você está pronto para começar a treinar seu modelo! Carregue o ViLT com ViltForQuestionAnswering. Especifique o número de rótulos juntamente com os mapeamentos de rótulos:

>>> from transformers import ViltForQuestionAnswering

>>> model = ViltForQuestionAnswering.from_pretrained(model_checkpoint, num_labels=len(id2label), id2label=id2label, label2id=label2id)

Neste ponto, restam apenas três etapas:

  1. Defina os hiperparâmetros de treinamento em TrainingArguments:
>>> from transformers import TrainingArguments

>>> repo_id = "MariaK/vilt_finetuned_200"

>>> training_args = TrainingArguments(
...     output_dir=repo_id,
...     per_device_train_batch_size=4,
...     num_train_epochs=20,
...     save_steps=200,
...     logging_steps=50,
...     learning_rate=5e-5,
...     save_total_limit=2,
...     remove_unused_columns=False,
...     push_to_hub=True,
... )
  1. Passe os argumentos de treinamento para Trainer junto com o modelo, o conjunto de dados, o processador e o data collator.
>>> from transformers import Trainer

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     data_collator=data_collator,
...     train_dataset=processed_dataset,
...     processing_class=processor,
... )
  1. Chame train() para fazer o ajuste fino do seu modelo.
>>> trainer.train()

Quando o treinamento for concluído, compartilhe seu modelo no Hub com o método push_to_hub() para compartilhar seu modelo final no 🤗 Hub:

>>> trainer.push_to_hub()

Inferência

Agora que você ajustou um modelo ViLT e o enviou para o 🤗 Hub, você pode usá-lo para inferência.

  1. Pegue uma imagem e uma pergunta e prepare-as para o modelo usando o processor do seu modelo.
  2. Encaminhe o resultado ou o pré-processamento pelo modelo.
  3. A partir dos logits, obtenha o id da resposta mais provável e encontre a resposta real no id2label.
>>> processor = ViltProcessor.from_pretrained("MariaK/vilt_finetuned_200")

>>> image = Image.open(example['image_id'])
>>> question = example['question']

>>> # prepare inputs
>>> inputs = processor(image, question, return_tensors="pt")

>>> model = ViltForQuestionAnswering.from_pretrained("MariaK/vilt_finetuned_200")

>>> # forward pass
>>> with torch.no_grad():
...     outputs = model(**inputs)

>>> logits = outputs.logits
>>> idx = logits.argmax(-1).item()
>>> print("Predicted answer:", model.config.id2label[idx])
Predicted answer: down

VQA zero-shot

O modelo anterior tratava a VQA como uma tarefa de classificação. Alguns modelos recentes, como BLIP, BLIP-2 e InstructBLIP, abordam a VQA como uma tarefa generativa. Vamos usar o BLIP-2 como exemplo. Ele introduziu um novo paradigma de pré-treinamento de visão-linguagem no qual qualquer combinação de codificador de visão pré-treinado e LLM pode ser utilizada (saiba mais no post no blog do BLIP-2). Isso permite alcançar resultados de estado da arte em várias tarefas de visão-linguagem, incluindo a resposta a perguntas visuais.

Vamos ilustrar como você pode usar este modelo para VQA. Primeiro, vamos carregar o modelo. Aqui, enviaremos explicitamente o modelo para uma GPU, se disponível, o que não precisávamos fazer anteriormente durante o treinamento, pois o Trainer trata disso automaticamente:

>>> from transformers import AutoProcessor, Blip2ForConditionalGeneration
from accelerate import Accelerator
>>> import torch

>>> processor = AutoProcessor.from_pretrained("Salesforce/blip2-opt-2.7b")
>>> model = Blip2ForConditionalGeneration.from_pretrained("Salesforce/blip2-opt-2.7b", dtype=torch.float16)
>>> device = Accelerator().device
>>> model.to(device)

O modelo recebe imagem e texto como entrada, então vamos usar exatamente o mesmo par de imagem/pergunta do primeiro exemplo no conjunto de dados VQA:

>>> example = dataset[0]
>>> image = Image.open(example['image_id'])
>>> question = example['question']

Para usar o BLIP-2 na tarefa de resposta a perguntas visuais, o prompt textual deve seguir um formato específico: Question: {} Answer:.

>>> prompt = f"Question: {question} Answer:"

Agora, precisamos pré-processar a imagem/prompt com o processador do modelo, passar a entrada processada pelo modelo e decodificar a saída:

>>> inputs = processor(image, text=prompt, return_tensors="pt").to(device, torch.float16)

>>> generated_ids = model.generate(**inputs, max_new_tokens=10)
>>> generated_text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0].strip()
>>> print(generated_text)
"He is looking at the crowd"

Como você pode ver, o modelo reconheceu a multidão e a direção do rosto (olhando para baixo), no entanto, parece não captar o fato de que a multidão está atrás do skater. Ainda assim, em casos em que a aquisição de datasets anotados por humanos não é viável, essa abordagem pode produzir resultados úteis rapidamente.

Update on GitHub