"gato" → 2103 en un vector?La pregunta correcta. Aquí están las matemáticas, sin saltarse ningún paso.
tabla[2103]. No hay multiplicación, ni suma, ni nada que calcular. Es un lookup de array.
Esto suele confundir porque a la gente le dicen "embedding layer", "capa de embeddings", y suena a que hay una transformación complicada. Pero no la hay. Es indexar un array.
PERO — y aquí viene lo interesante — existe una segunda forma de explicar lo mismo, usando multiplicación de matrices. Esta segunda vista es matemáticamente equivalente, y es la que usan los papers académicos. Vamos a ver ambas.
Para no perdernos, vamos a usar un vocabulario de solo 6 palabras, y embeddings de solo 4 dimensiones.
| ID | Palabra |
|---|---|
| 0 | "el" |
| 1 | "perro" |
| 2 | "gato" |
| 3 | "come" |
| 4 | "agua" |
| 5 | "corre" |
Para este ejemplo, "gato" tiene id = 2 (no 2103 como antes, pero la mecánica es idéntica — solo que con un vocab más chico se ve mejor).
Esto es lo que vive en memoria. Cada fila corresponde a una palabra. Los valores fueron inicializados aleatoriamente y luego "aprendidos" durante el entrenamiento.
| ID | palabra | dim 0 | dim 1 | dim 2 | dim 3 |
|---|---|---|---|---|---|
| 0 | "el" | 0.21 | -0.55 | 0.08 | 0.43 |
| 1 | "perro" | -0.34 | 0.61 | 0.77 | -0.12 |
| 2 | "gato" | -0.30 | 0.58 | 0.82 | -0.15 |
| 3 | "come" | 0.45 | -0.22 | -0.30 | 0.91 |
| 4 | "agua" | 0.67 | 0.18 | -0.40 | 0.50 |
| 5 | "corre" | 0.52 | -0.19 | -0.28 | 0.88 |
Fíjate que la fila de "gato" (id=2) y la de "perro" (id=1) son parecidas. Ese es el resultado de un entrenamiento bien hecho.
Así se implementa en la realidad. Es lo que hace PyTorch o cualquier framework por debajo.
// La matriz E vive en memoria como un array 2D: const E = [ [0.21, -0.55, 0.08, 0.43], // fila 0 → "el" [-0.34, 0.61, 0.77, -0.12], // fila 1 → "perro" [-0.30, 0.58, 0.82, -0.15], // fila 2 → "gato" [ 0.45, -0.22, -0.30, 0.91], // fila 3 → "come" [ 0.67, 0.18, -0.40, 0.50], // fila 4 → "agua" [ 0.52, -0.19, -0.28, 0.88], // fila 5 → "corre" ]; function embed(tokenId) { return E[tokenId]; // ¡ESO ES TODO! } const vectorGato = embed(2); // → [-0.30, 0.58, 0.82, -0.15]
array[index]. Si esto te parece anticlimático, es porque LO ES. La complejidad de los embeddings no está en CÓMO se busca el vector, sino en CÓMO se llegó a tener esos valores en la matriz (entrenamiento — paso 10).
Antes de ver la Vista 2 (que usa multiplicación de matrices), vamos a repasar los conceptos matemáticos básicos. Olvídate de lo que recuerdas o no del colegio — vamos a construir todo de cero, con números concretos y explicando por qué existe cada operación.
Un vector es simplemente una lista ordenada de números. Eso es todo. No te dejes intimidar por el nombre.
Ejemplo de un vector de 4 dimensiones:
Dimensiones = cuántos números tiene la lista. Este vector tiene 4 dimensiones porque tiene 4 números.
Para un programador: es exactamente un array:
const v = [3, -1, 7, 2]; v.length // → 4 (la dimensión del vector) v[0] // → 3 v[2] // → 7
Una matriz es una tabla rectangular de números. O equivalentemente, una lista de vectores apilados. También algo que ya conoces: un array bidimensional.
Ejemplo de una matriz 3×4 (3 filas, 4 columnas):
| 3 | -1 | 7 | 2 |
| 0 | 5 | -2 | 8 |
| 4 | 1 | 6 | -3 |
Notación de shape: cuando decimos "matriz [3×4]" significa 3 filas × 4 columnas. ¡SIEMPRE primero filas, después columnas! Esto importa muchísimo más adelante.
Para un programador: es un array de arrays:
const M = [ [3, -1, 7, 2], // fila 0 (un vector de 4 dim) [0, 5, -2, 8], // fila 1 [4, 1, 6, -3], // fila 2 ]; M.length // → 3 (cantidad de filas) M[0].length // → 4 (cantidad de columnas) M[1][2] // → -2 (fila 1, columna 2)
El producto punto (o dot product) es la operación más importante en deep learning. Toda la red neuronal está hecha de productos punto encadenados.
Receta: dados dos vectores del mismo tamaño:
Ejemplo con dos vectores de 4 dimensiones:
a · b
= (2 × 5) + (3 × 1) + (-1 × 2) + (4 × -3)
= 10 + 3 + (-2) + (-12)
= -1
function dotProduct(a, b) { let sum = 0; for (let i = 0; i < a.length; i++) { sum += a[i] * b[i]; } return sum; } dotProduct([2, 3, -1, 4], [5, 1, 2, -3]); // → -1
Modifica los valores y observa el cálculo en vivo:
Buena pregunta — porque "multiplicar y sumar" suena arbitrario. Pero el producto punto tiene un significado geométrico profundo:
Spoiler: multiplicar matrices NO es lo que parece. NO es multiplicar celda por celda. Es algo más interesante: es hacer muchos productos punto de forma organizada.
Para calcular C = A × B:
C[i][j] del resultado se calcula como el producto punto entre la fila i de A y la columna j de B.Léelo así: las dimensiones del medio (la K) tienen que ser iguales. El resultado toma la primera dimensión de A y la última de B.
Ejemplos:
[3×4] × [4×2] = [3×2] ✓ (los 4 del medio coinciden)[1×6] × [6×4] = [1×4] ✓ ← este es nuestro caso del one-hot![3×4] × [3×4] ❌ no se puede (el medio sería 4 vs 3)[2×3] × [3×2] = [2×2]Vamos a multiplicar dos matrices chiquitas y ver paso a paso cómo se llena el resultado.
| fila 0 | 1 | 2 | 3 |
| fila 1 | 4 | 5 | 6 |
| fila 0 | 7 | 8 |
| fila 1 | 9 | 10 |
| fila 2 | 11 | 12 |
| fila 0 | ? | ? |
| fila 1 | ? | ? |
La multiplicación de matrices es la operación más usada en deep learning. La razón principal: representa una transformación lineal de un vector. Cuando multiplicas una matriz W por un vector x, obtienes un nuevo vector y que es x transformado de cierta manera. La matriz W contiene los parámetros que el modelo aprende para hacer esa transformación útil. Lo veremos a fondo en self-attention (paso 4).
Ahora tenemos todo lo necesario para entender el truco del one-hot. Recuerda:
[0, 0, 1, 0, 0, 0] ← un solo "1", todo lo demás cero.[1 × 6] × [6 × 4] = [1 × 4] — el resultado es un vector de 4 dimensiones (¡exactamente lo que queremos!).Cuando hacemos one_hot × E:
Y aquí viene la magia. Calculemos el primer valor del resultado: producto punto entre [0, 0, 1, 0, 0, 0] y la primera columna de E (que es [0.21, -0.34, -0.30, 0.45, 0.67, 0.52]):
Fíjate qué pasó: todos los productos donde el one-hot tenía un 0 se anularon (porque 0 por lo que sea es 0). Solo sobrevivió el término donde el one-hot tenía un 1, que justamente está en la posición 2. Ese término "selecciona" el valor de la fila 2 de E.
El primer valor del resultado es E[2][0] = -0.30 (la fila 2, columna 0 de E). Si hacemos lo mismo para las otras 3 columnas, obtenemos exactamente la fila 2 completa de E: [-0.30, 0.58, 0.82, -0.15].
Esta es la forma en que los papers escriben lo mismo. Es matemáticamente equivalente al lookup, pero formulada como una multiplicación de matrices. ¿Por qué? Porque permite escribir todo el Transformer como una secuencia de matrices que se multiplican entre sí, lo cual es elegante en notación y permite que las derivadas (necesarias para entrenar) salgan limpias.
Un vector one-hot es un vector donde todas las posiciones son 0 excepto una, que es 1. La posición del 1 indica "qué palabra es esta".
| posición | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| significa | "el" | "perro" | "gato" | "come" | "agua" | "corre" |
| valor | 0 | 0 | 1 | 0 | 0 | 0 |
El "1" está en la posición 2 porque "gato" tiene id=2. Si quisiéramos representar "perro" sería [0, 1, 0, 0, 0, 0].
La operación es:
one_hot: shape [1 × 6] · E: shape [6 × 4] · resultado: shape [1 × 4]
Para multiplicar un vector fila por una matriz, calculamos el producto punto entre el vector y cada columna de la matriz. Hay 4 columnas → 4 productos punto → 4 números de salida.
0 × algo = 0. ¡El resultado de la multiplicación es exactamente la fila 2 de E!
nn.Embedding) y TensorFlow hacen por debajo.Te queda una pregunta legítima: si la matriz E tiene esos números específicos (−0.30, 0.58, 0.82, ...) para "gato", ¿quién los puso ahí?
La respuesta corta: nadie los puso a mano. La matriz E se inicializa con números aleatorios pequeños al inicio del entrenamiento. Luego, durante millones/billones de ejemplos de texto, el algoritmo de gradient descent los va ajustando.
Cada vez que el modelo se equivoca prediciendo la siguiente palabra, el gradiente del error "fluye hacia atrás" hasta llegar a la matriz E, y le dice: "moveme la fila de 'gato' un poquito en esta dirección para equivocarte menos la próxima". Después de muchos ejemplos, las filas convergen a vectores que capturan el significado.
vector = E[id].