El Problema: Las Imágenes No Son Secuencias

Los transformadores fueron diseñados para secuencias de tokens. El texto es naturalmente secuencial — una palabra sigue a otra, y el orden transmite significado. Las imágenes, sin embargo, son rejillas 2D de píxeles, típicamente representadas como tensores de $H \times W \times 3$ (alto, ancho y tres canales de color RGB). Una imagen estándar de $224 \times 224$ contiene $224 \times 224 \times 3 = 150{,}528$ valores de píxeles sin procesar. Si intentáramos tratar cada píxel como un token individual, tendríamos una secuencia de $224 \times 224 = 50{,}176$ posiciones — demasiado larga para la autoatención , que escala cuadráticamente con la longitud de la secuencia ($O(n^2)$). Con 50K tokens, la matriz de atención por sí sola tendría más de 2,500 millones de entradas por capa por cabeza.

Las redes neuronales convolucionales (CNNs) resolvieron la visión por computadora explotando la localidad espacial: cada capa convolucional observa un pequeño parche de la entrada (típicamente de $3 \times 3$ o $5 \times 5$ píxeles), y las capas más profundas combinan información de regiones más grandes, construyendo gradualmente desde bordes y texturas locales hasta una comprensión global de objetos y escenas. Este enfoque jerárquico funciona bien, pero las CNNs no se integran fácilmente con los modelos de lenguaje basados en transformadores que se encuentran en el centro de los modelos visión-lenguaje. Si queremos una arquitectura unificada donde las imágenes y el texto puedan interactuar a través del mismo mecanismo de atención, necesitamos una forma de convertir imágenes en secuencias de tokens que los transformadores puedan procesar de manera nativa.

El Vision Transformer (ViT) (Dosovitskiy et al., 2021) proporciona una solución elegante: dividir la imagen en parches de tamaño fijo, tratar cada parche como una "palabra" y alimentar la secuencia resultante a un codificador transformer estándar. Sin convoluciones, sin capas de pooling, sin cambios arquitectónicos al transformer en sí — solo una nueva forma de tokenizar la entrada.

Incrustación de Parches: Dividiendo Imágenes en Tokens

La idea central detrás de ViT es dividir la imagen en una rejilla de parches cuadrados no superpuestos de tamaño $P \times P$ píxeles, aplanar cada parche en un vector y proyectarlo linealmente a la dimensión de embedding del transformer. Esto convierte una imagen 2D en una secuencia 1D de tokens de parches que un transformer puede procesar exactamente como procesa tokens de palabras en texto.

Dada una imagen de tamaño $H \times W \times C$ (alto, ancho y canales — típicamente $C = 3$ para RGB) y un tamaño de parche $P$, el procedimiento es:

  • Paso 1: La imagen se divide en $N_p = \frac{H}{P} \times \frac{W}{P}$ parches no superpuestos. Cada parche cubre una región de $P \times P$ de la imagen original.
  • Paso 2: Cada parche es un tensor de $P \times P \times C$, que aplanamos en un solo vector $\mathbf{x}_p^i \in \mathbb{R}^{P^2 C}$. Esto simplemente concatena todos los valores de píxeles del parche en un vector largo.
  • Paso 3: Una proyección lineal aprendible $\mathbf{E} \in \mathbb{R}^{(P^2 C) \times D}$ mapea cada parche aplanado a la dimensión oculta $D$ del modelo, produciendo la incrustación inicial del parche.

La incrustación completa del parche $i$ es:

$$\mathbf{z}_0^i = \mathbf{x}_p^i \mathbf{E} + \mathbf{e}_{\text{pos}}^i$$

Desglosemos cada componente de esta fórmula para entender qué hace cada pieza y por qué está ahí.

$\mathbf{x}_p^i$ es el $i$-ésimo parche aplanado — un vector de $P^2 C$ valores de píxeles sin procesar. Para un parche RGB de $16 \times 16$, esto es $16 \times 16 \times 3 = 768$ valores. Estos son simplemente las intensidades de rojo, verde y azul para cada píxel del parche, dispuestos de extremo a extremo en un solo vector. En esta etapa, la representación es de alta dimensionalidad pero no contiene características aprendidas — son datos sensoriales sin procesar.

