SenseiTeoría desde cero0/21

1 · Lo básico de MLLección 7 de 8

Embeddings: el significado como coordenadas

Embeddings: meaning as coordinates

Cómo un modelo convierte una foto o una frase en una lista de números donde lo parecido queda cerca, cómo se mide ese «cerca» y cómo se busca entre 100 millones de esas listas en milisegundos.

⏱ 54 min lo esencial · 78 min todo20 conceptos nuevos2 calculadoras10 ejerciciosModelo
Secciones14
  1. 01Vectores y embeddings
  2. 02El producto punto
  3. 03Coseno, distancia y normalización L2
  4. 04Buscar el más cercano entre 100 millones
  5. 05Un grafo con atajos: HNSW extra
  6. 06Dimensiones, bytes y umbrales extra
  7. 07Palabras contra significado: búsqueda híbrida extra
  8. 08Cómo aprende un encoder: aprendizaje contrastivo extra
  9. 09Errores comunes
  10. 10En la vida real extra
  11. 11Ejercicios
  12. 12Lo que sigue
  13. 13Palabras que vas a oír
  14. 14Chequeo
La pregunta

¿Cómo encuentra una app «productos parecidos» a una foto, si la computadora solo sabe de números?

Antes de esta lección: Redes neuronales y CNNs.
01

Vectores y embeddings

Esencial

Vectors and embeddings

Empecemos por la palabra que vas a oír en todo lo que sigue. Un vector es una lista ordenada de números. Por ejemplo:

  • (4, 3) es un vector con 2 números.
  • (0.12, −0.80, 0.33) es un vector con 3 números.
  • Un vector de 512 números es lo mismo, solo que más largo.

La cantidad de números se llama la dimensión del vector. (4, 3) tiene dimensión 2. El orden importa: (4, 3) y (3, 4) son vectores distintos.

¿Por qué llamarle «vector» y no «lista»? Porque un vector se puede dibujar. Con 2 números, el primero dice cuántos pasos ir a la derecha y el segundo cuántos hacia arriba. El vector (4, 3) es el punto que queda 4 a la derecha y 3 arriba del origen (el punto (0, 0), donde se cruzan los ejes). También se dibuja como una flecha que sale del origen y llega a ese punto: el punto dice dónde; la flecha, además, hacia dónde apunta. Las dos ideas nos van a servir.

Con 3 números ya es un punto en el espacio: derecha, arriba y hacia el frente. Con 512 números ya no lo puedes imaginar, pero las cuentas son exactamente las mismas que en 2D. Por eso en esta lección todo se explica en 2D y luego se dice qué cambia al crecer.

Un mapa de productos

Imagina que tomamos cuatro productos del marketplace de C01 y los colocamos en un plano, de modo que los que se parecen queden juntos. Una forma posible:

ProductoVector
Tenis(4, 3)
Botas(3, 4)
Playera(−3, 4)
Bolsa(−4, 3)

Los dos calzados quedan a la derecha, muy juntos. La playera y la bolsa (ropa y accesorios) quedan a la izquierda, también juntas. Entre los dos grupos hay un buen espacio.

Cuatro productos como flechas que salen del origen en un plano. Tenis en (4, 3) y botas en (3, 4) apuntan casi en la misma dirección, con unos 16 grados entre ellas y similitud coseno 0.96. Playera en (−3, 4) y bolsa en (−4, 3) quedan juntas del otro lado. Entre tenis y playera hay 90 grados: coseno 0. Entre tenis y bolsa, coseno −0.28. UN EMBEDDING DE 2 NÚMEROS (0, 0) 5 −5 5 16° Tenis (4, 3) Botas (3, 4) Playera (−3, 4) Bolsa (−4, 3) SIMILITUD COSENO CON TENIS Botas 24 ÷ 25 = 0.96 casi igual Playera 0 ÷ 25 = 0 perpendiculares Bolsa −7 ÷ 25 = −0.28 algo opuestos
Cuatro productos como flechas desde el origen. Los calzados apuntan casi en la misma dirección; la playera queda a 90° de los tenis. Los números son inventados para el ejemplo.

Fíjate en algo importante: los ejes no tienen nombre. No dijimos «el eje x es qué tan deportivo es». Solo pedimos que lo parecido quede cerca, y estas coordenadas lo cumplen. Esa es exactamente la situación de un embedding real.

Qué es un embedding

Un embedding es el vector que un modelo produce para una cosa (una foto, una frase, un producto, un usuario) con una propiedad muy especial: cosas parecidas producen vectores cercanos.

¿De dónde sale? De una red neuronal ya entrenada. En la lección de redes viste que una red es una pila de capas, y que cada capa transforma los números que recibe. Las primeras capas detectan cosas simples (bordes, colores); las de más adentro, cosas más abstractas (una suela, unas agujetas). La última capa convierte todo eso en la respuesta final, por ejemplo las 500 categorías de C01.

Si cortas la red justo antes de esa última capa, lo que sale es un vector que resume la foto. Ese vector es un embedding. En la ResNet-50 de C01, justo antes de la última capa, sale un vector de 2,048 números.

También hay modelos entrenados a propósito para producir embeddings. Se llaman encoders (codificadores): entran datos, sale un vector. Muchos se entrenan con aprendizaje contrastivo: se les muestran pares que deben quedar juntos, como una foto y su título, y se les corrige cada vez que un par queda lejos o que quedan cerca cosas que no son pareja. Lo ves a fondo en una sección extra, más adelante.

¿Por qué no basta con un número por producto?

Cada producto ya tiene un id. ¿Para qué 512 números? Porque un id no dice nada de parecido. Si tenis = 7, botas = 8 y bolsa = 9, parecería que las botas están «entre» los tenis y la bolsa, y eso no significa nada: los números se pusieron en orden de llegada.

La siguiente idea es el one-hot: un vector con un 1 en la posición de su categoría y 0 en las demás. Con 3 categorías, tenis = (1, 0, 0), botas = (0, 1, 0) y bolsa = (0, 0, 1). Ya no hay un orden falso, pero tampoco hay parecido: cada producto es una esquina distinta, y todas las esquinas están a la misma distancia entre sí. Los tenis quedan igual de lejos de las botas que de la bolsa.

Un embedding resuelve justo eso: la distancia entre dos vectores significa algo. Y gana otra cosa: una publicación nueva, que el modelo nunca vio, recibe su vector igual, porque el modelo lo calcula a partir de su foto.

Tamaños reales

Un embedding real no tiene 2 números, tiene cientos o miles. Algunos que vas a oír:

ModeloQué codificaDimensión
ResNet-50 (antes de su última capa)Fotos2,048
BERT-baseTexto768
CLIP ViT-B/32Fotos y texto, en el mismo espacio512

Cada número se guarda normalmente como float32, que ocupa 4 bytes. Un embedding de 512 números ocupa entonces 512 × 4 bytes = 2,048 bytes ≈ 2 KB. Parece nada. Guarda esa cifra: cuando sean 100 millones de productos, va a decidir la arquitectura.

Texto e imágenes en el mismo espacio

Un embedding multimodal pone cosas de distinto tipo, como fotos y textos, en el mismo espacio, para compararlas directamente. El más conocido es CLIP, que OpenAI publicó en 2021: dos redes, una para fotos y otra para texto, entrenadas juntas para que una foto quede cerca de su descripción. El vector del texto «unos tenis blancos» queda cerca del vector de una foto de unos tenis blancos. Así puedes buscar fotos con texto, texto con fotos o fotos con fotos, con la misma búsqueda. La versión CLIP ViT-B/32 (su red de fotos es un ViT, un transformer que parte la foto en cuadritos de 32 × 32 píxeles) produce 512 números para las dos cosas.

02

El producto punto

Esencial

The dot product

Para comparar dos embeddings hace falta una cuenta. La más importante de toda la lección, y una de las más repetidas de todo ML, es el producto punto (dot product): multiplicas coordenada por coordenada y sumas todo. Se escribe a · b, y el resultado es un solo número, no otro vector.

No es una cuenta rara de matemáticos. La haces cada vez que pagas en una tienda: compras 2 playeras de $50, 1 gorra de $120 y 3 pares de calcetines de $15. Las cantidades son el vector (2, 1, 3) y los precios, (50, 120, 15). El total es su producto punto: 2 × 50 + 1 × 120 + 3 × 15 = 100 + 120 + 45 = $265. Cada cantidad se «pesa» con su precio y todo se suma.

Y ya lo viste en la lección de redes: una neurona multiplica cada entrada por su peso y suma. Eso es el producto punto entre las entradas y los pesos, más el sesgo. La última capa de la ResNet-50 de C01 hace 500 productos punto, uno por categoría, de 2,048 números cada uno: 2,048 × 500 = 1,024,000 multiplicaciones para sacar los 500 logits.

Lo que significa en el dibujo

El signo dice hacia qué lado apuntan las dos flechas:

  • Positivo si apuntan hacia el mismo lado, es decir, si forman menos de 90°: tenis y botas, 24.
  • Cero si son perpendiculares, a 90°: tenis y playera, 0.
  • Negativo si apuntan hacia lados opuestos, a más de 90°: tenis y bolsa, −7.

El tamaño también se lee. Primero, el producto punto de un vector consigo mismo da su largo al cuadrado: (4, 3) · (4, 3) = 16 + 9 = 25, y √25 = 5. Es el teorema de Pitágoras: la flecha de los tenis mide 5. Las botas también miden 5.

Segundo, el producto punto es una sombra. Desde la punta de los tenis baja una línea perpendicular hasta la línea que apunta hacia las botas. El pedazo de esa línea que va del origen al punto donde cae mide 4.8: es lo que los tenis avanzan en la dirección de las botas, su «sombra» sobre ellas. Multiplica esa sombra por el largo de las botas: 4.8 × 5 = 24. Es el producto punto.

El producto punto como sombra. Arriba, la flecha de los tenis, (4, 3), y la línea que apunta hacia las botas, (3, 4). Desde la punta de los tenis baja una perpendicular a esa línea; el pedazo entre el origen y ese punto mide 4.8: es la sombra de los tenis sobre la dirección de las botas. Como las botas miden 5, 4.8 por 5 da 24, que es el producto punto. Abajo, tres pares de flechas: tenis y botas forman menos de 90 grados y su producto punto es más 24; tenis y playera forman 90 grados y da 0; tenis y bolsa forman más de 90 grados y da menos 7. EL PRODUCTO PUNTO COMO SOMBRA hacia las botas (0, 0) Tenis (4, 3) sombra = 4.8 Lo que los tenis avanzan hacia las botas: 4.8 4.8 × 5 (largo de las botas) = 24 EL SIGNO DEPENDE DEL ÁNGULO +24 mismo lado MENOS DE 90° 0 perpendiculares 90° −7 lados opuestos MÁS DE 90°
Arriba, la sombra de los tenis sobre la dirección de las botas mide 4.8, y 4.8 × 5 = 24. Abajo, el signo del producto punto según el ángulo entre las flechas.

¿De qué depende el largo de esa sombra? Del largo de los tenis y del ángulo entre las dos flechas. Con el ángulo muy cerrado, la sombra es casi tan larga como la flecha; a 90°, desaparece; si el ángulo pasa de 90°, cae del lado contrario y cuenta como negativa. Esa fracción de la flecha que queda como sombra tiene un nombre que seguramente viste en la prepa: el coseno del ángulo, una función que vale 1 a 0°, 0 a 90° y −1 a 180°. Para tenis y botas el ángulo es de unos 16° y su coseno, 0.96: la sombra mide 5 × 0.96 = 4.8. Juntando todo:

a · b = (largo de a) × (largo de b) × (coseno del ángulo entre a y b)

Con tenis y botas: 5 × 5 × 0.96 = 24.

¿Por qué el producto punto está en todas partes? Porque es la forma más barata de medir «qué tanto apuntan juntos» dos vectores: si cada uno tiene d números (d es su dimensión), son d multiplicaciones y d − 1 sumas, sin nada más. Y muchos productos punto juntos son una multiplicación de matrices, la operación para la que están hechas las GPUs; lo ves en la lección de matrices y FLOPs. En la lección de transformers vas a ver que la atención compara palabras exactamente así.

03

Coseno, distancia y normalización L2

Esencial

Cosine, distance and L2 normalization

Si «parecido» significa «cerca», necesitamos medir qué tan cerca están dos vectores. Hay dos medidas que se usan muchísimo, y una forma de preparar los vectores que hace que las dos digan lo mismo.

Distancia euclidiana: la de la regla

La distancia euclidiana es la distancia en línea recta, la que medirías con una regla. Es el teorema de Pitágoras: restas coordenada por coordenada, elevas al cuadrado, sumas y sacas raíz.

Con 512 números es lo mismo, solo que restas, elevas y sumas 512 veces.

Similitud coseno: hacia dónde apuntan

La similitud coseno no mide la distancia entre las puntas, sino el ángulo entre las dos flechas. Si apuntan en la misma dirección, vale 1. Si son perpendiculares (90°), vale 0. Si apuntan en direcciones opuestas, vale −1. Se calcula con la fórmula de la sección anterior, despejada: el producto punto dividido entre los dos largos.

coseno = (a · b) ÷ (largo de a × largo de b)

El coseno entre tenis y botasLa cuenta
Producto punto
4 × 3 + 3 × 4
24
Largo de los tenisPitágoras
√(4² + 3²) = √25
5
Largo de las botas
√(3² + 4²) = √25
5
Coseno
24 ÷ (5 × 5)
0.96
Ánguloel que tiene ese coseno
arccos 0.96
≈ 16°
Resultado
0.96: casi la misma dirección. Con la playera: 0 ÷ 25 = 0 (90°, nada que ver). Con la bolsa: −7 ÷ 25 = −0.28 (≈ 106°, lados algo opuestos).
Coseno de dos vectoresCalculadora

Mueve las dos flechas. Empiezan en los tenis, a = (4, 3), y las botas, b = (3, 4).

Producto punto a · bmultiplica y suma24.00
Largo de a × largo de b25.00
Similitud cosenoproducto punto ÷ largos0.960
Ángulo entre las flechasen grados16.3
Distancia euclidianala de la regla1.41

Pon b = (1.5, 2), la mitad de las botas: el producto punto baja a la mitad y la distancia cambia, pero el coseno sigue en 0.96, porque la dirección es la misma. Con una flecha en (0, 0) no hay dirección, y el coseno sale «—».

Prueba también b = (−4, −3): son los tenis al revés, a 180°, y el coseno da −1.

¿Por qué ignorar el largo?

Imagina una publicación de tenis con una descripción larguísima, y que el modelo le da el vector (8, 6) en vez de (4, 3). Es la misma dirección, solo el doble de larga. La distancia euclidiana entre (4, 3) y (8, 6) es 5, más lejos que la playera de su bolsa (√2 ≈ 1.41). El coseno, en cambio, es (32 + 18) ÷ (5 × 10) = 50 ÷ 50 = 1: idénticos en dirección.

Muchas veces lo que importa es la dirección (de qué se trata) y no el largo. Para eso sirve normalizar.

Normalización L2: que todos midan 1

Normalizar un vector es dividir cada uno de sus números entre su largo, para que el vector mida exactamente 1. Se llama normalización L2 porque «norma L2» es el nombre técnico del largo de Pitágoras (la raíz de la suma de los cuadrados).

Con todos los vectores normalizados pasan dos cosas muy útiles:

  1. El coseno es solo el producto punto, porque los largos valen 1 y dividir entre 1 no cambia nada: (0.8, 0.6) · (0.6, 0.8) = 0.48 + 0.48 = 0.96. Una cuenta menos en cada comparación, y son millones de comparaciones.
  2. La distancia y el coseno ordenan igual a los vecinos. Con vectores de largo 1, la distancia al cuadrado es exactamente 2 − 2 × coseno. Mientras más alto el coseno, más chica la distancia.
Distancia y coseno, con vectores de largo 1La cuenta
Coseno = producto puntotenis (0.8, 0.6) y botas (0.6, 0.8), ya normalizados
0.8 × 0.6 + 0.6 × 0.8
0.96
Distancia al cuadrado
(0.8 − 0.6)² + (0.6 − 0.8)²
0.08
La misma, con la fórmula
2 − 2 × 0.96
0.08
Distancia
√0.08
≈ 0.28
Resultado
Ordenar a los vecinos por coseno (de mayor a menor) o por distancia (de menor a mayor) da exactamente la misma lista.

Por eso la Ronda dice que el encoder de C07 entrega vectores de 512 números «L2-normalizados»: el índice puede comparar con el producto punto, la cuenta más barata. Y cuando una publicación junta su foto y su título en un solo vector, se promedian los dos y el resultado se vuelve a normalizar, porque el promedio de dos flechas de largo 1 casi nunca mide 1 (lo calculas en los ejercicios).

Así se ve todo en Python, sin instalar nada:

python
import math

def punto(a, b):
    return sum(x * y for x, y in zip(a, b))

def largo(a):
    return math.sqrt(punto(a, a))

def coseno(a, b):
    return punto(a, b) / (largo(a) * largo(b))

def normalizar(a):
    n = largo(a)
    return [x / n for x in a]

tenis = [4, 3]
otros = {"botas": [3, 4], "playera": [-3, 4], "bolsa": [-4, 3], "tenis × 2": [8, 6]}
for nombre, v in otros.items():
    print(f"{nombre:10} punto = {punto(tenis, v):3}   coseno = {coseno(tenis, v):5.2f}")

t, b = normalizar(tenis), normalizar([3, 4])
print("tenis normalizado:", t, "  largo:", largo(t))
print("punto de los dos normalizados:", round(punto(t, b), 2))
Salida
botas      punto =  24   coseno =  0.96
playera    punto =   0   coseno =  0.00
bolsa      punto =  -7   coseno = -0.28
tenis × 2  punto =  50   coseno =  1.00
tenis normalizado: [0.8, 0.6]   largo: 1.0
punto de los dos normalizados: 0.96
04

Buscar el más cercano entre 100 millones

Esencial

Searching among 100 million

Con lo anterior ya puedes construir una búsqueda semántica, una búsqueda por significado: calculas el embedding de lo que el usuario busca y encuentras los productos cuyo embedding está más cerca. Encontrar los k más cercanos se llama búsqueda de vecinos más cercanos (nearest neighbors). Si k = 30, quieres los 30 más parecidos.

La forma más obvia es la búsqueda exacta, también llamada fuerza bruta (brute force): comparas el vector de la búsqueda contra todos los del catálogo, uno por uno, y te quedas con los mejores. Así se ve con un catálogo de juguete de 6 productos, con vectores inventados de 3 números:

python
import math

def normalizar(v):
    n = math.sqrt(sum(x * x for x in v))
    return [x / n for x in v]

catalogo = {                      # vectores inventados de 3 números
    "tenis blancos":  [0.9, 0.3, 0.1],
    "tenis negros":   [0.8, 0.5, 0.0],
    "botas cafés":    [0.5, 0.8, 0.2],
    "sandalias":      [0.6, 0.1, 0.7],
    "playera blanca": [0.1, 0.2, 0.9],
    "bolsa de piel":  [-0.2, 0.9, 0.3],
}
indice = {nombre: normalizar(v) for nombre, v in catalogo.items()}   # una sola vez

def buscar(consulta, k):
    q = normalizar(consulta)
    puntajes = []
    for nombre, v in indice.items():                 # fuerza bruta: contra todos
        puntajes.append((sum(a * b for a, b in zip(q, v)), nombre))
    puntajes.sort(reverse=True)
    return puntajes[:k]

for coseno, nombre in buscar([0.85, 0.35, 0.15], k=3):
    print(f"{coseno:.3f}  {nombre}")
Salida
0.996  tenis blancos
0.973  tenis negros
0.818  botas cafés

Fíjate en dos cosas. Los vectores del catálogo se normalizan una vez, al armar el índice; la consulta se normaliza en cada búsqueda. Y el ciclo for recorre todo el catálogo. Con 6 productos es instantáneo. Veamos con un catálogo de verdad.

Las cuentas de C07

En la Ronda, el caso C07 es una búsqueda por texto y por foto sobre el mismo marketplace de C01. Tiene ≈ 100 millones de publicaciones activas, cada una con un embedding de 512 números, y en la hora pico llegan ≈ 1,750 búsquedas por segundo.

Memoria para guardar los vectoresLa cuenta
Un vector512 números en float32
512 × 4 bytes
2,048 bytes ≈ 2 KB
El catálogo
100,000,000 × 2,048 bytes
204,800,000,000 bytes
En gigabytes
÷ 1,000,000,000
204.8 GB ≈ 205 GB
Resultado
Una laptop típica tiene 8 o 16 GB de memoria: esto no cabe ni en 10 laptops de 16 GB.
El trabajo de una búsqueda exactaLa cuenta
Una comparaciónun producto punto de 512 números
512 multiplicaciones
512
Una búsquedacontra todo el catálogo: 51.2 mil millones
100,000,000 × 512
51,200,000,000
Un segundo en el picounos 90 millones de millones
51,200,000,000 × 1,750
≈ 90,000,000,000,000
Leer los vectoresen cada búsqueda, a 200 GB por segundo
204.8 GB ÷ 200 GB/s
≈ 1 s
Resultado
Cada búsqueda tendría que leer los 205 GB completos de la memoria, y solo eso ya tarda del orden de un segundo.

La memoria de un servidor grande entrega del orden de unos cientos de GB por segundo. A 200 GB por segundo, solo leer los vectores tomaría ≈ 1 segundo por búsqueda; aun al doble de velocidad, medio segundo. Y C07 se compromete a que 99 de cada 100 búsquedas de texto respondan en menos de 300 ms.

Memoria y costo de un índiceCalculadora

Cuánto pesan los vectores y cuánto trabajo hace una búsqueda exacta. Empieza en C07: 100 millones de vectores de 512 números en float32, con 1,750 búsquedas por segundo en el pico.

Memoria de los vectoresN × d × bytes204.8 GB
Multiplicaciones por búsqueda exactaun producto punto contra cada vector5.12 × 10^10
Multiplicaciones por segundosi todas las búsquedas fueran exactas8.96 × 10^13
Solo leer los vectores, en cada búsquedacon memoria que entrega 200 GB por segundo1.02 s

Baja la dimensión a 128 o los bytes a 1 y todo baja en proporción. Lo que ninguna perilla arregla es que la búsqueda exacta lee el catálogo entero en cada consulta: por eso existen los índices aproximados.

