Transformers documentation

Trả lời câu hỏi trực quan

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

and get access to the augmented documentation experience

to get started

Trả lời câu hỏi trực quan

Visual Question Answering (VQA) là nhiệm vụ trả lời các câu hỏi mở dựa trên một hình ảnh. Đầu vào của các mô hình hỗ trợ nhiệm vụ này thường là sự kết hợp giữa một hình ảnh và một câu hỏi, và đầu ra là một câu trả lời được diễn đạt bằng ngôn ngữ tự nhiên.

Một số ví dụ về trường hợp sử dụng đáng chú ý cho VQA bao gồm:

  • Các ứng dụng hỗ trợ khả năng tiếp cận cho người khiếm thị.
  • Giáo dục: đặt câu hỏi về các tài liệu trực quan được trình bày trong các bài giảng hoặc sách giáo khoa. VQA cũng có thể được sử dụng trong các triển lãm bảo tàng tương tác hoặc các di tích lịch sử.
  • Dịch vụ khách hàng và thương mại điện tử: VQA có thể nâng cao trải nghiệm người dùng bằng cách cho phép người dùng đặt câu hỏi về các sản phẩm.
  • Tìm kiếm hình ảnh: Các mô hình VQA có thể được sử dụng để truy xuất các hình ảnh có đặc điểm cụ thể. Ví dụ, người dùng có thể hỏi “Có con chó không?” để tìm tất cả các hình ảnh có chó từ một tập hình ảnh.

Trong hướng dẫn này, bạn sẽ học cách:

  • Tinh chỉnh mô hình VQA phân loại, cụ thể là ViLT, trên dataset Graphcore/vqa.
  • Sử dụng ViLT đã tinh chỉnh của bạn để suy luận.
  • Chạy suy luận VQA zero-shot với một mô hình sinh, chẳng hạn như BLIP-2.

Fine-tuning ViLT

Mô hình ViLT tích hợp các vector nhúng văn bản vào Vision Transformer (ViT), cho phép thiết kế tối giản cho việc tiền huấn luyện kết hợp thị giác và ngôn ngữ (VLP). Mô hình này có thể được sử dụng cho một số tác vụ hạ nguồn. Đối với tác vụ VQA, một đầu phân loại được đặt phía trên (một lớp tuyến tính phía trên trạng thái ẩn cuối cùng của token [CLS]) và được khởi tạo ngẫu nhiên. Do đó, Visual Question Answering được xử lý như một vấn đề phân loại.

Các mô hình gần đây hơn, chẳng hạn như BLIP, BLIP-2 và InstructBLIP, xem VQA là một tác vụ sinh. Sau này trong hướng dẫn này, chúng tôi sẽ minh họa cách sử dụng chúng để suy luận VQA zero-shot.

Trước khi bắt đầu, hãy đảm bảo bạn đã cài đặt tất cả các thư viện cần thiết.

pip install -q transformers datasets

Chúng tôi khuyến khích bạn chia sẻ mô hình của mình với cộng đồng. Đăng nhập vào tài khoản Hugging Face của bạn để tải nó lên 🤗 Hub. Khi được yêu cầu, hãy nhập token của bạn để đăng nhập:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

Hãy định nghĩa checkpoint của mô hình như một biến toàn cục.

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

Tải dữ liệu

Để minh họa, trong hướng dẫn này, chúng tôi sử dụng một mẫu rất nhỏ từ bộ dữ liệu Graphcore/vqa về câu hỏi và trả lời trực quan đã được chú thích. Bạn có thể tìm thấy toàn bộ bộ dữ liệu trên 🤗 Hub.

Ngoài dataset Graphcore/vqa, bạn có thể tải cùng dữ liệu đó thủ công từ trang dataset VQA chính thức. Nếu bạn muốn làm theo tutorial với dữ liệu tùy chỉnh của mình, hãy xem hướng dẫn Tạo một image dataset trong tài liệu 🤗 Datasets.

Hãy tải 200 ví dụ đầu tiên từ tập kiểm tra và khám phá các đặc trưng của bộ dữ liệu:

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

