Skip to content
SDB
Computer Vision

Chapter 09 · advanced · 60 min

Vision-Language Models

CLIP, BLIP-2, LLaVA, and multimodal understanding

Subhendu Datta BhowmikAI Tutorials

CLIP: Contrastive Language-Image Pre-training

CLIP (Radford et al., OpenAI 2021) learns joint image-text embeddings by training on 400M (image, caption) pairs.

Architecture:

  • Image encoder: ViT-B/32 or ResNet
  • Text encoder: Transformer (77-token context)
  • Both encode to a shared embedding space of dimension dd

Contrastive InfoNCE loss: For a batch of NN pairs, compute the N×NN \times N cosine similarity matrix:

sij=fI(xi)fT(tj)fI(xi)fT(tj)eτs_{ij} = \frac{f_I(x_i) \cdot f_T(t_j)}{\|f_I(x_i)\| \|f_T(t_j)\|} \cdot e^\tau

Push diagonal (matching pairs) high, off-diagonal low.

Zero-shot classification: Compute similarity between image and text prompts "a photo of a {class}" for each class.

BLIP-2

BLIP-2 (Salesforce 2023) connects a frozen image encoder (EVA-CLIP ViT-G/14) and frozen LLM via a lightweight Q-Former:

  • 32 learned query tokens cross-attend to visual features
  • Q-Former output is projected to LLM token space
  • Only Q-Former trained (188M params) — encoders stay frozen

LLaVA

LLaVA (Liu et al., 2023) uses a simpler approach:

  1. CLIP ViT-L/14@336px image encoder (frozen in stage 1)
  2. MLP connector (trained throughout)
  3. LLaMA/Vicuna/Mistral LLM

Training stages:

  • Stage 1: Train only MLP connector on 595K image-caption pairs
  • Stage 2: Unfreeze LLM + connector; train on 158K visual instruction data

VLM Benchmarks

BenchmarkTaskMetric
VQAv2Open-ended VQAAccuracy
MMMUCollege-level multi-disciplineAccuracy
POPEHallucination probeF1
TextVQAText in imagesAccuracy
COCO CaptionsImage captioningCIDEr

Hallucination

VLMs frequently mention objects not in the image. Mitigations:

  • RLHF fine-tuning with feedback on hallucination
  • Higher visual token density (AnyRes)
  • Contrastive decoding (subtract unconditional LLM score)
CLIP zero-shot classification and image retrievalpython
from transformers import CLIPProcessor, CLIPModel
import torch
import torch.nn.functional as F
from PIL import Image

model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
model.eval()

# Zero-shot classification
def zero_shot_classify(image: Image.Image, class_names: list[str]):
    texts = [f"a photo of a {c}" for c in class_names]
    inputs = processor(text=texts, images=image, return_tensors="pt", padding=True)
    with torch.no_grad():
        outputs = model(**inputs)
    img_emb  = F.normalize(outputs.image_embeds, dim=-1)
    text_emb = F.normalize(outputs.text_embeds,  dim=-1)
    logits = (img_emb @ text_emb.T) * model.logit_scale.exp()
    probs  = logits.softmax(dim=-1)[0]
    for name, p in sorted(zip(class_names, probs.tolist()), key=lambda x: -x[1])[:3]:
        print(f"{name:<20} {p:.3f}")

img = Image.open("test.jpg").convert("RGB")
zero_shot_classify(img, ["cat", "dog", "bird", "car", "bicycle", "airplane"])

# Image retrieval from text query
def build_image_index(image_paths: list[str], batch_size=32):
    all_embeds = []
    for i in range(0, len(image_paths), batch_size):
        batch = [Image.open(p).convert("RGB") for p in image_paths[i:i+batch_size]]
        inputs = processor(images=batch, return_tensors="pt", padding=True)
        with torch.no_grad():
            embeds = F.normalize(model.get_image_features(**inputs), dim=-1)
        all_embeds.append(embeds)
    return torch.cat(all_embeds)

def text_search(query: str, image_index: torch.Tensor, image_paths: list[str], top_k=5):
    inputs = processor(text=[query], return_tensors="pt", padding=True)
    with torch.no_grad():
        text_emb = F.normalize(model.get_text_features(**inputs), dim=-1)
    sims = (text_emb @ image_index.T)[0]
    top_idx = sims.topk(top_k).indices.tolist()
    return [(image_paths[i], sims[i].item()) for i in top_idx]
BLIP-2 and LLaVA for visual question answeringpython
from transformers import Blip2Processor, Blip2ForConditionalGeneration
import torch
from PIL import Image

# BLIP-2 VQA
processor = Blip2Processor.from_pretrained("Salesforce/blip2-opt-2.7b")
model = Blip2ForConditionalGeneration.from_pretrained(
    "Salesforce/blip2-opt-2.7b", torch_dtype=torch.float16, device_map="auto",
)
model.eval()

image = Image.open("photo.jpg").convert("RGB")

def vqa_blip2(image, question):
    prompt = f"Question: {question} Answer:"
    inputs = processor(images=image, text=prompt, return_tensors="pt").to("cuda", torch.float16)
    out = model.generate(**inputs, max_new_tokens=30, num_beams=5)
    return processor.decode(out[0], skip_special_tokens=True).replace(prompt, "").strip()

for q in ["What is the main object?", "What color is it?", "How many are there?"]:
    print(f"Q: {q}  A: {vqa_blip2(image, q)}")

# LLaVA multi-turn conversation
from transformers import LlavaNextProcessor, LlavaNextForConditionalGeneration

lva_processor = LlavaNextProcessor.from_pretrained("llava-hf/llava-v1.6-mistral-7b-hf")
lva_model = LlavaNextForConditionalGeneration.from_pretrained(
    "llava-hf/llava-v1.6-mistral-7b-hf", torch_dtype=torch.float16, device_map="auto",
)

conversation = [{
    "role": "user",
    "content": [{"type": "image"}, {"type": "text", "text": "Describe this image in detail."}],
}]
prompt = lva_processor.apply_chat_template(conversation, add_generation_prompt=True)
inputs = lva_processor(images=image, text=prompt, return_tensors="pt").to("cuda")
output = lva_model.generate(**inputs, max_new_tokens=200, do_sample=False)
print(lva_processor.decode(output[0], skip_special_tokens=True))

Knowledge check

CLIP uses contrastive learning. In a batch of N image-text pairs, the target for the similarity matrix is:

Knowledge check

What is the role of the Q-Former in BLIP-2?

Knowledge check

A VQA model answers "a giraffe" for an image containing no giraffe. This is an example of:

Computer Vision