Transformers 文档

视觉问答

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

视觉问答

视觉问答 (VQA) 是根据图像回答开放式问题的任务。支持此任务的模型输入通常是图像和问题的组合,输出是自然语言表达的答案。

VQA 的一些值得注意的用例包括

  • 面向视障人士的无障碍应用。
  • 教育:对讲座或教科书中呈现的视觉材料提出问题。VQA 也可用于互动博物馆展览或历史遗迹。
  • 客户服务和电子商务:VQA 可以通过让用户询问产品问题来增强用户体验。
  • 图像检索:VQA 模型可用于检索具有特定特征的图像。例如,用户可以问“有狗吗?”以从一组图像中找到所有有狗的图像。

本指南中您将学习如何

  • Graphcore/vqa 数据集上微调一个分类 VQA 模型,特别是 ViLT
  • 使用您微调的 ViLT 进行推理。
  • 使用生成模型(如 BLIP-2)运行零样本 VQA 推理。

微调 ViLT

ViLT 模型将文本嵌入整合到 Vision Transformer (ViT) 中,使其具有最小化的视觉与语言预训练 (VLP) 设计。此模型可用于多个下游任务。对于 VQA 任务,分类器头部(位于 `[CLS]` 标记最终隐藏状态之上的线性层)被随机初始化。因此,视觉问答被视为一个**分类问题**。

最近的模型,如 BLIP、BLIP-2 和 InstructBLIP,将 VQA 视为一个生成任务。在本指南的后面,我们将演示如何使用它们进行零样本 VQA 推理。

在开始之前,请确保您已安装所有必要的库。

pip install -q transformers datasets

我们鼓励您与社区分享您的模型。登录您的 Hugging Face 帐户以将其上传到 🤗 Hub。出现提示时,输入您的令牌以登录

>>> from huggingface_hub import notebook_login

>>> notebook_login()

我们将模型检查点定义为全局变量。

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

加载数据

出于演示目的,在本指南中,我们使用带注释的视觉问答 `Graphcore/vqa` 数据集的一个非常小的样本。您可以在 🤗 Hub 上找到完整的数据集。

作为 `Graphcore/vqa` 数据集的替代方案,您可以从官方 VQA 数据集页面手动下载相同的数据。如果您喜欢使用自定义数据进行教程,请查阅 🤗 Datasets 文档中关于 创建图像数据集的指南。

让我们加载验证集中前 200 个示例并探索数据集的特征

>>> 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
})

让我们看一个例子来理解数据集的特征

>>> 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]}}

与任务相关的特性包括

  • question:要根据图像回答的问题
  • image_id:问题所指图像的路径
  • label:标注

我们可以移除其余不需要的特征

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

如您所见,`label` 特征包含由不同人工标注者收集的对同一个问题的几个答案(此处称为 `ids`)。这是因为问题的答案可能是主观的。在这种情况下,问题是“他在看哪里?”。有些人标注为“向下”,另一些人标注为“看桌子”,还有人标注为“滑板”等。

看看这张图片,想想你会给出什么答案

>>> from PIL import Image

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

由于问题和答案的模糊性,像这样的数据集被视为多标签分类问题(因为可能存在多个有效答案)。此外,除了创建独热编码向量外,还根据特定答案在标注中出现的次数创建了软编码。

例如,在上面的示例中,由于答案“向下”比其他答案选择的频率高得多,因此它的分数(在数据集中称为“权重”)为 1.0,而其余答案的分数小于 1.0。

为了后续使用合适的分类头实例化模型,我们创建两个字典:一个将标签名称映射到整数,另一个反之

>>> 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()}

现在我们有了映射,我们可以用它们的 ID 替换字符串答案,并展平数据集以便于进一步的预处理。

>>> 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)}

数据预处理

下一步是加载 ViLT 处理器,以准备图像和文本数据供模型使用。ViltProcessor 将 BERT tokenizer 和 ViLT 图像处理器封装成一个方便的单一处理器

>>> from transformers import ViltProcessor

>>> processor = ViltProcessor.from_pretrained(model_checkpoint)

为了预处理数据,我们需要使用 ViltProcessor 对图像和问题进行编码。处理器将使用 BertTokenizerFast 对文本进行分词,并为文本数据创建 `input_ids`、`attention_mask` 和 `token_type_ids`。对于图像,处理器将利用 ViltImageProcessor 来调整图像大小并进行归一化,并创建 `pixel_values` 和 `pixel_mask`。

