TF-IDF: Ponderando lo que Importa
Empecemos con una de las ideas de recuperación más simples: contar cuántas veces aparece cada palabra de la consulta en cada documento y devolver el documento con mayor coincidencia. Pero, ¿qué significa "mayor"? ¿Elegimos el documento que coincide con más palabras distintas de la consulta (digamos 5 de 7), o el que coincide con menos palabras pero una de ellas aparece 20 veces? ¿Y qué pasa con palabras de bajo valor como "el" o "de" — deberían contar? TF-IDF responde a cada una de estas preguntas combinando dos señales.
La primera señal maneja la repetición. Si "transformer" aparece 10 veces en un documento y una vez en otro, el primero probablemente trata más sobre transformers. Eso es la Frecuencia de Término (TF). Los conteos brutos funcionan en principio, pero un documento que repite una palabra 50 veces no es 50× más relevante que uno que la dice una vez, así que la mayoría de implementaciones aplican escala logarítmica para aplanar los valores extremos:
Pero la frecuencia por sí sola no resuelve el problema de "el". Palabras como "el", "es" y "de" aparecen en prácticamente todos los documentos, así que coincidir en ellas no dice nada sobre qué hace especial a un documento. Lo que realmente queremos es impulsar palabras que son raras en el corpus — palabras que distinguen un documento del resto. Eso es la Frecuencia Inversa de Documento (IDF), donde $N$ es el número total de documentos y $\text{df}(t)$ cuenta cuántos contienen el término $t$:
Multiplica TF por IDF y obtienes un peso que es alto cuando un término aparece a menudo en este documento particular pero raramente en el corpus. "El" queda aplastado (aparece en todos los documentos, así que IDF es casi cero). "Transformer" en un corpus de machine learning queda impulsado (aparece en una fracción pequeña de documentos). Exactamente la señal que queremos.
Para puntuar una consulta contra un documento, representamos ambos como vectores sobre el vocabulario completo — un peso por palabra, cero para las ausentes — y calculamos el producto punto:
La suma solo recorre palabras presentes en ambos. La mayoría de palabras no aparece en ninguno, así que estos vectores son extremadamente dispersos. Esa dispersidad es lo que hace rápido a TF-IDF: un índice invertido mapea cada palabra a la lista de documentos que la contienen, así que puntuar una consulta solo toca las listas de postings de sus términos, no cada documento del corpus.
BM25: Saturación y Normalización por Longitud
TF-IDF funciona, pero pruébalo en un corpus real y dos problemas se hacen evidentes. Primero, la frecuencia de términos crece sin límite: un documento que menciona "neural" 50 veces puntúa mucho más alto que uno que lo menciona una vez, aunque probablemente no sea tanto más relevante. El logaritmo en TF lo amortigua, pero no lo suficiente. Segundo, los documentos largos acumulan más palabras y tienden a puntuar más alto simplemente por ser más largos — un documento legal de 10,000 palabras superará a un resumen de 200 palabras en casi cualquier consulta, incluso cuando el resumen es más pertinente.
BM25 (Best Match 25) (Robertson et al., 1994) corrige ambos problemas en una sola fórmula:
Dos parámetros controlan la corrección:
- pone un techo a la frecuencia de términos. A medida que count$(t,d)$ crece, la puntuación se acerca a un techo finito de $(k_1 + 1) \cdot \text{IDF}(t)$ en lugar de subir indefinidamente. Decir "neural" 50 veces apenas ayuda más que decirlo 10. Esa es la saturación que nos faltaba.
- penaliza la longitud. El denominador divide la longitud del documento $|d|$ por el promedio del corpus $\text{avgdl}$. Con $b=0.75$ (el valor por defecto), un documento el doble de largo que el promedio necesita coincidencias proporcionalmente más fuertes para puntuar igual que uno más corto. Con $b=0$, se ignora la longitud por completo.
Para verlo en acción: toma un documento legal de 10,000 palabras y un resumen de 200, ambos mencionando el término de consulta dos veces. Sin normalización por longitud, TF-IDF los trata igual (mismo conteo bruto). Con $b=0.75$, BM25 reduce la puntuación del documento legal porque dos menciones en 10,000 palabras es mucho menos concentrado que dos menciones en 200.
BM25 sigue siendo la línea base dispersa dominante. Elasticsearch y OpenSearch lo usan por defecto. Cuando la gente dice "búsqueda por palabras clave", casi siempre se refiere a BM25.
El Problema de Incompatibilidad Vocabular
Todos los métodos que hemos visto comparten un punto ciego: solo encuentran documentos que usan las mismas palabras exactas que la consulta. Busca "paro cardíaco" y BM25 no devuelve nada si todos los documentos relevantes dicen "ataque al corazón". La fórmula de puntuación podría ser perfecta y no importaría — cero términos compartidos significa puntuación cero.
Este problema de incompatibilidad vocabular aparece en varias formas:
- "automóvil" vs "carro", "comenzar" vs "iniciar" — mismo concepto, diferente forma superficial.
- "¿Cómo funciona un transformer?" no encuentra documentos titulados "Mecanismo de auto-atención explicado".
- Sin stemming, "corriendo" no encuentra "corrió" o "corre".
Hay soluciones manuales: diccionarios de sinónimos, stemming ("corriendo" → "corr"), o retroalimentación por pseudo-relevancia (tomar los primeros resultados, extraer sus términos clave, relanzar la consulta con esos términos). Ayudan, pero son frágiles — las listas de sinónimos se desactualizan, el stemming falla con vocabulario técnico ("transformer" el modelo vs "transformador" el dispositivo eléctrico), y los ciclos de retroalimentación pueden amplificar ruido de malos resultados iniciales.
SPLADE: Aprendiendo Representaciones Dispersas
¿Y si, en vez de construir listas de sinónimos a mano, entrenáramos un modelo para expandir el vocabulario automáticamente? Dada la consulta "paro cardíaco", ¿podría una red neuronal aprender a activar también "corazón", "ataque", "coronario", "miocardio" — añadiendo sinónimos aprendidos de datos, sin que nadie escriba un diccionario?
Eso es lo que hace SPLADE (Formal et al., 2021) . Reutiliza la cabeza de modelo de lenguaje enmascarado (MLM) de BERT — la parte que predice qué palabra debe llenar un slot [MASK]. Para cada posición de token $i$ en la entrada, la cabeza MLM produce una puntuación $h_{ij}$ para cada palabra $j$ del vocabulario. SPLADE toma el máximo entre posiciones y aplica saturación logarítmica:
El máximo entre posiciones selecciona la señal más fuerte para cada término del vocabulario. ReLU asegura que no haya pesos negativos. Y el $\log(1 + \cdot)$ aplica la misma idea de saturación que vimos en BM25: rendimientos decrecientes para activaciones muy fuertes, evitando que un solo término domine la puntuación.
El resultado es un vector sobre el vocabulario donde términos relacionados se activan aunque nunca aparecieran en el texto original. Para "paro cardíaco", el modelo puede asignar pesos altos a "corazón", "ataque", "coronario" y "miocardio" — todo aprendido de los datos de entrenamiento, sin diccionario.
Pero hay un problema. Sin restricciones, el modelo tiende a activar casi todos los términos del vocabulario en algún grado — los vectores dejan de ser dispersos. Y necesitamos la dispersidad, porque los vectores dispersos son los que nos permiten usar la misma infraestructura de índice invertido que hace rápido a BM25. SPLADE impone dispersidad con un término de regularización FLOPS en la pérdida de entrenamiento:
Esto penaliza términos del vocabulario que se activan en muchos documentos de un lote de entrenamiento. El modelo aprende a activar un término solo cuando realmente importa para ese documento, manteniendo los vectores suficientemente dispersos para recuperación eficiente sin perder la expansión de vocabulario donde ayuda. El hiperparámetro $\lambda$ controla este balance: mayor $\lambda$ significa vectores más dispersos (recuperación más rápida, pero potencialmente menos expansión de vocabulario).
¿Por qué "FLOPS"? El número de operaciones de punto flotante durante la búsqueda en el índice es proporcional a cuántos términos no nulos se solapan entre los vectores de consulta y documento. Vectores más dispersos se intersectan en menos términos, así que reducir la activación promedio reduce directamente el costo de búsqueda.
En tiempo de recuperación, SPLADE funciona exactamente como BM25: vectores dispersos almacenados en un índice invertido, puntuados con un producto punto sobre términos compartidos. La diferencia es que "términos compartidos" ahora incluye palabras que ni la consulta original ni el documento contenían — el modelo las añadió.
Quiz
Pon a prueba tu comprensión de los métodos de recuperación dispersa.
En BM25, ¿qué controla el parámetro $k_1$?
¿Qué propiedad clave permite que los vectores SPLADE se usen con un índice invertido estándar?
¿Qué penaliza el término de regularización FLOPS en SPLADE?