Views
No views yet
(medical image, expert caption, candidate lay caption), it returns five
attribute scores in [0, 1] plus their mean overall score, and serves as
the headline metric for the
MedLayXPlain
benchmark.| Attribute | What it scores |
|---|---|
modality | Correctly identifies the imaging modality (CT, MRI, histology, …) |
anatomy | Correctly identifies the depicted anatomy / region |
finding | Correctly conveys the radiological / pathological finding |
factual | Factually consistent with the expert caption and image |
readability | Written in patient-facing lay language (no jargon) |
adapter_config.json
adapter_model.safetensors # PEFT LoRA, r=16, alpha=32, dropout=0.05
# targets q/k/v/o + gate/up/down projections
regression_head.pt # 2-layer MLP head: 2048 -> 256 -> 5 (+ Sigmoid)
config.json # base model config (Qwen2.5-VL-3B-Instruct)
generation_config.json
preprocessor_config.json
video_preprocessor_config.json
tokenizer.json, tokenizer_config.json, vocab.json, merges.txt
added_tokens.json, special_tokens_map.json, chat_template.jinja
model.py # VLMRegressor module (importable)
inference_example.py # minimal usage exampleadapter_config.json
points at Qwen/Qwen2.5-VL-3B-Instruct, which is fetched from the Hub at
load time. Users must accept the
Qwen license
for the base weights separately; the LoRA + head weights in this repo
are released under Apache 2.0.1import torch
2from PIL import Image
3from transformers import AutoProcessor, Qwen2_5_VLForConditionalGeneration
4from peft import PeftModel
5
6from model import VLMRegressor, ATTRS # shipped in this repo
7
8BASE = "Qwen/Qwen2.5-VL-3B-Instruct"
9CKPT = "." # this repo, after `huggingface_hub.snapshot_download`
10
11device = "cuda:0"
12processor = AutoProcessor.from_pretrained(BASE, max_pixels=448 * 448)
13if processor.tokenizer.pad_token is None:
14 processor.tokenizer.pad_token = processor.tokenizer.eos_token
15
16base = Qwen2_5_VLForConditionalGeneration.from_pretrained(
17 BASE, torch_dtype=torch.bfloat16, attn_implementation="sdpa",
18)
19vlm = PeftModel.from_pretrained(base, CKPT).merge_and_unload()
20hidden = vlm.config.hidden_size if hasattr(vlm.config, "hidden_size") else vlm.config.text_config.hidden_size
21
22model = VLMRegressor(vlm, hidden).to(device, dtype=torch.bfloat16)
23model.head.load_state_dict(torch.load(f"{CKPT}/regression_head.pt", map_location=device))
24model.head = model.head.to(device, dtype=torch.float32)
25model.eval()
26
27image = Image.open("example.png").convert("RGB")
28expert = "Axial chest CT showing a 1.2 cm spiculated nodule in the right upper lobe ..."
29lay = "The scan shows a small spot in the upper part of the right lung that ..."
30user_text = f"<expert>{expert[:1500]}</expert>\n<lay>{lay[:1500]}</lay>"
31
32messages = [{"role": "user", "content": [
33 {"type": "image"},
34 {"type": "text", "text": user_text},
35]}]
36text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
37inputs = processor(text=[text], images=[image], padding=True, truncation=True,
38 max_length=2048, return_tensors="pt").to(device)
39
40with torch.no_grad():
41 scores = model(**inputs).cpu().float().numpy()[0]
42
43print({a: float(s) for a, s in zip(ATTRS, scores)})
44print("overall:", float(scores.mean()))inference_example.py runs the same flow end-to-end on dummy inputs.Qwen/Qwen2.5-VL-3B-Instruct.q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj.Linear(2048, 256) → GELU → Dropout(0.1) → Linear(256, 5) → Sigmoid
on attention-mask-pooled last-hidden-state.[0, 1]
by the final sigmoid but the ranking, not the absolute value, is what
has been validated.@inproceedings{anonymous2026medlayxplain,
title = {MedLayXPlain: A Benchmark and Distilled Evaluator for Medical Lay-Language Generation},
author = {Anonymous},
booktitle = {NeurIPS Datasets and Benchmarks},
year = {2026}
}adapter_*), regression head (regression_head.pt),
model.py, and inference_example.py: Apache 2.0.Qwen/Qwen2.5-VL-3B-Instruct, which has its own license. Users are
responsible for accepting it separately.