from sklearn.feature_extraction.text import TfidfVectorizer
consultas = [
"quiero anular la matrícula de Estadística",
"cómo solicito la beca de comedor",
"no me aparece la nota de Estadística en el expediente",
"plazo para solicitar la beca general",
]
vectorizador = TfidfVectorizer()
matriz = vectorizador.fit_transform(consultas)
matriz.shape1 Del texto a los vectores
Una máquina no opera sobre texto. Opera sobre números, y todo lo que se ha hecho en procesamiento de lenguaje natural desde los años cincuenta consiste, en el fondo, en decidir qué números representan una frase. Las eras que se suelen contar como revoluciones son cambios en esa decisión.
Este capítulo hace ese recorrido con un mismo problema de ejemplo: una secretaría académica que recibe consultas de alumnos por escrito y quiere clasificarlas para enrutarlas. Es un problema aburrido y por eso sirve, porque se puede resolver con las herramientas de cualquiera de las cuatro épocas y comparar el resultado.
1.1 Las reglas que se escribían a mano
Durante cuarenta años la respuesta fue escribir el conocimiento lingüístico a mano: diccionarios, gramáticas y reglas. Para nuestra secretaría, algo así:
si el texto contiene ("anular" o "cancelar") y "matrícula" → BAJAS
si el texto contiene "beca" → BECAS
si el texto contiene ("nota" o "calificación") → ACTASFunciona. Funciona sorprendentemente bien el primer día, y esa es justo la trampa. Al segundo día llega “querría dar de baja la asignatura de Estadística” y no hay regla que la coja. Se añade. Al tercero llega “he decidido no continuar con Estadística”. Se añade otra. El coste de mantenimiento crece con cada caso nuevo y la cola de casos raros no se acaba nunca, porque el lenguaje no tiene un número finito de formas de decir las cosas.
Conviene no despacharlo como una anécdota histórica: las reglas siguen siendo la respuesta correcta cuando el dominio sí es finito. Validar un DNI, extraer un IBAN o detectar un número de expediente con una expresión regular es más barato, más rápido y más auditable que cualquier modelo. El error no es usar reglas, es usarlas para lo que tiene cola larga.
1.2 Contar palabras
El cambio de los noventa fue dejar de escribir reglas y empezar a contar. Antes de contar hay que decidir qué se cuenta, y ahí aparece un preproceso que sigue vivo:
- Tokenizar: partir el texto en unidades. En esta época eran palabras, no los trozos de palabra que usa un modelo actual.
- Quitar palabras vacías (stopwords): “de”, “la”, “que” aparecen en todo y no distinguen nada.
- Normalizar la forma: reducir “anulando”, “anulé” y “anular” a una sola entrada. Con lematización si se quiere la forma de diccionario (
anular), con derivación (stemming) si basta con cortar por lo sano (anul). - N-gramas: contar también pares o tríos de palabras consecutivas, para no perder del todo el orden.
Con eso ya se puede representar un documento como un vector: una posición por cada palabra del vocabulario y, en cada una, cuántas veces aparece. Es la bolsa de palabras, y su nombre es honesto sobre lo que hace, que es tirar el orden y quedarse con el contenido.
La mejora que la convirtió en útil fue TF-IDF: en lugar de contar, pesar. Cada término se multiplica por lo raro que es en el corpus, de modo que una palabra que aparece en todos los documentos acaba pesando casi cero y una que aparece en dos pesa mucho. La intuición es que lo informativo es lo poco frecuente.
(4, 21)Cuatro documentos, veintiuna dimensiones, una por palabra distinta del corpus. Y aquí está la primera limitación estructural: el vector tiene una dimensión por palabra del vocabulario. Con un corpus real son decenas de miles de dimensiones, casi todas a cero en cualquier documento concreto. Son vectores enormes y vacíos, y el significado no está en ellos sino repartido entre ellos.
1.3 Lo que contar no ve
Dos frases de nuestra secretaría:
A: "quiero anular la matrícula de Estadística"
B: "querría darme de baja en Estadística"Comparten una sola palabra de contenido. Para TF-IDF son documentos casi ortogonales, con una similitud próxima a cero, cuando para cualquier persona son la misma consulta. Esto es la sinonimia, y no tiene arreglo dentro del paradigma: se pueden meter listas de sinónimos a mano, con lo que volvemos a las reglas y a su cola infinita.
El segundo agujero es el orden. Estas dos frases tienen exactamente el mismo vector:
"el alumno debe 300 euros a la universidad"
"la universidad debe 300 euros al alumno"Los n-gramas tapan un poco este segundo agujero y nada del primero. Todo lo que vino después se puede leer como intentos de resolver estos dos problemas.
1.4 Que la posición signifique algo
La idea que desatascó la sinonimia es vieja y se resume en una frase de John Firth: una palabra se conoce por la compañía que tiene. Si “anular” y “cancelar” aparecen rodeadas de las mismas palabras, algo tendrán en común, y ese algo se puede aprender de un corpus sin que nadie lo anote.
Word2vec (Mikolov et al. 2013) lo convirtió en un procedimiento: se entrena una red muy sencilla a predecir el contexto de cada palabra y, cuando termina, se tira la red y se guardan los pesos intermedios. Esos pesos son el vector de cada palabra. GloVe, un año después, llegó a algo equivalente por la vía de factorizar la matriz de coocurrencias.
El resultado tiene tres propiedades que cambiaron el oficio:
| Propiedad | Qué significa |
|---|---|
| Denso | 300 dimensiones en lugar de 50.000, todas con valor |
| Aprendido | Nadie decidió qué mide cada dimensión |
| Geométrico | La distancia entre vectores se corresponde con el parecido de uso |
Esa tercera propiedad es la que sostiene todo lo que vino después, incluida la recuperación semántica de la segunda parte del manual. Buscar deja de ser encontrar documentos que contengan las palabras de la consulta y pasa a ser encontrar documentos cercanos a ella.
El ejemplo que hizo famoso a word2vec fue comprobar que restando el vector de “hombre” al de “rey” y sumando el de “mujer” se cae cerca del de “reina”. Es una demostración vistosa y conviene tomarla con calma: funciona en analogías bien elegidas y falla en muchas otras, y parte del efecto viene de cómo se evalúa.
Lo que sí es sólido es lo de fondo: el espacio tiene estructura, y esa estructura se aprendió sola de leer texto.
1.5 La misma palabra, distinto vector
Word2vec dejó un problema abierto que se ve bien en el dominio de la secretaría. Estas dos frases usan la misma palabra:
"tengo una matrícula pendiente de pago"
"sacó matrícula de honor en Estadística"Un embedding estático asigna un vector por palabra, así que “matrícula” tiene el mismo en las dos, un promedio borroso de sus dos sentidos que no representa bien a ninguno.
La solución fue calcular el vector en función de la frase entera, que es lo que hace BERT (Devlin et al. 2019). Se entrena tapando palabras al azar y pidiendo al modelo que las adivine mirando lo que hay a izquierda y derecha a la vez, de ahí lo de bidireccional. Cada aparición de “matrícula” sale con un vector distinto según su contexto.
BERT es un transformer, pero solo la mitad que lee: es un encoder. No genera texto y no le hace falta, porque su trabajo es producir representaciones para que otra cosa clasifique, busque o extraiga encima de ellas.
1.6 De la palabra a la frase
Para buscar y para clasificar no hace falta un vector por palabra sino uno por documento. La solución obvia, promediar los vectores de las palabras, funciona regular: un modelo entrenado para adivinar palabras tapadas no está entrenado para que la media de sus salidas sea comparable entre frases.
Lo que sí funciona es entrenar explícitamente para ello, que es lo que hacen los sentence transformers (Reimers y Gurevych 2019): se le enseñan pares de frases que significan lo mismo y pares que no, y se le pide que acerque las primeras y aleje las segundas. Es el modelo que hay detrás de cualquier índice vectorial.
from sentence_transformers import SentenceTransformer
modelo = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
pares = [
("quiero anular la matrícula", "querría darme de baja"),
("quiero anular la matrícula", "plazo para pedir la beca"),
]
for a, b in pares:
print(round(float(modelo.similarity(modelo.encode(a), modelo.encode(b))), 2), a, "|", b)0.64 quiero anular la matrícula | querría darme de baja
0.39 quiero anular la matrícula | plazo para pedir la becaEl par que TF-IDF daba por ortogonal sale ahora claramente por encima del que no tiene nada que ver. Eso es exactamente lo que separa una búsqueda por palabras de una búsqueda semántica.
Conviene fijarse también en el segundo número, porque suele sorprender: 0,39 no es cero, y esas dos frases no tienen nada en común más allá de ser consultas de una secretaría. Estos modelos casi nunca devuelven similitudes bajas, así que un umbral absoluto del tipo “recupera lo que pase de 0,5” no significa lo mismo de un modelo a otro y hay que calibrarlo con datos propios. Lo fiable es el orden, no el valor.
Dos vectores calculados con modelos distintos no se pueden comparar, aunque tengan el mismo número de dimensiones. La versión del modelo de embeddings forma parte de la identidad del índice, y cambiarla no es una migración sino reconstruirlo entero.
Es la clase de detalle que no duele hasta el día que alguien actualiza una dependencia y la búsqueda empieza a devolver ruido sin ningún error en los registros.
1.7 Lo que no ha muerto
Llegados aquí la tentación es concluir que lo nuevo sustituye a lo viejo. No es así, y esta sección es probablemente la que más dinero ahorra de todo el capítulo.
| Enfoque | Coste por consulta | Latencia típica | Dónde sigue ganando |
|---|---|---|---|
| Reglas | Nulo | Microsegundos | Formatos fijos: DNI, IBAN, expedientes |
| TF-IDF | Nulo | Microsegundos | Búsqueda por término exacto, corpus pequeño, línea base |
| Embeddings | Muy bajo, sin GPU | Milisegundos | Búsqueda semántica, agrupación, duplicados |
| Encoder ajustado | Bajo | Milisegundos | Clasificación de volumen alto y etiquetas estables |
| Modelo generativo | El más alto | Cientos de milisegundos | Tareas abiertas, pocas y variables |
La regla práctica es que el modelo generativo es la herramienta más cara y más lenta de la tabla, y por eso debería ser la última que se prueba y no la primera. Para clasificar diez mil consultas al día en seis categorías estables, un encoder ajustado cuesta una fracción, responde en milisegundos y no cambia de opinión de una semana a otra porque el proveedor haya actualizado el modelo.
Donde el generativo gana de calle es en lo contrario: tareas que no se pueden enumerar de antemano, volumen bajo y necesidad de redactar la respuesta además de clasificarla.
Hay un matiz que conviene anotar aquí porque reaparece en recuperación: la búsqueda por palabras y la semántica no compiten, se suman. Los sistemas serios combinan las dos, porque la semántica falla justo donde la léxica brilla, que es con nombres propios, códigos y números de expediente que el modelo nunca vio.
1.8 El cuaderno: un problema, cuatro épocas
El cuaderno de este capítulo monta el experimento entero sobre el mismo conjunto de consultas: reglas, TF-IDF con una regresión logística encima, embeddings con un clasificador y un modelo generativo con instrucciones. Mide el acierto y la latencia de las cuatro.
No pongo aquí los números porque dependen del conjunto de datos y envejecerían mal, pero sí la forma del resultado, que es estable y llamativa: la curva de acierto se aplana mucho antes que la de coste. Del primer al segundo enfoque el salto es grande. Del tercero al cuarto el acierto sube poco o nada y la latencia se multiplica por cientos, que en una API se traduce directamente en factura.
Esa forma es el argumento de fondo de esta parte del manual. Saber de dónde viene cada pieza sirve, sobre todo, para no pagar por la última cuando la penúltima resolvía el problema.
De propina, el cuaderno desmonta dos ideas bastante extendidas. La primera, que todo esto empezó en 2022: buena parte de la maquinaria (vectorizar texto, medir similitud, clasificar) lleva décadas funcionando. La segunda, que un modelo grande es siempre la respuesta. Al ver los cuatro enfoques medidos sobre las mismas consultas se entiende de otra manera la tabla de qué compensa según el problema.
Con el texto ya convertido en vectores, queda entender por qué la arquitectura que los calcula acabó siendo la que es. Eso es el siguiente capítulo.