| import os
|
| import torch
|
| from PIL import Image
|
| import matplotlib.pyplot as plt
|
| from datasets import load_dataset
|
| from transformers import AutoProcessor, Qwen2VLForConditionalGeneration, BitsAndBytesConfig
|
| from peft import PeftModel
|
| from evaluate import load
|
|
|
|
|
|
|
|
|
| MODEL_NAME = "Qwen/Qwen2-VL-2B-Instruct"
|
| CHECKPOINT_PATH = "./qlora-vlm"
|
| JSONL_PATH = "0508_clean.jsonl"
|
|
|
| NUM_SAMPLES = 3
|
| IMAGE_SIZE = 512
|
| MAX_NEW_TOKENS = 1024
|
|
|
|
|
|
|
|
|
| processor = AutoProcessor.from_pretrained(MODEL_NAME)
|
|
|
|
|
|
|
|
|
| bnb_config = BitsAndBytesConfig(
|
| load_in_4bit=True,
|
| bnb_4bit_compute_dtype=torch.float16,
|
| bnb_4bit_use_double_quant=True,
|
| bnb_4bit_quant_type="nf4"
|
| )
|
|
|
|
|
|
|
|
|
| print("Loading zero-shot model...")
|
| base_model_zero = Qwen2VLForConditionalGeneration.from_pretrained(
|
| MODEL_NAME,
|
| quantization_config=bnb_config,
|
| device_map="auto",
|
| )
|
| base_model_zero.eval()
|
|
|
|
|
|
|
|
|
| print("Loading fine-tuned model...")
|
| base_model = Qwen2VLForConditionalGeneration.from_pretrained(
|
| MODEL_NAME,
|
| quantization_config=bnb_config,
|
| device_map="auto",
|
| )
|
|
|
| model = PeftModel.from_pretrained(base_model, CHECKPOINT_PATH)
|
| model.eval()
|
|
|
|
|
|
|
|
|
| dataset = load_dataset("json", data_files=JSONL_PATH)["train"]
|
|
|
|
|
|
|
|
|
| def build_inputs(image):
|
| prompt = "Convert this document image into structured Markdown."
|
|
|
| messages = [
|
| {
|
| "role": "user",
|
| "content": [
|
| {"type": "image"},
|
| {"type": "text", "text": prompt}
|
| ]
|
| }
|
| ]
|
|
|
| text = processor.apply_chat_template(
|
| messages,
|
| tokenize=False,
|
| add_generation_prompt=True
|
| )
|
|
|
| inputs = processor(
|
| text=text,
|
| images=image,
|
| return_tensors="pt"
|
| )
|
|
|
| return inputs
|
|
|
| def generate(model_obj, image_path):
|
| image = Image.open(image_path).convert("RGB").resize((IMAGE_SIZE, IMAGE_SIZE))
|
| inputs = build_inputs(image).to(model_obj.device)
|
|
|
| with torch.no_grad():
|
| output = model_obj.generate(
|
| **inputs,
|
| max_new_tokens=MAX_NEW_TOKENS,
|
| do_sample=False
|
| )
|
|
|
| return processor.decode(output[0], skip_special_tokens=True)
|
|
|
|
|
|
|
|
|
| def compute_rouge(pred, gt):
|
| rouge = load("rouge")
|
| score = rouge.compute(predictions=[pred], references=[gt])
|
| return score
|
|
|
|
|
|
|
|
|
|
|
| def visualize(sample, title="Sample", save=False):
|
| image_path = sample["image"]
|
| gt = sample["markdown"]
|
|
|
| pred_zero = generate(base_model_zero, image_path)
|
| pred_ft = generate(model, image_path)
|
|
|
| image = Image.open(image_path).convert("RGB")
|
|
|
|
|
| plt.figure(figsize=(8, 5))
|
| plt.imshow(image)
|
| plt.axis("off")
|
| plt.title(title)
|
| if save:
|
| plt.savefig(f"{title.replace(' ', '_')}.png")
|
| plt.show()
|
|
|
| print("\n" + "="*100)
|
| print("๐ GROUND TRUTH:\n")
|
| print(gt[:1500])
|
|
|
| print("\n" + "-"*100)
|
| print("๐ค ZERO-SHOT OUTPUT:\n")
|
| print(pred_zero[:1500])
|
|
|
| print("\n" + "-"*100)
|
| print("๐ FINE-TUNED OUTPUT:\n")
|
| print(pred_ft[:1500])
|
|
|
|
|
| rouge_scores = compute_rouge(pred_ft, gt)
|
| print("\n๐ ROUGE (Fine-tuned):", rouge_scores)
|
|
|
| print("="*100)
|
|
|
|
|
|
|
|
|
| print("\n===== TRAIN SAMPLES =====\n")
|
| for i in range(NUM_SAMPLES):
|
| visualize(dataset[i], title=f"Train Sample {i+1}") |