Aumente la precisión de los sistemas de recomendación con LLM, utilizando Python

en la cultura americana es el siguiente:

“No puedes quedarte con tu pastel y comértelo también”.

Esta frase me parece extremadamente poética pero también muy práctica y útil. El mensaje de este dicho es sencillo: todo lo que se logra se logra mediante una compensación, ya que todo tiene un precio.

La discusión filosófica está fuera del alcance de este artículo, pero las consecuencias prácticas de estas consideraciones están muy en línea con la ciencia de datos y la ingeniería de software en general. Déjame explicarte.

En ingeniería de software y ciencia de datos, no existe el “diseño perfecto” per se. El mismo algoritmo que es fantástico para una aplicación determinada falla estrepitosamente en otras.

Piense en las compensaciones entre computación y memoria en los siguientes casos:

Tiene mucho sentido calcular previamente la distancia entre dos ciudades y almacenarlas en un conjunto de datos, pero no tiene sentido calcularlas durante el vuelo. Esto se debe a que se espera que el conjunto de datos requiera un mantenimiento razonablemente bajo (las ciudades no se mueven con frecuencia) y sería estúpido calcular la distancia entre Nueva York y San Francisco cada fracción de segundo. [Case A]

Sin embargo, sería igualmente estúpido (y probablemente imposible) que un chatbot memorice todas las posibles preguntas que un humano puede hacer y obtenga la respuesta a esa pregunta cada vez que se la haga. Esto se debe a que la naturaleza del problema es mucho más dinámica y requiere un cálculo “sobre la marcha”. [Case B]

En el caso A, estamos sacrificando memoria y obteniendo un cálculo extremadamente rápido. En el Caso B, dedicamos más tiempo de cálculo, pero no utilizamos ninguna memoria de “consulta”.

¿No puedes obtener tiempo de cálculo ni memoria? En realidad no, porque no puedes quedarte con tu pastel y comértelo también 🙂

Pero tomemos un ejemplo menos obvio y más “de moda”. Hablemos de modelos de lenguajes grandes (LLM).

Los LLM son los modelos de IA más poderosos que tenemos y están capacitados en todo el conocimiento disponible en el mundo. También son enormes. En realidad, son tan grandes que rara vez los tenemos internamente y, por lo general, los invocamos a través de API. Sin embargo, llamada API = tokens = costo.

Ahora imagina que quieres utilizar un sistema inteligente para elegir el mejor restaurante para esta noche. Le preguntarías a ChatGPT algo como: “¿Puedes proporcionarme un buen restaurante italiano que no sea muy caro pero sí romántico y en una buena ubicación?”

Ahora, imagina si el modelo GPT tuviera que explorar todos los restaurantes del universo y decidir si son italianos, no caros, en una buena ubicación y cerca de tu casa. En el mejor de los casos: gastarías millones en tokens y ya estarías en la cama cuando se ejecute el cálculo.

Sin embargo, tampoco queremos renunciar por completo a todo el jugoso poder de interpretación del lenguaje natural y de recuperación de información de los LLM. La clave es que, para usar el LLM y obtener información inteligente, no podemos usar la parte más inteligente del proceso todo el tiempo (eso sería como tener el pastel y comérselo también).

En este artículo, les daré una receta para estos sistemas de recomendación inteligentes mejorados por LLM, utilizando el ejemplo de recomendación de restaurantes que estábamos haciendo como caso de uso.

La entrada de este sistema será la descripción que hará el usuario de su restaurante ideal en una ciudad específica, y la salida será un conjunto de restaurantes recomendados.

¡Empecemos!

1. Diseño del sistema

El dicho del pastel que comentamos también se conoce en ingeniería como el triángulo Precisión-Escala-Tiempo:

Puedes hacer algo preciso y en un conjunto de datos masivo, pero será lento. Puedes hacer algo preciso y rápido, pero no se escalará bien en un conjunto de datos grande. Puedes hacer algo rápido y escalar bien, pero no será tan preciso.

Imagen realizada por el autor.

Por supuesto, queremos que nuestros resultados sean, en última instancia, precisos, por lo que la opción 3 por sí sola no será suficiente. Sin embargo, podemos refinar la opción 3 con un modelo más preciso además del primero. En otras palabras, la Opción 3 puede brindarnos una buena lista de candidatos con un tiempo de cálculo pequeño y podemos seleccionar la lista de recomendaciones más precisa utilizando un modelo de lenguaje grande.