Prueba los números de C08 de la Ronda: 100 millones de vectores de 128 números en int8 (1 byte). La memoria baja a 12.8 GB, pero la búsqueda exacta sigue leyendo todo en cada consulta.

La búsqueda exacta es perfecta en calidad e imposible en costo. Hace falta otra idea.

Búsqueda aproximada: no mirarlo todo

Casi siempre no necesitas los 30 vecinos exactos. Te basta con 30 que sean muy buenos, aunque de vez en cuando se te escape alguno de los verdaderos. Esa renuncia es la que abre la puerta: buscar los vecinos más cercanos aproximados, en inglés approximate nearest neighbors, o ANN.

Un ANN no compara contra todo el catálogo. Antes de que llegue cualquier búsqueda, organiza los vectores para que, cuando llegue una, solo tenga que revisar una parte pequeña y bien elegida. Esa organización guardada se llama índice vectorial.

Estantes: la idea más simple

Un índice vectorial por grupos, como estantes. El plano está dividido en cuatro estantes, A, B, C y D, cada uno con sus puntos. La búsqueda, una estrella, cae en el estante B, cerca de la orilla con el A, y solo se revisa el estante B: un cuarto del trabajo. Encuentra dos de sus tres vecinos más cercanos; el que falta, que de hecho es el más cercano de todos, quedó del otro lado de la orilla, en el estante A, y se pierde. El recall en 3 es 2 de 3, unos 0.67. UN ÍNDICE POR ESTANTES ESTANTE A ESTANTE B ESTANTE C ESTANTE D vecino verdadero, en otro estante: se pierde la búsqueda: cae en el estante B se revisa solo el B: ¼ del trabajo encontró 2 de los 3 más cercanos recall@3 = 2 ÷ 3 ≈ 0.67
Un índice por grupos. La búsqueda (la estrella) solo revisa el grupo más cercano. El vecino verdadero más cercano quedó del otro lado de la orilla, en otro grupo, y se pierde: eso es lo que mide el recall.

Velocidad contra recall

¿Qué tanto se pierde? Se mide comparando el ANN contra la búsqueda exacta en una muestra de búsquedas. Si la búsqueda exacta dice que estos son los 100 vecinos verdaderos y el ANN encontró 95 de ellos, su recall@100 es 95 ÷ 100 = 0.95. Es el mismo recall de la lección «¿Aprendió de verdad?»: de lo que había que encontrar, cuánto encontraste.

La perilla es cuánto del catálogo revisar. En un índice por estantes, cuántos estantes. En HNSW, el índice que elige C07, el tamaño de la lista de candidatos que mantiene al buscar, que se llama ef_search. HNSW une los vectores en una red de caminos y busca saltando de vecino en vecino; lo ves por dentro en la sección siguiente.

Mueves la perilla a…VelocidadRecall
Revisar muy poco (1 estante, una lista corta)Muy rápidoMás bajo: se escapan los vecinos de la orilla
Revisar más (10 estantes, una lista larga)Más lentoMás alto
Revisar todoTan lento como la búsqueda exacta1.0, perfecto

No existe el índice rápido y perfecto: existe el punto de la perilla que tu sistema puede pagar. En C07 se pide recall@100 ≥ 0.95, y se vigila en producción con un job (un programa que corre solo, termina y vuelve a correr) que cada hora lanza un set fijo de unas 1,000 búsquedas contra el ANN y contra la búsqueda exacta sobre los mismos vectores, y compara.

05

Un grafo con atajos: HNSW

Extra

A graph with shortcuts: HNSW

Uno de los índices más usados hoy, y el que elige C07, se llama HNSW (Hierarchical Navigable Small World, «mundo pequeño navegable por niveles»; Malkov y Yashunin, 2016). En lugar de estantes, organiza los vectores como una red de caminos: puntos unidos por líneas, lo que en matemáticas se llama un grafo.

  1. 1

    Cada vector se une con unos cuantos vecinos

    Al construir el índice, cada vector se conecta con unas decenas de vectores cercanos. Así queda una red de caminos cortos entre vectores parecidos.

  2. 2

    Buscar es caminar hacia la consulta

    Empiezas en un punto de entrada. Miras a sus vecinos y saltas al que quede más cerca de tu consulta. Repites desde ahí. Cuando ningún vecino te acerca más, te detienes: ahí están tus candidatos.

  3. 3

    Los niveles son los atajos

    Con puros caminos cortos, cruzar 100 millones de puntos tomaría muchísimos saltos. Por eso HNSW arma niveles: arriba, pocos puntos unidos por saltos largos; abajo, todos los puntos con caminos cortos. La búsqueda empieza arriba, se acerca rápido a la zona correcta, baja de nivel y termina con pasos finos.

Una búsqueda en un índice HNSW con dos niveles y 24 puntos. Arriba, el nivel 1 tiene solo 6 puntos, unidos por saltos largos. La búsqueda entra por el punto de la izquierda y da 3 saltos largos, cada uno al vecino más cercano a la consulta, una estrella abajo a la derecha. Luego baja al nivel 0, donde están los 24 puntos unidos por caminos cortos, y da 2 pasos cortos más hasta el vecino más cercano de verdad. En total comparó la consulta contra 12 de los 24 puntos. NIVEL 1 · POCOS PUNTOS, SALTOS LARGOS NIVEL 0 · TODOS, CON CAMINOS CORTOS baja de nivel 1 2 3 4 5 entrada la consulta lo que encontró también está en el nivel de arriba Comparó contra 12 de los 24 puntos
Una búsqueda en HNSW con 24 puntos. Arriba, tres saltos largos acercan rápido a la zona de la consulta (la estrella); abajo, dos pasos cortos terminan el trabajo. Con 100 millones de puntos, la parte que se revisa es muchísimo menor que la mitad.

Gracias a los atajos, el trabajo crece muy despacio con el tamaño del catálogo. En la Ronda lo vas a leer como «el recorrido de HNSW crece como log N», donde N es el número de vectores: si el catálogo se duplica, la búsqueda da unos pocos saltos más, no el doble de trabajo.

06

Dimensiones, bytes y umbrales

Extra

Dimensions, bytes and thresholds

La dimensionalidad (dimensión) de un embedding, cuántos números tiene, es una decisión de diseño con un precio claro. En la calculadora de memoria viste que la memoria y el trabajo crecen en proporción a la dimensión: 1,024 números cuestan el doble que 512. ¿Qué se compra con ese precio?

Más números, más lugar para los detalles

Con más dimensiones, el modelo tiene más lugar para separar cosas finas: el color exacto, el estilo, la marca. Con pocas tiene que apretar: lo general sobrevive y lo fino se pierde. Pero la ganancia se aplana: en general, cada vez que duplicas la dimensión ganas menos que la vez anterior, mientras el costo se sigue duplicando. Por eso se busca la dimensión más chica que cumpla con la calidad.

La Ronda elige distinto en cada caso, y siempre con la cuenta de memoria enfrente:

CasoQué guardaDimensiónLo que pesa
C08 · RecomendaciónUn vector por publicación, entrenado para recomendar128100 M × 128 × 1 byte = 12.8 GB en int8
C07 · BúsquedaFoto y título en el mismo espacio512100 M × 512 × 1 byte = 51.2 GB en int8
C11 · RAGPedazos de documentos de ≈ 400 tokens1,02412 M × 1,024 × 4 bytes ≈ 49.2 GB en float32

C08 usa 128 y no 512 porque sus vectores se entrenan solo para recomendar, no para describir la foto: cuatro veces menos memoria. Algunos modelos modernos dejan elegir la dimensión sin reentrenar: sus primeros números ya funcionan como un embedding más chico (se llaman embeddings Matryoshka, como las muñecas rusas que caben una dentro de otra). En C11, uno de los modelos candidatos permite cualquier dimensión de 32 a 1,024.

Bytes por número

Cada número tampoco tiene que ocupar 4 bytes:

FormatoBytes por número100 M vectores de 512Qué pierdes
float324204.8 GBNada: es la referencia
float162102.4 GBCasi nada
int8151.2 GBAlgo de precisión en las distancias

Guardar con menos bytes se llama cuantización; la ves con calma en la lección de cómo guarda números una computadora. C07 recorre el índice en int8 y, para el orden final, recalcula la distancia de sus mejores 200 candidatos de cada pedazo con los vectores completos en float32. Así, el recorrido del grafo, que toca muchísimos vectores, trabaja con 51.2 GB y no con 204.8, y los vectores completos solo se leen para esos 200 candidatos: se recupera casi toda la exactitud.

El índice pesa más que los vectores

Un índice HNSW guarda, además de los vectores, sus caminos. La perilla de construcción se llama M: cuántos vecinos guarda cada punto. En el nivel de abajo guarda hasta 2 × M, y cada vecino es un número de 4 bytes (su posición en la lista).

La memoria del índice de C07La cuenta
Vectores en int8
100,000,000 × 512 × 1 byte
51.2 GB
Caminos de un vectorcon M = 32: hasta 64 vecinos
64 × 4 bytes
256 bytes
Caminos de todo el catálogo
100,000,000 × 256 bytes
≈ 25.6 GB
Total
51.2 + 25.6
≈ 77 GB
Resultado
C07 lo parte en 4 pedazos (shards) de 25 M vectores, de ≈ 19 GB cada uno más lo que agrega el motor, y guarda 2 copias de cada pedazo en máquinas distintas.

Lo raro de muchas dimensiones

En muchas dimensiones pasa algo que no se ve en 2D: dos vectores al azar salen casi perpendiculares. Compruébalo:

python
import math, random

random.seed(7)

def coseno(a, b):
    p = sum(x * y for x, y in zip(a, b))
    return p / math.sqrt(sum(x * x for x in a) * sum(y * y for y in b))

for d in [2, 16, 128, 512]:
    cosenos = []
    for _ in range(2000):                     # 2,000 pares de vectores al azar
        a = [random.gauss(0, 1) for _ in range(d)]
        b = [random.gauss(0, 1) for _ in range(d)]
        cosenos.append(abs(coseno(a, b)))     # sin signo: qué tan lejos de 0
    tipico = sum(cosenos) / len(cosenos)
    arriba = sum(c > 0.2 for c in cosenos) / len(cosenos)
    print(f"d = {d:3}   coseno típico = {tipico:.3f}   pares con más de 0.2: {arriba:4.0%}")
Salida
d =   2   coseno típico = 0.637   pares con más de 0.2:  88%
d =  16   coseno típico = 0.204   pares con más de 0.2:  44%
d = 128   coseno típico = 0.071   pares con más de 0.2:   2%
d = 512   coseno típico = 0.037   pares con más de 0.2:   0%

En 2 dimensiones, dos flechas al azar suelen tener un coseno grande. En 512, casi nunca pasan de 0.2: el coseno típico baja más o menos como 1 ÷ √d. Hay tantas direcciones posibles que dos vectores sin relación casi no se «tocan».

Eso tiene dos consecuencias. Primera: un coseno de 0.3 entre dos embeddings de 512 números puede querer decir mucho, porque al azar saldría ≈ 0.04. Segunda: los vectores de un modelo real no están repartidos al azar; muchos modelos los dejan en una zona estrecha del espacio, y ahí hasta dos frases sin relación pueden dar un coseno alto. Por eso la escala de los cosenos cambia de un modelo a otro, y un umbral nunca se copia de un modelo a otro: se mide con pares revisados por personas, como en el ejemplo que sigue.

Un umbral: ¿es la misma foto?

