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

由于问题和答案的模糊性,像这样的数据集被视为多标签分类问题(因为可能存在多个有效答案)。此外,除了创建独热编码向量外,还根据特定答案在标注中出现的次数创建了软编码。
例如,在上面的示例中,由于答案“向下”比其他答案选择的频率高得多,因此它的分数(在数据集中称为“权重”)为 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)
此时,只剩下三个步骤
- 在 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。
>>> 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'}]
尽管不太自信,但模型确实学到了一些东西。通过更多的示例和更长的训练,您会得到更好的结果!
如果需要,您也可以手动复制管道的结果
- 取一张图片和一个问题,使用模型中的处理器为模型准备它们。
- 将预处理结果通过模型传递。
- 从逻辑中获取最可能的答案 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 上更新