Comparando diferentes embeddings vectoriales
Este artículo se publicó originalmente en The New Stack y se vuelve a publicar aquí con permiso.
¿En qué se diferencian los embeddings vectoriales generados por distintas redes neuronales, y cómo puedes evaluarlos en tu Jupyter Notebook?
Los modelos de lenguaje grandes (LLMs) están en tendencia y nos enfrentamos a un nuevo paradigma de aplicaciones de lenguaje como ChatGPT. Las bases de datos vectoriales serán una parte central del stack. Por eso, es importante entender los vectores y por qué son tan importantes.
Este proyecto demuestra la diferencia en los embeddings vectoriales entre modelos y muestra cómo usar múltiples colecciones de datos vectoriales en un Jupyter Notebook. En esta publicación, cubriremos qué son los embeddings vectoriales, por qué son importantes y cómo comparamos distintos embeddings vectoriales en tu Jupyter Notebook.
¿Qué son los embeddings vectoriales y por qué son importantes?
¿De dónde vienen los embeddings vectoriales?
En los términos más simples, los embeddings vectoriales son representaciones numéricas de datos. Se utilizan principalmente para representar datos no estructurados. Los datos no estructurados son imágenes, videos, audio, texto, imágenes moleculares y otros tipos de datos que no tienen una estructura formal. Los embeddings vectoriales se generan al pasar datos de entrada por una red neuronal preentrenada y tomar la salida de la penúltima capa.
Las redes neuronales tienen diferentes arquitecturas y se entrenan con diferentes conjuntos de datos, lo que hace que el embedding vectorial de cada modelo sea único. Por eso trabajar con datos no estructurados y embeddings vectoriales es un desafío. Más adelante veremos cómo modelos con la misma base, ajustados finamente con distintos conjuntos de datos, pueden producir diferentes embeddings vectoriales.
Las diferencias en las redes neuronales también significan que debemos usar modelos distintos para procesar diversas formas de datos no estructurados y generar sus embeddings. Por ejemplo, no puedes usar un modelo sentence transformer para generar embeddings de una imagen. Por otro lado, no querrías usar ResNet50, un modelo de imágenes, para generar embeddings de oraciones. Por lo tanto, es importante encontrar modelos que sean adecuados para tu tipo de datos.
¿Cómo comparamos los embeddings vectoriales?
A continuación, veamos cómo compararlos. Esta sección compara tres modelos multilingües diferentes basados en MiniLM de Hugging Face. Hay muchas formas de comparar vectores. En este ejemplo, usamos la métrica de distancia L2 y un índice de archivo invertido como índice vectorial.
Para este proyecto he desarrollado algunos de mis datos, inspirados en el reciente lanzamiento del álbum de Taylor Swift, definidos directamente en el Jupyter Notebook. Puedes usar mis ejemplos o crear tus propias oraciones. Una vez que tenemos los datos, obtenemos los distintos embeddings y almacenamos dos conjuntos de embeddings en una base de datos vectorial como Milvus. Los consultamos para hacer las comparaciones usando los embeddings del tercer modelo.
Buscamos ver si los resultados de búsqueda son diferentes y qué tan alejados están los resultados de búsqueda entre sí. En un entorno de producción, querrías saber los resultados que esperas ver y luego compararlos con los resultados devueltos.
Comparación de embeddings vectoriales de diferentes modelos
Los tres modelos que comparamos son el modelo base multilingüe de paráfrasis que usa MiniLM de Sentence Transformers, una versión que fue ajustada finamente para la detección de intenciones, y uno que Sprylab ajustó finamente sin detalles sobre para qué fue ajustado. Encontrar tres modelos compatibles que pudiera ejecutar en mi portátil fue una de las partes más difíciles de este proyecto.
Necesitamos vectores de la misma longitud/dimensión para comparar embeddings vectoriales. En este ejemplo, estamos usando vectores de 384 dimensiones según el modelo MiniLM Sentence Transformer. Ten en cuenta que esto no significa que estemos creando 384 “características” o “categorías” para cada dato, sino más bien generando una representación abstracta de los datos en un espacio de 384 dimensiones. Por ejemplo, ninguna de las dimensiones representará algo como una parte de la oración, el número de palabras en la frase, si algo es un nombre propio o cualquier cosa conceptual de ese tipo.
Datos de comparación de embeddings vectoriales
Estamos usando modelos sentence transformer, lo que significa que nuestros datos deben estar en forma de frases. Recomiendo tener al menos 50 frases para comparar. El notebook de ejemplo contiene 51. También recomiendo usar datos que tengan cierta similitud entre sí. En este ejemplo, usé letras de cuatro canciones de Taylor Swift: “Speak Now,” “Starlight,” “Sparks Fly,” y “Haunted.”
Elegí estas canciones porque muchas de las letras forman frases completas, así que es fácil pasarlas de formato de letra a formato de frase. También quería probar una hipótesis. Las primeras tres canciones son más o menos canciones de amor, mientras que la última, “Haunted,” se parece más a una canción de ruptura. Así que planteo la hipótesis de que es menos probable que “Haunted” aparezca en los resultados devueltos para las primeras tres canciones que ellas entre sí.
Comparar embeddings vectoriales en tu Jupyter Notebook
Entremos en el código. Con Milvus Lite, una versión ligera de Milvus, puedes comparar tus embeddings vectoriales directamente en un Jupyter Notebook. Para este código de ejemplo, tendrás que ejecutar ! pip install pymilvus milvus sentence-transformers. Solo necesitas unas pocas importaciones para este proyecto; la biblioteca time es opcional. Después de ejecutar las importaciones, inicia el default_server de Milvus y configura una conexión.
from sentence_transformers import SentenceTransformer
from milvus import default_server
from pymilvus import connections, utility, FieldSchema, CollectionSchema, DataType, Collection
from time import time
default_server.start()
connections.connect(host="127.0.0.1", port=default_server.listen_port)
Obtener los modelos sentence transformer de Hugging Face
El paso 1 es obtener nuestros modelos de embeddings vectoriales. Usamos la serie MiniLM multilingüe de paráfrasis para este ejemplo. El primero es la versión canónica. Los dos siguientes son las versiones ajustadas finamente de manera diferente. Esta elección de modelo nos da un ejemplo claro de cómo el ajuste fino puede cambiar notablemente tus vectores.
v12 = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
ft3_v12 = SentenceTransformer("Sprylab/paraphrase-multilingual-MiniLM-L12-v2-fine-tuned-3")
ft5_v12 = SentenceTransformer("hroth/psais-paraphrase-multilingual-MiniLM-L12-v2-5shot")
Cargar los conjuntos de datos en dos colecciones en una base de datos vectorial
He listado 51 frases del álbum “Speak Now” de Taylor Swift a continuación. Siéntete libre de copiar y pegar este fragmento de código. O léelo si quieres; es una gran letrista.
# inspirado en Speak Now (Taylor's Version)
# Letras de: Speak Now, Starlight, Sparks Fly, Haunted
sentences = [
"No soy el tipo de chica que debería irrumpir groseramente en una ocasión de velo blanco, pero tú no eres el tipo de chico que debería casarse con la chica equivocada",
"Me cuelo y veo a tus amigos y a su pequeña familia estirada todos vestidos de pastel y ella está gritándole a una dama de honor en algún lugar dentro de una habitación usando un vestido con forma de pastelito",
"Esto seguramente no es lo que pensaste que sería.",
"Me pierdo en una ensoñación donde me pongo de pie y digo: 'No digas que sí, huye ahora Te encontraré cuando salgas de la iglesia por la puerta trasera.'",
"No esperes, ni digas un solo voto, necesitas escucharme y ellos dijeron: 'Habla ahora'.",
"Se intercambian gestos cariñosos.",
"Y el órgano empieza a tocar una canción que suena como una marcha fúnebre.",
"Y estoy escondida entre las cortinas, parece que no fui invitada por tu encantadora futura novia.",
"Ella flota por el pasillo como una reina de certamen.",
"Pero sé que deseas que fuera yo, deseas que fuera yo, ¿no?",
"Oigo al predicador decir: 'Habla ahora o calla para siempre'",
"Está el silencio, está mi última oportunidad.",
"Me pongo de pie con las manos temblorosas, todos los ojos puestos en mí",
"Miradas horrorizadas de todos en la habitación pero yo solo te miro a ti.",
"Y dirás: 'Huyamos ahora' Te encontraré cuando salga de mi esmoquin por la puerta trasera",
"Cariño, no dije mis votos Tan feliz de que estuvieras cerca Cuando dijeron: 'Habla ahora'",
"Dije: 'Dios mío, qué melodía tan maravillosa'",
"Fue la mejor noche, nunca olvidaría cómo nos movíamos.",
"Todo el lugar estaba vestido de punta en blanco y estábamos bailando, bailando como si estuviéramos hechos de luz de estrellas",
"Conocí a Bobby en el paseo marítimo en el verano del 45",
"Me recogió tarde una noche por la ventana, teníamos diecisiete y estábamos locos corriendo salvajes, salvajes.",
"No puedo recordar qué canción estaba tocando cuando entramos.",
"La noche en que nos colamos en una fiesta de un club náutico fingiendo ser una duquesa y un príncipe.",
"Él dijo: 'Mírate, preocupándote tanto por cosas que no puedes cambiar Pasarás toda tu vida cantando blues Si sigues pensando de esa manera'",
"Él intentaba hacer saltar piedras en el océano diciéndome '¿No ves la luz de estrellas, luz de estrellas, no sueñas cosas imposibles?'",
"Ooh, ooh él habla como loco Ooh, ooh bailando conmigo Ooh, ooh podríamos casarnos Tener diez hijos y enseñarles a soñar",
"La forma en que te mueves es como una tormenta de lluvia total.",
"Y soy un castillo de naipes",
"Eres el tipo de imprudencia que debería hacerme salir corriendo, pero de algún modo sé que no llegaré lejos",
"Y estabas allí frente a mí, lo suficientemente cerca como para tocarte",
"Lo suficientemente cerca como para esperar que no pudieras ver en qué estaba pensando",
"Déjalo todo ahora",
"Encuéntrame bajo la lluvia torrencial",
"Bésame en la acera",
"Quita el dolor",
"Porque veo chispas volar, cada vez que sonríes",
"Atrápame con esos ojos verdes, cariño, mientras las luces se apagan",
"Dame algo que me atormente cuando no estés cerca",
"Mi mente olvida recordarme que eres una mala idea",
"Me tocas una vez y es realmente algo, descubres que soy incluso mejor de lo que imaginaste que sería",
"Estoy en guardia frente al resto del mundo pero contigo, sé que no sirve de nada"
"Y podría esperar pacientemente pero realmente desearía que lo hicieras"
"Paso mis dedos por tu cabello y veo cómo las luces se vuelven locas",
"Sigue manteniendo tus ojos en mí, es lo suficientemente incorrecto como para que se sienta bien",
"Y llévame por la escalera, ¿no susurrarás suave y lento? Estoy cautivada por ti, cariño, como un espectáculo de fuegos artificiales",
"Tú y yo caminamos sobre una línea frágil Lo he sabido todo este tiempo, Pero nunca pensé que viviría para verla romperse",
"Está oscureciendo y todo está demasiado silencioso Y no puedo confiar en nada ahora Y te está invadiendo como si todo fuera un gran error",
"Oh, contengo la respiración No volveré a perderte",
"Algo ha hecho que tus ojos se vuelvan fríos",
"Vamos, vamos, no me dejes así Pensé que te tenía descifrado",
"Algo ha salido terriblemente mal, eres todo lo que quería",
"No puedo respirar cada vez que te vas, no puedo volver atrás ahora, estoy embrujada",
"Solo sé que no te has ido, no puedes haberte ido, no",
]
Primero debemos definir los esquemas para introducir datos en una base de datos vectorial. Tenemos el mismo esquema para ambas colecciones en este ejemplo, así que solo necesitamos crear uno. Sin embargo, asegúrate de crear dos colecciones.
Una vez que las colecciones estén listas, codificamos todas las oraciones en embeddings para sus modelos y definimos los parámetros del índice vectorial. Usamos L2 como nuestra métrica de distancia y un índice de archivo invertido con cuatro centroides. Al fin y al cabo, solo hay 51 entradas. Luego pasamos a formatear los datos y cargarlos en Milvus.
# object should be inserted in the format of (title, date, location, speech embedding)
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="sentence", dtype=DataType.VARCHAR, max_length=500),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=DIMENSION),
]
schema = CollectionSchema(fields=fields)
collection_v12 = Collection(name=COLLECTION_V12, schema=schema)
collection_v12_ft5 = Collection(name=COLLECTION_V12_Q, schema=schema)
v12_embeds = {}
v12_q_embeds = {}
for sentence in sentences:
v12_embeds[sentence] = v12.encode(sentence)
v12_q_embeds[sentence] = ft5_v12.encode(sentence)
index_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 4},
}
collection_v12.create_index(field_name="embedding", index_params=index_params)
collection_v12.load()
collection_v12_ft5.create_index(field_name="embedding", index_params=index_params)
collection_v12_ft5.load()
for sentence in sentences:
v12_insert = [
{
"sentence": sentence,
"embedding": v12_embeds[sentence]
}
]
ft_insert = [
{
"sentence": sentence,
"embedding": v12_q_embeds[sentence]
}
]
collection_v12.insert(v12_insert)
collection_v12_ft5.insert(ft_insert)
collection_v12.flush()
collection_v12_ft5.flush()
Consultar los almacenes vectoriales para comparar los embeddings
A continuación, es hora de comparar. En este ejemplo, usaremos las dos primeras oraciones. Usamos el tercer modelo para generar los embeddings vectoriales para ellas.
search_embeds = {}
search_data = []
for sentence in sentences[0:2]:
vector_embedding = ft3_v12.encode(sentence)
search_embeds[sentence] = vector_embedding
search_data.append(vector_embedding)
Ahora consultamos Milvus. También estoy registrando el tiempo en esto. Los tiempos de las búsquedas que obtuve se muestran abajo. Asegúrate de pasar el mismo tipo de métrica en los parámetros de búsqueda.
start1 = time()
res_v12 = collection_v12.search(
data=search_data, # Embeded search value
anns_field="embedding", # Search across embeddings
param={"metric_type": "L2",
"params": {"nprobe": 2}},
limit = 3, # Limit to top_k results per search
output_fields=["sentence"])
time1 = time() - start1
print(f"Time for first search: {time1}")
start2 = time()
res_v12_ft5 = collection_v12_ft5.search(
data=search_data, # Embeded search value
anns_field="embedding", # Search across embeddings
param={"metric_type": "L2",
"params": {"nprobe": 2}},
limit = 3, # Limit to top_k results per search
output_fields=["sentence"])
time2 = time() - start2
print(f"Time for second search: {time2}")
Ahora veamos los resultados y comparemos. Vemos resultados sorprendentemente similares para el modelo original frente al ajustado por Sprylab. La única diferencia es que el primer resultado devuelto es la propia oración. Esto nos indica que los resultados en dos y tres son más similares a las dos oraciones de búsqueda de ejemplo que entre sí en estos dos espacios vectoriales.
for i, hits in enumerate(res_v12):
for hit in hits:
print(f"Query sentence: {sentences[i]}")
print(f"Nearest Neighbor Number {i}: {hit.entity.get('sentence')} ---- {hit.distance}\n")
A continuación, comparemos los dos modelos ajustados finamente. A partir de estos resultados, veremos que la oración que comienza con “I’m on my guard for the rest of the world …“ es semánticamente similar a nuestras oraciones de búsqueda porque aparece en ambas comparaciones. Dos puntos interesantes aquí son: 1) resultados diferentes de la primera consulta, y 2) la oración de la segunda consulta no aparece entre las tres primeras para la primera, pero lo contrario sí es cierto.
for hit in hits:
print(f"Query sentence: {sentences[i]}")
print(f"Nearest Neighbor Number {i}: {hit.entity.get('sentence')} ---- {hit.distance}\n")
Resumen
En este tutorial, aprendimos sobre las incrustaciones vectoriales y cómo compararlas, e hicimos algunas comparaciones nosotros mismos con nuestros datos personalizados. Como extra, también mostramos un ejemplo de cómo trabajar con dos colecciones diferentes al mismo tiempo. Así es como puedes consultar diferentes espacios vectoriales latentes.
Mostramos la diferencia entre un modelo y algunas versiones ajustadas finamente de este. También vimos cómo un resultado apareció en ambos espacios de incrustación. Un resultado de consulta en múltiples representaciones vectoriales significa que esa consulta debe ser semánticamente similar de numerosas maneras. Prueba a hacer esto con modelos de imágenes, modelos de lenguaje de diferente dimensionalidad o tus datos como próximos pasos.
Sigue leyendo

3 Easiest Ways to Use Claude Code on Your Mobile Phone
Run Claude Code from your phone with Remote Control, Happy Coder, or SSH + Tailscale. Comparison table, setup steps, and tools for typing, memory, and parallel tasks.

Announcing the General Availability of Single Sign-On (SSO) on Zilliz Cloud
SSO is GA on Zilliz Cloud, delivering the enterprise-grade identity management capabilities your teams need to deploy vectorDB with confidence.

Data Deduplication at Trillion Scale: How to Solve the Biggest Bottleneck of LLM Training
Explore how MinHash LSH and Milvus handle data deduplication at the trillion-scale level, solving key bottlenecks in LLM training for improved AI model performance.