A veces no quieres «los más parecidos», sino decidir sí o no: ¿estas dos fotos son la misma? Un vendedor sube la misma foto en tres publicaciones, una recortada y otra recomprimida. Byte por byte los archivos son distintos, pero sus embeddings quedan casi en el mismo punto. Encontrarlos para quedarse con uno, o para tratarlos como grupo, se llama deduplicación (deduplication, o dedup).

La regla es sencilla: si el coseno entre dos fotos pasa un umbral, se marcan como casi duplicadas. Lo difícil es elegir el umbral, y eso se hace con ejemplos revisados por personas, no a ojo.

Es el mismo juego de precision contra recall de la lección «¿Aprendió de verdad?», y el umbral correcto depende de qué cuesta cada error:

  • Antes de repartir un dataset en entrenamiento y examen conviene un umbral bajo, con recall alto. Un duplicado que se escapa pone la misma foto en los dos lados y el examen mide memoria: es leakage. Juntar de más dos fotos distintas cuesta poco: solo caen del mismo lado.
  • En moderación de contenido (C06 en la Ronda), una decisión se aplica a todo un grupo de casi duplicados: si quitas una imagen, quitas sus copias. Ahí conviene un umbral estricto, con precision alta, porque juntar de más castigaría algo distinto.

Dos detalles prácticos. Las copias exactas se detectan antes y más barato con un hash del archivo: una huella de sus bytes que cambia si cambia un solo byte (ya la usaste en «Datos, ejemplos, features y etiquetas»; a fondo, en «Tiempo, hashes e identificadores»). Los embeddings son para lo que no es idéntico: recortes, recompresiones, filtros. Y en un catálogo grande no se compara cada foto contra todas: para cada foto nueva se buscan sus vecinos más cercanos con un índice, como el de la búsqueda, y solo a esos se les aplica el umbral.

07

Palabras contra significado: búsqueda híbrida

Extra

Words versus meaning: hybrid search

Antes de los embeddings, los buscadores emparejaban palabras. Esa forma de buscar sigue siendo muy buena en algunas cosas, y por eso no desaparece: se combina.

La búsqueda por palabras y BM25

Un buscador por palabras guarda, para cada palabra, la lista de documentos que la contienen. Es un índice invertido, como el índice alfabético al final de un libro: buscas «tenis» y te dice en qué páginas está. Cuando alguien busca «tenis jordan blancos», junta las publicaciones que tienen esas palabras y las ordena con una fórmula. La más usada desde hace décadas es BM25 (Best Matching 25, de Robertson y su equipo, en los años noventa). Su intuición son tres ideas:

  1. Las palabras raras pesan más. «Para» aparece en casi todo y no ayuda a distinguir; «jordan» aparece en poquitas publicaciones, y si coincide, dice mucho. Ese peso se llama IDF (inverse document frequency, frecuencia inversa en los documentos).
  2. Repetir no vale tanto. Que «tenis» aparezca 10 veces en un título no lo hace 10 veces más relevante: con los valores que usa por defecto Lucene (la biblioteca que hay debajo de Elasticsearch y OpenSearch, dos motores de búsqueda muy usados), 10 repeticiones valen menos del doble que una. Así un vendedor no gana repitiendo la palabra.
  3. Los textos largos se castigan un poco. Una coincidencia en un título corto dice más que en una descripción de 300 palabras.

El peso de la primera idea se calcula con el logaritmo natural, que se escribe ln: una función que crece muy despacio (ln 10 ≈ 2.3 y ln 2,000 ≈ 7.6). En su versión más simple, el peso de una palabra es ln(publicaciones en total ÷ publicaciones que la tienen):

El peso de tres palabras en 1,000,000 de publicacionesLa cuenta
«blancos»está en 100,000
ln(1,000,000 ÷ 100,000) = ln 10
≈ 2.3
«tenis»está en 20,000
ln(1,000,000 ÷ 20,000) = ln 50
≈ 3.9
«jordan»está en 500
ln(1,000,000 ÷ 500) = ln 2,000
≈ 7.6
Resultado
Una coincidencia en «jordan» pesa unas 3 veces lo que una en «blancos». La fórmula exacta de BM25 agrega un 0.5 (para no dividir entre cero si ninguna publicación tiene la palabra) y un 1 (para que el peso nunca salga negativo), y aquí da prácticamente lo mismo: 2.30, 3.91 y 7.60.

Gracias al logaritmo, una palabra 200 veces más rara no pesa 200 veces más, sino unas 3.

Dónde falla cada una

Lo que pasaPor palabras (BM25)Por significado (embeddings)
Buscas «chamarra de mezclilla» y la publicación dice «chaqueta de jean»No la encuentra: ninguna palabra coincideLa encuentra: significan lo mismo
Buscas «algo para ir a la playa»Busca «playa» literalTrae sandalias, trajes de baño, sombreros
Escribes con un errorFalla si la palabra no existe tal cualMuchas veces lo tolera
Buscas «iPhone 13 Pro 128»Encuentra el modelo exactoLo confunde con «iPhone 12 Pro 256»: quedan cerca
Buscas «error E-4012» (C11)Encuentra el código exactoUn código no «significa» nada para el modelo

En C07, la búsqueda de hoy es BM25, y ≈ 8 % de las búsquedas no dan ningún resultado, sobre todo por vocabulario: «chamarra» contra «chaqueta», «tenis» contra «zapatillas». Pero BM25 es justo lo que encuentra marcas, modelos y tallas exactas.

Juntar las dos: RRF

La búsqueda híbrida corre las dos búsquedas en paralelo sobre el mismo catálogo y junta sus listas. El problema es que sus puntajes no se pueden comparar: BM25 da números como 13.8 (la suma de los tres pesos de arriba, para una publicación que tiene las tres palabras) y el coseno, como 0.82. La forma más usada de juntarlas no mira los puntajes, solo las posiciones: RRF (Reciprocal Rank Fusion, Cormack y su equipo, 2009). Cada lista le da a cada publicación 1 ÷ (k + su posición), con k = 60, y se suman.

RRF con k = 60La cuenta
1.ª en la semántica y 3.ª en BM25
1/61 + 1/63
≈ 0.0323
20.ª en las dos listas
1/80 + 1/80
0.025
1.ª solo en BM25no aparece en la otra
1/61
≈ 0.0164
Resultado
Ganan las publicaciones que las dos listas ponen arriba. Ser 20.ª en las dos vale más que ser 1.ª en una sola.

El 60 viene del artículo original y casi nadie lo cambia: suaviza las diferencias entre los primeros lugares, para que ninguna lista sola decida todo.

Primero rápido, después fino: retrieval y re-ranking

El índice vectorial compara vectores calculados por separado: el de la búsqueda y el de cada producto, cada uno sin saber del otro. Eso es un modelo de dos torres, una para las búsquedas y otra para los productos. Es rápido, pero se le escapan detalles: «tenis blancos sin agujetas» y «tenis blancos con agujetas» pueden quedar casi en el mismo punto. Por eso los sistemas de búsqueda trabajan en dos etapas:

  1. 1

    Retrieval: traer candidatos

    El retrieval (recuperación) (recuperación) usa el índice vectorial y BM25 para traer, en unas decenas de milisegundos, unos cientos de candidatos de entre los 100 millones. Su trabajo no es ordenarlos perfecto: es no dejar fuera lo bueno. Es barato por candidato porque solo compara vectores ya calculados.

  2. 2

    Re-ranking: ordenar bien los primeros

    El re-ranking (reordenar) (reordenar) usa un modelo más caro y más preciso, un cross-encoder, que lee la búsqueda y cada candidato juntos, palabra por palabra, y les pone una calificación nueva. Como solo mira los primeros (decenas, no millones), se puede dar el lujo de ser lento por candidato.

Una búsqueda híbrida, de punta a punta · Una búsqueda híbrida: el catálogo se convierte en vectores antes; cada búsqueda calcula un solo vector, corre BM25 y el ANN en paralelo, junta las listas con RRF y reordena unos pocos.
  • Catálogo · 100 M publicaciones
    Las ≈ 100 millones de publicaciones activas de C07, cada una con su foto y su título. Se convierten en vectores antes de que alguien busque, en lote, sin prisa.
  • Encoder · foto y título → vector
    Un modelo tipo CLIP, de dos torres: convierte la foto y el título de cada publicación en un embedding de 512 números, ya normalizado. Es multimodal: fotos y textos quedan en el mismo espacio.
  • Índice vectorial · HNSW · ya organizado
    Los 100 M vectores organizados para buscar sin revisarlos todos: un índice vectorial HNSW. En float32 pesarían ≈ 205 GB; C07 los guarda en int8 (51.2 GB) más el grafo (≈ 25.6 GB).
  • Búsqueda · «tenis blancos»
    Alguien escribe lo que busca. Todo el camino de abajo corre mientras espera: 99 de cada 100 búsquedas de texto tienen que responder en menos de 300 ms.
  • Encoder · texto → 512 números
    El mismo modelo que codificó el catálogo, en la misma versión: si no, los vectores no se pueden comparar. Por búsqueda calcula un solo vector, así que es rápido.
  • BM25 · índice de palabras
    La búsqueda por palabras de siempre, con BM25. Encuentra lo que los embeddings confunden (marcas, modelos, tallas). Arranca en cuanto llega la búsqueda, sin esperar al encoder, y es el respaldo si la parte semántica falla.
  • Fusión RRF · junta las dos listas
    Junta la lista semántica y la de palabras con RRF: cada lista le da a cada publicación 1 ÷ (60 + su posición) y se suman. Es la búsqueda híbrida: ganan las que las dos listas ponen arriba.
  • Re-ranker · reordena los 30 primeros
    El re-ranking (reordenar): un modelo más caro que lee la búsqueda y cada candidato juntos y los reordena. En C07 reordena solo los 30 primeros: con 100 costaría el triple de GPUs.

Fíjate en que hay dos caminos con presupuestos muy distintos. Convertir todo el catálogo en vectores se hace antes, en lote (todo junto, sin que nadie esté esperando), y puede tardar horas. Cada búsqueda, en cambio, tiene que contestar en milisegundos: solo calcula un vector (el de lo que escribió el usuario) y consulta el índice que ya existe.

Este patrón, «un paso barato y amplio, luego un paso caro y preciso», aparece en toda la Ronda: en búsqueda, en recomendación (C08, donde la Ronda lo llama embudo) y en moderación de contenido (C06, donde lo llama cascada: un modelo barato revisa todo y uno caro, solo lo dudoso).

08

Cómo aprende un encoder: aprendizaje contrastivo

Extra

How an encoder learns: contrastive learning

Un encoder como el de C07 no se entrena con etiquetas como «tenis» o «botas». Se entrena con pares: cosas que deben quedar juntas. Una foto y su título. Una pregunta y el párrafo que la responde. Un usuario y la publicación que contactó. Eso es el aprendizaje contrastivo (contrastive learning): acercar los vectores de cada par y alejar los de lo que no es pareja.

¿De dónde salen los «no pareja»? Del mismo batch. Si el batch trae B pares, cada foto tiene 1 título correcto y B − 1 incorrectos: los títulos de las otras fotos. No hay que etiquetar nada más; los pares ya existen (cada publicación tiene su foto y su título).

