Transformers documentation

시각 질의응답

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

시각 질의응답

Visual Question Answering(VQA)은 이미지를 기반으로 열린 형식의 질문에 답하는 작업입니다. 이 작업을 지원하는 모델의 입력은 일반적으로 이미지와 질문의 조합이며, 출력은 자연어로 표현된 답변입니다.

VQA의 주목할 만한 사용 사례 예시에는 다음이 포함됩니다:

  • 시각 장애인을 위한 접근성 애플리케이션.
  • 교육: 강의나 교재에서 제시된 시각 자료를 바탕으로 질문을 제기하는 것입니다. VQA는 인터랙티브 박물관 전시물이나 역사 유적지에서도 활용될 수 있습니다.
  • 고객 서비스 및 전자상거래: VQA는 사용자가 제품에 대해 질문할 수 있도록 하여 사용자 경험을 향상시킬 수 있습니다.
  • 이미지 검색: VQA 모델은 특정 특성을 가진 이미지를 검색하는 데 사용할 수 있습니다. 예를 들어, 사용자는 "개가 있나요?"라고 질문하여 이미지 세트에서 개가 포함된 모든 이미지를 찾을 수 있습니다.

이 가이드에서는 다음 내용을 배웁니다:

  • 구체적으로 ViLT인 분류 VQA 모델을 Graphcore/vqa 데이터셋으로 fine-tune합니다.
  • 파인튜닝한 ViLT를 추론에 사용하세요.
  • BLIP-2와 같은 생성 모델로 zero-shot VQA 추론을 실행합니다.

ViLT 파인튜닝

ViLT 모델은 텍스트 임베딩을 Vision Transformer(ViT)에 통합하여 Vision-and-Language Pre-training(VLP)에 최소한의 설계를 갖도록 합니다. 이 모델은 여러 다운스트림 작업에 사용할 수 있습니다. VQA 작업의 경우, 분류기 헤드가 [CLS] 토큰의 최종 히든 상태 위에 있는 선형 레이어로) 최상단에 배치되며 무작위로 초기화됩니다. 따라서 Visual Question Answering은 분류 문제로 처리됩니다.

BLIP, BLIP-2, InstructBLIP와 같은 최근 모델들은 VQA를 생성형 작업으로 취급합니다. 이 가이드의 후반부에서 이러한 모델들을 사용하여 zero-shot VQA 추론을 수행하는 방법을 보여드리겠습니다.

시작하기 전에, 필요한 라이브러리가 모두 설치되어 있는지 확인하세요.

pip install -q transformers datasets

모델을 커뮤니티와 공유하시도록 권장합니다. 🤗 Hub에 업로드하려면 Hugging Face 계정에 로그인하세요. 프롬프트가 표시되면 로그인할 토큰을 입력하세요:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

모델 체크포인트를 전역 변수로 정의해 보겠습니다.

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

데이터 로드

설명 목적으로, 이 가이드에서는 주석 처리된 시각 질의응답 Graphcore/vqa 데이터셋의 매우 작은 샘플을 사용합니다. 전체 데이터셋은 🤗 Hub에서 찾을 수 있습니다.

Graphcore/vqa 데이터셋의 대안으로, 공식 VQA 데이터셋 페이지에서 동일한 데이터를 수동으로 다운로드할 수 있습니다. 사용자 정의 데이터로 튜토리얼을 진행하기를 원하신다면, 🤗 Datasets 문서의 이미지 데이터셋 만들기 가이드를 확인해 보세요.

validation 분할에서 첫 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라고 함)가 포함되어 있습니다. 이는 질문에 대한 답변이 주관적일 수 있기 때문입니다. 이 경우 질문은 “그는 어디를 보고 있나요?”입니다. 어떤 주석자는 “down”으로, 다른 주석자는 “at table”로, 또 다른 주석자는 “skateboard”로 주석했습니다.

이미지를 살펴보고 어떤 답변을 제시할지 생각해 보세요:

>>> from PIL import Image

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

