PASO 3 / 10

Positional Encoding

Cómo le decimos al modelo "esta palabra es la primera, esa la segunda, aquella la tercera". Sin esto, "el perro come" y "come perro el" se ven idénticos.

🎯 De un vistazo

El positional encoding le inyecta a cada token la información de en qué posición está dentro de la frase.

PROPÓSITO

¿Para qué sirve?

Decirle al modelo el orden de las palabras, sumando a cada embedding un vector que codifica su posición (0, 1, 2, ...).

APORTE

¿Qué aporta al modelo?

Información de posición. Permite distinguir "el perro come" de "come perro el" aunque tengan exactamente las mismas palabras.

NECESIDAD

¿Por qué es indispensable?

El Transformer procesa todos los tokens en paralelo (no uno por uno), así que "pierde" el orden. Sin positional encoding, trataría la frase como un conjunto desordenado de palabras.

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

El problema: los embeddings no tienen orden

En el paso 2 vimos que tokenizar + buscar en la matriz E nos da un vector por cada token. Pero esos vectores no saben en qué posición están. El embedding de "perro" es exactamente el mismo si "perro" es la primera palabra o la última.

👀 Mira el problema en vivo

Aquí tienes una frase tokenizada y embebida. Después la misma frase, pero con las palabras desordenadas. Fíjate qué pasa con los vectores.

Frase 1: "el perro come"
Frase 2: "come perro el" (mismas palabras, otro orden)
¿Notás el problema? Los vectores en ambas frases son exactamente los mismos, solo cambian de orden. Si el modelo procesa estos vectores sin información extra, no puede distinguir entre las dos frases. Pero "el perro come" y "come perro el" significan cosas distintas (o ni siquiera son gramáticales).

En las redes neuronales antiguas (RNNs, LSTMs), el orden estaba implícito porque procesaban los tokens uno por uno, secuencialmente. Pero el Transformer los procesa todos a la vez en paralelo — esa es su gran ventaja en velocidad. El precio: pierde el orden, y hay que inyectárselo de otra forma.

La idea: añadir a cada vector de embedding otro vector que codifique "esta es la posición 0", "esta es la posición 1", etc. Ese vector extra se llama positional encoding.

Intento ingenuo (que no funciona): sumar el número de la posición

Antes de ver la solución correcta, veamos por qué la idea más obvia falla. Lo más simple sería: "al embedding del token de la posición 0 sumarle 0, al de la posición 1 sumarle 1, al de la posición 2 sumarle 2..."

🎮 Pruébalo y mira los problemas

Cantidad de tokens en la frase:

Los problemas:
  • Valores que crecen sin límite: en la posición 1000, sumarías 1000 a cada dimensión del embedding. Los embeddings típicos tienen valores entre -1 y 1 — sumarle 1000 los aplasta. La información del embedding desaparece.
  • Todas las dimensiones reciben el mismo valor: sumar "1" a las 4 dimensiones del embedding no aporta variedad. El modelo no tiene forma de distinguir información posicional de información de contenido.
  • Distancias relativas extrañas: la "distancia" entre posición 0 y 1 es la misma que entre 999 y 1000, pero el modelo necesita poder distinguir el efecto del orden a corto y largo plazo de forma distinta.

Necesitamos algo que: (1) esté acotado (no crezca sin límite), (2) sea distinto en cada dimensión, y (3) varíe de forma suave y predecible con la posición. La solución del paper "Attention is All You Need" (2017) usa senos y cosenos. Vamos a ver primero qué son esas funciones.

📐 Repaso: ¿qué son seno y coseno?

Si en el colegio te dijeron "seno es el cateto opuesto sobre la hipotenusa" y no entendiste nada, olvídate. Para nuestro propósito, los necesitamos por una sola razón: son las funciones más útiles para generar valores que oscilan suavemente entre -1 y +1.

0. El panorama: ¿qué son y para qué sirven (en general)?

La idea fundamental en una frase: seno y coseno son las funciones matemáticas que describen cualquier cosa que se repite, gira u oscila. Si algo va en círculos, sube y baja periódicamente, o vibra — seno y coseno están involucrados.

¿De dónde vienen? Del círculo

Imaginá una rueda girando (la vas a ver en vivo más abajo). Tomá un punto en el borde y miralo girar:

  • Su altura (cuánto sube y baja) → eso es seno.
  • Su distancia horizontal al centro → eso es coseno.