Entrenar dos torres con aprendizaje contrastivo · Un paso de entrenamiento de un modelo de dos torres. Toca cada caja o recorre el paso completo.
  • Pares del batch · B fotos con su título
    Pares que deben quedar juntos: la foto principal de una publicación y su título. El encoder de C07 se ajusta con pares (título, foto) del propio marketplace; CLIP usó ≈ 400 millones de pares de internet, en batches de 32,768.
  • Torre de imagen · foto → vector
    Una red de visión (una CNN o un ViT) que convierte cada foto en un vector de 512 números y lo normaliza. Es la mitad del modelo de dos torres.
  • Tabla B × B · coseno de cada par
    El coseno de cada foto contra cada título. Con B = 3 son 9 números; con B = 32,768, más de mil millones. La diagonal son los pares correctos; todo lo demás, los incorrectos.
  • Torre de texto · título → vector
    Un transformer de texto que convierte cada título en un vector del mismo espacio que las fotos. Eso es lo que hace al modelo multimodal.
  • Pérdida contrastiva · la diagonal debe ganar
    La pérdida del aprendizaje contrastivo: un softmax por fila (cada foto elige su título) y por columna (cada título elige su foto), con la diagonal como respuesta correcta. Baja cuando la diagonal gana con claridad.

Las dos torres convierten las B fotos y los B títulos en vectores normalizados, y se calcula el coseno de cada foto contra cada título: una tabla de B × B. La diagonal son los pares correctos. Cada fila de esa tabla se trata como una pregunta de opción múltiple: «foto, ¿cuál de estos títulos es el tuyo?». Los cosenos se dividen entre un número chico, la temperatura (se escribe τ, la letra griega tau), y pasan por un softmax, que los vuelve probabilidades. La pérdida es la entropía cruzada: −ln de la probabilidad que le tocó al título correcto. Es la misma pérdida de un clasificador, solo que aquí las «clases» son los títulos del batch.

Un batch de 3 pares en el aprendizaje contrastivo. Arriba, una tabla de 3 por 3 con el coseno de cada foto contra cada título. La diagonal, en verde, son los pares correctos: tenis con «Tenis blancos», 0.80; botas con «Botas cafés», 0.75; bolsa con «Bolsa de piel», 0.60. El entrenamiento sube esos números y baja los demás, como el 0.60 de la foto de botas contra el título de los tenis. Abajo, la foto de tenis elige su título con un softmax: con temperatura 1 le da una probabilidad de 0.44 al correcto; con temperatura 0.1, de 0.92, y su pérdida baja de 0.82 a 0.08. UN BATCH DE 3 PARES coseno de cada foto contra cada título Tenis blancos Botas cafés Bolsa de piel foto: tenis 0.80 ↑ 0.55 ↓ 0.10 ↓ foto: botas 0.60 ↓ 0.75 ↑ 0.05 ↓ foto: bolsa 0.15 ↓ 0.30 ↓ 0.60 ↑ los pares correctos: el entrenamiento los sube los demás: el entrenamiento los baja LA FOTO DE TENIS ELIGE SU TÍTULO probabilidad que le da a cada título CON τ = 1CON τ = 0.1 Tenis blancos 0.44 0.92 Botas cafés 0.34 0.08 Bolsa de piel 0.22 < 0.01 Pérdida = −ln(probabilidad del correcto) 0.82 con τ = 1 → 0.08 con τ = 0.1
Un batch de 3 pares. Arriba, el coseno de cada foto contra cada título: el entrenamiento sube la diagonal y baja lo demás. Abajo, la foto de tenis elige su título con dos temperaturas distintas.

Así se ve el batch completo en Python:

python
import math

# coseno de cada foto (fila) contra cada título (columna); la diagonal son los pares correctos
tabla = [[0.80, 0.55, 0.10],    # foto de tenis
         [0.60, 0.75, 0.05],    # foto de botas
         [0.15, 0.30, 0.60]]    # foto de bolsa

def perdida(tabla, tau):
    total = 0
    for i, fila in enumerate(tabla):
        exps = [math.exp(c / tau) for c in fila]   # softmax de los cosenos ÷ tau
        p = exps[i] / sum(exps)                    # lo que le da al título correcto
        print(f"  foto {i + 1}: P(título correcto) = {p:.2f}")
        total += -math.log(p)                      # entropía cruzada
    return total / len(tabla)

for tau in [1.0, 0.1]:
    print(f"temperatura {tau}")
    print(f"  pérdida promedio = {perdida(tabla, tau):.2f}")
Salida
temperatura 1.0
  foto 1: P(título correcto) = 0.44
  foto 2: P(título correcto) = 0.42
  foto 3: P(título correcto) = 0.42
  pérdida promedio = 0.85
temperatura 0.1
  foto 1: P(título correcto) = 0.92
  foto 2: P(título correcto) = 0.82
  foto 3: P(título correcto) = 0.94
  pérdida promedio = 0.11

Mira la foto 2, la de botas: es la que peor le va (0.82), porque su coseno con el título de los tenis, 0.60, está cerca del suyo, 0.75. Botas y tenis son los dos calzado; es un negativo difícil. Ahí la pérdida es más grande, y el gradiente empuja más: el entrenamiento gasta su esfuerzo justo en separar lo que confunde. Por eso los batches grandes ayudan: más negativos, y más negativos difíciles. CLIP usó ≈ 400 millones de pares de internet y batches de 32,768 pares. Además, CLIP hace la misma cuenta por columnas (cada título elige su foto) y promedia las dos.

Lo que sale: dos torres que hablan el mismo idioma

Al terminar, la torre de imagen y la de texto ponen sus vectores en el mismo espacio. Eso es lo que hace posible buscar fotos con texto. Y permite un truco: clasificar una foto sin entrenar una cabeza. Calculas su vector y lo comparas con los vectores de textos como «una foto de unos tenis», «una foto de unas botas» y «una foto de una bolsa»; gana el texto más cercano. Sirve para arrancar sin datos etiquetados, pero en categorías muy específicas, como las 500 de C01, un modelo ajustado con tus propias fotos suele acertar más.

La misma receta sirve fuera de las fotos. En C08 de la Ronda, un modelo de dos torres se reentrena cada semana: una torre convierte a cada usuario en un vector de 128 números; la otra, a cada publicación. Los pares positivos son los contactos y clics de la gente; los negativos, las otras publicaciones del mismo batch. Las dos torres se publican juntas, con la misma versión, porque un vector de una versión no se compara con uno de otra.

09

Errores comunes

Esencial

Common mistakes

Casi todos los errores con embeddings tienen algo en común: nada truena. La búsqueda responde, con el formato correcto, y solo baja la calidad. Estos son los que más se repiten.

Comparar vectores sin normalizar

Qué pasa: el índice usa producto punto y los vectores no miden 1. Los vectores largos salen arriba en todas las búsquedas, no porque se parezcan más, sino porque son largos. Pasa también cuando se promedian vectores (foto y título) y se olvida volver a normalizar.

Cómo evitarlo: normaliza al guardar y al consultar, o usa la métrica coseno. Antes de llenar el índice, revisa que el largo de una muestra de vectores dé 1. Y usa la métrica con la que se entrenó el modelo.

Mezclar versiones del modelo

Qué pasa: se actualiza el encoder a la versión 8, pero el índice todavía tiene vectores de la versión 7, o la mitad del catálogo se recalculó y la otra mitad no. Las cuentas salen, pero comparan coordenadas de dos mapas distintos.

Cómo evitarlo: un índice = una versión del modelo. Guarda la versión junto con cada vector, verifica en cada consulta que el encoder y el índice coincidan, y al cambiar de modelo recalcula todo el catálogo en un índice nuevo antes de cambiar el tráfico (en la Ronda, con blue/green: dos índices completos y un cambio de un solo golpe).

Creer que un coseno de 0.9 quiere decir «idéntico»

Qué pasa: se decide «son el mismo producto» porque el coseno es 0.9. Pero en otro modelo 0.9 es normal entre cosas distintas, y «iPhone 13 Pro 128» y «iPhone 12 Pro 256» pueden quedar así de cerca.

Cómo evitarlo: un umbral se mide con pares revisados por personas, para ese modelo y ese uso, y se vuelve a medir cuando cambia el modelo. Lo que tiene que ser exacto (modelo, talla, código) se compara con filtros o con BM25, no con un coseno.

Olvidar el recall del ANN

Qué pasa: el equipo de modelos mide la calidad con una búsqueda exacta sobre un catálogo chico, y en producción el mismo modelo va a un índice aproximado sobre 100 millones, con int8 y filtros. El número que aprobaste no es el que la gente ve.

Cómo evitarlo: mide el recall@k del índice contra la búsqueda exacta sobre los mismos vectores, con un set fijo de consultas que corre solo (en C07, cada hora, con un mínimo de 0.95), y evalúa los modelos nuevos sobre el índice real.

Filtrar después de buscar

Qué pasa: pides los 100 más cercanos y después quitas los que no son de la ciudad que pidió el usuario. Si esa ciudad tiene el 0.1 % del catálogo, de 100 resultados sobreviven en promedio 0.1: casi siempre la página sale vacía.

Cómo evitarlo: el filtro va dentro de la búsqueda. Los motores de índices vectoriales lo permiten; si el filtro deja muy pocos vectores, incluso conviene una búsqueda exacta sobre ese pedacito, que ahí sí es barata.

Usar solo embeddings para lo exacto

Qué pasa: la búsqueda semántica reemplaza a BM25 y empiezan a fallar las búsquedas de marcas, modelos, tallas y códigos de error, que antes funcionaban.

Cómo evitarlo: búsqueda híbrida: BM25 en paralelo, fusión con RRF, y BM25 solo como respaldo si lo semántico falla.

Calcular la memoria solo con los vectores

Qué pasa: «100 millones × 512 × 1 byte = 51.2 GB, cabe en una máquina de 64 GB». Falta el grafo de HNSW (≈ 25.6 GB más si cada vector guarda hasta 64 vecinos), lo que agrega el motor, las copias para no caerse y el índice nuevo que se construye al lado cuando cambia el modelo.

Cómo evitarlo: suma todo: vectores + grafo + ≈ 10 % del motor, por el número de copias, más espacio para el índice nuevo. Con eso decides en cuántos pedazos partirlo.

10

En la vida real

Extra

In real life

Los embeddings no son de un solo caso: aparecen en casi toda la Ronda, cada vez con otra cara.

C01 · Clasificación de fotos. La ResNet-50 ya produce 2,048 números por foto antes de su cabeza. Con ellos, el marketplace puede mostrar «publicaciones parecidas» a la foto de los tenis de Ana sin entrenar nada nuevo. Sirven también para limpiar el dataset: la misma foto aparece en varias publicaciones, y antes de repartir en entrenamiento y examen se quitan las copias exactas con un hash y los recortes y recompresiones con un hash perceptual o con embeddings. Y para vigilar el drift: el vector promedio de las fotos de hoy se compara contra el de una muestra de referencia; si se separan, cambió el contenido (por ejemplo, empiezan a llegar muchas fotos de un tipo de producto que antes casi no había), algo que las estadísticas de color o brillo no ven.

C07 · Búsqueda semántica. Todo lo de esta lección junto: 100 M publicaciones, vectores de 512 números normalizados, un índice HNSW en int8 de ≈ 77 GB partido en 4 pedazos, BM25 en paralelo, RRF, un re-ranker sobre los 30 primeros y 99 de cada 100 búsquedas de texto en menos de 300 ms.