En otras palabras, el diseño se ve así:

Una búsqueda rápida y sencilla encontrará los K restaurantes más cercanos (basado en reglas, alta recuperación, baja precisión). Un modelo de lenguaje grande, lento y muy inteligente, nos ayudará a elegir, entre los K principales, el mejor en función de la consulta. (Basado en IA, alta precisión)

Al hacer esto, no estamos perdiendo tiempo ni dinero en los lentos LLM, pero aún así obtenemos su inteligencia al usarlos en una lista seleccionada de candidatos.

Basta de ladridos. ¡Empecemos a codificar!

2. El guión

2.1 La configuración

Hice el trabajo sucio detrás de escena por ti 🙂

Todo está escrito en forma de programación orientada a objetos (POO), con scripts y un proceso que se encargará de todo el proceso. La carpeta de GitHub es esta, y para generar el resto del código, puedes clonarla y usar este bloque de importación aquí:

2.2 Generación de datos

Antes de que podamos recomendar algo, necesitamos algo que recomendar. En un sistema real, usaríamos una base de datos de restaurante en una ubicación S3. Para este artículo, generamos uno sintético para que todo sea completamente reproducible y de ejecución gratuita.

Este es el trabajo de la clase RestaurantDataGenerator dentro de datagenerator.py. Crea una tabla reproducible de ~10,000 restaurantes repartidos en ocho ciudades (Nueva York, San Francisco, Chicago, Austin, Seattle, Boston, Miami y Denver). Cada restaurante obtiene:

– un nombre ensamblado al azar

– una ciudad y una latitud/longitud muestreada alrededor del centro de esa ciudad (dentro de ~13 km),

– un estilo de cocina (italiana, japonesa, mexicana, tailandesa, francesa,…),

– un perfil dietético (omnívoro/vegetariano/vegano)

– una puntuación media

– un número de votos

– un rango de precios (10 / 100 / 1000, un billete medio por persona del orden de magnitud).

Este generador está diseñado para funcionar una vez. Generar los datos es tan simple como:

Esa única llamada escribe la tabla en data/restaurants.csv, que se ve así:

Perfecto, ahora que tenemos nuestros restaurantes, veamos cómo podemos recomendarlos.

2.3 Generando los Candidatos

Esta es la etapa 1 del embudo: la lista de candidatos barata, rápida y basada en reglas. El usuario nos dice en qué ciudad se encuentra y solo mantenemos los restaurantes geográficamente más cercanos. El código filtra la tabla hasta la ciudad, calcula la distancia del círculo máximo desde el usuario hasta cada restaurante e identifica N_DISTANCE_CANDIDATES (50 de forma predeterminada).

Esta etapa es deliberadamente de alta recuperación y baja precisión. Con este enfoque, podemos recorrer toda la mesa (10.000 restaurantes) sin una sola llamada API ni costos de token. Claro, aquí no hacemos nada particularmente inteligente o sofisticado, pero en realidad estamos filtrando todos los datos que no son candidatos viables para el usuario. Sólo eso es un gran problema.

Por ejemplo, probemos una solicitud real a la búsqueda:

“tacos veganos baratos con un ambiente animado” en varias ciudades

Esta es la salida:

Observe cómo la lista corta a continuación no tiene idea de “vegano”, “barato” o “tacos”: solo sabe de distancia. Sin embargo, esto está bien, ya que el objetivo de esta etapa es crear un punto de partida en la ciudad correcta que el LLM reclasificará en la Etapa 2.

¡Preparémonos para el LLM!

2.4 Selección de los candidatos

Esta es la Etapa 2, el final del embudo lento, inteligente, impulsado por LLM y de alta precisión. Esto se suma directamente a la lista de 50 restaurantes seleccionados de 2.3. El LLM nunca ve la tabla completa de 10.000 filas; solo ve la pequeña porción, ya relevante, que le entregó el filtro de distancia.

Hablamos con el modelo a través de un pequeño cliente OpenAI. La clave se lee de OPENAI_API_KEY (guardada en el entorno). El recomendador, definido como RestaurantRecommender, se ejecuta en la consulta y en la ciudad a través de RestaurantRecommender.recommender(consulta,ciudad):

Vale la pena mencionar un par de cosas:

