Instructions to use eitanlebras/ghosttrap-qwen2vl-2b-invariance with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use eitanlebras/ghosttrap-qwen2vl-2b-invariance with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
GhostTrap β a prompt-invariant camera-trap VLM
A QLoRA adapter for Qwen2-VL-2B-Instruct trained for one specific property:
giving the same answer when you ask the same question in different words.
On the 5 held-out prompt variants β wordings never seen in training β empty-frame false-positive spread falls from 98.37 points (stock) to 12.2 points (SFT+DPO), a 86.17-point reduction.
β οΈ This model does not output calibrated confidence. It was meant to, and it does not: the training targets omitted the confidence field, so the model learned not to emit one (0% coverage after DPO, 19% after SFT, versus 56% for stock). ECE is therefore undefined for the DPO adapter. The invariance result stands on its own; the calibration half of the original objective does not. Details in Results and Limitations.
Two adapters are published.
adapter_dpois the invariance model (held-out FP spread 12.2 pts, worst-case binary accuracy 86.0%).adapter_sftpreserves more capability (species accuracy 51.7% vs 36.5%) at a wider spread (46.8 pts). Pick by which you need; this is a real trade-off, not a ranking.
Why this, and not "fewer hallucinations"
An earlier stage of this project tried to reduce the empty-frame false-positive rate β the tendency of VLMs to invent animals in empty camera-trap frames, documented in Real-Wild-VLM (CVPR 2026 Workshops, Deng et al.). That turned out not to be a property worth training, because a prompt edit does it for free.
Holding the model, the images, and the decoding fixed and changing only the wording of the question, stock Qwen2-VL-2B's empty-frame false-positive rate moves across essentially the entire range:
| Model | variant set | FP spread β | FP range | FN spread | answer-rate spread |
|---|---|---|---|---|---|
| Stock Qwen2-VL-2B | all 15 | 99.59 | 0.0β99.59% | 53.25 | 100.0 |
| Stock Qwen2-VL-2B | held-out 5 | 98.37 | 0.0β98.37% | 53.25 | 100.0 |
| + SFT | held-out 5 | 46.75 | 10.57β57.32% | 9.09 | 80.08 |
| + SFT then DPO | held-out 5 | 12.2 | 5.28β17.48% | 24.02 | 0.0 |
Both endpoints are ordinary phrasings a real user might type. Neither is adversarial. So "our model has a low false-positive rate" is not a claim about a model β it is a claim about a prompt.
Prompt sensitivity is different: you cannot fix it with a prompt. That is what makes it the property in this project where post-training is necessary rather than merely sufficient.
Results
All numbers: same 400 held-out-location eval images, greedy decoding,
max_new_tokens=32, 256 visual tokens per image, identical parser.
| Model | variant set | FP spread β | FP range | FN spread | answer-rate spread |
|---|---|---|---|---|---|
| Stock Qwen2-VL-2B | all 15 | 99.59 | 0.0β99.59% | 53.25 | 100.0 |
| Stock Qwen2-VL-2B | held-out 5 | 98.37 | 0.0β98.37% | 53.25 | 100.0 |
| + SFT | held-out 5 | 46.75 | 10.57β57.32% | 9.09 | 80.08 |
| + SFT then DPO | held-out 5 | 12.2 | 5.28β17.48% | 24.02 | 0.0 |
Per-variant false-positive rate
| Prompt variant | split | Stock Qwen2-VL-2B FP | + SFT FP | + SFT then DPO FP |
|---|---|---|---|---|
v1_two_line_brackets |
train | 0.0% | β | β |
v2_plain_question |
HELD-OUT | 0.8% | 10.6% | 5.3% |
v3_labelled_no_brackets |
train | 21.9% | β | β |
v4_question_then_conf |
train | 2.9% | β | β |
v5_terse |
train | 2.0% | β | β |
v6_polite_request |
train | 96.8% | β | β |
v7_json |
HELD-OUT | 0.0% | 15.8% | 6.5% |
v8_no_empty_hint |
HELD-OUT | 98.4% | 57.3% | 17.5% |
v9_ecologist_role |
HELD-OUT | 14.2% | 12.6% | 6.9% |
v10_checklist |
train | 4.9% | β | β |
v11_binary_first |
HELD-OUT | 84.5% | 17.5% | 6.1% |
v12_single_word |
train | 30.5% | β | β |
v13_verbose_instruction |
train | 79.7% | β | β |
v14_uncertainty_ok |
train | 0.4% | β | β |
v15_species_word |
train | 99.6% | β | β |
Semantic sensitivity β the anti-cheat check
Paraphrase training has an obvious degenerate solution: ignore the prompt and emit one answer forever. That scores perfectly on invariance while being a strictly worse model. So the training mix includes semantically different questions with different correct answers, and the eval measures whether the model still answers the question it was asked.
| Model | species | presence | count | lighting | mean (non-species) | all beat majority? |
|---|---|---|---|---|---|---|
| Stock Qwen2-VL-2B | 70.3% | 53.0% | 59.1% | 90.0% | 67.3% | NO β see note |
| + SFT | 78.7% | 76.0% | 72.8% | 90.0% | 79.6% | yes |
| + SFT then DPO | 71.7% | 71.7% | 72.0% | 90.3% | 78.0% | NO β see note |
Majority-class baselines: species 61% / presence 61% / count 72% / lighting 58%.
Each task is reported against its majority-class baseline, because a collapsed model tends to match the majority label exactly β that is the tell.
Intended use
Screening camera-trap frames for whether an animal is present at all, robustly across however an operator phrases the request. That robustness is the reason to use this over the base model: worst-case binary accuracy across held-out phrasings is 86.0% versus 2.5% for stock.
It does not provide a confidence value to threshold on β see the warning above. If you need a tunable operating point today, threshold on your own downstream signal, not on the model's stated confidence.
Out of scope:
- General wildlife identification outside the Caltech Camera Traps domain (American Southwest, 22 categories).
- Species-level field research decisions. Species accuracy is well below what a purpose-built classifier achieves; use this to triage empty frames, not to identify animals.
- Any setting where a missed animal is costly without a human in the loop.
How to use
from peft import PeftModel
from transformers import AutoProcessor, Qwen2VLForConditionalGeneration
BASE = "Qwen/Qwen2-VL-2B-Instruct"
model = Qwen2VLForConditionalGeneration.from_pretrained(BASE, device_map="cuda")
model = PeftModel.from_pretrained(
model, "eitanlebras/ghosttrap-qwen2vl-2b-invariance",
subfolder="adapter_dpo") # or "adapter_sft"
processor = AutoProcessor.from_pretrained(BASE)
# The visual-token cap MUST match training, or results will not reproduce.
ip = processor.image_processor
ip.min_pixels, ip.max_pixels = 64 * 28 * 28, 256 * 28 * 28
ip.size = {**ip.size, "min_pixels": ip.min_pixels, "max_pixels": ip.max_pixels}
Ask in whatever wording you like β that is the point.
Training
| Base | Qwen/Qwen2-VL-2B-Instruct (Apache-2.0) |
| Method | QLoRA, nf4 on the LLM, vision tower kept in bf16 |
| LoRA | r=16, alpha=32, dropout 0.05, attention + MLP, language model only |
| Stages | SFT, then DPO on the SFT checkpoint |
| Data | 2900 SFT examples ({'species': 2000, 'count': 300, 'presence': 300, 'lighting': 300}), 1000 DPO pairs from 2171 mined stock failures |
| Hardware | 1Γ A40 48GB |
The vision tower is deliberately excluded from quantization: nf4 on visual
silently destroys it (see below).
Data provenance and licence
Derived from Caltech Camera Traps via LILA BC, CDLA-Permissive-1.0,
which permits redistribution of derived data with no share-alike obligation.
Split by camera location using the official
CaltechCameraTrapsSplits_v0.json β never randomly, because consecutive frames
from one camera are near-duplicates and a random split leaks eval into train.
Dataset: eitanlebras/ghosttrap-camera-trap-invariance
Beery, S., van Horn, G., Perona, P. "Recognition in Terra Incognita." ECCV 2018.
Adapter weights are a derivative of an Apache-2.0 base model and are released under Apache-2.0.
Limitations
Written worst-first, because the first one is genuinely the biggest.
- Single seed, single run per configuration. No variance estimate over training runs. Confidence intervals given anywhere in this card are binomial over images only and say nothing about run-to-run stability. This is the weakness most likely to change the conclusions.
- 2B parameters, one model family. Nothing here shows the effect generalises to other VLMs.
- 15 prompt variants is not prompt space. The held-out five were assigned by hash before any evaluation, which makes them an honest test, but five points do not characterise the distribution of things users write.
- Daytime-and-night RGB as a proxy for the infrared condition Real-Wild-VLM studied. Night frames here are IR/flash monochrome, which is close but not the same corpus or the same capture hardware.
- No evaluation against the actual Real-Wild-VLM benchmark, which is not publicly downloadable. Nothing in this card is a result on their data.
- Species accuracy is low in absolute terms and is scored after mapping open-vocabulary output onto 22 CCT categories.
- 256 visual tokens per image. Chosen because measured vision quality did not improve at 512 or 1024 on the reference frames, but it does cap what the model can resolve on small or distant animals.
- Semantic sensitivity is measured on the task phrasings seen in training, so it shows the capability survived β not that it transfers to unseen task wordings.
- No confidence output at all (0% coverage after DPO). The training targets omitted the field; the model learned the target distribution it was shown. This is a data-design error, not an inherent limit, and is the single cheapest thing to fix.
- The
counttraining signal was degenerate. A missing bounding-box file meant every count target was "0" for 10% of SFT examples. The count evaluation is valid (majority baseline 72%); the training data for that task was not. - DPO trades species accuracy for invariance (51.7% β 36.5%). If species
identification matters more than robustness, use
adapter_sft.
Claim discipline
The defensible claim: addressing the empty-frame bias documented in Real-Wild-VLM, evaluated on Caltech Camera Traps. Not SOTA on their benchmark; not evaluated on their benchmark at all.
Validation evidence β four silent failures this pipeline catches
These are not footnotes. Each passed a structural check and would have produced a confident, wrong result; they are the reason the numbers above should be trusted at all.
- nf4 quantization can blind the vision tower. Every structural assertion
passed β 4-bit modules present, image tokens in
input_ids,pixel_valuespopulated β while the model described a daylight bobcat as "a person standing in a field with a blue sky". Now caught by a behavioural guard whose threshold is calibrated against an unquantized bf16 run. top_k=1in the shippedgeneration_configsilently overridesdo_sample=True. Preference mining returned 0 hallucinations against a 9.2% base rate. Now caught by asserting that 20 samples are not identical.- Parser bugs invent false positives.
"Animal: blank"was once scored as a hallucination, which put correct abstentions into the REJECTED slot of preference pairs β training the model to hallucinate. Now gated by a 50-case hand-labelled regression test that must pass before any pair is written.
A fourth was found in this run: passing min_pixels/max_pixels to
AutoProcessor.from_pretrained sets the attributes but does not change the
output, because the processor reads its size dict. Now asserted on the
realised patch count.
The rule: assert on behaviour, not configuration state. A setting the API accepted is not a setting that took effect.
Code
https://github.com/eitanlebras/GhostTrap β eval harness, guards, frontier sweep, training, and the full decision log.
- Downloads last month
- -
