En este artículo, aprenderá cómo crear un motor de búsqueda semántico simple utilizando incrustaciones de oraciones y vecinos más cercanos.
Los temas que cubriremos incluyen:
Comprender las limitaciones de la búsqueda basada en palabras clave. Generación de incrustaciones de texto con un modelo de transformador de oraciones. Implementación de un canal de búsqueda semántica del vecino más cercano en Python.
Empecemos.
Cree una búsqueda semántica con incrustaciones de LLM
Imagen del editor
Introducción
Históricamente, los motores de búsqueda tradicionales se han basado en la búsqueda por palabras clave. En otras palabras, ante una consulta como “mejores templos y santuarios para visitar en Fukuoka, Japón”, los resultados se obtienen basándose en la concordancia de palabras clave, de modo que los documentos de texto que contienen coincidencias de palabras como “templo”, “santuario” y “Fukuoka” se consideran más relevantes.
Sin embargo, este enfoque clásico es notoriamente rígido, ya que se basa en gran medida en coincidencias exactas de palabras y pasa por alto otros matices semánticos importantes, como sinónimos o frases alternativas (por ejemplo, “perro joven” en lugar de “cachorro”). Como resultado, es posible que se omitan documentos muy relevantes sin darse cuenta.
La búsqueda semántica aborda esta limitación centrándose en el significado más que en la redacción exacta. Los modelos de lenguaje grande (LLM) juegan un papel clave aquí, ya que algunos de ellos están entrenados para traducir texto en representaciones vectoriales numéricas llamadas incrustaciones, que codifican la información semántica detrás del texto. Cuando dos textos como “los perros pequeños son muy curiosos por naturaleza” y “los cachorros son curiosos por naturaleza” se convierten en vectores incrustados, esos vectores serán muy similares debido a su significado compartido. Mientras tanto, los vectores de incorporación de “los cachorros son curiosos por naturaleza” y “Dazaifu es un santuario característico en Fukuoka” serán muy diferentes, ya que representan conceptos no relacionados.
Siguiendo este principio, que puede explorar con más profundidad aquí, el resto de este artículo le guiará a través del proceso completo de creación de un motor de búsqueda semántica compacto pero eficiente. Si bien es minimalista, funciona de manera efectiva y sirve como punto de partida para comprender cómo se construyen los sistemas modernos de búsqueda y recuperación, como las arquitecturas de recuperación de generación aumentada (RAG).
El código que se explica a continuación se puede ejecutar sin problemas en una instancia de Google Colab o Jupyter Notebook.
Guía paso a paso
Primero, realizamos las importaciones necesarias para este ejemplo práctico:
import pandas as pd
import json
from pydantic import BaseModel, Field
from openai import OpenAI
from google.colab import userdata
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.preprocessing import StandardScaler
import pandas as pd
import json
from pydantic import BaseModel , Field
from openai import OpenAI
from google . colab import userdata
from sklearn . ensemble import RandomForestClassifier
from sklearn . model_selection import train_test_split
from sklearn . metrics import classification_report
from sklearn . preprocessing import StandardScaler
Usaremos un conjunto de datos públicos de juguete llamado "ag_news", que contiene textos de artículos de noticias. El siguiente código carga el conjunto de datos y selecciona los primeros 1000 artículos.
from datasets import load_dataset
from sentence_transformers import SentenceTransformer
from sklearn.neighbors import NearestNeighbors
from datasets import load_dataset
from sentence_transformers import SentenceTransformer
from sklearn . neighbors import NearestNeighbors
Ahora cargamos el conjunto de datos y extraemos la columna "texto", que contiene el contenido del artículo. Luego, imprimimos una breve muestra del primer artículo para inspeccionar los datos:
print(“Loading dataset…”)
dataset = load_dataset(“ag_news”, split=”train[:1000]”)
# Extract the text column into a Python list
documents = dataset[“text”]
print(f”Loaded {len(documents)} documents.”)
print(f”Sample: {documents[0][:100]}…")
print ( "Cargando conjunto de datos…" )
conjunto de datos = load_dataset ( "ag_noticias" , dividir = "tren[:1000]" )
# Extraemos la columna de texto en una lista de Python
documentos = conjunto de datos [ "texto" ]
print ( f "Documentos {len(documentos)} cargados." )
print ( f "Muestra: {documentos[0][:100]}…" )
El siguiente paso es obtener vectores de incrustación (representaciones numéricas) para nuestros 1000 textos. Como se mencionó anteriormente, algunos LLM están capacitados específicamente para traducir texto en vectores numéricos que capturan características semánticas. Los modelos de transformadores de frases Hugging Face, como "all-MiniLM-L6-v2", son una opción común. El siguiente código inicializa el modelo y codifica el lote de documentos de texto en incrustaciones.
print(“Loading embedding model…”)
model = SentenceTransformer(“all-MiniLM-L6-v2”)
# Convert text documents into numerical vector embeddings
print(“Encoding documents (this may take a few seconds)…”)
document_embeddings = model.encode(documents, show_progress_bar=True)
print(f”Created {document_embeddings.shape[0]} incrustaciones.")
print ( "Cargando modelo de incrustación…" )
modelo = SentenceTransformer ( "todo-MiniLM-L6-v2" )
# Convertir documentos de texto en incrustaciones de vectores numéricos
print ( "Codificación de documentos (esto puede tardar unos segundos)…" )
incrustaciones_documentos = modelo . codificar ( documentos , show_progress_bar = Verdadero )
imprimir ( f "Creado {document_embeddings.shape[0]} incrustaciones." )
A continuación, inicializamos un objeto NearestNeighbors, que implementa una estrategia de vecino más cercano para encontrar los k documentos más similares a una consulta determinada. En términos de incrustaciones, esto significa identificar los vectores más cercanos (distancia angular más pequeña). Usamos la métrica del coseno, donde los vectores más similares tienen distancias de coseno más pequeñas (y valores de similitud de coseno más altos).
search_engine = NearestNeighbors(n_neighbors=5, metric=”cosine”)
search_engine.fit(document_embeddings)
print(“Search engine is ready!”)
search_engine = NearestNeighbors ( n_neighbors = 5 , metric = “cosine” )
search_engine . fit ( document_embeddings )
print ( “Search engine is ready!” )
La lógica central de nuestro motor de búsqueda se resume en la siguiente función. Toma una consulta de texto sin formato, especifica cuántos resultados principales recuperar mediante top_k, calcula la incrustación de la consulta y recupera los vecinos más cercanos del índice.
El bucle dentro de la función imprime los k resultados principales clasificados por similitud:
def semantic_search(query, top_k=3):
# Embed the incoming search query
query_embedding = model.encode([query])
# Retrieve the closest matches
distances, indices = search_engine.kneighbors(query_embedding, n_neighbors=top_k)
print(f”n🔍 Query: ‘{query}'”)
print(“-” * 50)
for i in range(top_k):
doc_idx = indices[0][i] # Convertir distancia coseno en similitud (1 – distancia) similitud = 1 – distancias[0][i] print(f"Resultado {i+1} (Similitud: {similitud:.4f})") print(f"Texto: {documentos[int(doc_idx)][:150]}….n")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def búsqueda_semántica ( consulta , top_k = 3 ) :
# Incrustar la consulta de búsqueda entrante
consulta_incrustación = modelo . codificar ( [ consulta ] )
# Recuperar las coincidencias más cercanas
distancias , índices = motor_de_búsqueda . vecinos ( query_embedding , n_vecinos = top_k )
imprimir ( f "n🔍 Consulta: '{consulta}'" )
imprimir ( "-" * 50 )
para i en rango ( top_k ) :
doc_idx = índices [ 0 ] [ i ]
# Convertir la distancia del coseno en similitud (1 – distancia)
semejanza = 1 – distancias [ 0 ] [ i ]
print ( f "Resultado {i+1} (Similitud: {similitud:.4f})" )
print ( f "Texto: {documentos[int(doc_idx)][:150]}….n" )
Y eso es todo. Para probar la función, podemos formular un par de consultas de búsqueda de ejemplo:
semantic_search(“Wall street and stock market trends”)
semantic_search(“Space exploration and rocket launches”)
semantic_search ( "Wall Street y tendencias del mercado de valores" )
semantic_search ( "Exploración espacial y lanzamiento de cohetes" )
Los resultados están clasificados por similitud (aquí truncados para mayor claridad):
🔍 Query: ‘Wall street and stock market trends’
————————————————–
Result 1 (Similarity: 0.6258)
Text: Stocks Higher Despite Soaring Oil Prices NEW YORK – Wall Street shifted higher Monday as bargain hunters shrugged off skyrocketing oil prices and boug…
Result 2 (Similarity: 0.5586)
Text: Stocks Sharply Higher on Dip in Oil Prices NEW YORK – A drop in oil prices and upbeat outlooks from Wal-Mart and Lowe’s prompted new bargain-hunting o…
Result 3 (Similarity: 0.5459)
Text: Strategies for a Sideways Market (Reuters) Reuters – The bulls and the bears are in this together, scratching their heads and wondering what’s going t…
🔍 Query: ‘Space exploration and rocket launches’
————————————————–
Result 1 (Similarity: 0.5803)
Text: Redesigning Rockets: NASA Space Propulsion Finds a New Home (SPACE.com) SPACE.com – While the exploration of the Moon and other planets in our solar s…
Result 2 (Similarity: 0.5008)
Text: Canadian Team Joins Rocket Launch Contest (AP) AP – The #36;10 million competition to send a private manned rocket into space started looking more li…
Result 3 (Similarity: 0.4724)
Text: The Next Great Space Race: SpaceShipOne and Wild Fire to Go For the Gold (SPACE.com) SPACE.com – A piloted rocket ship race to claim a #36;10 million…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
🔍 Query : ‘Wall street and stock market trends’
— — — — — — — — — — — — — — — — — — — — — — — — —
Result 1 ( Similarity : 0.6258 )
Text : Stocks Higher Despite Soaring Oil Prices NEW YORK – Wall Street shifted higher Monday as bargain hunters shrugged off skyrocketing oil prices and boug . . .
Result 2 ( Similarity : 0.5586 )
Text : Stocks Sharply Higher on Dip in Oil Prices NEW YORK – A drop in oil prices and upbeat outlooks from Wal – Mart and Lowe ‘s prompted new bargain-hunting o…
Result 3 (Similarity: 0.5459)
Text: Strategies for a Sideways Market (Reuters) Reuters – The bulls and the bears are in this together, scratching their heads and wondering what’ s going t . . .
🔍 Query : ‘Space exploration and rocket launches’
— — — — — — — — — — — — — — — — — — — — — — — — —
Result 1 ( Similarity : 0.5803 )
Text : Redesigning Rockets : NASA Space Propulsion Finds a New Home ( SPACE . com ) SPACE . com – While the exploration of the Moon and other planets in our solar s . . .
Resultado 2 ( Similitud : 0,5008 )
Texto : Equipo canadiense se une al concurso de lanzamiento de cohetes ( AP ) AP – El #36;La competencia de 10 millones para enviar un cohete tripulado privado al espacio comenzó a parecer más li…
Resultado 3 ( Similitud : 0,4724 )
Texto : La próxima gran carrera espacial : nave espacial uno y Fuego salvaje a Ir por el oro ( ESPACIO.com ) ESPACIO . com – A carrera de cohetes pilotados para afirmar a #36;10 millones…
Resumen
Lo que hemos construido aquí puede verse como una puerta de entrada a sistemas de generación aumentada de recuperación. Si bien este ejemplo es intencionalmente simple, los motores de búsqueda semántica como este forman la capa de recuperación fundamental en las arquitecturas modernas que combinan la búsqueda semántica con grandes modelos de lenguaje.
Ahora que sabe cómo crear un motor de búsqueda semántico básico, es posible que desee explorar los sistemas de recuperación de generación aumentada con más profundidad.