CLIP & Contrastive Learning
CLIP — Kết nối hình ảnh và ngôn ngữ
Bạn có 1000 ảnh chưa gán nhãn và muốn tìm tất cả ảnh có phở. Cách nào nhanh nhất?
Đây là trái tim của CLIP: ma trận cosine similarity giữa mọi ảnh và mọi mô tả trong cùng một batch. Hàng là ảnh, cột là mô tả. Mỗi ô hiển thị độ tương đồng (0 → 1) giữa embedding của ảnh hàng và embedding của mô tả cột.
Khi chưa huấn luyện, encoder khởi tạo ngẫu nhiên nên mọi ô đều xấp xỉ nhau. Khi đã huấn luyện bằng contrastive loss, đường chéo (ảnh i ↔ mô tả i — cặp đúng) sáng lên, còn các ô ngoài đường chéo (cặp sai) tối đi.
Hình minh họa
Di chuột qua một ô để xem cặp (ảnh ↔ mô tả) và similarity cụ thể.
Hàng i: embedding của ảnh Ii so với TẤT CẢ mô tả trong batch. Ô sáng nhất trong hàng phải là cột i.
Cột j: embedding của mô tả Tj so với TẤT CẢ ảnh trong batch. Ô sáng nhất trong cột phải là hàng j.
Loss đối xứng: CLIP tối ưu cả hai chiều — softmax theo hàng (image → text retrieval) và softmax theo cột (text → image retrieval). Loss cuối là trung bình cộng.
Đường chéo = tín hiệu học:mô hình chỉ "được điểm" khi softmax đặt xác suất cao nhất lên ô chéo. Mọi ô khác là "phân tán nhiễu" và bị loss phạt.
CLIP không phải bộ phân loại ảnh — nó là bộ dịch giữa hai ngôn ngữ: ngôn ngữ thị giác và ngôn ngữ văn bản. Bằng cách đặt cả hai vào cùng một không gian vector, bất kỳ ảnh nào cũng có thể được tìm bằng bất kỳ mô tả nào — mà không cần huấn luyện riêng cho từng task!
Nhìn ma trận ở bước 2: sau huấn luyện, đường chéo cháy rực còn ô ngoài tối đi — đó chính là khoảnh khắc hai "ngôn ngữ" tìm thấy nhau trong không gian chung 768 chiều.
Đã hiểu CLIP học gì, giờ ta xem nó dùng ra sao. Cho một ảnh bất kỳ và một danh sách nhãn dạng văn bản, CLIP sẽ chọn nhãn có embedding gần với ảnh nhất — không cần train lại.
Hình minh họa
1. Chọn một ảnh đầu vào:
2. Nhập danh sách nhãn (mỗi nhãn một dòng):
3. CLIP dự đoán:
Ảnh đầu vào
Tô phở bò
Thử sửa nhãn xem: thêm a photo of có làm xác suất cao hơn không? Thay "phở" bằng "noodle soup" thì sao?
Bạn dùng CLIP zero-shot để phân loại ảnh thành 3 nhóm: 'phở', 'bánh mì', 'cơm tấm'. Kết quả cho ảnh bún chả là 'phở: 40%, bánh mì: 25%, cơm tấm: 35%'. Tại sao?
Giải thích
CLIP (Contrastive Language-Image Pre-training) do OpenAI công bố năm 2021, học cách liên kết hình ảnh và văn bản trong cùng không gian vector thông qua học tương phản (contrastive learning) trên 400 triệu cặp ảnh-mô tả từ internet (WebImageText / WIT). CLIP là nền tảng cho các VLM, các kiến trúc unified multimodal, và hầu hết hệ thống text-to-image hiện đại.
1. Mã hoá song song: Ảnh qua Vision Transformer (ViT-L/14 hoặc ViT-B/32), văn bản qua Text Transformer (12 lớp, 512 chiều ẩn). Cả hai tạo ra vector đặc trưng.
2. Chiếu vào không gian chung: Linear projection (Wi, Wt) chiếu hai vector vào cùng không gian d chiều (thường 512 hoặc 768). Sau đó L2-normalize để cosine similarity = dot product.
3. Contrastive loss (InfoNCE): Trong batch N cặp (Ii, Ti), kéo N cặp đúng lại gần (cosine similarity cao) và đẩy N(N−1) cặp sai ra xa.
Loss function của CLIP (InfoNCE đối xứng):
là cosine similarity. là temperature (khởi tạo từ 0.07 và học như tham số). Loss đối xứng: cả ảnh tìm mô tả và mô tả tìm ảnh.
Về mặt trực giác, InfoNCE chính là cross-entropy trên ma trận N×N:
Ảnh i phải "phân loại" đúng mô tả i trong N lựa chọn. Đây là lý do contrastive learning cần batch lớn: batch càng lớn càng nhiều "nhiễu" cần phân biệt.
# Inference với CLIP — zero-shot classification ảnh ẩm thực Việt
import torch
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
# 1. Tải mô hình tiền huấn luyện của OpenAI
device = "cuda" if torch.cuda.is_available() else "cpu"
model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14").to(device)
processor = CLIPProcessor.from_pretrained("openai/clip-vit-large-patch14")
model.eval()
# 2. Chuẩn bị input
image = Image.open("mon-an.jpg").convert("RGB")
# Dùng prompt template — cải thiện ~5% accuracy
labels_raw = ["pho bo", "banh mi", "com tam", "bun cha"]
labels = [f"a photo of {lbl}, a Vietnamese dish" for lbl in labels_raw]
inputs = processor(
text=labels,
images=image,
return_tensors="pt",
padding=True,
).to(device)
# 3. Forward pass — lấy logits_per_image đã chia theo temperature
with torch.inference_mode():
outputs = model(**inputs)
logits = outputs.logits_per_image # shape: [1, num_labels]
probs = logits.softmax(dim=-1)
# 4. In kết quả — xếp theo xác suất giảm dần
scored = sorted(
zip(labels_raw, probs[0].cpu().tolist()),
key=lambda x: x[1],
reverse=True,
)
for label, p in scored:
print(f"{label:10s} → {p*100:5.1f}%")
# 5. Truy cập embedding trực tiếp (ví dụ cho image retrieval)
image_features = model.get_image_features(**{
k: v for k, v in inputs.items() if k in ("pixel_values",)
})
text_features = model.get_text_features(**{
k: v for k, v in inputs.items()
if k in ("input_ids", "attention_mask")
})
# L2-normalize rồi cosine = dot product
image_features = image_features / image_features.norm(dim=-1, keepdim=True)
text_features = text_features / text_features.norm(dim=-1, keepdim=True)
similarity = image_features @ text_features.T # [1, num_labels]
print("Cosine similarity:", similarity.cpu().tolist())Stable Diffusion: CLIP text encoder mã hoá prompt để dẫn dắt tạo ảnh (cross-attention trong U-Net).
Image search: Tìm ảnh bằng ngôn ngữ tự nhiên — Google Photos, Apple Photos, Unsplash đều dùng ý tưởng CLIP.
Zero-shot classification: Phân loại ảnh không cần dữ liệu huấn luyện riêng — đạt 76% top-1 ImageNet mà không thấy ImageNet label nào.
Image-text retrieval: Cho ảnh tìm mô tả, cho mô tả tìm ảnh — cơ sở của các vector database đa phương thức.
Content moderation: Dùng các prompt có hại làm query, similarity cao → cờ đỏ.
Bạn huấn luyện CLIP trên 100 triệu cặp với batch = 256. Accuracy zero-shot ImageNet chỉ đạt 45% (OpenAI đạt 76%). Lỗi có thể nằm ở đâu NHẤT?
Bias văn hoá và ngôn ngữ: Huấn luyện chủ yếu trên dữ liệu tiếng Anh từ web Mỹ/châu Âu, nên hiểu "ao dai" kém hơn "dress". Prompt tiếng Anh thường cho kết quả tốt hơn tiếng Việt.
Counting yếu: "3 con mèo" và "5 con mèo" có similarity gần nhau. CLIP không học số đếm chính xác — caption web hiếm khi nói chính xác số lượng.
Spatial reasoning kém: "mèo trên bàn" vs "bàn trên mèo" — CLIP không phân biệt tốt vị trí tương đối. Nó thiên về "bag of concepts" hơn là cấu trúc.
Fine-grained yếu: Phân biệt giống chó cụ thể (Golden Retriever vs Labrador), loài chim (Warbler vs Finch) kém so với ResNet fine-tune riêng.
Distribution shift: CLIP học từ internet 2020, không biết sự kiện mới. Prompt "iPhone 15" có thể bị confuse với iPhone cũ hơn.
- OpenCLIP (LAION, 2022): Cộng đồng tái hiện CLIP mã nguồn mở, huấn luyện trên LAION-2B, đạt hoặc vượt CLIP gốc.
- SigLIP (Google, 2023): Thay softmax bằng sigmoid loss → train batch lớn dễ hơn, accuracy cao hơn CLIP với cùng compute.
- EVA-CLIP (BAAI): Khởi tạo ViT từ EVA + huấn luyện CLIP → SOTA trên nhiều benchmark.
- Multilingual-CLIP: Huấn luyện text encoder đa ngôn ngữ cùng với CLIP vision encoder đông lạnh.
- CLIPA/CLIPA-v2: Giảm kích thước ảnh khi training → giảm FLOPs, tăng batch.
- CLIP liên kết ảnh và văn bản trong CÙNG MỘT không gian vector bằng contrastive learning trên 400M cặp.
- Ma trận N×N similarity: đường chéo = cặp đúng cần kéo gần, ngoài đường chéo = cặp sai cần đẩy xa.
- Zero-shot classification: viết mô tả cho mỗi nhãn, encode, so cosine — không cần fine-tune.
- InfoNCE loss đối xứng + temperature τ (~0.07) học được + batch lớn (32k) là ba yếu tố quyết định.
- CLIP là nền tảng cho Stable Diffusion (text encoder), image search, VLM, và moderation.
- Hạn chế: bias tiếng Anh, yếu counting/spatial reasoning, fine-grained chưa sánh được với classifier chuyên biệt.
Kiểm tra hiểu biết
CLIP được huấn luyện bằng phương pháp nào?