Hãy xem một ví dụ để hiểu các đặc điểm của bộ dữ liệu:

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

Các tính năng liên quan đến tác vụ bao gồm:

  • question: câu hỏi cần được trả lời từ hình ảnh
  • image_id: đường dẫn đến hình ảnh mà câu hỏi đề cập
  • label: các chú thích

Chúng ta có thể xóa các tính năng còn lại vì chúng sẽ không cần thiết:

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

Như bạn có thể thấy, đặc trưng label chứa một số câu trả lời cho cùng một câu hỏi (được gọi là ids ở đây) do các người chú thích khác nhau thu thập. Điều này là do câu trả lời cho một câu hỏi có thể mang tính chủ quan. Trong trường hợp này, câu hỏi là “anh ấy đang nhìn vào đâu?”. Một số người chú thích là “xuống dưới”, một số khác là “vào bàn”, một người nữa là “ván trượt”, v.v.

Hãy xem hình ảnh và cân nhắc xem bạn sẽ chọn câu trả lời nào:

>>> from PIL import Image

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

Do tính mơ hồ của các câu hỏi và câu trả lời, các bộ dữ liệu như thế này được xử lý như một bài toán phân loại đa nhãn (vì nhiều câu trả lời có thể đều hợp lệ). Hơn nữa, thay vì chỉ tạo một vector mã hóa one-hot, người ta tạo ra một mã hóa mềm (soft encoding), dựa trên số lần một câu trả lời nhất định xuất hiện trong các chú thích.

Ví dụ, trong ví dụ ở trên, vì câu trả lời “down” được chọn thường xuyên hơn nhiều so với các câu trả lời khác, nên nó có điểm (được gọi là weight trong bộ dữ liệu) là 1.0, và các câu trả lời còn lại có điểm < 1.0.

Để sau này khởi tạo mô hình với một đầu phân loại phù hợp, hãy tạo hai từ điển: một từ điển ánh xạ tên nhãn sang số nguyên và ngược lại:

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

Bây giờ khi đã có các ánh xạ, chúng ta có thể thay thế các câu trả lời dạng chuỗi bằng id tương ứng và làm phẳng bộ dữ liệu để tiền xử lý tiếp theo thuận tiện hơn.

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

Tiền xử lý dữ liệu

Bước tiếp theo là nạp một bộ xử lý ViLT để chuẩn bị dữ liệu ảnh và văn bản cho mô hình. ViltProcessor đóng gói một bộ phân từ BERT và bộ xử lý ảnh ViLT thành một bộ xử lý đơn giản, tiện lợi:

>>> from transformers import ViltProcessor

>>> processor = ViltProcessor.from_pretrained(model_checkpoint)

Để tiền xử lý dữ liệu, chúng ta cần mã hóa các hình ảnh và câu hỏi bằng ViltProcessor. Bộ xử lý sẽ sử dụng BertTokenizerFast để tách token cho văn bản và tạo ra input_ids, attention_mask và token_type_ids cho dữ liệu văn bản. Đối với hình ảnh, bộ xử lý sẽ tận dụng ViltImageProcessor để thay đổi kích thước và chuẩn hóa hình ảnh, đồng thời tạo ra pixel_values và pixel_mask.

Tất cả các bước tiền xử lý này được thực hiện bên trong, chúng ta chỉ cần gọi processor. Tuy nhiên, chúng ta vẫn cần chuẩn bị các nhãn đích. Trong biểu diễn này, mỗi phần tử tương ứng với một câu trả lời (nhãn) có thể. Đối với các câu trả lời đúng, phần tử chứa điểm (trọng số) tương ứng của chúng, trong khi các phần tử còn lại được đặt bằng không.

Hàm sau đây áp dụng processor cho các hình ảnh và câu hỏi, sau đó định dạng các nhãn như đã mô tả ở trên:

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

Để áp dụng hàm tiền xử lý lên toàn bộ bộ dữ liệu, hãy sử dụng hàm map của 🤗 Datasets. Bạn có thể tăng tốc map bằng cách đặt batched=True để xử lý nhiều phần tử của bộ dữ liệu cùng lúc. Tại thời điểm này, bạn có thể tự do xóa các cột không cần thiết.

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