$\mathbf{E}$ es la matriz de proyección que mapea del espacio de píxeles ($P^2 C$ dimensiones) a la dimensión oculta $D$ del transformer. Es una matriz de pesos aprendible — durante el entrenamiento, el modelo aprende qué características extraer de los parches sin procesar. Para ViT-Base con $P = 16$ y $D = 768$, esta matriz es de $768 \times 768$ — coincidentemente cuadrada en esta configuración particular, pero las dimensiones generalmente difieren. Con $P = 14$ y $D = 1024$ (ViT-Large), la matriz sería de $588 \times 1024$. Matemáticamente, esta proyección lineal es equivalente a una sola capa convolucional con kernel de tamaño $P \times P$ y stride $P$, aplicada a toda la imagen. Pero conceptualmente, sirve como el tokenizador: así como un tokenizador de texto mapea secuencias de caracteres a vectores de embedding, $\mathbf{E}$ mapea parches de píxeles a embeddings de tokens.

$\mathbf{e}_{\text{pos}}^i$ es una incrustación de posición aprendida que le dice al transformer dónde se ubica el parche $i$ en la imagen original. Sin ella, el transformer trataría la secuencia de parches como un conjunto sin orden — la autoatención es invariante a permutaciones por diseño, así que barajar los parches produciría las mismas salidas (salvo la misma permutación). La incrustación de posición rompe esta simetría, codificando la ubicación espacial. Explicamos cómo funciona esto en la siguiente sección.

El resultado $\mathbf{z}_0^i$ es un vector de $D$ dimensiones — la representación inicial del "token" para el parche $i$, lista para entrar a las capas del transformer. El subíndice $0$ indica que es la entrada a la capa 0 (antes de cualquier procesamiento del transformer). Después de pasar por las $L$ capas del transformer, este token llevará información contextual rica, no solo de su propio parche, sino de todos los demás parches a los que atendió.

Concretemos esto con un ejemplo numérico. Una imagen RGB de $224 \times 224$ con tamaño de parche $P = 16$ produce $(224 / 16) \times (224 / 16) = 14 \times 14 = 196$ parches. Cada parche es un vector de $16 \times 16 \times 3 = 768$ dimensiones de valores de píxeles sin procesar. Después de la proyección por $\mathbf{E}$, tenemos 196 tokens de dimensión $D = 768$. Compárese con el enfoque de píxel-como-token: 196 tokens vs. 50,176 píxeles. La autoatención sobre 196 tokens requiere una matriz de atención de $196 \times 196 = 38{,}416$ entradas — insignificante comparado con las $50{,}176 \times 50{,}176 \approx 2{,}500$ millones de entradas que necesitaríamos para atención a nivel de píxel.

💡 El tamaño del parche $P$ controla el compromiso entre resolución y cómputo. Parches más pequeños ($P = 14$ o incluso $P = 8$) producen más tokens y mayor detalle espacial, pero aumentan cuadráticamente el costo de atención. ViT-L/14 usa $P = 14$ (produciendo $16 \times 16 = 256$ parches para una imagen de $224 \times 224$), mientras que ViT-B/32 usa $P = 32$ (produciendo $7 \times 7 = 49$ parches) — aproximadamente 5 veces menos tokens y alrededor de 25 veces menos cómputo de atención, pero con una comprensión espacial más gruesa porque cada parche cubre una región más grande de la imagen.

Incrustaciones de Posición: Indicando a los Parches Dónde Están

La autoatención estándar es invariante a permutaciones: si barajas los tokens de entrada, la salida cambia solo por la misma permutación. Esta es una propiedad matemática del mecanismo de atención por producto punto — las puntuaciones de similitud query-key no dependen del orden de los tokens, solo de su contenido. Para el texto, la codificación posicional rompe esta simetría porque el orden de las palabras importa ("el perro mordió al hombre" y "el hombre mordió al perro" tienen significados muy diferentes). Para las imágenes, la posición espacial importa igual — un parche en la esquina superior izquierda de una imagen lleva información diferente que uno en la esquina inferior derecha, incluso si los valores de los píxeles son similares.

ViT usa incrustaciones de posición 1D aprendidas : un vector aprendible $\mathbf{e}_{\text{pos}}^i \in \mathbb{R}^D$ por cada posición de parche $i \in \{1, \ldots, N_p\}$. Los parches se numeran en orden ráster (de izquierda a derecha, de arriba a abajo, el mismo orden en que se lee una página), y cada índice de posición obtiene su propia incrustación aprendida que se suma a la incrustación del parche correspondiente. Para una rejilla de $14 \times 14$ parches, la posición 1 es el parche superior izquierdo, la posición 14 es el superior derecho, la posición 15 es el primer parche de la segunda fila, y la posición 196 es el inferior derecho.

