PASO 2 / 10

Embeddings

Cómo cada token (que es solo un número aburrido) se transforma en un vector que captura su significado.

🎯 De un vistazo

Los embeddings convierten los IDs (números arbitrarios del paso 1) en vectores que capturan significado.

PROPÓSITO

¿Para qué sirve?

Convertir cada ID de token en un vector de N números que representa el "significado" de esa palabra en un espacio matemático.

APORTE

¿Qué aporta al modelo?

Da significado matemático: palabras parecidas terminan con vectores parecidos (cerca en el espacio). Es la "tabla de conocimiento" base del modelo, aprendida durante el entrenamiento.

NECESIDAD

¿Por qué es indispensable?

Un ID es arbitrario: que "gato" sea 2103 no tiene relación numérica con "perro" = 5876. Sin embeddings, el modelo no podría relacionar palabras parecidas ni hacer operaciones significativas.

↓ A continuación, el detalle con ejemplos y visualizaciones ↓

El problema con los IDs de tokens

Al final del Paso 1 tenías algo así: "el gato come" → [847, 2103, 1492]. Solo IDs.

Pero esos números no significan nada matemáticamente. El hecho de que "gato" tenga ID 2103 y "perro" tenga ID 5876 no le dice al modelo que son animales parecidos. Tampoco que ID 2104 (si existiera) tenga relación con 2103 — los IDs son arbitrarios, los reparte el tokenizer en el orden que aparecieron al entrenarse.

Analogía: Es como si cada palabra del diccionario tuviera asignado un número de teléfono al azar. Saber el teléfono de "gato" (2103) no te dice nada sobre el teléfono de "perro" (5876). Necesitamos algo mejor: una representación donde palabras parecidas tengan representaciones parecidas.

Solución: convertir cada ID en un vector (una lista de números) llamado embedding. La gracia es que el modelo va a aprender esos vectores durante el entrenamiento, de manera que palabras con significados parecidos terminen con vectores parecidos.

¿Qué es un embedding exactamente?

Un embedding es simplemente un array de números reales (por ejemplo, 4 números, 768 números, o 12,288 números, según el modelo). Cada token del vocabulario tiene su propio array.

👀 Mira un embedding real (4 dimensiones)

Para el demo usamos 4 dimensiones. En modelos reales son cientos o miles.

Token: "gato" (id 2103)

Esos 4 números son el "significado" de "gato" para el modelo. Cada dimensión podría representar algún concepto abstracto (¿es animado? ¿es grande? ¿es comestible? — el modelo decide solo).

📜 Es solo un lookup en una tabla

// La matriz de embeddings:
// shape = [vocab_size, embedding_dim]
const embeddingMatrix = [
  [0.12, -0.45, 0.78, 0.03],  // id 0
  [0.91, 0.22, -0.11, 0.67],  // id 1
  // ... miles de filas más ...
  [-0.34, 0.55, 0.89, -0.21], // id 2103 = "gato"
];

function embed(tokenId) {
  return embeddingMatrix[tokenId];
  // ¡Sí, es literalmente un array[index]!
}
Clave para programadores: el "embedding layer" suena fancy, pero técnicamente es una tabla de lookup: array[tokenId] te devuelve el vector. Lo único especial es que los valores de esa tabla se aprenden durante el entrenamiento (en lugar de ser fijos).

El truco: palabras parecidas → vectores parecidos

Vamos a usar un mini-vocabulario con embeddings que yo armé a mano en 2D (para que podamos visualizarlos). En un modelo real serían cientos de dimensiones aprendidas con backpropagation — pero los principios son idénticos.

Mapa 2D de palabras

Cada palabra es un punto. Las que están cerca tienen significados relacionados. Pasa el mouse sobre cualquier palabra para destacarla.

Eje X (horizontal) ≈ dimensión 1 · Eje Y (vertical) ≈ dimensión 2

🔍 Selecciona una palabra

📏 Palabras más cercanas

Observaciones esperadas: los animales se agrupan, la realeza se agrupa, las cosas de género masculino vs femenino tienden a estar de un lado u otro. Esto no es magia — son las propiedades que un modelo de verdad aprende de leer miles de millones de oraciones donde "gato" y "perro" aparecen en contextos similares.

Subiendo a 3D: añadimos una dimensión más

En 2D ya empezamos a ver clusters. Pero los embeddings reales tienen cientos o miles de dimensiones. Imposible visualizarlos directamente. Lo más cerca que podemos llegar visualmente es a 3D — arrástralo con el mouse para rotar y ver la estructura desde distintos ángulos.

eje X · eje Y · eje Z — arrastra para rotar

💡 Por qué 3D ayuda a entender

En 2D, varias palabras se "pisan" porque solo tenemos 2 ejes para distinguir todas las propiedades semánticas posibles. Al añadir un 3er eje (eje Z), el modelo puede separar conceptos que en 2D parecían muy cerca.