Ambos son proyecciones de un movimiento circular sobre una línea recta. Por eso, cualquier cosa con algo de "circular" o "periódico" se puede describir con ellos.

¿Para qué se usan en el mundo real?

La lista es enorme. Algunos ejemplos:

🔁 Cosas que se repiten en el tiempo
  • Sonido: una nota musical pura es literalmente una onda senoidal.
  • Luz, radio, wifi: todas son ondas electromagnéticas.
  • Mareas, días, estaciones: ciclos predecibles.
  • Corriente eléctrica (AC) de tu casa: llega como onda senoidal.
🌀 Cosas que giran o rotan
  • Videojuegos: rotar un personaje, la cámara, un planeta.
  • Robots: el ángulo de cada articulación.
  • Astronomía: órbitas de planetas, posición del sol.
  • Motores, hélices, turbinas: todo lo rotatorio.
📐 Geometría y coordenadas
  • Convertir entre "(x, y)" y "(ángulo, distancia)".
  • Usado en mapas, GPS, navegación, gráficos.
🧠 Procesamiento de señales (¡el más profundo!)
  • Cualquier señal repetitiva se puede expresar como SUMA de senos (transformada de Fourier).
  • Por esto funcionan MP3, JPEG, wifi/5G, reducción de ruido.

¿Por qué son TAN populares en matemáticas y deep learning?

Cuatro propiedades que las hacen ideales:

  1. Están acotadas entre -1 y +1. No explotan, no se rompen.
  2. Son suaves: pequeños cambios en el input → pequeños cambios en el output. Ideal para optimización (gradient descent).
  3. Son periódicas y predecibles: si conocés un ciclo, conocés todo el patrón.
  4. Combinándolas con distintas frecuencias, podés representar casi cualquier patrón repetitivo. (La propiedad que aprovecha Fourier — y, en menor medida, el positional encoding.)
El modelo mental para recordar: pensá en seno/coseno como "la pluma universal para dibujar cosas que se repiten". Si tu problema involucra algo que gira, oscila, vibra o se repite con el tiempo — probablemente uses seno/coseno.

¿Y por qué se eligieron para el positional encoding?

Ahora con todo esto en la cabeza, podés ver por qué fueron la elección obvia para inyectar posición en el Transformer. Necesitábamos una función que:

Que esté acotada (no crezca sin límite) ✓ sen/cos viven en [-1, +1]
Que sea suave (posiciones cercanas → valores parecidos) ✓ las ondas son continuas y suaves
Que dé un patrón único para cada posición ✓ combinando varias frecuencias
Que funcione para cualquier longitud, incluso no vista en entrenamiento ✓ la fórmula calcula bien cualquier posición

Seno y coseno cumplen las 4 propiedades gratis, sin necesidad de aprender nada. Por eso fueron la elección natural.

1. La intuición visual

Imagina que tomas una rueda y le pintas un punto rojo en el borde. Ahora la haces girar lentamente. Si miras la rueda desde un lado, ves que el punto rojo sube y baja una y otra vez.

  • La altura del punto en cada instante es lo que llamamos seno.
  • La distancia horizontal del punto al centro es lo que llamamos coseno.

Ambos valores oscilan suavemente entre -1 y +1 (porque la rueda tiene radio 1).

🎮 Mira la rueda y la onda

Izquierda: la rueda girando. Derecha: el seno (rojo) y coseno (azul) de su posición a lo largo del tiempo. Fíjate cómo ambos son la misma onda, solo que desfasada — el coseno empieza arriba (1), el seno empieza en el medio (0).

2. La fórmula que usaremos

Las funciones seno y coseno toman un número (que llamaremos x) y devuelven otro número entre -1 y +1:

sen(x) ∈ [-1, +1]      cos(x) ∈ [-1, +1]

Para entender cómo cambia el output según x:

  • sen(0) = 0,   cos(0) = 1
  • sen(π/2) = 1,   cos(π/2) = 0   (π ≈ 3.14159...)
  • sen(π) = 0,   cos(π) = -1
  • sen(2π) = 0,   cos(2π) = 1   (volvió al punto de partida → es periódica)

Cada vez que x avanza (≈ 6.28), las funciones completan un ciclo. Esto se llama período.

🎮 Calculadora de seno/coseno

0.00

3. Frecuencia: qué tan rápido oscila la onda