La precisión aumenta. La etapa 1 fue de alta recuperación, baja precisión: devolvió los 50 restaurantes más cercanos independientemente de la solicitud. En realidad, la etapa 2 lee la consulta (tacos veganos baratos con un ambiente animado), descarta todo lo que no encaja y devuelve solo los mejores 5 a 10 con un fit_score honesto. Salida estructurada con Pydantic. Nunca analizamos texto de formato libre. El modelo se ve obligado a responder en la forma de un modelo Pydantic (a través de resultados estructurados de OpenAI), por lo que se garantiza que cada respuesta coincidirá con el esquema.

El esquema de salida lleva el restaurante_id y el nombre (de los candidatos), un fit_score, un valor entre 0 y 100 y una breve razón. La respuesta también va acompañada de un resumen amigable. Realizar la convocatoria para nuestras tres ciudades da, por ejemplo:

Si te fijas, esto es mucho mejor que las listas cortas de distancia brutas de 2.3. Allí, el restaurante más cercano en cada ciudad era una coincidencia esencialmente aleatoria (coreana, libanesa, mexicana pero vegetariana). Aquí, el modelo ha reordenado los mismos 50 candidatos en torno a lo que realmente pedimos: los lugares veganos y mexicanos flotan hacia la cima con highfit_scores, y el modelo es honesto cuando nada encaja perfectamente, marca las coincidencias parciales y explica por qué en el motivo. Esa es la precisión que nos compra el LLM, aplicada a una lista corta lo suficientemente pequeña como para seguir siendo barata a escala.

3. Resultados

Retrocedamos y veamos lo que realmente nos compró el embudo de dos etapas, utilizando la misma solicitud en tres ciudades: “tacos veganos baratos con un ambiente animado”.

La etapa 1 nos da la lista de candidatos. Las listas cortas de distancia de 2.3 tenían un alto nivel de recuperación y baja precisión por diseño. La etapa 2 identifica las recomendaciones reales. Enviar a los 50 candidatos de la Etapa 1 al LLM los reordena según lo que realmente se preguntó.

Aquí están las selecciones finales que arrojó el modelo para cada ciudad:

Nueva York: Golden Spoon (vegano, 4,9) y Maison Fork (mexicano, de presupuesto) suben a la cima con puntuaciones de ajuste de 90 y 85. Miami: Royal Tavern & Co. (vegano, mexicano, asequible) lidera con 85. Boston: Urban Spoon y Little House, ambos lugares mexicanos de presupuesto, ocupan los dos primeros puestos con 90 y 85.

En cada ciudad, el modelo promovió a los candidatos que coincidían con la intención de vegano, barato y mexicano/tacos, y fue honesto acerca de los ajustes imperfectos: los lugares que cumplían con la dieta pero no con la cocina (o viceversa) se mantuvieron como respaldo con puntajes de ajuste visiblemente más bajos.

4. Conclusiones

Gracias por pasar tiempo conmigo, significa mucho. ❤️ Esto es lo que hemos hecho juntos:

– Creó un embudo de recomendación de dos etapas que es escalable e inteligente.

– Se utilizó un filtro de distancia económico basado en reglas (Etapa 1) para reducir 10.000 restaurantes a los 50 más cercanos.

– Se utilizó una nueva clasificación de LLM (Etapa 2) para convertir a esos 50 candidatos en los mejores 5 a 10, con una puntuación honesta y una razón para cada uno.

En muchos proyectos reales, un embudo como el que construimos aquí suele ser muy popular. Este tipo de sistemas son muy escalables, ya que el LLM se usa de manera inteligente, e inteligentes, ya que utilizamos modelos que pueden comprender el contexto de manera muy eficiente.

7. ¡Antes de salir!

Gracias de nuevo por tu tiempo. Significa mucho. Mi nombre es Piero Paialunga y soy este chico:

Imagen realizada por el autor.

Soy originario de Italia, tengo un doctorado. de la Universidad de Cincinnati y trabaja como científico de datos en The Trade Desk en la ciudad de Nueva York. Escribo sobre IA, aprendizaje automático y la evolución del papel de los científicos de datos tanto aquí en TDS como en LinkedIn. Si te gustó el artículo y quieres saber más sobre el aprendizaje automático y seguir mis estudios, puedes:

R. Sígueme en Linkedin, donde publico todas mis historias.
B. Sígueme en GitHub, donde podrás ver todo mi código.
C. Si tienes dudas puedes enviarme un correo electrónico a piero.paialunga@hotmail