Modelos de Lenguaje Grandes y Bases de Datos Vectoriales para Recomendaciones de Noticias

Modelos de Lenguaje Avanzados y Bases de Datos Vectoriales para Recomendaciones de Noticias

Foto de Roman Kraft en Unsplash

Convirtiendo los modelos de lenguaje avanzados en entornos de producción utilizando Sentence Transformers y Qdrant

Los modelos de lenguaje avanzados (LLMs, por sus siglas en inglés) han causado sensación en la comunidad de machine learning con los recientes lanzamientos de herramientas de inteligencia artificial generativa como Chat-GPT, Bard y otros similares. Una de las ideas principales detrás de estas soluciones es calcular una representación numérica de datos no estructurados (como textos e imágenes) y encontrar similitudes entre estas representaciones.

Sin embargo, llevar todos estos conceptos a un entorno de producción conlleva sus propios desafíos en ingeniería de machine learning:

  • ¿Cómo generar estas representaciones rápidamente?
  • ¿Cómo almacenarlas en una base de datos adecuada?
  • ¿Cómo calcular rápidamente las similitudes en entornos de producción?

En este artículo, presento dos soluciones de código abierto que buscan resolver estas preguntas:

  • Sentence Transformers [1]: una técnica de generación de embeddings basada en información textual; y
  • Qdrant: una base de datos de vectores capaz de almacenar embeddings y proporcionar una interfaz fácil para consultarlos.

Estas herramientas se aplican al conjunto de datos de recomendación de portal de noticias NPR [2] (disponible de manera abierta en Kaggle), que fue creado para apoyar a la comunidad académica en el desarrollo de algoritmos de recomendación. Al final del artículo, podrás ver cómo:

  • Generar embeddings de noticias con Sentence Transformers
  • Almacenar embeddings con Qdrant
  • Consultar embeddings para recomendar artículos de noticias

Todo el código de este artículo está disponible en Github.

1. Generando embeddings con Sentence Transformers

En primer lugar, necesitamos encontrar una manera de convertir los datos de entrada en vectores, a los que llamaremos embeddings (si quieres profundizar en el concepto de embedding, recomiendo el artículo de Boykis ¿Qué son los embeddings? [3]).

Así que echemos un vistazo a qué tipo de datos podemos trabajar con el conjunto de datos NPR:

import pandas as pddf = pd.read_parquet("articles.parquet")df.tail()
Datos de muestra de NPR (imagen generada por el autor)

Podemos ver que NPR tiene datos textuales interesantes como el título y el contenido del artículo. Podemos utilizarlos en un proceso de generación de embeddings como muestra la siguiente imagen:

Proceso de generación de embeddings (imagen por el autor)

Una vez que hemos definido las características textuales de nuestros datos de entrada, necesitamos establecer un modelo de embedding para generar nuestra representación numérica. Afortunadamente, existe sitios web como HuggingFace donde puedes buscar modelos pre-entrenados adecuados para lenguajes o tareas específicas. En nuestro ejemplo, podemos usar el modelo neuralmind/bert-base-portuguese-cased, que fue entrenado en portugués brasileño para las siguientes tareas:

  • Reconocimiento de Entidades Nombradas
  • Similitud Textual de Oraciones
  • Reconocimiento de Implicaciones Textuales

En cuanto al código, así es como traducimos el proceso de generación de embedding:

from sentence_transformers import SentenceTransformermodel_name = "neuralmind/bert-base-portuguese-cased"encoder = SentenceTransformer(model_name_or_path=model_name)title = """  Paraguaios vão às urnas neste domingo (30) para escolher novo presidente"""sentence = titlesentence_embedding = encoder.encode(sentence)print (sentence_embedding)# output: np.array([-0.2875876, 0.0356041, 0.31462672, 0.06252239, ...])

Entonces, dada una entrada de ejemplo de datos, podemos concatenar el título y el contenido de las etiquetas en un solo texto y pasarlo a un codificador para generar el embedding de texto.