Si en lugar de sen(x) calculamos sen(k · x), la onda oscila más rápido (si k > 1) o más lento (si k < 1). A ese factor k se le llama frecuencia.

  • Frecuencia alta (k grande): la onda sube y baja muchas veces en poco espacio. Cambia rápido.
  • Frecuencia baja (k chico): la onda sube y baja lentamente. Cambia lento.

🎮 Compara distintas frecuencias

1.0

La curva azul es sen(x) (frecuencia 1). La curva amarilla es sen(k·x), con el k que controlas. Mueve el slider para ver el efecto.

Recapitulando: seno y coseno son funciones que toman un número y devuelven uno entre -1 y +1, oscilando suavemente. Si las multiplicamos por una frecuencia distinta, podemos hacer que oscilen más rápido o más lento. Esa flexibilidad va a ser clave en el siguiente paso.

La solución: positional encoding sinusoidal

La idea genial del paper original: para cada posición pos, generar un vector de d valores usando seno y coseno con frecuencias distintas en cada par de dimensiones.

La fórmula completa

PE(pos, 2i) = sen(pos / 100002i/d)

PE(pos, 2i+1) = cos(pos / 100002i/d)

Sí, se ve intimidante. Vamos a desarmar cada pedazo:

📖 Diccionario de variables

  • pos = la posición del token en la frase (0, 1, 2, 3, ...). En "el perro come": "el" → pos=0, "perro" → pos=1, "come" → pos=2.
  • i = el índice del PAR de dimensiones (0, 1, 2, ..., d/2 − 1). OJO: i NO es el número de la dimensión individual — es el número del par. Cada valor de i produce DOS dimensiones a la vez: una usa seno (la 2i), otra usa coseno (la 2i+1). Lo aclaramos abajo con una tabla.
  • d = la cantidad total de dimensiones del embedding (ej: 4, 768, 12288). Como i indexa pares, hay d/2 valores de i.
  • 10000 = una constante elegida por los autores. Controla cuánto cambian las frecuencias entre dimensiones. No tiene nada mágico — eligieron 10000 porque funcionó bien empíricamente.

🚨 Punto crítico: la diferencia entre i y "dimensión"

Este es el detalle que más confunde a la gente. En la fórmula, i NO es el número de la dimensión que estamos calculando — es el número del par al que pertenece esa dimensión. Cada i produce dos dimensiones simultáneamente:

📊 Tabla: cómo se relacionan dimensión, i y la fórmula (con d=4)

dim del vector ¿qué par es? i 2i exponente (2i/d) divisor (10000^exp) función
0 par #0 0 0 0/4 = 0 10000⁰ = 1 seno
1 par #0 0 0 0/4 = 0 10000⁰ = 1 coseno
2 par #1 1 2 2/4 = 0.5 10000⁰·⁵ = 100 seno
3 par #1 1 2 2/4 = 0.5 10000⁰·⁵ = 100 coseno

Observaciones clave de la tabla:

  • Las dimensiones 0 y 1 son el "par #0": ambas usan i=0, comparten el divisor 1, pero una usa seno y la otra coseno.
  • Las dimensiones 2 y 3 son el "par #1": ambas usan i=1, comparten el divisor 100, pero una usa seno y la otra coseno.
  • Por eso con d=4 hay solo 4/2 = 2 valores posibles de i: 0 y 1.

🧮 Ejemplo completo trabajado: "el perro come" con d=4

Vamos a calcular el PE para cada una de las 3 palabras de nuestra frase de ejemplo. Recordá: "el" está en pos=0, "perro" en pos=1, "come" en pos=2.

📍 Paso a paso para "perro" (pos = 1)

Elegimos pos=1 para verlo en detalle porque pos=0 es un caso trivial (todos los ángulos son 0).

Par i=0 (produce las dimensiones 0 y 1):
  divisor = 10000^(2·0/4) = 10000^0 = 1
  ángulo  = pos / divisor = 1 / 1 = 1.000

  dim 0 = sen(1.000) = +0.841
  dim 1 = cos(1.000) = +0.540
              ↑ mismo ángulo que dim 0 (ambos son del par i=0),
                pero uno usa sen y el otro cos.

Par i=1 (produce las dimensiones 2 y 3):
  divisor = 10000^(2·1/4) = 10000^0.5 = 100
  ángulo  = pos / divisor = 1 / 100 = 0.010

  dim 2 = sen(0.010) = +0.010
  dim 3 = cos(0.010) = +0.99995
              ↑ mismo ángulo que dim 2 (ambos son del par i=1),
                pero uno usa sen y el otro cos.

