Phase 11 - Lesson 04
Embeddings y Representaciones Vectoriales
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
El texto es discreto. Las matemáticas son continuas. Cada vez que le pides a un LLM que encuentre documentos "similares", compare significados o busque más allá de las palabras clave, estás confiando en un puente entre estos dos mundos. Ese puente es un embedding. Si no entiendes los embeddings, no entiendes la IA moderna. Simplemente la usas.
Tipo: Build Lenguajes: Python Prerrequisitos: Fase 11, Lección 01 (Ingeniería de Prompts) Tiempo: ~75 minutos Relacionado: La Fase 5 · 22 (Inmersión Profunda en Modelos de Embedding) cubre denso vs disperso vs multi-vector, truncamiento Matryoshka y selección de modelo por eje. Esta lección se centra en el pipeline de producción (bases de datos vectoriales, HNSW, matemática de similitud). Lee la Fase 5 · 22 antes de elegir un modelo.
Objetivos de Aprendizaje
- Generar embeddings de texto utilizando proveedores de API y modelos de código abierto, y calcular la similitud del coseno entre ellos
- Explicar por qué los embeddings resuelven el problema de la incompatibilidad de vocabulario que la búsqueda por palabras clave no puede manejar
- Construir un índice de búsqueda semántica que recupere documentos por significado en lugar de por coincidencia exacta de palabras clave
- Evaluar la calidad de los embeddings utilizando benchmarks de recuperación (precisión@k, recall) y elegir el modelo de embedding adecuado para tu tarea
El Problema
Tienes 10,000 tickets de soporte. Un cliente escribe "mi pago no se procesó". Necesitas encontrar tickets anteriores similares. La búsqueda por palabras clave encuentra tickets que contienen "pago" y "no se procesó". Pasa por alto "fallo en la transacción", "el cargo fue rechazado" y "error de facturación". Estos tickets describen exactamente el mismo problema con palabras completamente diferentes.
Este es el problema de la incompatibilidad de vocabulario (vocabulary mismatch). El lenguaje humano tiene docenas de formas de decir lo mismo. La búsqueda por palabras clave trata cada palabra como un símbolo independiente sin significado. No puede saber que "rechazado" y "no se procesó" se refieren al mismo concepto.
Necesitas una representación de texto donde el significado, y no la ortografía, determine la similitud. Necesitas una forma de colocar "mi pago no se procesó" y "la transacción fue rechazada" cerca en algún espacio matemático, mientras alejas "mi pago llegó a tiempo", a pesar de que compartan la palabra "pago".
Esa representación es un embedding.
El Concepto
¿Qué es un Embedding?
Un embedding es un vector denso de números de punto flotante que representa el significado de un texto. La palabra "denso" importa: cada dimensión contiene información, a diferencia de las representaciones dispersas (bag-of-words, TF-IDF) donde la mayoría de las dimensiones son cero.
"The cat sat on the mat" se convierte en algo como [0.023, -0.041, 0.087, ..., 0.012], una lista de 768 a 3072 números según el modelo. Estos números codifican el significado. Nunca los inspeccionas directamente. Los comparas.
El Gran Avance de Word2Vec
En 2013, Tomas Mikolov y sus colegas de Google publicaron Word2Vec. La idea central: entrenar una red neuronal para predecir una palabra a partir de sus vecinas (or las vecinas a partir de una palabra), y los pesos de la capa oculta se convierten en representaciones vectoriales significativas.
El famoso resultado:
king - man + woman = queen
La aritmética vectorial en embeddings de palabras captura relaciones semánticas. La dirección de "man" a "woman" es aproximadamente la misma dirección que la de "king" a "queen". Este fue el momento en que el campo se dio cuenta de que la geometría podía codificar el significado.
Word2Vec producía vectores de 300 dimensiones. Cada palabra obtenía un solo vector independientemente del contexto. "Bank" en "river bank" (orilla del río) y "bank account" (cuenta bancaria) tenía el mismo embedding. Esta limitación impulsó la siguiente década de investigación.
De Palabras a Sentencias
Los embeddings de palabras representan tokens individuales. Los sistemas en producción necesitan incrustar sentencias completas, párrafos o documentos. Surgieron cuatro enfoques:
Promedio (Averaging): toma la media de todos los vectores de palabras en la oración. Económico, con pérdida de información, pero sorprendentemente decente para textos cortos. Pierde el orden de las palabras por completo: "dog bites man" (el perro muerde al hombre) y "man bites dog" (el hombre muerde al perro) obtienen embeddings idénticos.
Token CLS: los modelos transformer (BERT, 2018) generan un embedding de token especial [CLS] que representa toda la entrada. Es mejor que el promedio, pero el token [CLS] se entrenó para la predicción de la siguiente oración, no para la similitud.
Aprendizaje contrastivo (Contrastive learning): entrena el modelo explícitamente para acercar pares similares y alejar pares disímiles. Sentence-BERT (Reimers & Gurevych, 2019) utilizó este enfoque y se convirtió en la base de los modelos de embedding modernos. Dados "How do I reset my password?" y "I need to change my password", el modelo aprende que estos deben tener vectores casi idénticos.
Embeddings ajustados por instrucciones (Instruction-tuned embeddings): el enfoque más reciente. Modelos como E5 y GTE aceptan un prefijo de tarea ("search_query:", "search_document:") que le dice al modelo qué tipo de embedding producir. Esto permite que un único modelo sirva para múltiples tareas.
graph LR
subgraph "2013: Word2Vec"
W1["king"] --> V1["[0.2, -0.1, ...]"]
W2["queen"] --> V2["[0.3, -0.2, ...]"]
end
subgraph "2019: Sentence-BERT"
S1["¿Cómo restablezco mi contraseña?"] --> E1["[0.04, 0.12, ...]"]
S2["Necesito cambiar mi contraseña"] --> E2["[0.05, 0.11, ...]"]
end
subgraph "2024: Ajustado por Instrucciones"
I1["search_query: restablecer contraseña"] --> T1["[0.08, 0.09, ...]"]
I2["search_document: Para restablecer su contraseña, haga clic..."] --> T2["[0.07, 0.10, ...]"]
end
Modelos de Embedding Modernos
El mercado se ha consolidado en un puñado de opciones de grado de producción (puntuaciones MTEB a principios de 2026, MTEB v2):
| Modelo | Proveedor | Dimensiones | MTEB | Contexto | Costo / 1M tokens |
|---|---|---|---|---|---|
| Gemini Embedding 2 | 3072 (Matryoshka) | 67.7 (recuperación) | 8192 | $0.15 | |
| embed-v4 | Cohere | 1024 (Matryoshka) | 65.2 | 128K | $0.12 |
| voyage-4 | Voyage AI | 1024/2048 (Matryoshka) | 66.8 | 32K | $0.12 |
| text-embedding-3-large | OpenAI | 3072 (Matryoshka) | 64.6 | 8192 | $0.13 |
| text-embedding-3-small | OpenAI | 1536 (Matryoshka) | 62.3 | 8192 | $0.02 |
| BGE-M3 | BAAI | 1024 (dense+sparse+ColBERT) | 63.0 multilingüe | 8192 | Pesos abiertos |
| Qwen3-Embedding | Alibaba | 4096 (Matryoshka) | 66.9 | 32K | Pesos abiertos |
| Nomic-embed-v2 | Nomic | 768 (Matryoshka) | 63.1 | 8192 | Pesos abiertos |
MTEB (Massive Text Embedding Benchmark) v2 cubre más de 100 tareas en recuperación, clasificación, agrupamiento (clustering), reordenación (reranking) y resumen. Más alto es mejor. Para 2026, los modelos de pesos abiertos (Qwen3-Embedding, BGE-M3) igualan o superan a los modelos alojados patentados en la mayoría de los ejes. Gemini Embedding 2 lidera en recuperación pura; Voyage/Cohere lideran en dominios específicos (finanzas, derecho, código). Realiza siempre benchmarks con tus propias consultas antes de comprometerte.
Métricas de Similitud
Dados dos vectores de embedding, hay tres formas de medir qué tan similares son:
Similitud del coseno (Cosine similarity): el coseno del ángulo entre dos vectores. Varía de -1 (opuesto) a 1 (dirección idéntica). Ignora la magnitud: una oración de 10 palabras y un documento de 500 palabras pueden obtener una puntuación de 1.0 si apuntan en la misma dirección. Este es el valor predeterminado para el 90% de los casos de uso.
cosine_sim(a, b) = dot(a, b) / (||a|| * ||b||)
Producto escalar (Dot product): el producto interno bruto de dos vectores. Es idéntico a la similitud del coseno cuando los vectores están normalizados (longitud unitaria). Es más rápido de calcular. Los embeddings de OpenAI están normalizados, por lo que el producto escalar y el coseno ofrecen la misma clasificación.
dot(a, b) = sum(a_i * b_i)
Distancia Euclidiana (L2): distancia en línea recta en el espacio vectorial. Menor = más similar. Sensible a las diferencias de magnitud. Úsala cuando importe la posición absoluta en el espacio, no solo la dirección.
L2(a, b) = sqrt(sum((a_i - b_i)^2))
Cuando usar | Evitar cuando:
| Métrica | Cuándo usar | Cuándo evitar |
|---|---|---|
| Similitud del coseno | Al comparar textos de diferentes longitudes; en la mayoría de las tareas de recuperación | Cuando la magnitud contiene información |
| Producto escalar | Cuando los embeddings ya están normalizados; velocidad máxima | Cuando los vectores tienen magnitudes variables |
| Distancia Euclidiana | Agrupamiento (clustering); problemas espaciales de vecinos más cercanos | Al comparar documentos de longitudes extremadamente diferentes |
Bases de Datos Vectoriales y HNSW
Una búsqueda de similitud por fuerza bruta compara la consulta con cada vector almacenado. Con 1 millón de vectores de 1536 dimensiones, eso representa 1.5 mil millones de operaciones de multiplicación y suma por consulta. Demasiado lento.
Las bases de datos vectoriales resuelven esto con algoritmos de Vecino Más Cercano Aproximado (ANN). El algoritmo dominante es HNSW (Hierarchical Navigable Small World):
- Construye un grafo de vectores de múltiples capas
- Las capas superiores son dispersas: conexiones de largo alcance entre clústeres distantes
- Las capas inferiores son densas: conexiones detalladas entre vectores cercanos
- La búsqueda comienza en la capa superior, desciendo de forma codiciosa (greedy) para refinar los resultados
- Devuelve resultados top-k aproximados en un tiempo de O(log n) en lugar de O(n)
HNSW sacrifica una pequeña pérdida de precisión (generalmente del 95 al 99% de recall) a cambio de enormes aumentos de velocidad. Con 10 millones de vectores, la fuerza bruta tarda segundos; HNSW tarda milisegundos.
graph TD
subgraph "Capas HNSW"
L2["Capa 2 (dispersa)"] -->|"saltos largos"| L1["Capa 1 (media)"]
L1 -->|"saltos más cortos"| L0["Capa 0 (densa, todos los vectores)"]
end
Q["Vector de consulta"] -->|"entrar en la parte superior"| L2
L0 -->|"vecinos más cercanos"| R["Resultados top-k"]
Opciones de producción:
| Base de datos | Tipo | Mejor para | Escala máxima |
|---|---|---|---|
| Pinecone | SaaS administrado | Producción sin operaciones (zero-ops) | Miles de millones |
| Weaviate | Código abierto | Autoalojado, búsqueda híbrida | 100M+ |
| Qdrant | Código abierto | Alto rendimiento, filtrado | 100M+ |
| ChromaDB | Embebido | Prototipado, desarrollo local | 1M |
| pgvector | Extensión de Postgres | Si ya utilizas Postgres | 10M |
| FAISS | Biblioteca | En proceso, investigación | 1B+ |
Estrategias de Fragmentación (Chunking)
Los documentos son demasiado largos para incrustarse como vectores únicos. Un PDF de 50 páginas cubre docenas de temas: su embedding se convierte en un promedio de todo, sin parecerse a nada en particular. Debes dividir los documentos en fragmentos (chunks) e incrustar cada uno.
Fragmentación de tamaño fijo (Fixed-size chunking): divide el texto cada N tokens con una superposición de M tokens. Simple y predecible. Funciona bien cuando los documentos no tienen una estructura clara. Un fragmento de 512 tokens con una superposición de 50 tokens: el fragmento 1 corresponde a los tokens 0-511, el fragmento 2 a los tokens 462-973.
Fragmentación basada en oraciones (Sentence-based chunking): divide en los límites de las oraciones, agrupando oraciones hasta alcanzar el límite de tokens. Cada fragmento es al menos una oración completa. Es mejor que el tamaño fijo porque nunca corta una idea por la mitad.
Fragmentación recursiva (Recursive chunking): intenta dividir primero en el límite más grande (encabezados de sección). Si sigue siendo demasiado grande, intenta con los límites de párrafo, luego de oración y finalmente con límites de caracteres. Así es como funciona RecursiveCharacterTextSplitter de LangChain y funciona bien para conjuntos de documentos con formatos mixtos.
Fragmentación semántica (Semantic chunking): incrusta cada oración y luego agrupa oraciones consecutivas cuyas representaciones vectoriales sean similares. Cuando la similitud de los embeddings cae por debajo de un umbral, comienza un nuevo fragmento. Es costosa (requiere incrustar cada oración individualmente) pero produce los fragmentos más coherentes.
| Estrategia | Complejidad | Calidad | Mejor para |
|---|---|---|---|
| Tamaño fijo | Baja | Aceptable | Texto no estructurado, logs |
| Basada en oraciones | Baja | Buena | Artículos, correos electrónicos |
| Recursiva | Media | Buena | Markdown, HTML, documentos mixtos |
| Semántica | Alta | Excelente | Calidad de recuperación crítica |
El punto ideal para la mayoría de los sistemas: fragmentos de 256 a 512 tokens con una superposición de 50 tokens.
Bi-Encoders vs Cross-Encoders
Un bi-encoder incrusta la consulta y los documentos de forma independiente, y luego compara los vectores. Es rápido: incrustas la consulta una vez y la comparas con los embeddings de documentos precalculados. Esto es lo que se utiliza para la recuperación.
Un cross-encoder toma la consulta y un documento como una única entrada y genera una puntuación de relevancia. Es lento: procesa cada par consulta-documento a través del modelo completo. Sin embargo, es mucho más preciso porque puede prestar atención a los tokens de la consulta y del documento simultáneamente.
El patrón de producción: el bi-encoder recupera los 100 mejores candidatos, y el cross-encoder los reordena para obtener los 10 mejores. Este es el pipeline de recuperación y reordenamiento (retrieve-then-rerank).
graph LR
Q["Consulta"] --> BE["Bi-Encoder: incrustar consulta"]
BE --> VS["Búsqueda vectorial: top 100"]
VS --> CE["Cross-Encoder: reordenar"]
CE --> R["Resultados top 10"]
Modelos de reordenación (reranking): Cohere Rerank 3.5 (