¿Por qué 1D en lugar de 2D? Podría parecer natural codificar la fila y la columna de cada parche por separado — después de todo, las imágenes tienen dos dimensiones espaciales. El artículo original de ViT probó codificaciones posicionales 2D explícitas (donde cada parche recibe una incrustación de fila y una de columna que se concatenan o se suman) y encontró que tienen un rendimiento comparable al de las incrustaciones 1D aprendidas más simples. El modelo aprende la estructura 2D implícitamente a partir de los datos. Durante el entrenamiento, las incrustaciones de posición para parches espacialmente adyacentes terminan naturalmente siendo similares entre sí — el modelo descubre la disposición de la rejilla por sí solo, sin que nosotros tengamos que codificarla explícitamente.

💡 Si se visualizan las incrustaciones de posición aprendidas (calculando la similitud coseno entre todos los pares), emerge un claro patrón de rejilla 2D: cada incrustación es más similar a sus vecinos espaciales. La posición 1 (superior izquierda) es más similar a las posiciones 2 (vecino derecho) y 15 (vecino inferior), y menos similar a la posición 196 (esquina inferior derecha). El modelo redescubre la estructura 2D a partir de índices 1D puramente mediante descenso de gradiente.

El Token CLS

ViT antepone un token aprendible extra — llamado $[\text{CLS}]$ (tomando prestado el nombre del token de clasificación de BERT ) — a la secuencia de tokens de parches. Este token no tiene un parche de imagen correspondiente. La secuencia de entrada completa al codificador transformer es:

$$[\mathbf{z}_{\text{CLS}},\, \mathbf{z}_0^1,\, \mathbf{z}_0^2,\, \ldots,\, \mathbf{z}_0^{N_p}] \in \mathbb{R}^{(N_p + 1) \times D}$$

La longitud total de la secuencia es $N_p + 1$: los 196 tokens de parches más el token CLS, dando 197 tokens para una imagen de $224 \times 224$ con $P = 16$. El token CLS se inicializa aleatoriamente y participa plenamente en la autoatención — puede atender a cada token de parche, y cada token de parche puede atender a él. A través de las capas del transformer, aprende a agregar información de todos los parches en una sola representación global.

Después de la última capa $L$ del transformer, la salida del token CLS sirve como la representación global de la imagen:

$$\mathbf{v}_I = \text{LayerNorm}(\mathbf{z}_L^{\text{CLS}})$$

Aquí $\mathbf{z}_L^{\text{CLS}}$ es la representación del token CLS después de que las $L$ capas del transformer lo han procesado, y LayerNorm normaliza el vector para estabilizar su escala antes de usarlo en tareas posteriores.

¿Por qué necesitamos esto? En muchas aplicaciones — clasificación de imágenes, aprendizaje contrastivo con texto en CLIP, búsqueda por similitud — necesitamos un solo vector de tamaño fijo para representar toda la imagen. El token CLS actúa como un "resumen" aprendible: dado que la autoatención le permite atender a cada parche, puede aprender a extraer y combinar la información más relevante de toda la imagen en un solo vector de $D$ dimensiones. En las primeras capas, el token CLS tiende a atender de manera amplia a todos los parches; en las capas posteriores, tiende a enfocarse en las regiones más informativas.

Una alternativa al token CLS es el global average pooling (GAP): simplemente tomar la media de todas las salidas de tokens de parches después de la última capa: $\mathbf{v}_I = \frac{1}{N_p} \sum_{i=1}^{N_p} \mathbf{z}_L^i$. Algunas variantes de ViT usan GAP en lugar del token CLS y logran resultados comparables. El compromiso es que GAP da peso igual a todos los parches, mientras que el token CLS puede aprender a ponderar los parches de manera diferente a través de la atención — potencialmente útil cuando algunos parches (por ejemplo, los que contienen el objeto principal) son más importantes que otros (por ejemplo, parches de fondo).

Configuraciones y Escala de ViT