Podemos aplicar el mismo proceso para todos los demás artículos en el conjunto de datos de NPR:

def generate_item_sentence(item: pd.Series, text_columns=["title"]) -> str:    return ' '.join([item[column] for column in text_columns])df["sentence"] = df.apply(generate_item_sentence, axis=1)df["sentence_embedding"] = df["sentence"].apply(encoder.encode)

Nota: ten en cuenta que este proceso puede llevar un poco más de tiempo dependiendo de la capacidad de procesamiento de tu máquina.

Una vez que tenemos los embeddings para todos los artículos de noticias, definamos una estrategia para almacenarlos.

2. Almacenando embeddings

Dado que generar los embeddings puede ser un proceso costoso, podemos utilizar una base de datos de vectores para almacenar estos embeddings y ejecutar consultas basadas en diversas estrategias.

Existen varios software de bases de datos de vectores para lograr esta tarea, pero utilizaré Qdrant para este artículo, que es una solución de código abierto con APIs disponibles para lenguajes de programación populares como Python, Go y Typescript. Para una mejor comparación entre estas bases de datos de vectores, consulta este artículo [4].

Configurando Qdrant

Para manejar todas las operaciones de Qdrant, necesitamos crear un objeto cliente que apunte a una base de datos de vectores. Qdrant te permite crear un servicio de nivel gratuito para probar la conexión remota a una base de datos, pero por simplicidad, crearé y persistiré la base de datos localmente:

from qdrant_client import QdrantClientclient = QdrantClient(path="./qdrant_data")

Una vez que se establece esta conexión, podemos crear una colección en la base de datos que almacenará los embeddings de los artículos de noticias:

from qdrant_client import modelsfrom qdrant_client.http.models import Distance, VectorParamsclient.create_collection(    collection_name = "news-articles",    vectors_config = models.VectorParams(        size = encoder.get_sentence_embedding_dimension(),        distance = models.Distance.COSINE,    ),)print (client.get_collections())# output: CollectionsResponse(collections=[CollectionDescription(name='news-articles')])

Observa que los parámetros de configuración de vectores se utilizan para crear la colección. Estos parámetros le indican a Qdrant algunas propiedades de los vectores, como su tamaño y la métrica de distancia que se utilizará al comparar vectores (utilizaré la similitud coseno, pero también puedes usar otras estrategias como el producto interno o la distancia euclidiana).

Generando Puntos de Vectores

Antes de finalmente poblar la base de datos, debemos crear los objetos adecuados para cargar. En Qdrant, los vectores se pueden almacenar utilizando una clase PointStruct, que puedes usar para definir las siguientes propiedades:

  • id: el ID del vector (en el caso de NPR, es el newsId)
  • vector: un array unidimensional que representa el vector (generado por el modelo de embedding)
  • payload: un diccionario que contiene cualquier otra metadato relevante que se pueda usar posteriormente para consultar vectores en una colección (en el caso de NPR, el título, el cuerpo y las etiquetas del artículo)
from qdrant_client.http.models import PointStructmetadata_columns = df.drop(["newsId", "sentence", "sentence_embedding"], axis=1).columnsdef create_vector_point(item:pd.Series) -> PointStruct:    """Convertir vectores en PointStruct"""    return PointStruct(        id = item["newsId"],        vector = item["sentence_embedding"].tolist(),        payload = {            field: item[field]            for field in metadata_columns            if (str(item[field]) not in ['None', 'nan'])        }    )points = df.apply(create_vector_point, axis=1).tolist()

Cargando Vectores

Finalmente, después de convertir todos los elementos en estructuras de puntos, podemos cargarlos en fragmentos en la base de datos:

TAMAÑO_FRAGMENTO = 500n_fragmentos = np.ceil(len(puntos)/TAMAÑO_FRAGMENTO)para i, fragmento_puntos in enumerate(np.array_split(puntos, n_fragmentos)):    cliente.upsert(        nombre_colección="artículos-noticias",        esperar=True,        puntos=fragmento_puntos.tolist()    )