Vector PE final para pos=1 ("perro"):
PE(1) = [+0.841, +0.540, +0.010, +0.99995]

📋 Repetimos para las 3 posiciones (resumen)

Aplicando exactamente la misma receta a pos=0 y pos=2:

palabra pos dim 0 (sen) dim 1 (cos) dim 2 (sen) dim 3 (cos)
"el" 0 sen(0/1) = 0.000 cos(0/1) = +1.000 sen(0/100) = 0.000 cos(0/100) = +1.000
"perro" 1 sen(1/1) = +0.841 cos(1/1) = +0.540 sen(1/100) = +0.010 cos(1/100) = +0.99995
"come" 2 sen(2/1) = +0.909 cos(2/1) = -0.416 sen(2/100) = +0.020 cos(2/100) = +0.9998

➕ Sumamos embedding + PE para obtener el input final

Cada vector PE se suma posición por posición al embedding de su palabra correspondiente. Estos embeddings vienen de la matriz E (paso 2) — recordá que para esta guía usamos valores específicos para cada palabra:

"el" (pos=0):
  embedding:     [+0.21, -0.55, +0.08, +0.43]
  + PE(0):       [+0.00, +1.00, +0.00, +1.00]
  ──────────────────────────────────────────────────────
  = input final: [+0.21, +0.45, +0.08, +1.43]

"perro" (pos=1):
  embedding:     [-0.34, +0.61, +0.77, -0.12]
  + PE(1):       [+0.84, +0.54, +0.01, +1.00]
  ──────────────────────────────────────────────────────
  = input final: [+0.50, +1.15, +0.78, +0.88]

"come" (pos=2):
  embedding:     [+0.45, -0.22, -0.30, +0.91]
  + PE(2):       [+0.91, -0.42, +0.02, +1.00]
  ──────────────────────────────────────────────────────
  = input final: [+1.36, -0.64, -0.28, +1.91]

Estos 3 vectores finales son lo que entra al Paso 4 (Self-Attention). Fíjate que si reordenáramos la frase a "come perro el", la palabra "el" estaría en pos=2 en lugar de pos=0, así que se le sumaría PE(2) en lugar de PE(0) — y el input final sería distinto. Ese es justamente el punto del positional encoding.

Resumen para no volver a confundirte: Cuando veas la fórmula, leé "2i" como un solo símbolo que vale 0, 2, 4, ... (no "i = 0, 1, 2"). Cada par produce dos dimensiones consecutivas, así que i hace de "contador de pares", no de "contador de dimensiones".

¿Por qué dos fórmulas (seno y coseno)?

Las dimensiones con índice par dentro del vector (dim 0, dim 2, dim 4, ...) usan seno. Las dimensiones con índice impar (dim 1, dim 3, dim 5, ...) usan coseno. Esto alterna las dos funciones a lo largo del vector. Tener ambas (no solo seno) le da al modelo más información sobre cada frecuencia — si solo tuvieras sen(x), valores como 0.5 podrían corresponder a múltiples ángulos. Con sen y cos juntos, el ángulo queda determinado de forma única.

¿Qué hace el 100002i/d en el divisor?

Esta es la parte clave. Recuerda que en seno/coseno, un divisor pequeño = frecuencia alta (oscila rápido con la posición), y un divisor grande = frecuencia baja (oscila lento con la posición).

Cada par i tiene su propio divisor, y como i va de 0 a d/2 − 1, los divisores recorren un rango muy amplio:

Resultado: los primeros pares de dimensiones del vector PE cambian rápido con la posición, y los últimos pares cambian lento. Es como tener un reloj con muchas manecillas a distintas velocidades — combinando todas, cada posición tiene una "hora" única.

🎮 Calculemos un PE paso a paso

Vamos a calcular el positional encoding para un par de dimensiones específicas, con números reales. Recordá: cada valor de i produce dos dimensiones (2i con seno y 2i+1 con coseno).

🎮 El vector PE completo para una posición

Y ahora calculemos el vector PE completo (con todas las dimensiones) para una sola posición:

Visualización: el "mapa de calor" del positional encoding

Aquí ves el PE completo para una secuencia de posiciones. Cada fila es una posición; cada columna es una dimensión. Los colores representan el valor (azul = negativo, blanco = cero, naranja = positivo).

