Given the object words a VLM produced for each image and the ground-truth objects actually present, compute the two CHAIR hallucination rates and list which objects were hallucinated.
CHAIR (Caption Hallucination Assessment with Image Relevance) measures how often a captioner names objects that are not in the image. It has two variants:
CHAIRi=#{all objects mentioned}#{hallucinated objects mentioned},CHAIRs=#{captions}#{captions with≥1 hallucination}
CHAIR_i is instance-level (how noisy is the average mention) and CHAIR_s is sentence-level (what fraction of outputs are contaminated at all). CHAIR_s is always at least as alarming as CHAIR_i, because one bad word condemns a whole caption.
Three details are load-bearing:
Implement:
def chair_metrics(captions, ground_truth, synonym_map):
Return [chair_i, chair_s, hallucinated_objects] where the first two are floats rounded to 4 decimals and the third is a sorted list of the unique canonical object names that were hallucinated anywhere in the dataset.
[float, float, [str, ...]]
caps = [["a", "man", "riding", "a", "horse"], ["dog", "dog", "frisbee"]]
gt = [["person", "horse"], ["dog"]]
syn = {"man": "person", "woman": "person", "horse": "horse", "dog": "dog", "frisbee": "frisbee"}
print(chair_metrics(caps, gt, syn))
Output:
[0.25, 0.5, ['frisbee']]
caps = [["a", "man", "riding", "a", "horse"], ["dog", "dog", "frisbee"]]
gt = [["person", "horse"], ["dog"]]
syn = {"man": "person", "woman": "person", "horse": "horse", "dog": "dog", "frisbee": "frisbee"}
print(chair_metrics(caps, gt, syn))[0.25, 0.5, ['frisbee']]
Caption 1 mentions person and horse, both present. Caption 2 mentions dog (twice, counted once) and frisbee, which is absent. That is 1 hallucination out of 4 mentions = 0.25, and 1 of 2 captions contaminated = 0.5.
synonym_map; words that are not keys are NOT object mentionschair_i divides by the total number of (deduplicated) mentions across all captionschair_s divides by the number of captionsset0.0 for chair_i when there are no mentions at all