Vectorembeddings uitgelegd voor ontwikkelaars
Dit artikel legt uit hoe vector embeddings werken en hoe u ze praktisch inzet voor semantische zoekopdrachten, specifiek voor ontwikkelaars die met Python en OpenAI's API werken.
Vereisten
- Python 3.10 of hoger - `openai` Python-package versie 1.35.0 of hoger - Een OpenAI API-sleutel met toegang tot het `text-embedding-3-small` model - `numpy` versie 1.26.0 of hoger voor vectorberekeningen
Installeer de dependencies:
pip install openai==1.35.0 numpy==1.26.0
Stappen
1. Maak een embedding aan
Een embedding is een lijst van floating-point nummers die de semantische betekenis van een tekst representeert. OpenAI's `text-embedding-3-small` model zet tekst om naar een vector van 1536 dimensies.
from openai import OpenAI</p><p>client = OpenAI(api_key="uw-api-sleutel")</p><p>response = client.embeddings.create(
model="text-embedding-3-small",
input="De kat zit op de mat"
)</p><p>embedding = response.data[0].embedding
print(len(embedding)) Output: 15362. Sla embeddings op in een vectordatabase
Voor productie gebruikt u een dedicated vectordatabase zoals Pinecone, Weaviate of pgvector. Voor lokale ontwikkeling volstaat een NumPy-array.
import numpy as np</p><p>Sla embeddings op als matrix
documents = [
"De kat zit op de mat",
"De hond rent door het park",
"Een vogel vliegt over het water"
]</p><p>embeddings = []
for doc in documents:
response = client.embeddings.create(
model="text-embedding-3-small",
input=doc
)
embeddings.append(response.data[0].embedding)</p><p>embedding_matrix = np.array(embeddings)
np.save('document_embeddings.npy', embedding_matrix)3. Bereken cosine similarity
Cosine similarity meet de hoek tussen twee vectoren. Een waarde van 1 betekent identiek, 0 betekent geen overlap. Dit is de standaardmanier om semantische gelijkenis te bepalen.
def cosine_similarity(vec_a, vec_b):
dot_product = np.dot(vec_a, vec_b)
norm_a = np.linalg.norm(vec_a)
norm_b = np.linalg.norm(vec_b)
return dot_product / (norm_a * norm_b)</p><p>Test met twee gerelateerde zinnen
query = "Een poes ligt op de vloer"
query_response = client.embeddings.create(
model="text-embedding-3-small",
input=query
)
query_embedding = query_response.data[0].embedding</p><p>similarities = [
cosine_similarity(query_embedding, doc_emb)
for doc_emb in embeddings
]</p><p>print(similarities)
Output: [0.82, 0.31, 0.28] — de eerste zin is het meest gerelateerd4. Bouw een semantische zoekfunctie
Semantische zoekopdrachten vinden resultaten op basis van betekenis, niet op exacte woordovereenkomst. Dit werkt omdat embeddings woorden met vergelijkbare betekenis dicht bij elkaar plaatsen in de vectorruimte.
def semantic_search(query, embeddings, documents, top_k=3):
query_response = client.embeddings.create(
model="text-embedding-3-small",
input=query
)
query_embedding = query_response.data[0].embedding
similarities = [
cosine_similarity(query_embedding, doc_emb)
for doc_emb in embeddings
]
top_indices = np.argsort(similarities)[-top_k:][::-1]
results = []
for idx in top_indices:
results.append({
'document': documents[idx],
'similarity': similarities[idx]
})
return results</p><p>results = semantic_search("Een dier ligt te slapen", embeddings, documents)
for r in results:
print(f"{r['similarity']:.2f}: {r['document']}")
Output:
0.79: De kat zit op de mat
0.45: De hond rent door het park
0.33: Een vogel vliegt over het water5. Reduceer dimensionaliteit met PCA
1536 dimensies zijn veel voor visualisatie of opslag in het geheugen. Principal Component Analysis (PCA) reduceert de dimensionaliteit terwijl de relatieve afstanden behouden blijven.
from sklearn.decomposition import PCA</p><p>pca = PCA(n_components=2)
reduced_embeddings = pca.fit_transform(embedding_matrix)</p><p>print(reduced_embeddings.shape) Output: (3, 2)6. Implementeer caching voor embeddings
Embeddings genereren kost API-calls en tijd. Cache ze lokaal om herhaalde berekeningen te voorkomen.
import json
import hashlib
from pathlib import Path</p><p>CACHE_FILE = Path("embedding_cache.json")</p><p>def get_cached_embedding(text):
if CACHE_FILE.exists():
cache = json.loads(CACHE_FILE.read_text())
else:
cache = {}
text_hash = hashlib.sha256(text.encode()).hexdigest()
if text_hash in cache:
return cache[text_hash]
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
embedding = response.data[0].embedding
cache[text_hash] = embedding
CACHE_FILE.write_text(json.dumps(cache))
return embedding</p><p>Gebruik de cache
emb = get_cached_embedding("De kat zit op de mat") API call
emb_cached = get_cached_embedding("De kat zit op de mat") Uit cache7. Batchverwerking voor efficiëntie
Verwerk meerdere documenten in één API-call. Dit verlaagt de latentie en kosten aanzienlijk.
teksten = [
"Eerste document over kunstmatige intelligentie",
"Tweede document over machine learning",
"Derde document over natuurlijke taalverwerking"
]</p><p>batch_response = client.embeddings.create(
model="text-embedding-3-small",
input=teksten
)</p><p>batch_embeddings = [item.embedding for item in batch_response.data]
print(len(batch_embeddings)) # Uitvoer: 38. Implementeer een volledige zoekpipeline
Combineer alle stappen in een herbruikbare pipeline die u rechtstreeks in een applicatie kunt integreren.
class EmbeddingSearch:
def __init__(self, model="text-embedding-3-small"):
self.client = OpenAI()
self.model = model
self.documents = []
self.embeddings = np.array([])
def add_documents(self, docs):
self.documents.extend(docs)
response = self.client.embeddings.create(
model=self.model,
input=docs
)
new_embeddings = np.array([item.embedding for item in response.data])
if self.embeddings.size == 0:
self.embeddings = new_embeddings
else:
self.embeddings = np.vstack([self.embeddings, new_embeddings])
def search(self, query, top_k=5):
response = self.client.embeddings.create(
model=self.model,
input=query
)
query_embedding = np.array(response.data[0].embedding)
similariteiten = [
cosine_similarity(query_embedding, doc_emb)
for doc_emb in self.embeddings
]
top_indices = np.argsort(similariteiten)[-top_k:][::-1]
return [
(self.documents[idx], similariteiten[idx])
for idx in top_indices
]</p><p># Gebruik van de pipeline
zoekmachine = EmbeddingSearch()
zoekmachine.add_documents(documenten)
resultaten = zoekmachine.search("Hoe werkt een kat?", top_k=2)</p><p>for doc, score in resultaten:
print(f"{score:.3f} - {doc}")Verificatie
Controleer of uw embeddings correct werken door de dimensionaliteit en semantische relaties te testen:
python -c "
from openai import OpenAI
import numpy as np</p><p>client = OpenAI()
response = client.embeddings.create(
model='text-embedding-3-small',
input=['hond', 'kat', 'auto']
)
embeddings = np.array([item.embedding for item in response.data])</p><p># Check dimensionaliteit
assert embeddings.shape == (3, 1536), f'Fout: verwacht (3, 1536), kreeg {embeddings.shape}'</p><p># Check semantische relaties
def cos_sim(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))</p><p>hond_kat = cos_sim(embeddings[0], embeddings[1])
hond_auto = cos_sim(embeddings[0], embeddings[2])</p><p>print(f'Dimensionaliteit: {embeddings.shape[1]}')
print(f'Hond vs kat similariteit: {hond_kat:.3f}')
print(f'Hond vs auto similariteit: {hond_auto:.3f}')
assert hond_kat > hond_auto, 'Semantische relatie klopt niet'
print('Alle checks geslaagd')
"Veelgestelde problemen
1. API-fout: "model_not_found"
Uw API-sleutel heeft geen toegang tot het `text-embedding-3-small` model. Gebruik een oudere modelversie of upgrade uw account.
Oplossing: Vervang `text-embedding-3-small` door `text-embedding-ada-002` en pas de dimensionaliteit aan van 1536 naar 3072.
2. Lage similariteitsscores voor gerelateerde teksten
Embeddings werken goed voor semantische gelijkenis, maar niet voor exacte woordovereenkomst. Twee zinnen met dezelfde woorden maar een andere volgorde kunnen lage scores geven.
Oplossing: Combineer embeddings met een lexicale zoekmethode zoals BM25 voor hybride zoekopdrachten. Gebruik `text-embedding-3-large` voor betere semantische precisie.
3. Geheugenproblemen bij veel documenten
Het opslaan van honderdduizenden embeddings van 1536 dimensies kost veel RAM. Een miljoen documenten verbruikt ongeveer 6 GB.
Oplossing: Gebruik een dedicated vectordatabase zoals Pinecone of Weaviate. Deze slaan embeddings op schijf op en gebruiken approximate nearest neighbor (ANN)-algoritmen voor snelle zoekopdrachten.
De overstap van ruwe tekst naar embeddings is een fundamentele verschuiving: uw code werkt niet langer met woorden, maar met posities in een continue vectorruimte — en die ruimte bepaalt uiteindelijk wat uw zoekmachine wel en niet vindt.