3. Consultando Vectores

Ahora que las colecciones están finalmente pobladas con vectores, podemos comenzar a realizar consultas a la base de datos. Hay muchas formas en las que podemos ingresar información para consultar la base de datos, pero creo que hay 2 entradas muy útiles que podemos utilizar:

  • Texto de entrada
  • ID de vector de entrada

3.1 Consultando vectores con un vector de entrada

Supongamos que construimos esta base de datos de vectores para utilizarla en un motor de búsqueda. En este caso, esperamos que la entrada del usuario sea un texto de entrada y debemos devolver los elementos más relevantes.

Dado que todas las operaciones en una base de datos de vectores se realizan con … VECTORES, primero debemos transformar el texto de entrada del usuario en un vector para poder encontrar elementos similares en función de esa entrada. Recordemos que utilizamos Sentence Transformers para codificar datos textuales en incrustaciones, por lo que podemos usar el mismo codificador para generar una representación numérica para el texto de entrada del usuario.

Dado que NPR contiene artículos de noticias, supongamos que el usuario escribió “Donald Trump” para aprender sobre las elecciones en Estados Unidos:

texto_consulta = "Donald Trump"vector_consulta = codificador.encode(texto_consulta).tolist()print (vector_consulta)# salida: [-0.048, -0.120, 0.695, ...]

Una vez que se calcula el vector de consulta de entrada, podemos buscar los vectores más cercanos en la colección y definir qué tipo de resultados queremos de esos vectores, como su ID de noticias, título y temas:

from qdrant_client.models import Filtrofrom qdrant_client.http import modeloscliente.buscar(    nombre_colección="artículos-noticias",    vector_consulta=vector_consulta,    con_carga=["ID de noticias", "título", "temas"],    filtro_consulta=None)

Nota: por defecto, Qdrant utiliza Vecinos Más Cercanos Aproximados para buscar incrustaciones rápidamente, pero también puedes hacer una búsqueda completa y obtener los vecinos más cercanos exactos — solo ten en cuenta que esta es una operación mucho más costosa.

Después de ejecutar esta operación, aquí están los títulos generados (traducidos al inglés para una mejor comprensión):

  • Oración de Entrada: Donald Trump
  • Resultado 1: Los paraguayos acuden a las urnas este domingo (30) para elegir un nuevo presidente
  • Resultado 2: Los votantes dicen que Biden y Trump no deberían postularse en 2024, según una encuesta de Reuters/Ipsos
  • Resultado 3: Escritora acusa a Trump de abuso sexual en la década de 1990
  • Resultado 4: Mike Pence, ex vicepresidente de Donald Trump, declara en un tribunal que podría complicar al expresidente

Parece que además de traer noticias relacionadas con Trump mismo, el modelo de incrustación también logró representar temas relacionados con elecciones presidenciales. Observa que en el primer resultado, no hay una referencia directa al término de entrada “Donald Trump” más allá de las elecciones presidenciales.

También he omitido los parámetros de filtro_consulta. Esta es una herramienta muy útil si deseas especificar que los resultados deben cumplir con alguna condición dada. Por ejemplo, en un portal de noticias, a menudo es importante filtrar solo los artículos más recientes (digamos, a partir de los últimos 7 días). Por lo tanto, podrías consultar los artículos de noticias que cumplan con una marca de tiempo mínima de publicación.

Nota: en el contexto de recomendación de noticias, existen múltiples aspectos preocupantes a considerar, como la equidad y la diversidad. Este es un tema abierto de discusión, pero si estás interesado en esta área, echa un vistazo a los artículos del Taller NORMalize.

3.2 Consultando vectores con un ID de vector de entrada

Por último, podemos pedirle a la base de datos vectorial que “recomiende” elementos que estén más cerca de algunos identificadores vectoriales deseados pero lejos de los identificadores vectoriales no deseados. Los identificadores deseados y no deseados se llaman ejemplos positivos y negativos, respectivamente, y se consideran como semillas para la recomendación.

