Transformers documentation
Réponse à des questions visuelles
Réponse à des questions visuelles
Le Visual Question Answering (VQA) est la tâche de répondre à des questions ouvertes à partir d'une image. L'entrée des modèles prenant en charge cette tâche est généralement une combinaison d'une image et d'une question, et la sortie est une réponse exprimée en langage naturel.
Voici quelques exemples d'utilisation notables pour le VQA :
- Applications d'accessibilité pour les personnes malvoyantes.
- Éducation : poser des questions sur des supports visuels présentés lors de cours ou dans des manuels. Le VQA peut également être utilisé dans des expositions de musées interactives ou sur des sites historiques.
- Service client et e-commerce : la VQA peut améliorer l'expérience utilisateur en permettant aux utilisateurs de poser des questions sur les produits.
- Récupération d’images : les modèles de VQA peuvent être utilisés pour récupérer des images présentant des caractéristiques spécifiques. Par exemple, l’utilisateur peut poser la question « Y a-t-il un chien ? » afin de trouver toutes les images contenant des chiens dans un ensemble d’images.
Dans ce guide, vous apprendrez à :
- Ajuster finement un modèle de VQA de classification, plus précisément ViLT, sur le jeu de données
Graphcore/vqa. - Utilisez votre ViLT affiné pour l'inférence.
- Exécuter une inférence VQA en zéro tirage avec un modèle génératif, comme BLIP-2.
Ajustement fin de ViLT
Le modèle ViLT intègre des embeddings de texte dans un Vision Transformer (ViT), ce qui lui permet d’adopter une conception minimale pour le pré-entraînement Vision-and-Language (VLP). Ce modèle peut être utilisé pour plusieurs tâches en aval. Pour la tâche VQA, une tête de classification est placée au sommet (une couche linéaire au-dessus de l’état caché final du jeton [CLS]) et initialisée aléatoirement. Le Visual Question Answering est ainsi traité comme un problème de classification.
Les modèles plus récents, tels que BLIP, BLIP-2 et InstructBLIP, traitent le VQA comme une tâche générative. Plus loin dans ce guide, nous illustrons comment les utiliser pour une inférence VQA en zéro tirage.
Avant de commencer, assurez-vous d'avoir installé toutes les bibliothèques nécessaires.
pip install -q transformers datasets
Nous vous encourageons à partager votre modèle avec la communauté. Connectez-vous à votre compte Hugging Face pour le téléverser sur le 🤗 Hub. Lorsqu'on vous le demande, saisissez votre jeton pour vous connecter :
>>> from huggingface_hub import notebook_login
>>> notebook_login()Définissons le point de contrôle du modèle comme variable globale.
>>> model_checkpoint = "dandelin/vilt-b32-mlm"Charger les données
À titre d’illustration, ce guide utilise un très petit échantillon du jeu de données de question-réponse visuelle annoté Graphcore/vqa.
Vous pouvez trouver le jeu de données complet sur 🤗 Hub.
En alternative au jeu de données Graphcore/vqa, vous pouvez télécharger manuellement les mêmes données depuis la page officielle du jeu de données VQA. Si vous préférez suivre le tutoriel avec vos propres données, consultez le guide Créer un jeu de données d'images dans la documentation de 🤗 Datasets.
Chargons les 200 premiers exemples de la partition de validation et explorons les caractéristiques du jeu de données :
>>> 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
})Examinons un exemple pour comprendre les caractéristiques du jeu de données :
>>> 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]}}Les fonctionnalités pertinentes pour la tâche incluent :
question: la question à répondre à partir de l'imageimage_id: le chemin de l'image à laquelle la question fait référencelabel: les annotations
Nous pouvons supprimer le reste des fonctionnalités, car elles ne seront pas nécessaires :
>>> dataset = dataset.remove_columns(['question_type', 'question_id', 'answer_type'])Comme vous pouvez le constater, la fonction label contient plusieurs réponses à la même question (appelées ids ici), collectées par différents annotateurs humains.
Cela est dû au fait que la réponse à une question peut être subjective. Dans ce cas, la question est « where is he looking? ». Certains l’ont annotée avec « down », d’autres avec « at table », un autre avec « skateboard », etc.
Regardez l'image et réfléchissez à la réponse que vous donneriez :
>>> from PIL import Image
>>> image = Image.open(dataset[0]['image_id'])
>>> image
En raison de l’ambiguïté des questions et des réponses, ce type de jeu de données est traité comme un problème de classification multi-étiquettes (puisque plusieurs réponses peuvent être valides). De plus, plutôt que de simplement créer un vecteur one-hot, on crée un encodage doux, basé sur le nombre de fois où une réponse donnée est apparue dans les annotations.
Par exemple, dans l’exemple ci-dessus, comme la réponse « down » est sélectionnée beaucoup plus souvent que les autres réponses, elle a un score (appelé weight dans le jeu de données) de 1.0, et les autres réponses ont des scores < 1.0.
Pour instancier ensuite le modèle avec une tête de classification appropriée, créons deux dictionnaires : l'un qui associe le nom de l'étiquette à un entier, et l'inverse :
>>> 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()}Maintenant que nous disposons des correspondances, nous pouvons remplacer les réponses sous forme de chaînes par leurs identifiants, puis aplatir le jeu de données pour faciliter le prétraitement ultérieur.
>>> 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étraitement des données
L'étape suivante consiste à charger un processeur ViLT afin de préparer les données d'image et de texte pour le modèle. ViltProcessor encapsule un tokeniseur BERT et un processeur d'image ViLT dans un unique processeur pratique :
>>> from transformers import ViltProcessor
>>> processor = ViltProcessor.from_pretrained(model_checkpoint)Pour prétraiter les données, nous devons encoder les images et les questions à l’aide du ViltProcessor. Le processeur utilisera
le BertTokenizerFast pour tokeniser le texte et créer input_ids, attention_mask et token_type_ids pour les données textuelles.
Quant aux images, le processeur s’appuiera sur ViltImageProcessor pour redimensionner et normaliser l’image, et créer pixel_values et pixel_mask.
Toutes ces étapes de prétraitement sont effectuées en interne, il nous suffit d’appeler le processor. Il reste toutefois à préparer les étiquettes cibles. Dans cette représentation, chaque élément correspond à une réponse possible (étiquette). Pour les réponses correctes, l’élément contient leur score (poids) respectif, tandis que les éléments restants sont mis à zéro.
La fonction suivante applique le processor aux images et aux questions, puis met en forme les étiquettes comme décrit ci-dessus :
>>> 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 encodingPour appliquer la fonction de prétraitement à l’ensemble du jeu de données, utilisez la fonction map de 🤗 Datasets. Vous pouvez accélérer map en définissant batched=True pour traiter plusieurs éléments du jeu de données à la fois. À ce stade, n’hésitez pas à supprimer les colonnes dont vous n’avez pas besoin.
>>> 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
})En dernière étape, créez un lot d'exemples à l'aide de DefaultDataCollator :
>>> from transformers import DefaultDataCollator
>>> data_collator = DefaultDataCollator()Entraîner le modèle
Vous êtes prêt à commencer l’entraînement de votre modèle ! Chargez ViLT avec ViltForQuestionAnswering. Spécifiez le nombre de labels ainsi que les correspondances de labels :
>>> from transformers import ViltForQuestionAnswering
>>> model = ViltForQuestionAnswering.from_pretrained(model_checkpoint, num_labels=len(id2label), id2label=id2label, label2id=label2id)À ce stade, il ne reste que trois étapes :
- Définissez vos hyperparamètres d’entraînement dans 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,
... )- Transmettez les arguments d’entraînement à Trainer en même temps que le modèle, le jeu de données, le processeur et le data collator.
>>> from transformers import Trainer
>>> trainer = Trainer(
... model=model,
... args=training_args,
... data_collator=data_collator,
... train_dataset=processed_dataset,
... processing_class=processor,
... )- Appel de train() pour l’ajustement fin de votre modèle.
>>> trainer.train()Une fois l’entraînement terminé, partagez votre modèle sur le Hub à l’aide de la méthode push_to_hub() pour publier votre modèle final sur le 🤗 Hub :
>>> trainer.push_to_hub()Inférence
Maintenant que vous avez affiné un modèle ViLT et l'avez téléversé sur le 🤗 Hub, vous pouvez l'utiliser pour l'inférence.
- Prenez une image et une question, puis préparez-les pour le modèle à l'aide du processeur de votre modèle.
- Transmettez le résultat ou le prétraitement au modèle.
- À partir des logits, obtenez l’id de la réponse la plus probable, puis trouvez la réponse réelle dans
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: downVQA en zéro tir
Le modèle précédent traitait la VQA comme une tâche de classification. Certains modèles récents, tels que BLIP, BLIP-2 et InstructBLIP, abordent la VQA comme une tâche générative. Prenons BLIP-2 comme exemple. Il a introduit un nouveau paradigme de pré-entraînement visuel-langagier dans lequel toute combinaison d’encodeur visuel pré-entraîné et de LLM peut être utilisée (en savoir plus dans le article de blog BLIP-2). Cela permet d’obtenir des résultats de pointe sur plusieurs tâches visuelles et langagières, y compris la réponse à des questions visuelles.
Illustrons comment utiliser ce modèle pour du VQA. Commençons par charger le modèle. Ici, nous envoyons explicitement le modèle sur un GPU, s’il est disponible, ce qui n’était pas nécessaire lors de l’entraînement, car Trainer gère cela automatiquement :
>>> 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)Le modèle prend en entrée une image et du texte, utilisons donc la même paire image/question que dans le premier exemple du jeu de données VQA :
>>> example = dataset[0]
>>> image = Image.open(example['image_id'])
>>> question = example['question']Pour utiliser BLIP-2 pour la tâche de question-réponse visuelle, le prompt textuel doit suivre un format spécifique : Question: {} Answer:.
>>> prompt = f"Question: {question} Answer:"Il faut maintenant prétraiter l’image/le prompt avec le processeur du modèle, faire passer l’entrée prétraitée à travers le modèle, puis décoder la sortie :
>>> 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"Comme vous pouvez le constater, le modèle a reconnu la foule et l’orientation du visage (regard vers le bas), mais il semble manquer le fait que la foule se trouve derrière le patineur. Néanmoins, dans les cas où l’acquisition de données annotées par des humains n’est pas réalisable, cette approche peut rapidement produire des résultats utiles.
Update on GitHub