Fíjate cómo las primeras columnas (dimensiones bajas) cambian rápido entre filas — alta frecuencia. Las últimas columnas (dimensiones altas) casi no cambian — baja frecuencia. Cada fila tiene un patrón único y eso es lo que el modelo usa como "huella digital" de la posición.

Propiedades importantes del PE sinusoidal:
  • Cada posición tiene un vector único.
  • Posiciones cercanas tienen vectores parecidos (smooth).
  • Los valores están acotados entre -1 y +1.
  • Funciona para cualquier longitud: aunque el modelo se entrene con secuencias de 512 tokens, los valores del PE se siguen calculando bien para posición 5000.

Combinando embedding + positional encoding

Ahora la pregunta del millón: ¿cómo se mezclan el embedding del token (que tiene "qué" palabra es) y el positional encoding (que tiene "dónde" está)? La respuesta es muy simple:

input_final = embedding_del_token + positional_encoding

Sí, literalmente se suman. Element-wise (posición por posición). Como ambos vectores tienen la misma dimensión d, la suma es directa.

🎮 Visualízalo en vivo

Aquí tienes una frase de 3 tokens con sus embeddings, sus positional encodings, y la suma resultante:

¿Por qué SUMAR y no concatenar?

Concatenar significaría poner los dos vectores uno al lado del otro: si embedding es de 4 dim y PE es de 4 dim, el resultado sería de 8 dim. Pero sumar tiene varias ventajas:

Esto sorprende al principio: "¿no se está perdiendo información al sumar dos cosas distintas?" Resulta que no, porque los embeddings y los PEs viven en partes distintas del espacio de N dimensiones. Como tienes muchas dimensiones (cientos o miles), hay "espacio de sobra" para que ambas señales coexistan sin pisarse demasiado. El modelo aprende a desenredarlas.

Probemos que funciona

Volvamos al ejemplo del principio: "el perro come" vs "come perro el". Ahora con positional encoding sumado a los embeddings:

Frase 1: "el perro come" (con positional encoding)
Frase 2: "come perro el" (con positional encoding)
¡Ahora SÍ son distintos! Aunque las palabras son las mismas, cada token recibió un PE diferente según su posición, así que la suma final cambia. El modelo ahora tiene la información que necesita para entender que el orden importa.

🎯 Lo que ocurre en LLMs modernos

Click para ver las variantes modernas (RoPE, ALiBi, learned)

El PE sinusoidal del paper original funciona, pero en 2026 la mayoría de los LLMs grandes usan variantes mejores:

  • Rotary Position Embedding (RoPE): usado en LLaMA, GPT-NeoX, Claude. En lugar de sumar un vector, "rota" los vectores Q y K en self-attention según la posición. Es matemáticamente más elegante y captura mejor la posición relativa entre tokens. Lo vamos a ver de costado en el paso 4.
  • Learned positional embeddings: usado en GPT-2/3 y BERT. En vez de calcular con sen/cos, hay una tabla de embeddings de posición (una fila por posición, como la matriz de embeddings de tokens), y el modelo aprende los valores durante el entrenamiento. Más flexible pero limitado a longitudes vistas en entrenamiento.
  • ALiBi (Attention with Linear Biases): no inyecta posición en el embedding, sino que penaliza la atención a tokens lejanos directamente. Útil para extender el modelo a contextos más largos.

Pero la idea fundamental sigue siendo la misma: hay que inyectar información posicional de alguna manera, porque self-attention pura no la tiene. El paper original es la primera y más clara forma de hacerlo, por eso lo estudiamos primero.

✅ Resumen: lo que entra al siguiente paso

  1. Tokenizamos: texto → IDs [847, 2103, 1492]
  2. Embeddings: IDs → vectores [v_el, v_perro, v_come]
  3. Calculamos positional encodings: [PE(0), PE(1), PE(2)]
  4. Sumamos: [v_el + PE(0), v_perro + PE(1), v_come + PE(2)]
  5. El resultado es una matriz [n_tokens × d] que tiene tanto el qué como el dónde de cada token.

En el Paso 4: Self-Attention, esta matriz se convierte en la entrada del corazón del Transformer. Ahí cada token va a "mirar" a los demás y decidir cuáles son relevantes para entenderse a sí mismo. Es el paso más importante de todos — y donde se concentra la magia.