Búsqueda semántica con NumPy
Creará un sistema de búsqueda semántica basado exclusivamente en Python que use NumPy para calcular la similitud coseno entre el embedding de una consulta y una colección de embeddings de documentos.
Búsqueda semántica con NumPy es una lección gratuita de AI Engineering Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Engineering Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Engineering Academy incluye 4 lecciones en total.
Búsqueda semántica sin una base de datos
La búsqueda semántica encuentra los documentos más relevantes para una consulta basándose en el significado, no en la coincidencia de palabras clave. La implementación más sencilla utiliza NumPy para calcular la similitud del coseno entre el embedding de una consulta y todos los embeddings de documentos en memoria, sin necesidad de una base de datos externa.
Este enfoque funciona bien con hasta decenas de miles de documentos y es ideal para crear prototipos antes de invertir en una base de datos vectorial.
Creación del corpus de documentos
Comience recopilando sus documentos y generando un embedding por documento mediante la API de OpenAI. Almacene los embeddings como una matriz de NumPy bidimensional, donde cada fila sea el vector de un documento. Mantenga una lista paralela con los textos de los documentos para poder recuperar el contenido original después de encontrar las coincidencias más relevantes.
import numpy as np
from openai import OpenAI
client = OpenAI()
documents = [
'Python is a high-level programming language.',
'NumPy provides fast numerical computing for Python.',
'Embeddings represent text as dense vectors.',
'Cosine similarity measures angle between vectors.',
'RAG combines retrieval with language generation.'
]
response = client.embeddings.create(
model='text-embedding-3-small',
input=documents
)
corpus_embeddings = np.array([item.embedding for item in response.data])
print(f'Corpus shape: {corpus_embeddings.shape}') # (5, 1536)Generación del embedding de la consulta del usuario
Cuando un usuario envíe una consulta de búsqueda, genere su embedding utilizando el mismo modelo que se utilizó para generar los embeddings de los documentos. Mezclar modelos —por ejemplo, usar text-embedding-3-small para los documentos y text-embedding-3-large para las consultas— producirá vectores en espacios diferentes y puntuaciones de similitud sin sentido.
from openai import OpenAI
import numpy as np
client = OpenAI()
query = 'How do I compute similarity between text?'
response = client.embeddings.create(
model='text-embedding-3-small', # must match corpus model
input=query
)
query_embedding = np.array(response.data[0].embedding)
print(f'Query vector shape: {query_embedding.shape}') # (1536,)Cálculo de la similitud del coseno con NumPy
Para encontrar la similitud entre la consulta y cada documento en una sola operación, calcule el producto escalar del vector de la consulta con la matriz de vectores de los documentos. Como ambos embeddings de OpenAI están normalizados según L2, esto equivale a la similitud del coseno para todos los documentos a la vez: O(n * d), donde n es el número de documentos y d es la dimensión.
import numpy as np
# corpus_embeddings: (n_docs, 1536)
# query_embedding: (1536,)
def semantic_search(query_vec, corpus_vecs):
# Matrix-vector dot product: shape (n_docs,)
similarities = corpus_vecs @ query_vec
return similarities
# Example call (assuming pre-computed embeddings)
# sims = semantic_search(query_embedding, corpus_embeddings)
# print(sims) # array of similarity scores, one per documentClasificación y recuperación de los resultados principales
Utilice np.argsort para clasificar los documentos por puntuación de similitud en orden descendente y, después, seleccione los índices de los k primeros resultados. Así obtendrá los índices de los documentos más relevantes, que podrá utilizar para buscar el texto original en su lista paralela.
import numpy as np
def get_top_k(query_vec, corpus_vecs, documents, k=3):
similarities = corpus_vecs @ query_vec
# argsort gives ascending order; [::-1] reverses to descending
ranked_indices = np.argsort(similarities)[::-1]
top_k_indices = ranked_indices[:k]
return [
{'text': documents[i], 'score': float(similarities[i])}
for i in top_k_indices
]
# results = get_top_k(query_embedding, corpus_embeddings, documents, k=3)
# for r in results:
# print(f'{r["score"]:.4f}: {r["text"]}')Ejemplo completo de búsqueda semántica
Uniendo todos los pasos: genere el embedding del corpus, genere el embedding de la consulta, calcule las similitudes y devuelva los resultados clasificados. Este patrón completo es la base de cada paso de recuperación de RAG, incluso cuando una base de datos vectorial sustituye a NumPy internamente.
import numpy as np
from openai import OpenAI
client = OpenAI()
docs = [
'Embeddings map text to numerical vectors.',
'Python lists store ordered collections.',
'Cosine similarity compares vector directions.',
'RAG retrieves documents to ground LLM answers.',
'Dictionaries store key-value pairs in Python.'
]
corpus_resp = client.embeddings.create(model='text-embedding-3-small', input=docs)
corpus = np.array([d.embedding for d in corpus_resp.data])
query = 'finding similar text using angles'
q_resp = client.embeddings.create(model='text-embedding-3-small', input=query)
q_vec = np.array(q_resp.data[0].embedding)
scores = corpus @ q_vec
for i in np.argsort(scores)[::-1][:3]:
print(f'{scores[i]:.3f}: {docs[i]}')Umbral de puntuación
No todos los resultados principales de k son realmente relevantes; a veces, incluso la mejor coincidencia es semánticamente deficiente. Añada un umbral de puntuación para filtrar los resultados con baja similitud. Un umbral habitual es de 0.70 a 0.80 para la similitud del coseno, pero debe calibrarlo para su dominio específico utilizando consultas reales.
import numpy as np
def search_with_threshold(query_vec, corpus_vecs, documents, k=5, threshold=0.75):
similarities = corpus_vecs @ query_vec
ranked = np.argsort(similarities)[::-1][:k]
results = []
for i in ranked:
if similarities[i] >= threshold:
results.append({'text': documents[i], 'score': float(similarities[i])})
return results
# Only returns documents above the minimum similarity thresholdCaracterísticas de rendimiento de la búsqueda con NumPy
La búsqueda de similitud con NumPy tiene una complejidad temporal de O(n * d) por consulta, donde n es el número de documentos y d es la dimensión del embedding. Para embeddings de 1536 dimensiones:
- 10.000 documentos: ~5 ms por consulta en una CPU moderna
- 100.000 documentos: ~50 ms por consulta
- 1.000.000 de documentos: ~500 ms; demasiado lento, cambie a una base de datos vectorial
NumPy es excelente para crear prototipos, pero no incluye búsqueda aproximada, filtrado ni persistencia.
Persistencia de embeddings en disco
Volver a calcular los embeddings en cada ejecución desperdicia llamadas a la API y dinero. Guarde en disco los embeddings del corpus y los textos de los documentos para volver a generarlos solo cuando cambie el corpus.
np.save almacena la matriz de embeddings de forma eficiente, y puede guardar la lista de documentos como JSON. Al iniciar, cargue ambos archivos en lugar de llamar a la API.
import numpy as np
import json
# Save
np.save('/tmp/corpus_embeddings.npy', corpus_embeddings)
with open('/tmp/corpus_docs.json', 'w') as f:
json.dump(documents, f)
# Load
corpus_embeddings = np.load('/tmp/corpus_embeddings.npy')
with open('/tmp/corpus_docs.json') as f:
documents = json.load(f)
print(f'Loaded {len(documents)} docs, shape {corpus_embeddings.shape}')Gestión incremental de documentos nuevos
Cuando lleguen documentos nuevos, no necesita volver a generar los embeddings de todo el corpus. Genere embeddings únicamente para los documentos nuevos y utilice np.vstack para añadir sus vectores a la matriz existente. Recuerde añadir también los textos nuevos a su lista de documentos en el mismo orden.
import numpy as np
from openai import OpenAI
client = OpenAI()
# Assume these exist from a previous session:
# corpus_embeddings: (n, 1536)
# documents: list of strings
new_docs = ['New document about vector search.']
resp = client.embeddings.create(model='text-embedding-3-small', input=new_docs)
new_vecs = np.array([item.embedding for item in resp.data])
corpus_embeddings = np.vstack([corpus_embeddings, new_vecs])
documents.extend(new_docs)
print(f'Corpus now has {len(documents)} documents')Limitaciones de la búsqueda en memoria
La búsqueda semántica con NumPy tiene limitaciones importantes en comparación con una base de datos vectorial diseñada específicamente para este fin:
- Sin persistencia: todo reside en la RAM y se pierde al reiniciar
- Sin filtrado de metadatos: no puede filtrar los resultados por fecha, categoría o autor
- Solo recorrido lineal: no hay indexación aproximada de vecinos más cercanos
- Sin acceso simultáneo: no es adecuada para implementaciones de producción multiusuario
Estas limitaciones justifican el uso de una base de datos vectorial dedicada para sistemas RAG en producción.
Comprobación rápida
Compruebe su comprensión de los conceptos de ingeniería de IA de esta lección.
Resumen de la lección
En esta lección ha aprendido que los productos escalares de matrices con embeddings normalizados según L2 calculan la similitud del coseno para todo el corpus en una sola operación, que np.argsort con inversión recupera los k documentos más similares y que la búsqueda con NumPy es ideal para prototipos, pero carece de persistencia y filtrado de metadatos. A continuación, utilizaremos clustering y UMAP para descubrir la estructura temática de una colección de embeddings.
Aprende Python con un tutor de IA — gratis
Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.
- Cursos
- 30
- Lecciones
- 120
Preguntas frecuentes
¿La lección «Búsqueda semántica con NumPy» es gratis?
Sí — el texto completo de «Búsqueda semántica con NumPy» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Engineering Academy, actualiza a CoddyKit PRO. El curso de AI Engineering Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Búsqueda semántica con NumPy»?
Creará un sistema de búsqueda semántica basado exclusivamente en Python que use NumPy para calcular la similitud coseno entre el embedding de una consulta y una colección de embeddings de documentos. Practicas AI Engineering Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar AI Engineering Academy?
No se requiere experiencia previa. AI Engineering Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Búsqueda semántica con NumPy»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de AI Engineering Academy?
Sí. Cada lección de AI Engineering Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- ¿Qué son los vector embeddings?
- Generación de embeddings con OpenAI
- Búsqueda semántica con NumPy
- Clustering y visualización de embeddings