질문과 답변의 모호성으로 인해, 이러한 데이터셋은 다중 레이블 분류 문제로 취급됩니다(여러 답변이 동시에 유효할 수 있기 때문이죠). 또한, 단순히 one-hot 인코딩 벡터를 생성하는 대신, 특정 답변이 어노테이션에 나타난 횟수에 기반한 soft 인코딩을 생성합니다.

예를 들어, 위 예시에서는 “down”이라는 답변이 다른 답변들보다 훨씬 더 자주 선택되므로, 점수(데이터셋에서는 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를 사용하여 이미지와 질문을 인코딩해야 합니다. 프로세서가 텍스트를 토큰화하고 텍스트 데이터에 대한 input_ids, attention_mask, token_type_ids를 생성하는 데 BertTokenizerFast를 사용합니다. 이미지의 경우, 프로세서가 ViltImageProcessor를 활용하여 이미지를 리사이즈하고 정규화한 뒤 pixel_values와 pixel_mask를 생성합니다.

이 모든 전처리 단계는 내부적으로 수행되며, 우리는 processor만 호출하면 됩니다. 그러나 여전히 대상 라벨을 준비해야 합니다. 이 표현 방식에서 각 요소는 가능한 답변(라벨) 하나를 나타냅니다. 정답인 경우 해당 요소에는 각각의 점수(가중치)가 저장되고, 나머지 요소는 0으로 설정됩니다.

다음 함수는 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 함수를 사용합니다. map의 속도를 높이려면 batched=True를 설정하여 데이터셋의 여러 요소를 한 번에 처리할 수 있습니다. 이 시점에서 필요하지 않은 열을 자유롭게 제거할 수 있습니다.

>>> 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를 사용하여 예시 배치(batch)를 생성합니다:

>>> 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에 업로드했으므로 이제 추론에 사용할 수 있습니다.

  1. 이미지와 질문을 가져온 후, 모델의 processor를 사용하여 모델에 맞게 준비합니다.
  2. 결과 또는 전처리 결과를 모델로 전달합니다.
  3. 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

Zero-shot VQA

이전 모델은 VQA를 분류 작업으로 취급했습니다. BLIP, BLIP-2, InstructBLIP와 같은 최근 모델들은 VQA를 생성 작업으로 접근합니다. BLIP-2를 예로 들어 보겠습니다. BLIP-2는 사전 학습된 비전 인코더와 LLM의 임의의 조합을 사용할 수 있는 새로운 시각-언어 사전 학습 패러다임을 도입했습니다(BLIP-2 블로그 게시물에서 자세히 알아보세요). 이를 통해 시각 질의응답을 포함한 여러 시각-언어 작업에서 최첨단 결과를 달성할 수 있습니다.

이 모델로 VQA를 수행하는 방법을 살펴보겠습니다. 먼저 모델을 로드합니다. 여기서는 Trainer가 자동으로 처리해 주었기 때문에 학습 시에는 필요하지 않았던 GPU로 모델을 명시적으로 전송합니다(사용 가능한 경우):

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

모델은 이미지와 텍스트를 입력으로 받으므로, VQA 데이터셋의 첫 번째 예제에 사용된 이미지/질문 쌍을 그대로 사용하겠습니다:

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

BLIP-2를 시각 질의응답 작업에 사용하려면 텍스트 프롬프트가 특정 형식을 따라야 합니다: Question: {} Answer:.

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

이제 모델의 processor로 이미지/프롬프트를 전처리하고, 전처리된 입력을 모델에 전달한 후 출력을 디코딩해야 합니다:

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

확인할 수 있듯이, 모델은 군중과 얼굴의 방향(아래를 보고 있음)을 인식했지만, 군중이 스케이터 뒤에 있다는 사실은 놓친 것으로 보입니다. 그럼에도 불구하고, 사람이 직접 라벨을 붙인 데이터셋을 확보하기 어려운 경우, 이 접근법은 유용한 결과를 빠르게 생성할 수 있습니다.

Update on GitHub