Phase 04 - Lesson 17
Visión Autosupervisada — SimCLR, DINO, MAE
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Las etiquetas son el cuello de botella de la visión supervisada. El preentrenamiento autosupervisado las elimina: aprende características visuales a partir de 100M de imágenes sin etiquetar y haz el fine-tuning sobre 10 mil etiquetadas.
Tipo: Aprender + Construir Lenguajes: Python Prerrequisitos: Fase 4 Lección 04 (Clasificación de Imágenes), Fase 4 Lección 14 (ViT) Tiempo: ~75 minutos
Objetivos de Aprendizaje
- Recorrer las tres grandes familias autosupervisadas — contrastiva (SimCLR), profesor-estudiante (DINO), reconstrucción enmascarada (MAE) — y enunciar qué optimiza cada una
- Implementar una pérdida InfoNCE desde cero y explicar por qué un batch de 512 funciona pero uno de 32 falla
- Explicar por qué la tasa de enmascaramiento del 75% de MAE no es arbitraria y en qué se diferencia del 15% de BERT para texto
- Usar checkpoints DINOv2 o MAE de ImageNet para linear probing y recuperación zero-shot
El Problema
ImageNet supervisada tiene 1,3M de imágenes etiquetadas, cuya anotación costó un estimado de US$ 10M. Los conjuntos de datos médicos e industriales son más pequeños y aún más caros de etiquetar. Todo equipo de visión se pregunta: ¿podemos preentrenar con datos sin etiquetar baratos — frames de YouTube, web crawls, grabaciones de webcam, barridos satelitales — y luego hacer el fine-tuning sobre un pequeño conjunto etiquetado?
El aprendizaje autosupervisado es la respuesta. Un ViT autosupervisado moderno entrenado en LAION o JFT alcanza o supera la precisión de ImageNet supervisada cuando se le hace fine-tuning. Además transfiere mejor a tareas downstream (detección, segmentación, profundidad) que el preentrenamiento supervisado. DINOv2 (Meta, 2023) y MAE (Meta, 2022) son los estándares de producción actuales para características visuales transferibles.
El cambio conceptual es que la tarea-pretexto — aquello que el modelo se entrena para hacer — no tiene por qué ser la tarea downstream. Lo que importa es que obligue al modelo a aprender características útiles. Predecir el color de imágenes en escala de grises, rotar imágenes y pedirle al modelo que clasifique la rotación, enmascarar patches y reconstruirlos — todo ha funcionado. Los tres enfoques que escalan son el aprendizaje contrastivo, la destilación profesor-estudiante y la reconstrucción enmascarada.
El Concepto
Tres familias
flowchart LR
A["Contrastiva<br/>SimCLR, MoCo, CLIP"] --> AT["pares positivos<br/>(misma imagen, 2 augs)<br/>acercados,<br/>negativos alejados"]
B["Profesor-estudiante<br/>DINO, BYOL, iBOT"] --> BT["el estudiante predice<br/>la salida del profesor;<br/>el profesor es la EMA del estudiante"]
C["Reconstrucción enmascarada<br/>MAE, BEiT, SimMIM"] --> CT["enmascarar 75% de los patches;<br/>reconstruir objetivos de<br/>pixel o token"]
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
style C fill:#dcfce7,stroke:#16a34a
Aprendizaje contrastivo (SimCLR)
Toma una imagen, aplica dos augmentations aleatorias y obtén dos views. Pasa ambas por el mismo encoder más una projection head. Minimiza una pérdida que dice "estos dos embeddings deben estar cerca" y "este embedding debe estar lejos de los embeddings de cualquier otra imagen del batch."
Loss for positive pair (z_i, z_j) among 2N views per batch:
L_ij = -log( exp(sim(z_i, z_j) / tau) / sum_k in batch \ {i} exp(sim(z_i, z_k) / tau) )
sim = cosine similarity
tau = temperature (0.1 standard)
Esta es la pérdida InfoNCE. Requiere muchos negativos por positivo, así que el tamaño del batch importa — SimCLR necesita 512-8192. MoCo introdujo una cola de momentum con batches pasados para desacoplar el recuento de negativos del tamaño del batch.
Profesor-estudiante (DINO)
Dos redes con la misma arquitectura: estudiante y profesor. El profesor es una media móvil exponencial (EMA) de los pesos del estudiante. Ambos ven views augmentadas de la imagen. La salida del estudiante se entrena para coincidir con la del profesor — sin negativos explícitos.
loss = CE( student_output(view_1), teacher_output(view_2) )
+ CE( student_output(view_2), teacher_output(view_1) )
teacher_weights = m * teacher_weights + (1 - m) * student_weights (m ≈ 0.996)
Por qué no colapsa a "predecir una constante": la salida del profesor se centra (resta la media por dimensión) y se afila (divide por una temperatura pequeña). El centrado impide que una dimensión domine; el afilado impide que la salida colapse a una distribución uniforme.
DINO es lo que DINOv2 escala, sobre 142M de imágenes curadas. Las características resultantes son el SOTA actual para recuperación visual zero-shot y predicción densa.
Reconstrucción enmascarada (MAE)
Enmascara el 75% de los patches de una entrada de ViT. Pasa solo el 25% visible por el encoder. Un pequeño decoder recibe la salida del encoder más mask tokens en las posiciones enmascaradas y se entrena para reconstruir los pixeles de los patches enmascarados.
Encoder: visible 25% of patches -> features
Decoder: features + mask tokens at masked positions -> reconstructed pixels
Loss: MSE between reconstructed and original pixels on masked patches only
Decisiones de diseño clave que hacen funcionar a MAE:
- Tasa de enmascaramiento del 75% — alta. Obliga al encoder a aprender características semánticas; reconstruir el 25% sería casi trivial (los pixeles vecinos están tan correlacionados que una CNN lo lograría).
- Encoder/decoder asimétricos — el gran encoder ViT solo ve los patches visibles; un pequeño decoder (8 capas, 512 dimensiones) se encarga de la reconstrucción. Preentrenamiento 3x más rápido que el BEiT ingenuo.
- Objetivo de reconstrucción en el espacio de pixeles — más simple que el objetivo tokenizado de BEiT y funciona mejor en ViT.
Tras el preentrenamiento, descarta el decoder. El encoder es el extractor de características.
Por qué 75% y no 15%
BERT enmascara el 15% de los tokens. MAE enmascara el 75%. La diferencia es la densidad de información.
- El lenguaje natural tiene alta entropía por token. Predecir el 15% de los tokens sigue siendo difícil porque cada posición enmascarada tiene muchas continuaciones plausibles.
- Los patches de imagen tienen baja entropía — un vecindario no enmascarado a menudo determina los pixeles del patch enmascarado casi con exactitud. Para que la predicción exija comprensión semántica, hay que enmascarar de forma agresiva.
75% es lo bastante alto para que la simple extrapolación espacial no pueda resolver la tarea; el encoder debe representar el contenido de la imagen.
Evaluación por linear-probe
Tras el preentrenamiento autosupervisado, la evaluación estándar es un linear probe: congela el encoder, entrena un único clasificador lineal encima usando las etiquetas de ImageNet. Reporta la precisión top-1.
- SimCLR ResNet-50: ~71% (2020)
- DINO ViT-S/16: ~77% (2021)
- MAE ViT-L/16: ~76% (2022)
- DINOv2 ViT-g/14: ~86% (2023)
El linear probe es una medida pura de la calidad de las características; el fine-tuning normalmente suma de 2 a 5 puntos, pero también mezcla el efecto del reentrenamiento de la head.
Constrúyelo
Paso 1: Pipeline de augmentation de dos views
import torch
import torchvision.transforms as T
two_view_train = lambda: T.Compose([
T.RandomResizedCrop(96, scale=(0.2, 1.0)),
T.RandomHorizontalFlip(),
T.ColorJitter(0.4, 0.4, 0.4, 0.1),
T.RandomGrayscale(p=0.2),
T.ToTensor(),
])
class TwoViewDataset(torch.utils.data.Dataset):
def __init__(self, base):
self.base = base
self.aug = two_view_train()
def __len__(self):
return len(self.base)
def __getitem__(self, i):
img, _ = self.base[i]
v1 = self.aug(img)
v2 = self.aug(img)
return v1, v2
Cada getitem devuelve dos views augmentadas de la misma imagen; no se necesitan etiquetas.
Paso 2: Pérdida InfoNCE
import torch.nn.functional as F
def info_nce(z1, z2, tau=0.1):
"""
z1, z2: (N, D) L2-normalised embeddings of paired views
"""
N, D = z1.shape
z = torch.cat([z1, z2], dim=0) # (2N, D)
sim = z @ z.T / tau # (2N, 2N)
mask = torch.eye(2 * N, dtype=torch.bool, device=z.device)
sim = sim.masked_fill(mask, float("-inf"))
targets = torch.cat([torch.arange(N, 2 * N), torch.arange(0, N)]).to(z.device)
return F.cross_entropy(sim, targets)
Normaliza los embeddings con L2 antes de llamar. tau=0.1 es el valor por defecto de SimCLR; valores más bajos hacen la pérdida más afilada y requieren más negativos.
Paso 3: Sanity check de InfoNCE
z1 = F.normalize(torch.randn(16, 32), dim=-1)
z2 = z1.clone()
loss_same = info_nce(z1, z2, tau=0.1).item()
z2_random = F.normalize(torch.randn(16, 32), dim=-1)
loss_random = info_nce(z1, z2_random, tau=0.1).item()
print(f"InfoNCE with identical pairs: {loss_same:.3f}")
print(f"InfoNCE with random pairs: {loss_random:.3f}")
Los pares idénticos deben dar una pérdida baja (cercana a 0 para un batch grande y temperatura fría). Los pares aleatorios deben dar log(2N-1) = ~log(31) = ~3,4 con un batch de 16 pares.
Paso 4: Enmascaramiento al estilo MAE
def random_mask_indices(num_patches, mask_ratio=0.75, seed=0):
g = torch.Generator().manual_seed(seed)
n_keep = int(num_patches * (1 - mask_ratio))
perm = torch.randperm(num_patches, generator=g)
visible = perm[:n_keep]
masked = perm[n_keep:]
return visible.sort().values, masked.sort().values
num_patches = 196
visible, masked = random_mask_indices(num_patches, mask_ratio=0.75)
print(f"visible: {len(visible)} / {num_patches}")
print(f"masked: {len(masked)} / {num_patches}")
Simple, rápido y determinista para una semilla dada. Las implementaciones reales de MAE lo hacen en batch y mantienen máscaras por muestra.
Úsalo
DINOv2 es el estándar de producción en 2026:
import torch
from transformers import AutoImageProcessor, AutoModel
processor = AutoImageProcessor.from_pretrained("facebook/dinov2-base")
model = AutoModel.from_pretrained("facebook/dinov2-base")
model.eval()
# Per-image embeddings for zero-shot retrieval
with torch.no_grad():
inputs = processor(images=[pil_image], return_tensors="pt")
outputs = model(**inputs)
embedding = outputs.last_hidden_state[:, 0] # CLS token
El embedding resultante de 768 dimensiones es la columna vertebral de los pipelines modernos de recuperación de imágenes, correspondencia densa y transferencia zero-shot. El fine-tuning sobre una tarea downstream rara vez necesita más que una head lineal.
Para embeddings de imagen-texto, SigLIP u OpenCLIP es el equivalente; para fine-tuning al estilo MAE, el repositorio timm trae todos los checkpoints de MAE.
Entrégalo
Esta lección produce:
outputs/prompt-ssl-pretraining-picker.md— un prompt que elige SimCLR / MAE / DINOv2 dados el tamaño del conjunto de datos, el compute y la tarea downstream.outputs/skill-linear-probe-runner.md— una skill que escribe la evaluación por linear-probe para cualquier encoder congelado + conjunto de datos etiquetado.
Ejercicios
- (Fácil) Verifica que la pérdida InfoNCE baja cuando disminuyes la temperatura para embeddings bien alineados y sube cuando disminuyes la temperatura para embeddings aleatorios. Produce un gráfico de
tau in [0.05, 0.1, 0.2, 0.5]vs pérdida. - (Medio) Implementa un buffer de centro al estilo DINO. Muestra que, sin el centrado, el estudiante colapsa a un vector constante en pocas épocas.
- (Difícil) Entrena MAE en CIFAR-100 usando la TinyUNet de la Lección 10 como backbone. Reporta la precisión del linear-probe a las 10, 50 y 200 épocas. Muestra que un linear probe preentrenado con MAE supera a un linear probe supervisado entrenado desde cero sobre el mismo subconjunto de 1.000 imágenes.
Términos Clave
| Término | Lo que la gente dice | Lo que realmente significa |
|---|---|---|
| Autosupervisado | "Sin etiquetas" | Una tarea-pretexto que produce representaciones útiles a partir de datos sin etiquetar |
| Tarea-pretexto | "La tarea falsa" | El objetivo usado durante la SSL (reconstruir patches, emparejar views); descartado tras el preentrenamiento |
| Linear probe | "Encoder congelado + head lineal" | Evaluación estándar de SSL: entrenar solo un clasificador lineal encima de características congeladas |
| InfoNCE | "Pérdida contrastiva" | softmax sobre similitudes de coseno; el par positivo es la clase objetivo, todos los demás son negativos |
| Profesor EMA | "Profesor de media móvil" | Profesor cuyos pesos son una media móvil exponencial de los del estudiante; usado por BYOL, MoCo, DINO |
| Tasa de enmascaramiento | "% de patches ocultos" | Fracción de patches enmascarados durante MAE; 75% para visión, 15% para texto |
| Colapso de representación | "Salida constante" | Fallo de SSL en que el encoder produce un vector constante para todas las entradas; evitado por centrado, afilado o negativos |
| DINOv2 | "Backbone de SSL de producción" | El ViT autosupervisado de Meta de 2023; las características de imagen de propósito general más fuertes en 2026 |
Lectura Adicional
- SimCLR (Chen et al., 2020) — referencia de aprendizaje contrastivo
- DINO (Caron et al., 2021) — profesor-estudiante con momentum, centrado, afilado
- MAE (He et al., 2022) — preentrenamiento de autoencoder enmascarado para ViT
- DINOv2 (Oquab et al., 2023) — escalando el ViT autosupervisado a características de producción