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:

  1. Para cada clase, compón un prompt: "a photo of a {class}".
  2. Codifica todos los prompts de clase con el codificador de texto -> T de forma (C, d).
  3. Codifica la imagen de prueba -> I de forma (1, d).
  4. Similitud = I @ T.T de forma (1, C).
  5. 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

  1. (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%.
  2. (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.
  3. (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

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).