所有这些预处理步骤都在底层完成,我们只需要调用 `processor`。但是,我们仍然需要准备目标标签。在这种表示中,每个元素对应一个可能的答案(标签)。对于正确答案,元素包含其各自的分数(权重),而其余元素则设置为零。

以下函数将 `processor` 应用于图像和问题,并按上述方式格式化标签

>>> 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

要在整个数据集上应用预处理函数,请使用 🤗 Datasets 的 `map` 函数。通过设置 `batched=True` 来一次处理数据集的多个元素,可以加快 `map` 的速度。此时,您可以随意删除不需要的列。

>>> 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
})

最后一步,使用 DefaultDataCollator 创建一个示例批次

>>> from transformers import DefaultDataCollator

>>> data_collator = DefaultDataCollator()

训练模型

现在您已准备好开始训练模型了!使用 ViltForQuestionAnswering 加载 ViLT。指定标签数量以及标签映射

>>> from transformers import ViltForQuestionAnswering

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

此时,只剩下三个步骤

  1. 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. 将训练参数与模型、数据集、处理器和数据整理器一同传递给 Trainer
>>> from transformers import Trainer

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     data_collator=data_collator,
...     train_dataset=processed_dataset,
...     processing_class=processor,
... )
  1. 调用 train() 来微调您的模型。
>>> trainer.train()

训练完成后,使用 push_to_hub() 方法将您的最终模型分享到 🤗 Hub。

>>> trainer.push_to_hub()

推理

现在您已经微调了一个 ViLT 模型并将其上传到 🤗 Hub,您可以将其用于推理。尝试微调模型进行推理最简单的方法是在 Pipeline 中使用它。

>>> from transformers import pipeline

>>> pipe = pipeline("visual-question-answering", model="MariaK/vilt_finetuned_200")

本指南中的模型只训练了 200 个示例,所以不要期望太多。让我们看看它是否至少从数据中学到了一些东西,并以数据集中的第一个示例为例进行推理

>>> example = dataset[0]
>>> image = Image.open(example['image_id'])
>>> question = example['question']
>>> print(question)
>>> pipe(image, question, top_k=1)
"Where is he looking?"
[{'score': 0.5498199462890625, 'answer': 'down'}]

尽管不太自信,但模型确实学到了一些东西。通过更多的示例和更长的训练,您会得到更好的结果!

如果需要,您也可以手动复制管道的结果

  1. 取一张图片和一个问题,使用模型中的处理器为模型准备它们。
  2. 将预处理结果通过模型传递。
  3. 从逻辑中获取最可能的答案 ID,并在 `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

之前的模型将 VQA 视为分类任务。一些最近的模型,如 BLIP、BLIP-2 和 InstructBLIP,将 VQA 视为生成任务。让我们以 BLIP-2 为例。它引入了一种新的视觉-语言预训练范式,其中可以使用任何预训练的视觉编码器和 LLM 组合(在 BLIP-2 博客文章中了解更多)。这使得在多个视觉-语言任务(包括视觉问答)上取得最先进的结果。

让我们演示如何将此模型用于 VQA。首先,让我们加载模型。这里我们将显式地将模型发送到 GPU(如果可用),这在之前的训练中不需要做,因为 Trainer 会自动处理

>>> from transformers import AutoProcessor, Blip2ForConditionalGeneration
>>> import torch
>>> from accelerate.test_utils.testing import get_backend

>>> processor = AutoProcessor.from_pretrained("Salesforce/blip2-opt-2.7b")
>>> model = Blip2ForConditionalGeneration.from_pretrained("Salesforce/blip2-opt-2.7b", torch_dtype=torch.float16)
>>> device, _, _ = get_backend() # automatically detects the underlying device type (CUDA, CPU, XPU, MPS, etc.)
>>> model.to(device)

模型以图像和文本作为输入,所以让我们使用 VQA 数据集中第一个示例的完全相同的图像/问题对

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

要使用 BLIP-2 进行视觉问答任务,文本提示必须遵循特定格式:`Question: {} Answer:`。

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

现在我们需要用模型的处理器预处理图像/提示,通过模型传递处理后的输入,然后解码输出

>>> 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"

正如您所见,模型识别了人群,以及面部的方向(向下看),但是它似乎忽略了人群在滑板手后面的事实。尽管如此,在无法获取人工标注数据集的情况下,这种方法可以快速产生有用的结果。

< > 在 GitHub 上更新