Phase 04 - Lesson 18
Visión de Vocabulario Abierto — CLIP
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Entrena un codificador de imagen y un codificador de texto juntos para que los pares (imagen, descripción) coincidentes caigan en el mismo punto de un espacio compartido. Ese es todo el truco.
Tipo: Construir + Usar Lenguajes: Python Requisitos previos: Fase 4 Lección 14 (ViT), Fase 4 Lección 17 (Autosupervisión) Tiempo: ~45 minutos
Objetivos de Aprendizaje
- Explicar la arquitectura de dos torres de CLIP y el objetivo de entrenamiento contrastivo
- Usar un CLIP (o SigLIP) preentrenado para clasificación zero-shot sin ningún entrenamiento específico de la tarea
- Implementar clasificación zero-shot desde cero: codificar prompts de clase, calcular similitud de coseno, tomar el argmax
- Distinguir los modelos CLIP, SigLIP, OpenCLIP y LLaVA/LLaMA-vision — para qué sirve cada uno en 2026
El Problema
Los clasificadores tradicionales tienen vocabulario cerrado: un modelo ImageNet de 1000 clases solo puede predecir 1000 etiquetas. Cada categoría nueva requiere datos etiquetados y una cabeza reentrenada.
CLIP (Radford et al., OpenAI 2021) mostró que entrenar con 400M de pares (imagen, descripción) extraídos de la web produce un modelo capaz de clasificar en cualquier conjunto de categorías en la inferencia, descritas puramente en lenguaje natural. Le das una clase nueva escribiendo una oración.
Esa capacidad — transferencia zero-shot — es la razón por la que todo sistema de visión moderno empieza con un checkpoint de la familia CLIP. Detección (Grounding DINO, OWL-ViT), segmentación (CLIPSeg, SAM), recuperación, moderación de contenido, VLMs y generación de texto a imagen, todos se construyen sobre embeddings conjuntos al estilo CLIP.
El Concepto
Dos torres
flowchart LR
IMG["Imagen"] --> IENC["Codificador de imagen<br/>(ViT-L/14)"] --> IEMB["Embedding de imagen<br/>(1024,)"]
TXT["Descripción"] --> TENC["Codificador de texto<br/>(transformer)"] --> TEMB["Embedding de texto<br/>(1024,)"]
IEMB --> SIM["Similitud de coseno"]
TEMB --> SIM
style IENC fill:#dbeafe,stroke:#2563eb
style TENC fill:#fef3c7,stroke:#d97706
style SIM fill:#dcfce7,stroke:#16a34a
Ambos codificadores terminan con una proyección lineal hacia la misma dimensión de embedding (512 para CLIP-B/32, 1024 para CLIP-L/14). Normaliza con L2 y calcula la similitud de coseno.
El objetivo
Dado un lote de N pares (imagen, descripción), construye una matriz de similitud NxN. Entrena ambos codificadores para que la diagonal (pares coincidentes) tenga alta similitud y las posiciones fuera de la diagonal (no coincidentes) tengan baja similitud.
sim_matrix = image_embeddings @ text_embeddings.T / tau
loss_i2t = cross_entropy(sim_matrix, targets=arange(N))
loss_t2i = cross_entropy(sim_matrix.T, targets=arange(N))
loss = (loss_i2t + loss_t2i) / 2
Simétrico porque tanto la recuperación imagen-a-texto como texto-a-imagen deben funcionar. tau (temperatura) suele aprenderse como un parámetro escalar, inicializado en 0.07.
SigLIP: una mejor pérdida
SigLIP (Zhai et al., 2023) reemplazó el softmax por un sigmoide por par:
loss = mean over pairs of log(1 + exp(-y_ij * sim_ij))
y_ij = +1 if matching, -1 otherwise
La pérdida por par elimina la normalización a nivel de lote que CLIP requiere. SigLIP entrena mejor con lotes pequeños e iguala o supera a CLIP con la misma cantidad de datos.
Clasificación zero-shot
Dado un CLIP entrenado:
- Para cada clase, compón un prompt: "a photo of a {class}".
- Codifica todos los prompts de clase con el codificador de texto ->
Tde forma (C, d). - Codifica la imagen de prueba ->
Ide forma (1, d). - Similitud =
I @ T.Tde forma (1, C). - Argmax -> clase predicha.
La ingeniería de prompts importa. OpenAI publicó 80 plantillas de prompt para ImageNet ("a photo of a {}", "a blurry photo of a {}", "a sketch of a {}", ...). Promedia los embeddings de todas las plantillas por clase para ganar entre 1 y 3% extra de exactitud top-1.
Dónde se usan los modelos al estilo CLIP en 2026
- Clasificación zero-shot — uso directo.
- Recuperación de imágenes — codifica todas las imágenes una vez, genera el embedding de la consulta en la inferencia.
- Detección condicionada por texto — Grounding DINO, OWL-ViT envuelven una torre de texto CLIP alrededor de un detector.
- Segmentación condicionada por texto — CLIPSeg; SAM usa entradas de prompt de texto vía CLIP.
- VLMs — LLaVA, Qwen-VL, InternVL conectan un codificador de visión de la familia CLIP a un LLM.
- Generación de texto a imagen — Stable Diffusion, DALL-E 3 se condicionan a embeddings de texto de CLIP.
Una vez que tienes un espacio de embedding compartido, toda tarea de visión+lenguaje se convierte en un cálculo de distancia.
Constrúyelo
Paso 1: Un modelo de dos torres diminuto
El CLIP real es ViT + transformer. Para esta lección las torres son pequeñas MLPs sobre features preextraídas, de modo que la señal de entrenamiento sea visible en la CPU.
import torch
import torch.nn as nn
import torch.nn.functional as F
class TwoTower(nn.Module):
def __init__(self, img_in=128, txt_in=64, emb=64):
super().__init__()
self.image_proj = nn.Sequential(nn.Linear(img_in, 128), nn.ReLU(), nn.Linear(128, emb))
self.text_proj = nn.Sequential(nn.Linear(txt_in, 128), nn.ReLU(), nn.Linear(128, emb))
self.logit_scale = nn.Parameter(torch.ones([]) * 2.6592) # ln(1/0.07)
def forward(self, img_feats, txt_feats):
i = F.normalize(self.image_proj(img_feats), dim=-1)
t = F.normalize(self.text_proj(txt_feats), dim=-1)
return i, t, self.logit_scale.exp()
Dos proyecciones, salida de dimensión compartida, temperatura aprendida. Misma forma que la API real de CLIP.
Paso 2: Pérdida contrastiva
def clip_loss(image_emb, text_emb, logit_scale):
N = image_emb.size(0)
sim = logit_scale * image_emb @ text_emb.T
targets = torch.arange(N, device=sim.device)
l_i = F.cross_entropy(sim, targets)
l_t = F.cross_entropy(sim.T, targets)
return (l_i + l_t) / 2
Simétrica. logit_scale más alto = softmax más pronunciado = más seguro pero con riesgo de inestabilidad.
Paso 3: Clasificador zero-shot
@torch.no_grad()
def zero_shot_classify(model, image_feats, class_text_feats, class_names):
"""
image_feats: (N, img_in)
class_text_feats: (C, txt_in) one averaged embedding per class
"""
i = F.normalize(model.image_proj(image_feats), dim=-1)
t = F.normalize(model.text_proj(class_text_feats), dim=-1)
sim = i @ t.T
pred = sim.argmax(dim=-1)
return [class_names[p] for p in pred.tolist()]
Una línea por paso. Este es exactamente el procedimiento zero-shot usado con un checkpoint CLIP de producción.
Paso 4: Verificación de sanidad
torch.manual_seed(0)
model = TwoTower()
img = torch.randn(8, 128)
txt = torch.randn(8, 64)
i, t, scale = model(img, txt)
loss = clip_loss(i, t, scale)
print(f"batch size: {i.size(0)} loss: {loss.item():.3f}")
La pérdida debe quedar cerca de log(N) = log(8) = 2.08 para un modelo inicializado aleatoriamente — el objetivo de entropía cruzada simétrica cuando aún no se ha aprendido ninguna estructura.
Úsalo
OpenCLIP es el estándar de la comunidad en 2026:
import open_clip
import torch
from PIL import Image
model, _, preprocess = open_clip.create_model_and_transforms("ViT-B-32", pretrained="laion2b_s34b_b79k")
tokenizer = open_clip.get_tokenizer("ViT-B-32")
image = preprocess(Image.open("dog.jpg")).unsqueeze(0)
text = tokenizer(["a photo of a dog", "a photo of a cat", "a photo of a car"])
with torch.no_grad():
image_features = model.encode_image(image)
text_features = model.encode_text(text)
image_features = image_features / image_features.norm(dim=-1, keepdim=True)
text_features = text_features / text_features.norm(dim=-1, keepdim=True)
probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)
print(probs)
SigLIP es más nuevo, entrena mejor a escalas pequeñas y es preferido para trabajos nuevos: google/siglip-base-patch16-224. Hugging Face ofrece ambos.
Entrégalo
Esta lección produce:
outputs/prompt-zero-shot-class-picker.md— un prompt que diseña plantillas de clase para CLIP zero-shot dada una lista de clases y un dominio.outputs/skill-image-text-retriever.md— una skill que construye un índice de embeddings de imagen con cualquier checkpoint CLIP, soporta consulta por texto y consulta por imagen.
Ejercicios
- (Fácil) Usa un OpenCLIP ViT-B/32 preentrenado y haz clasificación zero-shot en CIFAR-10 con el conjunto de 80 plantillas de prompt. Reporta la exactitud top-1; debería rondar el 85-90%.
- (Medio) Compara embeddings de plantilla única ("a photo of a {}") frente al promedio de 80 plantillas en la misma tarea de CIFAR-10. Cuantifica la diferencia y explica por qué las plantillas ayudan.
- (Difícil) Construye un índice de recuperación de imágenes zero-shot: genera embeddings de 1.000 imágenes con CLIP, construye un índice FAISS, consulta con una descripción en lenguaje natural. Reporta el recall@5 de recuperación para 20 consultas de prueba que escribas a mano.
Términos Clave
| Término | Lo que la gente dice | Lo que realmente significa |
|---|---|---|
| Dos torres | "Codificador dual" | Codificadores de imagen y texto separados que terminan en una cabeza de proyección de dimensión compartida |
| Zero-shot | "Sin entrenamiento específico de la tarea" | Clasificar en clases descritas solo por texto en la inferencia; no se toca ninguna etiqueta |
| Temperatura / logit_scale | "tau" | Escalar aprendido que escala la matriz de similitud antes del softmax |
| Plantilla de prompt | "A photo of a {}" | Envoltorio en lenguaje natural alrededor de los nombres de clase; promediar muchas plantillas aumenta la exactitud zero-shot |
| CLIP | "Modelo de imagen+texto" | El modelo de OpenAI de 2021; vocabulario del campo en 2026 |
| SigLIP | "CLIP con sigmoide" | Cambia el softmax por un sigmoide por par; entrena mejor con lotes pequeños |
| OpenCLIP | "Reproducción abierta" | Variantes de CLIP entrenadas por la comunidad sobre LAION; estándar de producción para pipelines de código abierto |
| VLM | "Modelo de visión-lenguaje" | Un codificador de la familia CLIP más un LLM, entrenado para responder preguntas sobre imágenes |
Lecturas Adicionales
- CLIP: Learning Transferable Visual Models from Natural Language Supervision (Radford et al., 2021)
- SigLIP: Sigmoid Loss for Language-Image Pre-Training (Zhai et al., 2023)
- OpenCLIP — la base de código de la comunidad
- DINOv2 vs CLIP vs MAE: a features comparison — guía de HF con casos de uso lado a lado