C11 · RAG. Un asistente que responde con los documentos internos de una empresa. ≈ 1.5 millones de documentos se parten en ≈ 12 millones de pedazos de ≈ 400 tokens, cada uno con un embedding de 1,024 números: ≈ 49.2 GB en float32 y ≈ 24.6 GB en float16. Cada pregunta corre BM25 y la búsqueda vectorial en paralelo (50 candidatos cada una), los junta con RRF y un re-ranker elige los 8 pedazos que se le dan al LLM. BM25 es indispensable aquí: códigos de error, siglas y nombres de sistemas internos. Lo ves en la lección de LLMs, como RAG.

C06 · Moderación de contenido. Las copias de una imagen que viola las reglas se suben miles de veces, recortadas o con filtros. Para imágenes se usa un hash perceptual (PDQ, de 256 bits: dos imágenes casi iguales tienen hashes con pocos bits distintos); para textos parecidos, métodos de casi duplicados o embeddings con un umbral estricto. Los casi duplicados se agrupan y una persona decide una sola vez por grupo: 10,000 resubidas de la misma imagen son una sola revisión.

C08 · Recomendación. Para llenar la pantalla de inicio de cada usuario, un modelo de dos torres convierte al usuario en un vector de 128 números y un índice HNSW devuelve ≈ 500 candidatos de entre 100 millones de publicaciones, ≈ 3,100 veces por segundo en el pico. Su índice pesa ≈ 38 GB (12.8 GB de vectores en int8 más 25.6 GB del grafo) y cabe entero en una máquina.

Fuera del ML. El producto punto es la cuenta de cualquier ticket de compra (cantidades por precios), y la idea de «parecido = cerca» es la de cualquier mapa: dos ciudades vecinas tienen coordenadas parecidas.

11

Ejercicios

Esencial

Exercises

Resuélvelos con lápiz antes de abrir la solución. Los primeros cuatro son directos; los últimos dos piden juntar varias ideas.

  1. 1Fácil

    Calcula el producto punto de (2, −1, 3) y (4, 0, −2). ¿Las dos flechas apuntan hacia el mismo lado?

    Ver la solución paso a paso

    Multiplica coordenada por coordenada y suma: 2 × 4 + (−1) × 0 + 3 × (−2) = 8 + 0 − 6 = 2.

    Es positivo, así que forman menos de 90°: apuntan hacia el mismo lado, aunque apenas. Su coseno lo confirma: los largos son √(4 + 1 + 9) = √14 ≈ 3.74 y √(16 + 0 + 4) = √20 ≈ 4.47, y 2 ÷ (3.74 × 4.47) ≈ 0.12, muy cerca de 0.

  2. 2Fácil

    Normaliza el vector (6, 8) y comprueba que el resultado mide 1.

    Ver la solución paso a paso

    Largo: √(6² + 8²) = √(36 + 64) = √100 = 10.

    Normalizado: (6 ÷ 10, 8 ÷ 10) = (0.6, 0.8).

    Comprobación: 0.6² + 0.8² = 0.36 + 0.64 = 1, y √1 = 1. Es la misma dirección que las botas (3, 4).

  3. 3Fácil

    ¿Cuál es la similitud coseno entre (1, 2) y (3, 6)? ¿Y su distancia euclidiana? ¿Qué te dice la diferencia?

    Ver la solución paso a paso

    Producto punto: 1 × 3 + 2 × 6 = 15. Largos: √(1 + 4) = √5 ≈ 2.24 y √(9 + 36) = √45 ≈ 6.71. Coseno: 15 ÷ (2.24 × 6.71) = 15 ÷ 15 = 1. (Exacto: √5 × √45 = √225 = 15.)

    Distancia: √((3 − 1)² + (6 − 2)²) = √(4 + 16) = √20 ≈ 4.47.

    (3, 6) es (1, 2) multiplicado por 3: la misma dirección, el triple de largo. El coseno dice «idénticos»; la distancia dice «lejos». Si el largo no significa nada para tu modelo, el coseno (o normalizar antes) es la medida correcta.

  4. 4Fácil

    Un catálogo tiene 10 millones de embeddings de 768 números. ¿Cuánta memoria ocupan en float32? ¿Y en float16?

    Ver la solución paso a paso

    float32: 10,000,000 × 768 × 4 bytes = 30,720,000,000 bytes ≈ 30.7 GB.

    float16 usa 2 bytes por número, la mitad: ≈ 15.4 GB.

  5. 5Medio

    Para una consulta, la búsqueda exacta dice que los 10 vecinos verdaderos son A, B, C, D, E, F, G, H, I y J. El índice ANN devolvió A, B, C, D, E, F, G, H, X y Y. ¿Cuál es su recall@10? Si te piden 0.95, ¿qué mueves y qué pagas?

    Ver la solución paso a paso

    De los 10 verdaderos, el ANN encontró 8 (de la A a la H). Recall@10 = 8 ÷ 10 = 0.8.

    Para subirlo hay que revisar más del catálogo: en HNSW, una lista de candidatos más larga (ef_search); en un índice por estantes, más estantes. Se paga en latencia y en CPU por búsqueda, y por lo tanto en máquinas para atender el mismo tráfico. Y una consulta no basta para decidir: el recall se mide sobre un set de cientos o miles de consultas.

  6. 6Medio

    Junta con RRF (k = 60: cada lista le da a cada publicación 1 ÷ (60 + su posición), y se suman) tres publicaciones. P está 1.ª en BM25 y no aparece en la lista semántica. Q está 4.ª en BM25 y 2.ª en la semántica. R está 10.ª en las dos. ¿En qué orden quedan?

    Ver la solución paso a paso
    RRF de las tresLa cuenta
    Psolo en BM25
    1/61
    ≈ 0.0164
    Qen las dos
    1/64 + 1/62 ≈ 0.0156 + 0.0161
    ≈ 0.0318
    Ren las dos
    1/70 + 1/70
    ≈ 0.0286
    Resultado
    Orden: Q, R, P.

    P fue la primera de una lista, pero solo de una. Q y R aparecen en las dos, y RRF premia ese acuerdo.

  7. 7Medio

    En C07, el vector de una publicación es el promedio del vector de su foto y el de su título. Si la foto da f = (1, 0) y el título t = (0, 1), los dos normalizados, ¿cuánto mide el promedio? ¿Qué hay que hacer y por qué?

    Ver la solución paso a paso

    Promedio: ((1 + 0) ÷ 2, (0 + 1) ÷ 2) = (0.5, 0.5). Largo: √(0.25 + 0.25) = √0.5 ≈ 0.71. No mide 1.

    Hay que normalizarlo otra vez: (0.5 ÷ 0.71, 0.5 ÷ 0.71) ≈ (0.71, 0.71).

    Si no se normaliza y el índice compara con producto punto, todos los puntajes de esa publicación salen multiplicados por 0.71: quedaría castigada en cada búsqueda frente a las publicaciones bien normalizadas, sin ninguna razón.

  8. 8Medio

    Con los 8 pares revisados del ejemplo de deduplicación, ¿qué precision y recall da un umbral de 0.92? ¿Lo usarías para limpiar un dataset antes de repartirlo? ¿Y para moderar?

    Ver la solución paso a paso

    Con 0.92 se marcan los pares con coseno de 0.92 o más: 1, 2, 3, 4 y 5, o sea 5 pares. De esos, son duplicados de verdad 1, 2, 3 y 5: 4 aciertos.

    • Precision: 4 ÷ 5 = 0.8.
    • Recall: encontró los 4 duplicados que había: 4 ÷ 4 = 1.0.

    Para limpiar un dataset antes de repartirlo es una buena opción: no se escapa ningún duplicado, y el error (el par 4, dos fotos distintas de los mismos tenis) solo hace que dos fotos caigan del mismo lado. Para moderar, donde una decisión se aplica a todo el grupo, es demasiado laxo: juntaría el par 4 y castigaría una foto que es otra.

  9. 9Reto

    C11 tiene 12 millones de pedazos de documentos, cada uno con un embedding de 1,024 números. (a) ¿Cuánta memoria ocupan en float32 y en float16? (b) ¿Cuántas multiplicaciones haría una búsqueda exacta? (c) Si la memoria entrega 200 GB por segundo, ¿cuánto tarda solo en leer los vectores float32 en cada búsqueda? ¿Cabe en los ≈ 50 a 150 ms que C11 estima para toda esa etapa de búsqueda?

    Ver la solución paso a paso

    (a) 12,000,000 × 1,024 × 4 bytes = 49,152,000,000 bytes ≈ 49.2 GB en float32. En float16, la mitad: ≈ 24.6 GB.

    (b) Un producto punto de 1,024 números por cada pedazo: 12,000,000 × 1,024 = 12,288,000,000 ≈ 12.3 mil millones de multiplicaciones por pregunta.

    (c) 49.2 GB ÷ 200 GB por segundo ≈ 0.25 s = ≈ 250 ms, solo para leer. No cabe: es más que toda la etapa. En float16 serían ≈ 123 ms: casi toda la etapa se iría solo en leer, sin contar las multiplicaciones. Por eso C11 usa un índice HNSW, aunque reciba solo ≈ 2 preguntas por segundo: el problema no es el volumen, es la latencia de cada pregunta.

    Un matiz: si un filtro de permisos deja solo unos miles de pedazos (los que una persona puede ver), la búsqueda exacta sobre ese pedacito sí es barata, y los motores la usan en ese caso.

  10. 10Reto

    Demuestra que, si a y b miden 1, la distancia euclidiana al cuadrado entre ellos es 2 − 2 × coseno. Pista: la distancia al cuadrado es (a − b) · (a − b), y el producto punto se puede repartir como una multiplicación normal.

    Ver la solución paso a paso

    La distancia al cuadrado es la suma de las diferencias al cuadrado, que es el producto punto de (a − b) consigo mismo:

    distancia² = (a − b) · (a − b) = a · a − 2 (a · b) + b · b

    Ahora usa lo que sabes:

    • a · a es el largo de a al cuadrado: 1² = 1. Igual b · b = 1.
    • Como los dos miden 1, a · b es exactamente su coseno.

    Entonces: distancia² = 1 − 2 × coseno + 1 = 2 − 2 × coseno.

    Comprobación con tenis y botas normalizados: coseno 0.96, así que 2 − 1.92 = 0.08, y la cuenta directa (0.8 − 0.6)² + (0.6 − 0.8)² = 0.04 + 0.04 = 0.08. La consecuencia práctica: con vectores normalizados, ordenar por distancia o por coseno da los mismos vecinos, así que cualquiera de las dos métricas sirve.

12

Lo que sigue

Esencial

What comes next

En la Ronda:

  • M02 · Patrones de inferencia usa la búsqueda por embeddings como ejemplo del patrón híbrido: los embeddings del catálogo se calculan en lote; el de la consulta, en cada request. Ahí también está la cascada: un modelo barato revisa todo y uno caro, solo lo que queda en duda.
  • M07 · Servidores de modelos sirve los encoders y los re-rankers (en Triton, o en vLLM; son programas que ponen modelos a responder) y menciona /v1/embeddings, la dirección de la API a la que se le piden embeddings.
  • M09 · Datos y features deduplica por hash y por casi duplicados antes de repartir el dataset. Cita un estudio sobre CIFAR-10 y CIFAR-100, dos datasets públicos de fotos chiquitas muy usados en investigación: el 3.3 % de las fotos de examen del primero y el 10 % de las del segundo tienen un casi duplicado en el entrenamiento.
  • M12 · Monitoreo detecta drift en imágenes comparando embeddings de hoy contra una referencia, desde la distancia entre los promedios hasta métodos más finos.
  • Los casos C07 (búsqueda), C11 (RAG), C06 (moderación) y C08 (recomendación) hacen las cuentas de esta lección en grande: 205 GB en float32 y 51.2 GB en int8, el grafo de 25.6 GB, recall@100 ≥ 0.95, RRF con k = 60, el re-ranker de 30 contra 100 candidatos y las dos torres de 128 números.