Por ejemplo, "vino" y "café" pueden estar cerca en X-Y (ambos son bebidas) pero separados en Z (uno es alcohol, otro no). Cuantas más dimensiones, más matices puede capturar el modelo.

En modelos reales, las dimensiones no corresponden a conceptos limpios como "animado" o "género". Son combinaciones aprendidas, raras e interpretables solo a medias. Pero la idea es la misma: cada dimensión extra es un grado de libertad adicional para distinguir significados.

¿Por qué no podemos visualizar 768 dimensiones?

Nuestros ojos viven en 3D. Para "ver" más dimensiones se usan técnicas de reducción de dimensionalidad como PCA, t-SNE o UMAP: comprimen los vectores N-D a 2D/3D tratando de preservar las distancias relativas. Cuando ves esos gráficos de "embeddings visualizados" en papers, es eso: una proyección aproximada — no los vectores reales.

Midiendo "qué tan parecidos" son dos vectores

Para medir similitud entre dos embeddings, los LLMs usan similitud coseno. Suena intimidante pero la idea es simple:

🎮 Calcula similitud entre dos palabras

📜 La fórmula

function cosineSim(a, b) {
  // 1. Producto punto: suma de a[i]*b[i]
  let dot = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
  }

  // 2. Magnitud (largo) de cada vector
  const magA = Math.sqrt(a.reduce((s,x) => s + x*x, 0));
  const magB = Math.sqrt(b.reduce((s,x) => s + x*x, 0));

  // 3. Coseno del ángulo entre ellos
  return dot / (magA * magB);
}

// → 1.0  = idénticos en dirección
// → 0.0  = perpendiculares (nada que ver)
// → -1.0 = opuestos

El truco famoso: aritmética con palabras

Una propiedad alucinante que emerge de los embeddings: puedes hacer matemáticas con conceptos. El ejemplo clásico (de Word2Vec, 2013):

"rey""hombre"+"mujer""reina"

Esto funciona porque los embeddings aprenden direcciones que codifican conceptos. El vector "rey − hombre" captura aproximadamente el concepto de "realeza sin género". Sumarle "mujer" añade el componente femenino, y el resultado cae cerca de "reina".

🎮 Prueba aritmética de conceptos

+ = ?

📜 Cómo se resuelve

function analogy(a, b, c) {
  // 1. Vector resultado = embed(a) - embed(b) + embed(c)
  const target = a.map((v, i) => v - b[i] + c[i]);

  // 2. Buscar la palabra del vocab con
  //    vector más parecido (coseno más alto)
  let bestWord = null, bestSim = -Infinity;

  for (const [word, vec] of vocab) {
    const sim = cosineSim(target, vec);
    if (sim > bestSim) {
      bestSim = sim;
      bestWord = word;
    }
  }
  return bestWord;
}

La matriz de embeddings completa

Así se ve la "tabla" que el modelo consulta. Cada fila es una palabra; cada columna es una dimensión. En modelos reales esto es enorme (ej: GPT-3 tiene 50,257 filas × 12,288 columnas ≈ 617 millones de parámetros, solo en la capa de embeddings).

🎯 Lo que ocurre en un LLM real

Click para detalles del embedding layer de Claude / GPT
  • Dimensiones reales: GPT-3 usa 12,288. GPT-2 small usaba 768. Modelos como Claude usan en el orden de miles.
  • Aprendizaje: los valores de la matriz comienzan aleatorios y se ajustan durante el entrenamiento con gradient descent (paso 10). Después de leer billones de tokens, "gato" y "perro" terminan cerca porque aparecen en contextos parecidos.
  • Hipótesis distribucional (Firth, 1957): "una palabra se conoce por la compañía que mantiene". Los embeddings explotan esto: palabras que aparecen en contextos similares terminan con vectores similares.
  • Embeddings compartidos: en muchos modelos, la misma matriz se usa para convertir input → vector Y para convertir vector → output (esto se llama weight tying; ahorra parámetros).
  • No solo palabras: también hay embeddings de posición (paso 3) y a veces de "tipo" (token, segmento). Todos se suman.

✅ Resumen: lo que entra al siguiente paso

// Después de tokenizar:
[847, 2103, 1492]
// Después de la capa de embeddings (cada ID → vector):
[
  [0.12, -0.45, 0.78, 0.03, ...], // "el"
  [-0.34, 0.55, 0.89, -0.21, ...], // "gato"
  [0.67, -0.12, 0.44, 0.91, ...], // "come"
]
// shape = [n_tokens, embedding_dim] — una matriz

Pero hay un problema sutil: estos vectores no contienen información sobre el orden. Si reordenara los tokens a ["come", "gato", "el"], obtendría exactamente los mismos vectores pero en otro orden — y el modelo (en su forma básica) los trataría como un conjunto sin orden.

El Paso 3: Positional Encoding resuelve esto: añade información de "yo soy el primer token", "yo soy el segundo", etc. directamente a los vectores.