El artículo original de ViT introdujo varias configuraciones de modelo a diferentes escalas, siguiendo una convención de nombres ViT-{tamaño}/{tamaño de parche}. Las tres configuraciones estándar son:

  • ViT-B/16 (Base, parche 16): 12 capas de transformer, dimensión oculta $D = 768$, 12 cabezas de atención, 86M parámetros, produciendo 196 parches para una imagen de $224 \times 224$.
  • ViT-L/14 (Large, parche 14): 24 capas de transformer, dimensión oculta $D = 1024$, 16 cabezas de atención, 307M parámetros, produciendo 256 parches para una imagen de $224 \times 224$.
  • ViT-H/14 (Huge, parche 14): 32 capas de transformer, dimensión oculta $D = 1280$, 16 cabezas de atención, 632M parámetros, produciendo 256 parches para una imagen de $224 \times 224$.

La convención de nombres codifica las dos decisiones de diseño más importantes: el tamaño del modelo (Base, Large o Huge) y el tamaño de parche (que determina el número de tokens). El mejor codificador de imágenes de CLIP es ViT-L/14, y muchos VLMs posteriores — incluyendo SigLIP y OpenVLA — lo usan a él o a una variante cercana como su backbone visual.

Los modelos más grandes con parches más pequeños tienden a rendir mejor, pero el costo crece pronunciadamente. ViT-H/14 tiene aproximadamente 7 veces los parámetros de ViT-B/16 (632M vs. 86M), y procesa 30% más tokens por imagen (256 vs. 196). Pero los parámetros son solo parte de la historia del cómputo. El costo de atención escala cuadráticamente con la longitud de secuencia: $256^2 / 196^2 \approx 1.7$, así que solo la atención es 70% más costosa por capa — y ViT-H/14 tiene 2.7 veces más capas (32 vs. 12) que ViT-B/16. En conjunto, ViT-H/14 es considerablemente más costoso por imagen, por lo que la elección de la variante de ViT siempre es un compromiso entre precisión y presupuesto computacional.

Cómo ViT Encaja en los VLMs

En CLIP (cubierto en el artículo anterior), el ViT actúa como el codificador de imágenes: cada imagen pasa por el ViT completo, y la salida del token CLS se convierte en $\mathbf{v}_I$, la incrustación de imagen utilizada para el aprendizaje contrastivo contra incrustaciones de texto. Solo se usa el vector CLS — toda la información espacial codificada en los tokens de parches individuales se descarta después del ViT, comprimida en un solo resumen de $D$ dimensiones.

En VLMs generativos — modelos como LLaVA, BLIP-2 y Flamingo (que cubriremos en artículos posteriores de este track) — el enfoque es diferente y más expresivo. En lugar de usar solo el token CLS, estos modelos conservan todos los tokens de parches de salida $\mathbf{z}_L^1, \mathbf{z}_L^2, \ldots, \mathbf{z}_L^{N_p}$ del ViT y los alimentan al modelo de lenguaje como tokens de contexto visual. El modelo de lenguaje entonces atiende a estos tokens de parches junto con los tokens de texto, permitiéndole razonar sobre regiones espaciales específicas de la imagen.

Esto preserva información espacial que el token CLS podría comprimir en exceso. Por ejemplo, si un usuario pregunta "¿De qué color es el objeto en la esquina superior izquierda?", el modelo de lenguaje puede atender específicamente a los tokens de parches de esa región. El compromiso es una secuencia de entrada más larga para el LLM: 196 tokens extra (para ViT-B/16) o 256 (para ViT-L/14) se anteponen a los tokens de texto, aumentando tanto el uso de memoria como el cómputo. Algunos modelos usan una capa de proyección o un módulo de atención cruzada para comprimir los tokens de parches antes de alimentarlos al LLM, reduciendo la longitud de secuencia mientras intentan preservar la información visual más importante.

Quiz

Pon a prueba tu comprensión de la arquitectura del Vision Transformer, desde la incrustación de parches hasta el token CLS.

¿Por qué no podemos simplemente tratar cada píxel de una imagen de 224×224 como un token separado en un transformer?

En la fórmula de incrustación de parches $\mathbf{z}_0^i = \mathbf{x}_p^i \mathbf{E} + \mathbf{e}_{\text{pos}}^i$, ¿qué papel juega la matriz de proyección $\mathbf{E}$?

¿Por qué ViT necesita incrustaciones de posición añadidas a los tokens de parches?

¿Cómo produce el token CLS una representación global de la imagen?