En el Dojo: la estación 1 (Datos y etiquetas) cruza huellas de contenido contra el golden set para que ninguna foto del examen se cuele al entrenamiento. Es deduplicación de copias exactas; los casi duplicados son el siguiente paso, con lo que viste aquí.

En Sensei: la siguiente lección, Transformers y LLMs, convierte cada token en un embedding, compara palabras con productos punto en la atención y usa todo lo de esta lección en RAG. Después, «Cómo guarda números una computadora» explica int8 y la cuantización; «Matrices, FLOPs y qué tan rápido calcula una GPU», por qué miles de productos punto juntos son una multiplicación de matrices, y «Comparar distribuciones», cómo se mide el drift con más cuidado que un promedio.

13

Palabras que vas a oír

Esencial

Words you will hear

Estos son los conceptos que presentó esta lección. Cada uno tiene su tarjeta con la definición completa.

VectorvectorUna lista ordenada de números. Si tiene 2 números se puede dibujar como un punto (o una flecha) en un plano; si tiene 512, es un punto en un espacio de 512 dimensiones.Más

Cada número es una coordenada, y su posición en la lista importa: (4, 3) no es lo mismo que (3, 4). La cantidad de números se llama la dimensión del vector. Un vector es un tensor de una sola dimensión, una fila de números; ojo, que en un tensor «dimensión» cuenta las direcciones de la tabla, y en un vector, cuántos números trae.

Ejemplo. (4, 3) es un vector de dimensión 2: un punto 4 pasos a la derecha y 3 hacia arriba. El embedding de un producto en C07 de la Ronda es un vector de dimensión 512.

Ver en el glosario →

Embeddingembedding · vector embeddingEl vector que produce un modelo para representar algo (una foto, una frase, un producto) de forma que las cosas parecidas queden cerca.Más

Lo produce una red ya entrenada: entra la foto o el texto y sale una lista de, por ejemplo, 512 números. Cada número por separado no significa nada que se pueda nombrar; lo que importa es la distancia entre vectores. Dos embeddings solo se pueden comparar si los produjo el mismo modelo, en la misma versión.

Analogía. Como las coordenadas de una ciudad en un mapa: ciudades cercanas tienen coordenadas parecidas. Se rompe en que un mapa tiene 2 ejes con nombre (latitud y longitud) y un embedding tiene cientos de ejes sin nombre.

Ejemplo. CLIP ViT-B/32 convierte una foto o un texto en 512 números; BERT-base convierte un texto en vectores de 768; la ResNet-50 de C01, justo antes de su última capa, produce 2,048.

Ver en el glosario →

Producto puntodot product · inner productMultiplicar dos vectores coordenada por coordenada y sumar todo. Da un solo número: positivo si apuntan hacia el mismo lado, cero si son perpendiculares y negativo si apuntan hacia lados opuestos.Más

Es igual a (largo de a) × (largo de b) × el coseno del ángulo entre ellos, así que mezcla dirección y largo: una flecha más larga saca productos punto más grandes con todo. Visto en el dibujo, es la «sombra» de una flecha sobre la dirección de la otra, multiplicada por el largo de la otra. Con vectores normalizados es exactamente la similitud coseno. Es la cuenta más repetida de ML: una neurona es un producto punto más un sesgo, la última capa de un clasificador hace uno por clase, la atención compara vectores con productos punto y una búsqueda exacta hace uno por cada vector del catálogo.

Analogía. Un ticket de compra: las cantidades (2, 1, 3) por los precios (50, 120, 15), sumadas, dan el total, 265. Se rompe en que en un ticket casi nunca hay cantidades negativas, y en un embedding los números negativos son normales: por eso el producto punto puede salir negativo.

Ejemplo. (4, 3) · (3, 4) = 4 × 3 + 3 × 4 = 24. (4, 3) · (−3, 4) = −12 + 12 = 0: son perpendiculares. Con 512 números son 512 multiplicaciones y 511 sumas.

Ver en el glosario →

Similitud cosenocosine similarityQué tan parecida es la dirección de dos vectores: el coseno del ángulo entre ellos. Va de −1 (opuestos) a 1 (misma dirección); 0 es perpendicular.Más

Se calcula como el producto punto de los dos vectores dividido entre el producto de sus largos. No le importa qué tan largo es cada vector, solo hacia dónde apunta. Si los vectores ya vienen normalizados (largo 1), la similitud coseno es simplemente el producto punto, que es lo que calculan los índices vectoriales. Qué coseno cuenta como «muy parecido» depende de cada modelo: se decide con ejemplos, no a ojo.

Ejemplo. a = (4, 3) y b = (3, 4): producto punto 4×3 + 3×4 = 24; cada uno mide 5; cos = 24 ÷ (5 × 5) = 0.96. Casi la misma dirección.

Ver en el glosario →

Distancia euclidianaEuclidean distance · L2 distanceLa distancia «en línea recta» entre dos puntos, la de la regla: la raíz de la suma de las diferencias al cuadrado, coordenada por coordenada.Más

Es el teorema de Pitágoras extendido a cualquier número de dimensiones. A diferencia de la similitud coseno, sí cambia si un vector es más largo que otro. Con vectores normalizados, las dos medidas ordenan a los vecinos igual, porque la distancia al cuadrado es 2 − 2 × coseno.

Ejemplo. Entre (4, 3) y (3, 4): diferencias 1 y −1; al cuadrado 1 y 1; suman 2; la raíz de 2 ≈ 1.41.

Ver en el glosario →

Normalización L2L2 normalizationDividir un vector entre su largo para que mida exactamente 1. Conserva la dirección y borra el largo.Más

El largo de Pitágoras (la raíz de la suma de los cuadrados) se llama norma L2; de ahí el nombre. Con embeddings normalizados, la similitud coseno es solo el producto punto, y la distancia euclidiana y el coseno ordenan a los vecinos igual (distancia² = 2 − 2 × coseno). No es la normalización de las fotos, que le resta a cada píxel un promedio y lo divide entre una desviación. Si promedias dos vectores normalizados, el resultado ya no mide 1 y hay que volver a normalizar.

Ejemplo. (4, 3) mide √(16 + 9) = 5; normalizado queda (0.8, 0.6), y 0.8² + 0.6² = 1. El vector (8, 6), que mide 10, también queda en (0.8, 0.6).

Ver en el glosario →

Deduplicacióndeduplication · dedupEncontrar las copias y casi copias de un mismo contenido para quedarse con una o tratarlas como grupo. Con embeddings: si el coseno entre dos cosas pasa un umbral, se marcan como duplicadas.Más

Las copias exactas se detectan antes y más barato con un hash de los bytes; los recortes, recompresiones y filtros, con un hash perceptual o con embeddings y un umbral elegido con pares revisados por personas. El umbral depende de qué cuesta cada error: antes de repartir un dataset conviene recall alto, para que la misma foto no quede en train y en test (leakage); en moderación, donde una decisión se aplica a todo el grupo, conviene precision alta. En un catálogo grande no se compara todo contra todo: para cada elemento nuevo se buscan sus vecinos más cercanos y solo a ellos se les aplica el umbral.

Ejemplo. Con 8 pares revisados (4 duplicados de verdad), un umbral de 0.95 marca 4 pares y acierta 3: precision 0.75 y recall 0.75. Uno de 0.90 encuentra los 4 duplicados (recall 1.0), con precision 0.67.

Ver en el glosario →

Búsqueda semánticasemantic searchBuscar por significado y no por palabras: se compara el embedding de la consulta con los de los documentos y se devuelven los más cercanos.Más

Encuentra sinónimos («chamarra» y «chaqueta»), descripciones vagas («algo para ir a la playa») y, con un modelo multimodal, fotos a partir de un texto. Falla con lo exacto: un modelo, una talla o un código quedan cerca de otros parecidos. Por eso casi siempre va junto con BM25 en una búsqueda híbrida. Por debajo es una búsqueda de vecinos más cercanos sobre un índice vectorial.

Ejemplo. En C07 de la Ronda, «tenis blancos» se convierte en un vector de 512 números y el índice devuelve las publicaciones más cercanas de entre 100 millones, en milisegundos.

Ver en el glosario →

Vecinos más cercanosnearest neighbors · k-NNDado un vector, encontrar los k vectores de una colección que están más cerca de él. Es la operación que hay debajo de toda búsqueda por embeddings.Más

La forma exacta (fuerza bruta, brute force) compara contra todos: con 100 millones de vectores de 512 números son 51.2 mil millones de multiplicaciones por búsqueda. Por eso en producción casi siempre se usa la versión aproximada, el ANN.

Ejemplo. k = 30: los 30 productos cuyo embedding se parece más al de la búsqueda «tenis blancos».

Ver en el glosario →

ANN (vecinos cercanos aproximados)approximate nearest neighborsBuscar los vecinos más cercanos sin compararse contra todos: se revisa solo una parte bien elegida de la colección. Es muchísimo más rápido, a cambio de perder de vez en cuando algún vecino verdadero.Más

Cuánto se pierde se mide con el recall contra la búsqueda exacta: de los 100 vecinos verdaderos, cuántos encontró el ANN. Mirar más de la colección sube el recall y baja la velocidad: es una perilla, no un defecto. Las técnicas más usadas son los grafos (HNSW) y los grupos o «estantes» (IVF).

Analogía. Una biblioteca ordenada por estantes de tema: para buscar un libro de cocina vas directo al estante de cocina y no revisas los otros. Se rompe en que un libro que quedó en el estante de al lado (un libro de nutrición con recetas) no lo vas a ver, aunque fuera el que querías.

Ejemplo. En C07 de la Ronda se pide recall@100 ≥ 0.95: de los 100 vecinos exactos, el ANN tiene que encontrar al menos 95.

Ver en el glosario →

Índice vectorialvector indexUna estructura de datos que guarda millones de vectores ya organizados (en grupos o en un grafo) para encontrar los más cercanos a uno nuevo sin revisarlos todos.Más

Se construye antes, sin prisa, y se consulta en milisegundos. Los más usados, como HNSW, viven en la memoria: 100 millones de vectores de 512 números pesan ≈ 205 GB en float32. Lo ofrecen librerías (FAISS), bases de datos vectoriales y motores de búsqueda (OpenSearch k-NN). Cada uno trae perillas que cambian velocidad, memoria y recall.

Ejemplo. En C07 de la Ronda: un índice HNSW con los vectores guardados en int8 (51.2 GB) más el grafo (≈ 25.6 GB), partido en 4 pedazos.

Ver en el glosario →

HNSWHierarchical Navigable Small WorldUn índice de vecinos aproximados muy usado: conecta cada vector con unos cuantos vecinos y busca saltando de vecino en vecino hacia la consulta, con niveles de atajos para llegar rápido.Más

