Transformers 文档
视觉问答
并获得增强的文档体验
开始使用
视觉问答
视觉问答 (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

由于问题和答案的歧义,像这样的数据集被视为多标签分类问题(因为多个答案可能是有效的)。此外,与其仅仅创建一个 one-hot 编码向量,不如创建一个软编码,该编码基于某个答案在注释中出现的次数。
例如,在上面的示例中,由于答案“向下”被选择的频率远高于其他答案,因此它的得分(在数据集中称为 weight
)为 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 分词器和 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)
此时,仅剩三个步骤
- 在 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,
... )
- 将训练参数传递给 Trainer,以及模型、数据集、处理器和数据整理器。
>>> from transformers import Trainer
>>> trainer = Trainer(
... model=model,
... args=training_args,
... data_collator=data_collator,
... train_dataset=processed_dataset,
... processing_class=processor,
... )
- 调用 train() 以微调您的模型。
>>> trainer.train()
训练完成后,使用 push_to_hub() 方法将您的模型共享到 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'}]
即使不是很自信,模型确实学到了一些东西。通过更多示例和更长时间的训练,您将获得更好的结果!
如果您愿意,您也可以手动复制管道的结果
- 拍摄图像并提出问题,使用模型中的处理器为模型准备它们。
- 通过模型转发预处理的结果。
- 从 logits 中,获取最有可能的答案的 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 上更新