Spaces:
Sleeping
Sleeping
| # Image Captioning with Transformers — Complete Learning Guide | |
| --- | |
| ## 1. What is Image Captioning? | |
| **Image Captioning** is the task of automatically generating a natural language description for a given image. It bridges **computer vision** and **natural language processing**. | |
| **Example:** | |
| - **Input:** A photo of a dog catching a frisbee in a park | |
| - **Output:** *"A dog jumps to catch a flying disc in a grassy field."* | |
| --- | |
| ## 2. Why Transformers for Image Captioning? | |
| Traditional approaches used CNN + RNN (LSTM/GRU), but Transformers changed the game: | |
| | Aspect | CNN + RNN | Vision-Language Transformers | | |
| |--------|-----------|------------------------------| | |
| | Long-range dependencies | Weak (vanishing gradients) | Strong (self-attention) | | |
| | Parallelization | Sequential (slow) | Fully parallel (fast) | | |
| | Pretraining | Limited | Massive (web-scale) | | |
| | Transfer learning | Hard | Easy (one model, many tasks) | | |
| --- | |
| ## 3. Core Architecture: Encoder-Decoder | |
| The standard transformer-based image captioning model has two parts: | |
| ``` | |
| ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ | |
| │ Input Image │─────▶│ Vision Encoder │─────▶│ Image Features │ | |
| │ (H × W × 3) │ │ (ViT/Swin/ResNet│ │ (N × D) │ | |
| └─────────────────┘ └──────────────────┘ └─────────────────┘ | |
| │ | |
| ▼ | |
| ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ | |
| │ Generated Text │◀─────│ Text Decoder │◀─────│ Cross-Attention │ | |
| │ "A dog..." │ │ (GPT/BERT-style)│ │ (Image ⟷ Text) │ | |
| └─────────────────┘ └──────────────────┘ └─────────────────┘ | |
| ``` | |
| ### Key Components: | |
| 1. **Vision Encoder**: Extracts visual features from images | |
| - **ViT** (Vision Transformer): Patch-based, most common | |
| - **Swin Transformer**: Hierarchical, better for multi-scale | |
| - **CLIP Vision Encoder**: Pre-trained on image-text pairs | |
| 2. **Text Decoder**: Generates captions autoregressively | |
| - **GPT-style**: Autoregressive (most common for generation) | |
| - **BERT-style**: Masked (less common for captioning) | |
| 3. **Cross-Attention**: Connects vision and language | |
| - Decoder attends to image features when generating each word | |
| --- | |
| ## 4. Popular Models | |
| ### 4.1 BLIP (Bootstrapping Language-Image Pre-training) | |
| - **Paper**: "BLIP: Bootstrapping Language-Image Pre-training" (Salesforce, 2022) | |
| - **Encoder**: ViT | |
| - **Decoder**: Transformer decoder (causal LM) | |
| - **Key Feature**: Unified architecture for understanding + generation | |
| - **Strength**: Strong zero-shot captioning, filter noisy web data | |
| - **Variants**: BLIP (base), BLIP-2 (with Q-Former for frozen LLMs) | |
| ### 4.2 GIT (Generative Image-to-text Transformer) | |
| - **Paper**: "GIT: A Generative Image-to-text Transformer for Vision and Language" (Microsoft, 2022) | |
| - **Architecture**: Simple single-stream transformer (image patches as tokens) | |
| - **Strength**: Simpler than BLIP, very strong performance | |
| - **Pre-training**: Large-scale image-text pairs | |
| ### 4.3 BLIP-2 | |
| - **Paper**: "BLIP-2: Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models" (2023) | |
| - **Innovation**: Q-Former bridges frozen vision encoder and frozen LLM | |
| - **LLM Backbones**: OPT, Flan-T5 | |
| - **Best for**: When you want to leverage large LLMs without training them | |
| ### 4.4 ViT-GPT2 / Vision Encoder-Decoder (Hugging Face) | |
| - **Architecture**: Any ViT encoder + any GPT decoder | |
| - **Easy to use**: Hugging Face `VisionEncoderDecoderModel` | |
| - **Best for**: Learning, fine-tuning on custom datasets | |
| ### 4.5 LLaVA, MiniGPT-4, InstructBLIP | |
| - **Type**: Instruction-tuned multimodal models | |
| - **Best for**: Conversational image understanding, not just captioning | |
| --- | |
| ## 5. Hands-On: Using Pre-trained Models (Hugging Face) | |
| ### 5.1 Quick Start with BLIP | |
| ```python | |
| from transformers import BlipProcessor, BlipForConditionalGeneration | |
| from PIL import Image | |
| import requests | |
| # Load model and processor | |
| processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base") | |
| model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base") | |
| # Load image | |
| url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/coco.png" | |
| image = Image.open(requests.get(url, stream=True).raw).convert('RGB') | |
| # Generate caption | |
| inputs = processor(image, return_tensors="pt") | |
| out = model.generate(**inputs) | |
| caption = processor.decode(out[0], skip_special_tokens=True) | |
| print(f"Caption: {caption}") | |
| # Output: "a soccer game with a player in yellow and white uniforms" | |
| ``` | |
| ### 5.2 Using BLIP-2 (More Powerful) | |
| ```python | |
| from transformers import BlipProcessor, BlipForConditionalGeneration | |
| from PIL import Image | |
| import torch | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-large") | |
| model = BlipForConditionalGeneration.from_pretrained( | |
| "Salesforce/blip-image-captioning-large" | |
| ).to(device) | |
| image = Image.open("your_image.jpg").convert("RGB") | |
| # Conditional generation (start with a prompt) | |
| text = "a photography of" | |
| inputs = processor(image, text, return_tensors="pt").to(device) | |
| out = model.generate(**inputs, max_new_tokens=50) | |
| caption = processor.decode(out[0], skip_special_tokens=True) | |
| print(caption) | |
| ``` | |
| ### 5.3 Using VisionEncoderDecoder (Flexible) | |
| ```python | |
| from transformers import VisionEncoderDecoderModel, ViTImageProcessor, AutoTokenizer | |
| from PIL import Image | |
| # Load a ViT-GPT2 model | |
| model = VisionEncoderDecoderModel.from_pretrained("nlpconnect/vit-gpt2-image-captioning") | |
| feature_extractor = ViTImageProcessor.from_pretrained("nlpconnect/vit-gpt2-image-captioning") | |
| tokenizer = AutoTokenizer.from_pretrained("nlpconnect/vit-gpt2-image-captioning") | |
| image = Image.open("image.jpg").convert("RGB") | |
| pixel_values = feature_extractor(images=image, return_tensors="pt").pixel_values | |
| generated_ids = model.generate(pixel_values, max_length=50) | |
| generated_caption = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0] | |
| print(generated_caption) | |
| ``` | |
| --- | |
| ## 6. Fine-Tuning on Your Own Dataset | |
| ### 6.1 Dataset Preparation (COCO-style) | |
| ```python | |
| from datasets import load_dataset | |
| from torch.utils.data import Dataset | |
| from PIL import Image | |
| # Example: COCO Captions dataset | |
| dataset = load_dataset("yerevann/coco-karpathy", "default") | |
| class ImageCaptioningDataset(Dataset): | |
| def __init__(self, images, captions, processor): | |
| self.images = images | |
| self.captions = captions | |
| self.processor = processor | |
| def __len__(self): | |
| return len(self.images) | |
| def __getitem__(self, idx): | |
| image = self.images[idx] | |
| caption = self.captions[idx] | |
| # Process image and text | |
| encoding = self.processor( | |
| images=image, | |
| text=caption, | |
| padding="max_length", | |
| return_tensors="pt" | |
| ) | |
| # Remove batch dimension added by processor | |
| encoding = {k: v.squeeze(0) for k, v in encoding.items()} | |
| return encoding | |
| ``` | |
| ### 6.2 Training Loop | |
| ```python | |
| from transformers import BlipForConditionalGeneration, BlipProcessor | |
| from torch.utils.data import DataLoader | |
| import torch | |
| from tqdm import tqdm | |
| # Setup | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base") | |
| model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base") | |
| model.to(device) | |
| # Create dataloader (assuming you have images and captions) | |
| # train_dataset = ImageCaptioningDataset(images, captions, processor) | |
| # train_dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True) | |
| optimizer = torch.optim.AdamW(model.parameters(), lr=5e-5) | |
| model.train() | |
| for epoch in range(3): | |
| for batch in tqdm(train_dataloader): | |
| input_ids = batch["input_ids"].to(device) | |
| pixel_values = batch["pixel_values"].to(device) | |
| attention_mask = batch["attention_mask"].to(device) | |
| outputs = model( | |
| input_ids=input_ids, | |
| pixel_values=pixel_values, | |
| attention_mask=attention_mask, | |
| labels=input_ids | |
| ) | |
| loss = outputs.loss | |
| loss.backward() | |
| optimizer.step() | |
| optimizer.zero_grad() | |
| print(f"Epoch {epoch} | Loss: {loss.item():.4f}") | |
| # Save model | |
| model.save_pretrained("./my-captioning-model") | |
| processor.save_pretrained("./my-captioning-model") | |
| ``` | |
| --- | |
| ## 7. Key Datasets for Image Captioning | |
| | Dataset | Size | Images | Captions/Image | Domain | | |
| |---------|------|--------|----------------|--------| | |
| | **COCO Captions** | ~120K | 120K | 5 | General | | |
| | **Flickr30K** | 30K | 30K | 5 | General | | |
| | **Flickr8K** | 8K | 8K | 5 | General (small) | | |
| | **Conceptual Captions (CC3M/CC12M)** | 3M/12M | - | 1 | Web-scraped | | |
| | **LAION-400M** | 400M | - | 1 | Web-scale | | |
| | **TextCaps** | 28K | - | 1 | Text in images | | |
| | ** nocaps** | 15K | - | 10 | Novel objects | | |
| **Recommended for beginners:** COCO Captions or Flickr8K (small, manageable) | |
| **Recommended for pre-training:** Conceptual Captions (CC12M) or LAION | |
| --- | |
| ## 8. Evaluation Metrics | |
| | Metric | Description | Range | Good Score | | |
| |--------|-------------|-------|------------| | |
| | **BLEU-4** | N-gram precision | 0-1 | >0.35 | | |
| | **METEOR** | Synonym/paraphrase aware | 0-1 | >0.28 | | |
| | **ROUGE-L** | Longest common subsequence | 0-1 | >0.55 | | |
| | **CIDEr** | TF-IDF weighted n-grams | 0-10 | >1.0 | | |
| | **SPICE** | Scene graph matching | 0-1 | >0.20 | | |
| ```python | |
| from evaluate import load | |
| # Using Hugging Face evaluate library | |
| bleu = load("bleu") | |
| meteor = load("meteor") | |
| rouge = load("rouge") | |
| predictions = ["a dog plays with a frisbee"] | |
| references = [["a dog is catching a frisbee in the park"]] | |
| results = bleu.compute(predictions=predictions, references=references) | |
| print(results) | |
| ``` | |
| --- | |
| ## 9. Advanced Topics | |
| ### 9.1 Beam Search vs. Nucleus Sampling | |
| ```python | |
| # Beam Search (more deterministic, higher quality) | |
| out = model.generate( | |
| **inputs, | |
| num_beams=5, | |
| max_length=50, | |
| early_stopping=True | |
| ) | |
| # Nucleus Sampling (more diverse, creative) | |
| out = model.generate( | |
| **inputs, | |
| do_sample=True, | |
| top_p=0.9, | |
| temperature=0.7, | |
| max_length=50 | |
| ) | |
| ``` | |
| ### 9.2 Multi-GPU Training (DistributedDataParallel) | |
| ```python | |
| import torch.distributed as dist | |
| from torch.nn.parallel import DistributedDataParallel as DDP | |
| # Initialize process group | |
| dist.init_process_group("nccl") | |
| model = DDP(model, device_ids=[local_rank]) | |
| # ... training loop ... | |
| ``` | |
| ### 9.3 Quantization for Inference (Faster, Smaller) | |
| ```python | |
| from transformers import BitsAndBytesConfig | |
| bnb_config = BitsAndBytesConfig( | |
| load_in_4bit=True, | |
| bnb_4bit_compute_dtype=torch.float16 | |
| ) | |
| model = BlipForConditionalGeneration.from_pretrained( | |
| "Salesforce/blip-image-captioning-large", | |
| quantization_config=bnb_config | |
| ) | |
| ``` | |
| --- | |
| ## 10. Architecture Deep Dive: How Cross-Attention Works | |
| ```python | |
| import torch | |
| import torch.nn as nn | |
| import math | |
| class CrossAttention(nn.Module): | |
| """ | |
| Cross-attention: Text queries attend to Image keys/values | |
| """ | |
| def __init__(self, d_model, num_heads): | |
| super().__init__() | |
| self.num_heads = num_heads | |
| self.d_head = d_model // num_heads | |
| self.q_proj = nn.Linear(d_model, d_model) # From text | |
| self.k_proj = nn.Linear(d_model, d_model) # From image | |
| self.v_proj = nn.Linear(d_model, d_model) # From image | |
| self.out_proj = nn.Linear(d_model, d_model) | |
| def forward(self, text_hidden, image_features, text_mask=None): | |
| batch_size = text_hidden.size(0) | |
| # Project | |
| Q = self.q_proj(text_hidden) # (B, T, D) | |
| K = self.k_proj(image_features) # (B, N, D) | |
| V = self.v_proj(image_features) # (B, N, D) | |
| # Reshape for multi-head | |
| Q = Q.view(batch_size, -1, self.num_heads, self.d_head).transpose(1, 2) | |
| K = K.view(batch_size, -1, self.num_heads, self.d_head).transpose(1, 2) | |
| V = V.view(batch_size, -1, self.num_heads, self.d_head).transpose(1, 2) | |
| # Attention scores | |
| scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_head) | |
| if text_mask is not None: | |
| scores = scores.masked_fill(text_mask.unsqueeze(1).unsqueeze(1) == 0, float('-inf')) | |
| attn = torch.softmax(scores, dim=-1) | |
| context = torch.matmul(attn, V) | |
| # Concatenate heads | |
| context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) | |
| return self.out_proj(context) | |
| ``` | |
| --- | |
| ## 11. Project Ideas for Practice | |
| 1. **Instagram Caption Generator** — Fine-tune on social media captions | |
| 2. **Medical Image Captioning** — Train on radiology reports + X-rays | |
| 3. **News Image Captioning** — Generate journalistic captions | |
| 4. **Art Description** — Describe paintings/artwork in detail | |
| 5. **E-commerce Product Description** — Generate product descriptions from images | |
| 6. **Accessibility Tool** — Screen reader for visually impaired users | |
| 7. **Meme Caption Generator** — Understand humor + image context | |
| --- | |
| ## 12. Essential Papers to Read | |
| | Paper | Authors | Year | Why Read | | |
| |-------|---------|------|----------| | |
| | **"Attention Is All You Need"** | Vaswani et al. | 2017 | Foundation of Transformers | | |
| | **"An Image is Worth 16x16 Words"** | Dosovitskiy et al. | 2020 | ViT - Vision Transformer | | |
| | **"BLIP"** | Li et al. (Salesforce) | 2022 | Best unified V+L model | | |
| | **"BLIP-2"** | Li et al. (Salesforce) | 2023 | Bridging vision and LLMs | | |
| | **"GIT"** | Wang et al. (Microsoft) | 2022 | Simple and strong baseline | | |
| | **"Show, Attend and Tell"** | Xu et al. | 2015 | CNN+RNN classic (historical) | | |
| | **"CLIP"** | Radford et al. (OpenAI) | 2021 | Contrastive pretraining | | |
| --- | |
| ## 13. Quick Reference: Hugging Face Model Hub | |
| | Model | Path | Size | Best For | | |
| |-------|------|------|----------| | |
| | BLIP Base | `Salesforce/blip-image-captioning-base` | ~400M | Fast inference | | |
| | BLIP Large | `Salesforce/blip-image-captioning-large` | ~1B | Better quality | | |
| | ViT-GPT2 | `nlpconnect/vit-gpt2-image-captioning` | ~300M | Learning/fine-tuning | | |
| | BLIP-2 OPT-2.7B | `Salesforce/blip2-opt-2.7b` | ~2.7B | Strong captions | | |
| | BLIP-2 Flan-T5-XL | `Salesforce/blip2-flan-t5-xl` | ~3B | Instruction following | | |
| | GIT Base | `microsoft/git-base-coco` | ~300M | COCO fine-tuned | | |
| | GIT Large | `microsoft/git-large-coco` | ~800M | Best quality | | |
| --- | |
| ## 14. Common Pitfalls & Tips | |
| ### ⚠️ Pitfalls: | |
| 1. **Forgetting to resize images** — Models expect specific sizes (e.g., 224x224 for ViT) | |
| 2. **Not handling special tokens** — `<pad>`, `<eos>`, `<unk>` must be properly managed | |
| 3. **Evaluating on training data** — Always split train/val/test properly | |
| 4. **Ignoring CIDEr/SPICE** — BLEU alone is misleading for caption quality | |
| 5. **Not using mixed precision** — Training without `fp16` is 2-3x slower | |
| ### ✅ Tips: | |
| 1. **Start with pre-trained models** — Don't train from scratch initially | |
| 2. **Use gradient checkpointing** — Trade compute for memory on large models | |
| 3. **Data augmentation** — Random crops, flips, color jitter help generalization | |
| 4. **Label smoothing** — Improves generation diversity | |
| 5. **Ensemble decoding** — Average multiple model outputs for best results | |
| --- | |
| ## 15. Full Example: End-to-End Pipeline | |
| ```python | |
| """ | |
| Complete image captioning pipeline using BLIP | |
| """ | |
| import torch | |
| from transformers import BlipProcessor, BlipForConditionalGeneration | |
| from PIL import Image | |
| import os | |
| class ImageCaptioner: | |
| def __init__(self, model_name="Salesforce/blip-image-captioning-base", device=None): | |
| self.device = device or ("cuda" if torch.cuda.is_available() else "cpu") | |
| self.processor = BlipProcessor.from_pretrained(model_name) | |
| self.model = BlipForConditionalGeneration.from_pretrained(model_name).to(self.device) | |
| self.model.eval() | |
| def caption(self, image_path, num_captions=1, max_length=50): | |
| """Generate caption(s) for an image.""" | |
| image = Image.open(image_path).convert("RGB") | |
| inputs = self.processor(image, return_tensors="pt").to(self.device) | |
| with torch.no_grad(): | |
| if num_captions == 1: | |
| outputs = self.model.generate( | |
| **inputs, | |
| max_length=max_length, | |
| num_beams=5, | |
| early_stopping=True | |
| ) | |
| else: | |
| outputs = self.model.generate( | |
| **inputs, | |
| max_length=max_length, | |
| num_return_sequences=num_captions, | |
| num_beams=num_captions * 2, | |
| do_sample=True, | |
| temperature=0.8, | |
| top_p=0.9 | |
| ) | |
| captions = self.processor.batch_decode(outputs, skip_special_tokens=True) | |
| return captions[0] if num_captions == 1 else captions | |
| def caption_folder(self, folder_path, output_file="captions.txt"): | |
| """Caption all images in a folder.""" | |
| results = [] | |
| for fname in os.listdir(folder_path): | |
| if fname.lower().endswith(('.png', '.jpg', '.jpeg')): | |
| path = os.path.join(folder_path, fname) | |
| caption = self.caption(path) | |
| results.append(f"{fname}: {caption}") | |
| print(f"{fname} -> {caption}") | |
| with open(output_file, "w") as f: | |
| f.write("\n".join(results)) | |
| return results | |
| # Usage | |
| if __name__ == "__main__": | |
| captioner = ImageCaptioner() | |
| caption = captioner.caption("photo.jpg") | |
| print(caption) | |
| # Multiple diverse captions | |
| captions = captioner.caption("photo.jpg", num_captions=3) | |
| for i, cap in enumerate(captions, 1): | |
| print(f"{i}. {cap}") | |
| ``` | |
| --- | |
| ## 16. Learning Roadmap | |
| ### Week 1: Foundations | |
| - [ ] Read "Attention Is All You Need" | |
| - [ ] Understand ViT architecture | |
| - [ ] Run pre-trained BLIP on sample images | |
| - [ ] Explore the Hugging Face model hub | |
| ### Week 2: Hands-On | |
| - [ ] Load COCO dataset | |
| - [ ] Fine-tune BLIP-base on a small subset | |
| - [ ] Evaluate with BLEU/ROUGE metrics | |
| - [ ] Experiment with beam search vs. sampling | |
| ### Week 3: Advanced | |
| - [ ] Implement custom dataset loader | |
| - [ ] Train on full COCO/Flickr30K | |
| - [ ] Try different model combinations (ViT + GPT2, etc.) | |
| - [ ] Implement gradient checkpointing for larger models | |
| ### Week 4: Production | |
| - [ ] Optimize inference (ONNX/TensorRT) | |
| - [ ] Build a simple API (FastAPI/Flask) | |
| - [ ] Deploy with Docker | |
| - [ ] Add batch processing support | |
| --- | |
| ## Resources | |
| - **Hugging Face Transformers Docs**: https://huggingface.co/docs/transformers | |
| - **BLIP GitHub**: https://github.com/salesforce/BLIP | |
| - **COCO Dataset**: https://cocodataset.org/ | |
| - **Papers with Code**: https://paperswithcode.com/task/image-captioning | |
| - **Course**: Stanford CS231n (CNNs), CS224N (NLP) | |
| --- | |
| *Happy Learning! Start with the pre-trained models and work your way up to training from scratch.* | |