Arriba hay pocos puntos unidos por saltos largos; abajo, todos los puntos con caminos cortos. La búsqueda empieza arriba, baja de nivel y termina con pasos finos, así que el trabajo crece muy despacio con el tamaño del catálogo (como log N). Sus perillas: M, cuántos vecinos guarda cada punto (más recall y más memoria), y ef_search, cuántos candidatos mantiene mientras busca (más recall y más latencia). Vive en memoria y borrar de él es caro. Lo propusieron Malkov y Yashunin en 2016.

Analogía. Manejar a otra ciudad: autopista hasta la ciudad correcta y luego calles hasta la puerta. Se rompe en que HNSW no lleva mapa: en cada punto solo ve a sus vecinos, y por eso a veces se queda con uno que no es el más cercano de todos.

Ejemplo. En C07 de la Ronda, con M = 32 el grafo guarda hasta 64 vecinos por vector en el nivel de abajo: 64 × 4 bytes = 256 bytes; para 100 M vectores, ≈ 25.6 GB además de los vectores.

Ver en el glosario →

Dimensionalidad (dimensión)dimensionality · embedding dimensionCuántos números tiene un vector. En un embedding es una decisión de diseño: más números dan lugar a más detalle, pero la memoria y el trabajo de cada búsqueda crecen en proporción.Más

Un embedding de 1,024 números ocupa el doble de memoria y cuesta el doble por comparación que uno de 512. Cada duplicación suele ayudar menos que la anterior, así que se busca la dimensión más chica que cumpla con la calidad. En muchas dimensiones pasan cosas raras: dos vectores al azar salen casi perpendiculares (en 512 dimensiones, su coseno típico es ≈ 0.04), así que la escala de los cosenos depende de cada modelo.

Ejemplo. En la Ronda, C08 usa 128 (100 M vectores en int8 = 12.8 GB), C07 usa 512 (51.2 GB en int8) y C11 usa 1,024 (12 M pedazos de documentos = 49.2 GB en float32).

Ver en el glosario →

BM25BM25 · Okapi BM25La fórmula clásica para ordenar documentos por las palabras de la búsqueda: premia las palabras que coinciden, más si son raras, sin premiar de más la repetición ni los textos largos.Más

Funciona sobre un índice invertido: para cada palabra, la lista de documentos que la contienen. El peso de una palabra (su IDF) crece mientras más rara es: en 1,000,000 de documentos, una palabra que está en 500 pesa ≈ ln 2,000 ≈ 7.6 y una que está en 100,000, ≈ ln 10 ≈ 2.3. Repetir una palabra se satura: con k1 = 1.2, diez repeticiones valen menos del doble que una. Es la búsqueda por defecto de Lucene, Elasticsearch y OpenSearch. Es muy buena con marcas, modelos, tallas y códigos, y mala con sinónimos y descripciones vagas; por eso se combina con embeddings en la búsqueda híbrida.

Ejemplo. En C07 de la Ronda, BM25 en OpenSearch es la búsqueda de hoy: falla con «chamarra» contra «chaqueta» (≈ 8 % de las búsquedas no dan resultados), pero encuentra «iPhone 13 Pro 128» exacto. Se queda como parte del híbrido y como respaldo.

Ver en el glosario →

Búsqueda híbridahybrid searchCorrer una búsqueda por palabras (BM25) y una por significado (embeddings) en paralelo sobre el mismo catálogo y juntar sus listas. Cada una tapa los huecos de la otra.Más

Las listas se juntan casi siempre con RRF (Reciprocal Rank Fusion, Cormack y otros, 2009): cada lista le da a cada documento 1 ÷ (k + su posición), con k = 60, y se suman. Usa solo posiciones, así que no hay que comparar puntajes de escalas distintas. Si la parte semántica falla, la de palabras sigue respondiendo sola.

Ejemplo. Con RRF (k = 60), una publicación 1.ª en la lista semántica y 3.ª en la de BM25 suma 1/61 + 1/63 ≈ 0.0323; una 1.ª solo en BM25, 1/61 ≈ 0.0164. C07 y C11 de la Ronda la usan.

Ver en el glosario →

Retrieval (recuperación)retrieval · candidate generationLa primera etapa de una búsqueda: traer rápido y barato unos cientos de candidatos de entre millones, aunque el orden no sea perfecto.Más

Suele ser una búsqueda en un índice vectorial, muchas veces junto con una búsqueda por palabras (BM25): eso es la búsqueda híbrida. Su trabajo es no dejar fuera lo bueno; ordenarlo bien le toca al re-ranking (reordenar). En RAG es la parte que busca los documentos antes de que el LLM responda.

Ejemplo. En C07 de la Ronda, el índice vectorial aporta ≈ 200 candidatos y BM25 otros ≈ 200, de entre 100 millones de publicaciones, en unas decenas de milisegundos. El re-ranker solo ve los 30 primeros.

Ver en el glosario →

Re-ranking (reordenar)re-ranking · rerankerLa segunda etapa de una búsqueda: después de una primera búsqueda rápida, un modelo más caro y más preciso vuelve a calificar solo los primeros candidatos y los reordena.Más

Puede darse el lujo de ser lento por candidato porque mira pocos (decenas, no millones). Un re-ranker típico (un cross-encoder) lee la consulta y el candidato juntos, en vez de comparar dos vectores calculados por separado, como hace un modelo de dos torres. Cuántos candidatos reordenar es una decisión de costo.

Ejemplo. En C07 de la Ronda, reordenar los 100 primeros de cada búsqueda de texto pediría ≈ 24 GPUs L4; los 30 primeros, ≈ 8.

Ver en el glosario →

MultimodalmultimodalQue trabaja con más de un tipo de dato a la vez, como texto e imágenes. Un embedding multimodal pone fotos y textos en el mismo espacio, para compararlos directamente.Más

CLIP (OpenAI, 2021) tiene dos redes, una para imágenes y otra para texto, entrenadas juntas con aprendizaje contrastivo sobre unos 400 millones de pares (imagen, texto) para que una foto y su descripción queden cerca. Así, el vector de «tenis blancos» queda cerca del vector de la foto de unos tenis blancos.

Ejemplo. En C07 de la Ronda, un encoder tipo CLIP (512 dimensiones) permite buscar productos con texto o subiendo una foto.

Ver en el glosario →

Aprendizaje contrastivocontrastive learningEntrenar un encoder con pares que deben quedar juntos (una foto y su título): se acercan los vectores de cada par y se alejan los de las cosas que no son pareja.Más

En cada batch de B pares, cada foto tiene 1 título correcto y B − 1 incorrectos: los de las otras fotos (los «negativos dentro del batch»). Se calcula una tabla de B × B cosenos, se divide entre una temperatura, se aplica un softmax por fila y la pérdida es la entropía cruzada con la diagonal como respuesta correcta. Así se entrenó CLIP, y así se entrenan muchos modelos de embeddings de texto y los modelos de dos torres de recomendación.

Analogía. Un memorama: volteas una foto y tienes que encontrar su tarjeta de texto entre muchas. Se rompe en que en el memorama las tarjetas no cambian; aquí cada error mueve las tarjetas (los vectores) para que la próxima vez la pareja quede más cerca.

Ejemplo. Con cosenos 0.80 (su título), 0.55 y 0.10, y temperatura 0.1, la foto de tenis le da 92 % a su título y su pérdida es −ln 0.92 ≈ 0.08. CLIP usó ≈ 400 millones de pares y batches de 32,768.

Ver en el glosario →

Dos torres (two-tower)two-tower model · dual encoder · bi-encoderUn modelo con dos redes que convierten por separado dos tipos de cosas (consultas y productos, o usuarios y publicaciones) en vectores del mismo espacio. El parecido es el producto punto entre ellos.Más

Como cada lado se calcula por separado, los vectores del catálogo se calculan antes, en lote, y en cada consulta solo se calcula uno: eso permite buscar entre millones con un ANN. Es más rápido y menos preciso que un cross-encoder, que lee la consulta y el candidato juntos; por eso se combinan: dos torres para el retrieval (recuperación) y cross-encoder para el re-ranking (reordenar). Se entrena con aprendizaje contrastivo. Las dos torres se publican juntas, con la misma versión.

Ejemplo. En C08 de la Ronda, la torre de usuario calcula un vector de 128 números en cada visita al inicio de la app, y el índice que llenó la torre de publicaciones devuelve ≈ 500 candidatos de entre 100 M. En C07, el encoder tipo CLIP tiene una torre de imagen y una de texto.

Ver en el glosario →

14

Chequeo

Esencial

Check

Intenta responder en tu cabeza antes de abrir cada respuesta.

  1. 1¿Qué es un embedding y por qué sirve para buscar cosas parecidas?Ver respuesta

    Es el vector que un modelo produce para una cosa, de forma que cosas parecidas reciben vectores cercanos. Así, «buscar algo parecido» se vuelve «buscar el vector más cercano», que es una cuenta.

  2. 2Calcula la similitud coseno entre (1, 0) y (0, 2). ¿Qué significa?Ver respuesta

    Producto punto: 1 × 0 + 0 × 2 = 0. El coseno es 0 ÷ (1 × 2) = 0. Son perpendiculares: para el modelo, no tienen nada que ver. (El largo distinto no importa: el coseno solo mira la dirección.)

  3. 3¿Por qué se guardan los embeddings normalizados?Ver respuesta

    Porque con largo 1 el coseno es solo el producto punto, una cuenta más barata, y la distancia y el coseno ordenan igual a los vecinos. Además, ningún vector «gana» solo por ser largo.

  4. 4¿Por qué no se hace la búsqueda exacta sobre 100 millones de vectores?Ver respuesta

    Porque cada búsqueda tendría que comparar contra todos: 100 M × 512 = 51.2 mil millones de multiplicaciones, y leer ≈ 205 GB de memoria. Con cientos o miles de búsquedas por segundo no hay forma de pagarlo. Un ANN revisa solo una parte bien elegida, a cambio de perder de vez en cuando algún vecino verdadero.

  5. 5Un índice ANN tiene recall@100 = 0.90 y te piden 0.95. ¿Qué perilla mueves y qué pagas?Ver respuesta

    Revisar más del catálogo: más estantes en un índice por grupos, una lista de candidatos más larga (ef_search) en HNSW. Sube el recall, pero cada búsqueda tarda más y necesitas más máquinas para la misma cantidad de búsquedas por segundo.

  6. 6¿Por qué C07 no usa solo búsqueda semántica?Ver respuesta

    Porque los embeddings confunden lo exacto: «iPhone 13 Pro 128» queda cerca de «iPhone 12 Pro 256». BM25 encuentra marcas, modelos y tallas; los embeddings, sinónimos y descripciones vagas. Juntas, con RRF, cada una tapa los huecos de la otra, y BM25 queda de respaldo.

  7. 7¿Por qué el re-ranker solo mira los primeros candidatos y no todo el catálogo?Ver respuesta

    Porque es caro por candidato: lee la búsqueda y cada candidato juntos. Sobre decenas de candidatos es pagable; sobre millones, imposible. El retrieval barato trae los candidatos y el re-ranker caro ordena bien los primeros.

Sigue · 08Transformers y LLMsLo básico de ML Léelo en la RondaM02 · Patrones de inferenciaLo que acabas de leer, con el ojo del entrevistador. ← 06 · Redes neuronales y CNNs