Por ejemplo, supongamos que tenemos el siguiente ID positivo:

seed_id = '8bc22460-532c-449b-ad71-28dd86790ca2'# título (traducido): 'Descubre por qué Joe Biden lanzó su candidatura para la reelección este martes'

A continuación, podemos solicitar elementos similares a este ejemplo:

client.recommend(    collection_name="news-articles",    positive=[seed_id],    negative=None,    with_payload=["newsId", "title", "topics"])

Después de ejecutar esta operación, aquí están los títulos traducidos de salida:

  • Elemento de entrada: Descubre por qué Joe Biden lanzó su candidatura para la reelección este martes
  • Salida 1: Biden anuncia que se postulará para la reelección
  • Salida 2: EE.UU.: las 4 razones que llevaron a Biden a postularse para la reelección
  • Salida 3: Los votantes dicen que Biden y Trump no deberían postularse en 2024, según una encuesta de Reuters/Ipsos
  • Salida 4: El desliz del asesor de Biden que planteó dudas sobre un posible segundo gobierno después de las elecciones

Conclusión

Este artículo demuestra cómo combinar LLMs y bases de datos vectoriales para ofrecer recomendaciones. En particular, se utilizaron Sentence Transformers para generar representaciones numéricas (incrustaciones) a partir de artículos de noticias de texto en el conjunto de datos NPR. Una vez que se calculan estas incrustaciones, pueden poblar una base de datos vectorial como Qdrant, que facilita la consulta de vectores basada en varias estrategias.

Se pueden hacer muchas mejoras después de los ejemplos en este artículo, como:

  • probar otros modelos de incrustación
  • probar otras métricas de distancia
  • probar otras bases de datos vectoriales
  • usar lenguajes de programación basados en compilación como Go para mejorar el rendimiento
  • crear una API para ofrecer recomendaciones

En otras palabras, pueden surgir muchas ideas para mejorar la ingeniería del aprendizaje automático en las recomendaciones con LLMs. Así que, si te apetece compartir tus ideas sobre estas mejoras, no dudes en enviarme un mensaje aquí 🙂

Sobre mí

Soy un científico de datos senior en Globo, una empresa de medios tecnológicos brasileña. Trabajando en el equipo de recomendaciones de la empresa, estoy rodeado de un equipo increíble y talentoso que dedica mucho esfuerzo para ofrecer contenido personalizado a millones de usuarios a través de productos digitales como G1, GE, Globoplay y muchos otros. Este artículo no habría sido posible sin su conocimiento indispensable.

Referencias

[1] N. Reimers e I. Gurevych, Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks (2019), Association for Computational Linguistics.

[2] J. Pinho, J. Silva y L. Figueiredo, NPR: un conjunto de datos de recomendaciones de portal de noticias (2023), ACM Conference on Recommender Systems

[3] V. Boykis, ¿Qué son las incrustaciones?, blog personal

[4] M. Ali, Las 5 principales bases de datos vectoriales (2023), blog de DataCamp

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Un estudio encuentra que ChatGPT aumenta la productividad de los trabajadores en algunas tareas de escritura

Un nuevo informe realizado por investigadores del MIT destaca el potencial de la IA generativa para ayudar a los trab...

Inteligencia Artificial

Conoce a AnomalyGPT Un nuevo enfoque de IAD basado en Modelos de Visión-Lenguaje de Gran Escala (LVLM) para detectar anomalías industriales

En varias tareas de Procesamiento del Lenguaje Natural (NLP), los Modelos de Lenguaje de Gran Tamaño (LLMs) como GPT-...

Inteligencia Artificial

Prediciendo Touchdowns de Futbol Americano con Aprendizaje Automático

Fútbol. Un pasatiempo estadounidense que une a los fans en toda la nación. Con un promedio de 16.7 millones de espect...