| # TOSC Benchmark — Evaluation |
|
|
| Evaluation code for the **Triadic Object-State Consistency (TOSC)** benchmark. |
|
|
| TOSC probes whether a vision-language model keeps its object descriptions |
| consistent across three versions of the same scene: |
|
|
| | State | Folder | Filename pattern | Meaning | |
| |-------|--------|------------------|---------| |
| | **origin** | `benchmark/original/images/` | `{N}.jpg` | the unedited image | |
| | **removed** | `benchmark/repair/images/` | `{N}_masked_{orig}_repair.png` | the original object inpainted out | |
| | **replaced** | `benchmark/insert/images/` | `{N}_masked_{orig}_insert_{repl}.png` | the original object swapped for a different one | |
|
|
| Each index `N` forms one triplet (5,969 in total). The model is asked the same |
| question for every image — *"Describe this image in detail."* — and we check |
| which objects its caption mentions. |
|
|
| ## Metrics |
|
|
| For a triplet with original object `o` and replacement object `r`, let: |
|
|
| - `m_orig_o` = `o` mentioned in the **origin** caption |
| - `m_rem_o` = `o` mentioned in the **removed** caption |
| - `m_rep_o` = `o` mentioned in the **replaced** caption |
| - `m_rep_r` = `r` mentioned in the **replaced** caption |
|
|
| | Metric | Formula | Higher = | |
| |--------|---------|----------| |
| | **OPA** (object present) | `m_orig_o` | sees the object when it is there | |
| | **RCA** (removal consistency) | `1 - m_rem_o` | stops mentioning it once removed | |
| | **RUA** (replacement utility) | `m_rep_r · (1 - m_rep_o)` | sees the new object, not the old | |
| | **TOSC** (overall) | `m_orig_o · (1 - m_rem_o) · m_rep_r · (1 - m_rep_o)` | all of the above for one triplet | |
| | OldPersist_rem | `m_rem_o` | (lower better) hallucinated old object after removal | |
| | OldPersist_rep | `m_rep_o` | (lower better) hallucinated old object after replacement | |
| | RepFail | `1 - m_rep_r` | (lower better) missed the replacement object | |
| | CC | `(1 - m_rep_o) · (1 - m_rep_r)` | (lower better) saw neither object in the replaced image | |
| | MixConf | `m_rep_o · m_rep_r` | (lower better) saw both old and new objects at once | |
|
|
| Object mentions are detected with a local COCO synonym table plus |
| lemmatization (`eval_masked_obj_generative.py` supplies `OBJECT_SYNONYMS`); no |
| external API or LLM judge is used. |
|
|
| ## Files |
|
|
| All evaluation code lives in `eval/`, except the vendored inference package |
| which sits at the repo root so it imports as `llava`: |
|
|
| | Path | Role | |
| |------|------| |
| | `eval/build_index.py` | Scans the image folders → writes `benchmark/TOSC_dataset.jsonl` + `benchmark/insertions.jsonl`. | |
| | `eval/run_eval.sh` | End-to-end runner: caption generation → merge → scoring. | |
| | `eval/eval_tosc.py` | Scoring engine (consumes captions + the two jsonl files). | |
| | `eval/eval_masked_obj_generative.py` | COCO object synonym table used by the scorer. | |
| | `llava/` | Vendored inference package (`llava.eval.model_vqa` + the LLaVA-1.5 model loader and Qwen helpers). | |
| | `requirements.txt` | Runtime dependencies. | |
|
|
| `TOSC_dataset.jsonl` / `insertions.jsonl` are committed for convenience but are |
| fully regenerable from the images with `build_index.py`. |
|
|
| ## Requirements |
|
|
| This repo is **self-contained** — caption generation uses the vendored `llava` |
| package at the repo root (`llava/eval/model_vqa.py`); nothing outside the repo is |
| needed at runtime. |
|
|
| ```bash |
| pip install -r requirements.txt |
| ``` |
|
|
| This covers both the LLaVA-1.5 path (custom loader for the `non_lora_trainables` |
| LoRA format) and the Qwen2-VL / Qwen2.5-VL path (`transformers` + `peft`). The |
| Qwen path requests `flash_attention_2`, so install `flash-attn` for those models |
| (or change `attn_implementation` to `"sdpa"` in |
| `llava/eval/utils/hf_utils.py`). `nltk` is optional for the scorer — it falls |
| back to a simple singularizer if WordNet is unavailable. |
|
|
| ## Usage |
|
|
| ```bash |
| # 1. Build the index files (run once; regenerable any time) |
| python eval/build_index.py |
| |
| # 2. Run the full pipeline for one model. |
| # Edit the model preset at the top of run_eval.sh, or override via env vars. |
| GPU_LIST=0,1,2,3 bash eval/run_eval.sh |
| ``` |
|
|
| Results are written to `${TOSC_RESULTS:-/home/yilin/tmp/tosc_results}` (outside |
| the repo by default): merged captions in `answers/<OUTPUT_NAME>.jsonl`, full |
| per-triplet results in `<OUTPUT_NAME>_eval.json`, and aggregate metrics in |
| `<OUTPUT_NAME>_eval_summary.json`. |
|
|
| ### Shipped model presets |
|
|
| `run_eval.sh` carries presets for the four LoRA adapters in `lora/`: |
|
|
| | OUTPUT_NAME | Base model | LoRA | |
| |-------------|-----------|------| |
| | `LLaVA_v1_5_7b-TSA-DPO` | `liuhaotian/llava-v1.5-7b` | `lora/LLaVA_v1_5_7b-TSA-DPO` | |
| | `LLaVA_v1_5_13b-TSA-DPO` | `liuhaotian/llava-v1.5-13b` | `lora/LLaVA_v1_5_13b-TSA-DPO` | |
| | `Qwen2_VL_7B-TSA-DPO` | `Qwen/Qwen2-VL-7B-Instruct` | `lora/Qwen2_VL_7B-TSA-DPO` | |
| | `Qwen2_5_VL_7B-TSA-DPO` | `Qwen/Qwen2.5-VL-7B-Instruct` | `lora/Qwen2_5_VL_7B-TSA-DPO` | |
| |
| `model_vqa.py` auto-selects the inference path: names containing `llava` + `1.5` |
| use the LLaVA loader, everything else uses the HuggingFace/Qwen path. Set |
| `LORA_NAME=""` to evaluate a base model without the adapter. |
|
|