Transformers documentation
Jawaban pertanyaan dokumen
Jawaban pertanyaan dokumen
Document Question Answering, yang juga disebut Document Visual Question Answering, adalah tugas yang melibatkan pemberian jawaban atas pertanyaan yang diajukan tentang gambar dokumen. Input untuk model yang mendukung tugas ini biasanya berupa kombinasi gambar dan pertanyaan, dan outputnya adalah jawaban yang dinyatakan dalam bahasa alami. Model-model ini memanfaatkan beberapa modalitas, termasuk teks, posisi kata (bounding box), dan gambar itu sendiri.
Panduan ini mengilustrasikan cara:
- Lakukan fine-tuning LayoutLMv2 pada dataset DocVQA.
- Gunakan model yang telah Anda fine-tune untuk inferensi.
Untuk melihat semua arsitektur dan checkpoint yang kompatibel dengan tugas ini, kami menyarankan untuk memeriksa halaman tugas
LayoutLMv2 menyelesaikan tugas question-answering dokumen dengan menambahkan head question-answering di atas hidden states akhir dari token, untuk memprediksi posisi token awal dan akhir dari jawaban. Dengan kata lain, masalah ini diperlakukan sebagai question answering ekstraktif: diberikan konteks, ekstrak bagian informasi mana yang menjawab pertanyaan. Konteks berasal dari output mesin OCR, di sini menggunakan Tesseract dari Google.
Sebelum memulai, pastikan Anda telah menginstal semua pustaka yang diperlukan. LayoutLMv2 bergantung pada detectron2, torchvision, dan tesseract.
pip install -q transformers datasets
pip install 'git+https://github.com/facebookresearch/detectron2.git'
pip install torchvisionsudo apt install tesseract-ocr
pip install -q pytesseractSetelah Anda menginstal semua dependensi, restart runtime Anda.
Kami mendorong Anda untuk membagikan model Anda kepada komunitas. Masuk ke akun Hugging Face Anda untuk mengunggahnya ke 🤗 Hub. Saat diminta, masukkan token Anda untuk masuk:
>>> from huggingface_hub import notebook_login
>>> notebook_login()Mari kita definisikan beberapa variabel global.
>>> model_checkpoint = "microsoft/layoutlmv2-base-uncased"
>>> batch_size = 4Muat data
Pada panduan ini, kami menggunakan sampel kecil DocVQA yang sudah dipreprocess, yang dapat Anda temukan di 🤗 Hub. Jika Anda ingin menggunakan dataset DocVQA lengkap, Anda dapat mendaftar dan mengunduhnya di halaman utama DocVQA. Jika Anda melakukannya, untuk melanjutkan panduan ini, lihat cara memuat file ke dalam dataset 🤗.
>>> from datasets import load_dataset
>>> dataset = load_dataset("nielsr/docvqa_1200_examples")
>>> dataset
DatasetDict({
train: Dataset({
features: ['id', 'image', 'query', 'answers', 'words', 'bounding_boxes', 'answer'],
num_rows: 1000
})
test: Dataset({
features: ['id', 'image', 'query', 'answers', 'words', 'bounding_boxes', 'answer'],
num_rows: 200
})
})Seperti yang dapat Anda lihat, dataset sudah dibagi menjadi set latih dan set uji. Lihatlah contoh acak untuk membiasakan diri dengan fitur-fiturnya.
>>> dataset["train"].featuresBerikut adalah arti dari masing-masing field:
id: id dari contohimage: objek PIL.Image.Image yang berisi gambar dokumenquery: string pertanyaan - pertanyaan dalam bahasa alami, dalam beberapa bahasaanswers: daftar jawaban benar yang diberikan oleh anotator manusiawordsdanbounding_boxes: hasil OCR, yang tidak akan kami gunakan di sinianswer: jawaban yang cocok dengan model lain yang tidak akan kami gunakan di sini
Mari kita biarkan hanya pertanyaan berbahasa Inggris, dan buang fitur answer yang tampaknya berisi prediksi dari model lain.
Kita juga akan mengambil jawaban pertama dari set yang disediakan oleh para anotator. Sebagai alternatif, Anda dapat mengambil sampel secara acak.
>>> updated_dataset = dataset.map(lambda example: {"question": example["query"]["en"]}, remove_columns=["query"])
>>> updated_dataset = updated_dataset.map(
... lambda example: {"answer": example["answers"][0]}, remove_columns=["answer", "answers"]
... )Perhatikan bahwa checkpoint LayoutLMv2 yang kami gunakan dalam panduan ini telah dilatih dengan max_position_embeddings = 512 (Anda dapat
menemukan informasi ini di file config.json checkpoint).
Kita dapat memotong (truncate) contoh-contoh tersebut, tetapi untuk menghindari situasi di mana jawaban mungkin berada di akhir dokumen besar dan akhirnya terpotong,
di sini kita akan menghapus beberapa contoh di mana embedding kemungkinan akan menjadi lebih panjang dari 512.
Jika sebagian besar dokumen dalam dataset Anda panjang, Anda dapat menerapkan strategi sliding window - lihat notebook ini untuk detailnya.
>>> updated_dataset = updated_dataset.filter(lambda x: len(x["words"]) + len(x["question"].split()) < 512)Pada titik ini, mari kita juga hapus fitur OCR dari dataset ini. Fitur-fitur ini merupakan hasil OCR untuk fine-tuning model lain. Mereka masih memerlukan beberapa pemrosesan jika kita ingin menggunakannya, karena tidak sesuai dengan persyaratan input dari model yang kita gunakan dalam panduan ini. Sebagai gantinya, kita dapat menggunakan LayoutLMv2Processor pada data asli untuk OCR dan tokenisasi. Dengan cara ini, kita akan mendapatkan input yang sesuai dengan input yang diharapkan oleh model. Jika Anda ingin memproses gambar secara manual, lihat dokumentasi model LayoutLMv2 untuk mempelajari format input yang diharapkan oleh model.
>>> updated_dataset = updated_dataset.remove_columns("words")
>>> updated_dataset = updated_dataset.remove_columns("bounding_boxes")Terakhir, eksplorasi data tidak akan lengkap jika kita tidak melihat contoh gambar.
>>> updated_dataset["train"][11]["image"]
Praproses data
Tugas Document Question Answering adalah tugas multimodal, dan Anda perlu memastikan bahwa input dari setiap modalitas dipreproses sesuai dengan ekspektasi model. Mari kita mulai dengan memuat LayoutLMv2Processor, yang secara internal menggabungkan image processor yang dapat menangani data gambar dan tokenizer yang dapat mengkodekan data teks.
>>> from transformers import AutoProcessor
>>> processor = AutoProcessor.from_pretrained(model_checkpoint)Prapemrosesan gambar dokumen
Pertama, mari kita siapkan gambar dokumen untuk model dengan bantuan image_processor dari processor.
Secara default, image processor mengubah ukuran gambar menjadi 224x224, memastikan urutan channel warna yang benar,
dan menerapkan OCR dengan tesseract untuk mendapatkan kata-kata dan bounding box ternormalisasi. Dalam tutorial ini, semua default tersebut memang yang kita butuhkan.
Tulis fungsi yang menerapkan pemrosesan gambar default pada batch gambar dan mengembalikan hasil OCR.
>>> image_processor = processor.image_processor
>>> def get_ocr_words_and_boxes(examples):
... images = [image.convert("RGB") for image in examples["image"]]
... encoded_inputs = image_processor(images)
... examples["image"] = encoded_inputs.pixel_values
... examples["words"] = encoded_inputs.words
... examples["boxes"] = encoded_inputs.boxes
... return examplesUntuk menerapkan prapemrosesan ini ke seluruh dataset dengan cepat, gunakan map.
>>> dataset_with_ocr = updated_dataset.map(get_ocr_words_and_boxes, batched=True, batch_size=2)Prapemrosesan data teks
Setelah OCR diterapkan pada gambar, kita perlu mengkodekan bagian teks dari dataset untuk mempersiapkannya bagi model.
Hal ini melibatkan konversi kata dan kotak yang diperoleh pada langkah sebelumnya menjadi input_ids, attention_mask, token_type_ids, dan bbox pada tingkat token. Untuk prapemrosesan teks, kita akan membutuhkan tokenizer dari processor.
>>> tokenizer = processor.tokenizerSelain pra-pemrosesan yang disebutkan di atas, kita juga perlu menambahkan label untuk model. Untuk model xxxForQuestionAnswering di 🤗 Transformers, label terdiri dari start_positions dan end_positions, yang menunjukkan token mana yang berada di awal dan token mana yang berada di akhir jawaban.
Mari kita mulai dari sana. Definisikan fungsi bantu yang dapat menemukan subdaftar (jawaban yang dipecah menjadi kata-kata) di dalam daftar yang lebih besar (daftar kata).
Fungsi ini akan menerima dua daftar sebagai input, words_list dan answer_list. Kemudian, fungsi ini akan mengiterasi words_list dan memeriksa
apakah kata saat ini dalam words_list (words_list[i]) sama dengan kata pertama dari answer_list (answer_list[0]) dan apakah
sublist dari words_list yang dimulai dari kata saat ini dan memiliki panjang yang sama dengan answer_list sama to answer_list.
Jika kondisi ini benar, berarti kecocokan telah ditemukan, dan fungsi akan mencatat kecocokan tersebut, indeks awalnya (idx),
dan indeks akhirnya (idx + len(answer_list) - 1). Jika lebih dari satu kecocokan ditemukan, fungsi hanya akan mengembalikan yang pertama.
Jika tidak ada kecocokan yang ditemukan, fungsi mengembalikan (None, 0, dan 0).
>>> def subfinder(words_list, answer_list):
... matches = []
... start_indices = []
... end_indices = []
... for idx, i in enumerate(range(len(words_list))):
... if words_list[i] == answer_list[0] and words_list[i : i + len(answer_list)] == answer_list:
... matches.append(answer_list)
... start_indices.append(idx)
... end_indices.append(idx + len(answer_list) - 1)
... if matches:
... return matches[0], start_indices[0], end_indices[0]
... else:
... return None, 0, 0Untuk mengilustrasikan bagaimana fungsi ini menemukan posisi jawaban, mari kita gunakan pada sebuah contoh:
>>> example = dataset_with_ocr["train"][1]
>>> words = [word.lower() for word in example["words"]]
>>> match, word_idx_start, word_idx_end = subfinder(words, example["answer"].lower().split())
>>> print("Question: ", example["question"])
>>> print("Words:", words)
>>> print("Answer: ", example["answer"])
>>> print("start_index", word_idx_start)
>>> print("end_index", word_idx_end)
Question: Who is in cc in this letter?
Words: ['wie', 'baw', 'brown', '&', 'williamson', 'tobacco', 'corporation', 'research', '&', 'development', 'internal', 'correspondence', 'to:', 'r.', 'h.', 'honeycutt', 'ce:', 't.f.', 'riehl', 'from:', '.', 'c.j.', 'cook', 'date:', 'may', '8,', '1995', 'subject:', 'review', 'of', 'existing', 'brainstorming', 'ideas/483', 'the', 'major', 'function', 'of', 'the', 'product', 'innovation', 'graup', 'is', 'to', 'develop', 'marketable', 'nove!', 'products', 'that', 'would', 'be', 'profitable', 'to', 'manufacture', 'and', 'sell.', 'novel', 'is', 'defined', 'as:', 'of', 'a', 'new', 'kind,', 'or', 'different', 'from', 'anything', 'seen', 'or', 'known', 'before.', 'innovation', 'is', 'defined', 'as:', 'something', 'new', 'or', 'different', 'introduced;', 'act', 'of', 'innovating;', 'introduction', 'of', 'new', 'things', 'or', 'methods.', 'the', 'products', 'may', 'incorporate', 'the', 'latest', 'technologies,', 'materials', 'and', 'know-how', 'available', 'to', 'give', 'then', 'a', 'unique', 'taste', 'or', 'look.', 'the', 'first', 'task', 'of', 'the', 'product', 'innovation', 'group', 'was', 'to', 'assemble,', 'review', 'and', 'categorize', 'a', 'list', 'of', 'existing', 'brainstorming', 'ideas.', 'ideas', 'were', 'grouped', 'into', 'two', 'major', 'categories', 'labeled', 'appearance', 'and', 'taste/aroma.', 'these', 'categories', 'are', 'used', 'for', 'novel', 'products', 'that', 'may', 'differ', 'from', 'a', 'visual', 'and/or', 'taste/aroma', 'point', 'of', 'view', 'compared', 'to', 'canventional', 'cigarettes.', 'other', 'categories', 'include', 'a', 'combination', 'of', 'the', 'above,', 'filters,', 'packaging', 'and', 'brand', 'extensions.', 'appearance', 'this', 'category', 'is', 'used', 'for', 'novel', 'cigarette', 'constructions', 'that', 'yield', 'visually', 'different', 'products', 'with', 'minimal', 'changes', 'in', 'smoke', 'chemistry', 'two', 'cigarettes', 'in', 'cne.', 'emulti-plug', 'te', 'build', 'yaur', 'awn', 'cigarette.', 'eswitchable', 'menthol', 'or', 'non', 'menthol', 'cigarette.', '*cigarettes', 'with', 'interspaced', 'perforations', 'to', 'enable', 'smoker', 'to', 'separate', 'unburned', 'section', 'for', 'future', 'smoking.', '«short', 'cigarette,', 'tobacco', 'section', '30', 'mm.', '«extremely', 'fast', 'buming', 'cigarette.', '«novel', 'cigarette', 'constructions', 'that', 'permit', 'a', 'significant', 'reduction', 'iretobacco', 'weight', 'while', 'maintaining', 'smoking', 'mechanics', 'and', 'visual', 'characteristics.', 'higher', 'basis', 'weight', 'paper:', 'potential', 'reduction', 'in', 'tobacco', 'weight.', '«more', 'rigid', 'tobacco', 'column;', 'stiffing', 'agent', 'for', 'tobacco;', 'e.g.', 'starch', '*colored', 'tow', 'and', 'cigarette', 'papers;', 'seasonal', 'promotions,', 'e.g.', 'pastel', 'colored', 'cigarettes', 'for', 'easter', 'or', 'in', 'an', 'ebony', 'and', 'ivory', 'brand', 'containing', 'a', 'mixture', 'of', 'all', 'black', '(black', 'paper', 'and', 'tow)', 'and', 'ail', 'white', 'cigarettes.', '499150498']
Answer: T.F. Riehl
start_index 17
end_index 18Namun, setelah contoh dikodekan, bentuknya akan seperti ini:
>>> encoding = tokenizer(example["question"], example["words"], example["boxes"])
>>> tokenizer.decode(encoding["input_ids"])
[CLS] who is in cc in this letter? [SEP] wie baw brown & williamson tobacco corporation research & development ...Kita perlu menemukan posisi jawaban dalam input yang telah di-encode.
token_type_idsmemberi tahu token mana yang merupakan bagian dari pertanyaan dan token mana yang merupakan bagian dari kata-kata dokumen.tokenizer.cls_token_idakan membantu menemukan token khusus di awal input.word_idsakan membantu mencocokkan jawaban yang ditemukan dalamwordsasli dengan jawaban yang sama dalam input terenkapsulasi penuh, serta menentukan posisi awal/akhir jawaban dalam input terenkapsulasi.
Dengan hal itu, mari kita buat fungsi untuk mengkodekan batch contoh dalam dataset:
>>> def encode_dataset(examples, max_length=512):
... questions = examples["question"]
... words = examples["words"]
... boxes = examples["boxes"]
... answers = examples["answer"]
... # encode the batch of examples and initialize the start_positions and end_positions
... encoding = tokenizer(questions, words, boxes, max_length=max_length, padding="max_length", truncation=True)
... start_positions = []
... end_positions = []
... # loop through the examples in the batch
... for i in range(len(questions)):
... cls_index = encoding["input_ids"][i].index(tokenizer.cls_token_id)
... # find the position of the answer in example's words
... words_example = [word.lower() for word in words[i]]
... answer = answers[i]
... match, word_idx_start, word_idx_end = subfinder(words_example, answer.lower().split())
... if match:
... # if match is found, use `token_type_ids` to find where words start in the encoding
... token_type_ids = encoding["token_type_ids"][i]
... token_start_index = 0
... while token_type_ids[token_start_index] != 1:
... token_start_index += 1
... token_end_index = len(encoding["input_ids"][i]) - 1
... while token_type_ids[token_end_index] != 1:
... token_end_index -= 1
... word_ids = encoding.word_ids(i)[token_start_index : token_end_index + 1]
... start_position = cls_index
... end_position = cls_index
... # loop over word_ids and increase `token_start_index` until it matches the answer position in words
... # once it matches, save the `token_start_index` as the `start_position` of the answer in the encoding
... for id in word_ids:
... if id == word_idx_start:
... start_position = token_start_index
... else:
... token_start_index += 1
... # similarly loop over `word_ids` starting from the end to find the `end_position` of the answer
... for id in word_ids[::-1]:
... if id == word_idx_end:
... end_position = token_end_index
... else:
... token_end_index -= 1
... start_positions.append(start_position)
... end_positions.append(end_position)
... else:
... start_positions.append(cls_index)
... end_positions.append(cls_index)
... encoding["image"] = examples["image"]
... encoding["start_positions"] = start_positions
... encoding["end_positions"] = end_positions
... return encodingSekarang bahwa kita memiliki fungsi pra-pemrosesan ini, kita dapat mengkodekan seluruh dataset:
>>> encoded_train_dataset = dataset_with_ocr["train"].map(
... encode_dataset, batched=True, batch_size=2, remove_columns=dataset_with_ocr["train"].column_names
... )
>>> encoded_test_dataset = dataset_with_ocr["test"].map(
... encode_dataset, batched=True, batch_size=2, remove_columns=dataset_with_ocr["test"].column_names
... )Mari kita periksa bagaimana fitur dari dataset yang telah di-encode:
>>> encoded_train_dataset.features
{'image': Sequence(feature=Sequence(feature=Sequence(feature=Value(dtype='uint8', id=None), length=-1, id=None), length=-1, id=None), length=-1, id=None),
'input_ids': Sequence(feature=Value(dtype='int32', id=None), length=-1, id=None),
'token_type_ids': Sequence(feature=Value(dtype='int8', id=None), length=-1, id=None),
'attention_mask': Sequence(feature=Value(dtype='int8', id=None), length=-1, id=None),
'bbox': Sequence(feature=Sequence(feature=Value(dtype='int64', id=None), length=-1, id=None), length=-1, id=None),
'start_positions': Value(dtype='int64', id=None),
'end_positions': Value(dtype='int64', id=None)}Evaluasi
Evaluasi untuk document question answering memerlukan banyak pascapemrosesan. Agar tidak menghabiskan terlalu banyak waktu Anda, panduan ini melewati langkah evaluasi. Trainer tetap menghitung evaluasi loss selama pelatihan sehingga Anda tidak sepenuhnya buta terhadap kinerja model Anda. Extractive question answering biasanya dievaluasi menggunakan F1/exact match. Jika Anda ingin mengimplementasikannya sendiri, lihat bab Question Answering dari kursus Hugging Face untuk inspirasi.
Latih
Selamat! Anda telah berhasil melewati bagian tersulit dari panduan ini dan kini siap untuk melatih model Anda sendiri. Pelatihan melibatkan langkah-langkah berikut:
- Muat model dengan AutoModelForDocumentQuestionAnswering menggunakan checkpoint yang sama seperti pada pra-pemrosesan.
- Definisikan hiperparameter pelatihan Anda di TrainingArguments.
- Definisikan fungsi untuk mengelompokkan contoh menjadi batch, di sini DefaultDataCollator sudah cukup baik
- Lewatkan argumen pelatihan ke Trainer bersama dengan model, dataset, dan data collator.
- Panggil train() untuk melakukan finetuning pada model Anda.
>>> from transformers import AutoModelForDocumentQuestionAnswering
>>> model = AutoModelForDocumentQuestionAnswering.from_pretrained(model_checkpoint)Pada TrainingArguments, gunakan output_dir untuk menentukan lokasi penyimpanan model Anda, dan atur hiperparameter sesuai kebutuhan.
Jika Anda ingin membagikan model Anda kepada komunitas, setel push_to_hub ke True (Anda harus masuk ke Hugging Face untuk mengunggah model Anda).
Dalam hal ini, output_dir juga akan menjadi nama repo tempat checkpoint model Anda akan diunggah.
>>> from transformers import TrainingArguments
>>> # REPLACE THIS WITH YOUR REPO ID
>>> repo_id = "MariaK/layoutlmv2-base-uncased_finetuned_docvqa"
>>> training_args = TrainingArguments(
... output_dir=repo_id,
... per_device_train_batch_size=4,
... num_train_epochs=20,
... save_steps=200,
... logging_steps=50,
... eval_strategy="steps",
... learning_rate=5e-5,
... save_total_limit=2,
... remove_unused_columns=False,
... push_to_hub=True,
... )Definisikan data collator sederhana untuk mengelompokkan contoh ke dalam batch.
>>> from transformers import DefaultDataCollator
>>> data_collator = DefaultDataCollator()Terakhir, gabungkan semuanya, lalu panggil train():
>>> from transformers import Trainer
>>> trainer = Trainer(
... model=model,
... args=training_args,
... data_collator=data_collator,
... train_dataset=encoded_train_dataset,
... eval_dataset=encoded_test_dataset,
... processing_class=processor,
... )
>>> trainer.train()Untuk menambahkan model akhir ke 🤗 Hub, buat model card dan panggil push_to_hub:
>>> trainer.create_model_card()
>>> trainer.push_to_hub()Inferensi
Sekarang Anda telah melakukan finetuning pada model LayoutLMv2 dan mengunggahnya ke 🤗 Hub, Anda dapat menggunakannya untuk inferensi. Cara termudah untuk mencoba model hasil finetuning Anda untuk inferensi adalah dengan menggunakannya dalam Pipeline.
Mari kita ambil sebuah contoh:
>>> example = dataset["test"][2]
>>> question = example["query"]["en"]
>>> image = example["image"]
>>> print(question)
>>> print(example["answers"])
'Who is ‘presiding’ TRRF GENERAL SESSION (PART 1)?'
['TRRF Vice President', 'lee a. waller']Selanjutnya, instansiasi pipeline untuk document question answering dengan model Anda, lalu lewati kombinasi gambar + pertanyaan tersebut ke pipeline.
>>> from transformers import pipeline
>>> qa_pipeline = pipeline("document-question-answering", model="MariaK/layoutlmv2-base-uncased_finetuned_docvqa")
>>> qa_pipeline(image, question)
[{'score': 0.9949808120727539,
'answer': 'Lee A. Waller',
'start': 55,
'end': 57}]Anda juga dapat mereplikasi hasil pipeline secara manual jika Anda mau:
- Ambil sebuah gambar dan pertanyaan, lalu siapkan keduanya untuk model menggunakan processor dari model Anda.
- Lewatkan hasil atau prapemrosesan melalui model.
- Model mengembalikan
start_logitsdanend_logits, yang menunjukkan token mana yang berada di awal jawaban dan token mana yang berada di akhir jawaban. Keduanya memiliki bentuk (batch_size, sequence_length). - Lakukan argmax pada dimensi terakhir dari
start_logitsdanend_logitsuntuk mendapatkanstart_idxdanend_idxyang diprediksi. - Decode jawaban dengan tokenizer.
>>> import torch
>>> from transformers import AutoProcessor
>>> from transformers import AutoModelForDocumentQuestionAnswering
>>> processor = AutoProcessor.from_pretrained("MariaK/layoutlmv2-base-uncased_finetuned_docvqa")
>>> model = AutoModelForDocumentQuestionAnswering.from_pretrained("MariaK/layoutlmv2-base-uncased_finetuned_docvqa")
>>> with torch.no_grad():
... encoding = processor(image.convert("RGB"), question, return_tensors="pt")
... outputs = model(**encoding)
... start_logits = outputs.start_logits
... end_logits = outputs.end_logits
... predicted_start_idx = start_logits.argmax(-1).item()
... predicted_end_idx = end_logits.argmax(-1).item()
>>> processor.tokenizer.decode(encoding.input_ids.squeeze()[predicted_start_idx : predicted_end_idx + 1])
'lee a. waller'