Là bước cuối cùng, hãy tạo một lô ví dụ bằng cách sử dụng DefaultDataCollator:

>>> from transformers import DefaultDataCollator

>>> data_collator = DefaultDataCollator()

Huấn luyện mô hình

Bạn đã sẵn sàng để bắt đầu huấn luyện mô hình của mình! Nạp ViLT với ViltForQuestionAnswering. Chỉ định số lượng nhãn cùng với ánh xạ nhãn:

>>> from transformers import ViltForQuestionAnswering

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

Tới bước này, chỉ còn ba bước nữa:

  1. Định nghĩa các siêu tham số huấn luyện của bạn trong 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. Truyền các đối số huấn luyện vào Trainer cùng với mô hình, tập dữ liệu, bộ xử lý và bộ sắp xếp dữ liệu.
>>> from transformers import Trainer

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     data_collator=data_collator,
...     train_dataset=processed_dataset,
...     processing_class=processor,
... )
  1. Gọi train() để tinh chỉnh mô hình của bạn.
>>> trainer.train()

Khi quá trình huấn luyện hoàn tất, hãy chia sẻ mô hình của bạn lên Hub bằng phương thức push_to_hub() để chia sẻ mô hình cuối cùng của bạn trên 🤗 Hub:

>>> trainer.push_to_hub()

Suy luận

Giờ đây, khi bạn đã tinh chỉnh một mô hình ViLT và tải nó lên 🤗 Hub, bạn có thể sử dụng nó để suy luận.

  1. Nhận một hình ảnh và một câu hỏi, chuẩn bị chúng cho mô hình bằng cách sử dụng processor từ mô hình của bạn.
  2. Truyền kết quả hoặc tiền xử lý qua mô hình.
  3. Từ logits, lấy id của câu trả lời có khả năng cao nhất và tìm câu trả lời thực tế trong 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

Mô hình trước đó xem VQA là một bài toán phân loại. Một số mô hình gần đây, chẳng hạn như BLIP, BLIP-2 và InstructBLIP, tiếp cận VQA như một bài toán sinh. Hãy lấy BLIP-2 làm ví dụ. Nó đã giới thiệu một hệ thống tiền huấn luyện thị giác-ngôn ngữ mới, trong đó bất kỳ sự kết hợp nào giữa bộ mã hóa thị giác đã được tiền huấn luyện và LLM đều có thể được sử dụng (tìm hiểu thêm trong bài đăng trên blog BLIP-2). Điều này cho phép đạt được kết quả tiên tiến nhất trên nhiều tác vụ thị giác-ngôn ngữ, bao gồm cả trả lời câu hỏi thị giác.

Hãy minh họa cách bạn có thể sử dụng mô hình này cho VQA. Trước tiên, hãy tải mô hình. Ở đây, chúng ta sẽ gửi mô hình một cách rõ ràng đến GPU, nếu có sẵn, điều mà trước đó khi huấn luyện chúng ta không cần làm, vì Trainer xử lý điều này một cách tự động:

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

Mô hình nhận ảnh và văn bản làm đầu vào, vì vậy hãy sử dụng cặp ảnh/câu hỏi chính xác từ ví dụ đầu tiên trong bộ dữ liệu VQA:

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

Để sử dụng BLIP-2 cho tác vụ trả lời câu hỏi về hình ảnh, prompt văn bản phải tuân theo một định dạng cụ thể: Question: {} Answer:.

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

Bây giờ chúng ta cần tiền xử lý ảnh/lệnh bằng trình xử lý của mô hình, truyền đầu vào đã xử lý qua mô hình và giải mã đầu ra:

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

Như bạn có thể thấy, mô hình đã nhận ra đám đông và hướng của khuôn mặt (nhìn xuống), tuy nhiên, dường như nó bỏ qua thực tế là đám đông nằm phía sau người trượt ván. Mặc dù vậy, trong những trường hợp việc thu thập các bộ dữ liệu được chú thích bởi con người là không khả thi, cách tiếp cận này có thể nhanh chóng tạo ra những kết quả hữu ích.

Update on GitHub