Încorporare și căutare vectorială: BERT vs Sentence Transformers
În primul articol al acestei serii am explorat arhitectura RAG și rolul acesteia în rezolva halucinațiile LLM. Inima care bate a fiecărui sistem RAG este regăsire: capacitatea de a găsi, într-o bază de cunoștințe potențial uriașe, cele mai relevante documente pentru o cerere. Această abilitate se bazează în întregime pe înglobări și mai departe căutare vectorială.
O încorporare este o reprezentare numerică a sensului unui text: o secvență de numere (un vector) care surprinde relațiile semantice dintre cuvinte, propoziții și documente. Calitatea înglobărilor determină în mod direct calitatea extragerii și, prin urmare calitatea întregului sistem RAG. Alegerea greșită a modelului de încorporare înseamnă construiește fundația casei pe nisip.
În acest al doilea articol al seriei Inginerie AI și RAG avansat, vom face o călătorie completă: de la originile înglobărilor cu Word2Vec, până la revoluția BERT, până la Sentence Transformers moderne. Vom vedea cum se generează înglobări, cum să comparați textele în spațiul vectorial, cum să construiți un motor căutare semantică cu FAISS și cum să alegeți modelul potrivit pentru cazul dvs. de utilizare.
Prezentare generală a seriei
| # | Articol | Concentrează-te |
|---|---|---|
| 1 | RAG a explicat | Fundamente și arhitectură completă |
| 2 | Sunteți aici - Embeddings and Semantic Search | Cum textele devin vectori |
| 3 | Baza de date vectorială în profunzime | Stocare, indexare, căutare de similaritate |
| 4 | RAG cu LangChain și Python | Implementare practică de la capăt la capăt |
| 5 | Recuperare hibridă și reclasificare | Cuvânt cheie hibrid + căutare semantică |
| 6 | Fereastra context și inginerie promptă | Optimizați contextul pentru LLM |
| 7 | RAG în producție | Scalare, monitorizare, evaluare |
| 8 | Grafice de cunoștințe și RAG-uri | Grafice de cunoștințe + regăsire |
| 9 | Sisteme multi-agenți | Agenți AI colaborativi |
| 10 | Viitorul RAG | Tendințe, cercetare și următorii pași |
Ce vei învăța
- Ce este o încorporare și cum reprezintă ea sensul în formă numerică
- Evoluția de la Word2Vec la BERT la Sentence Transformers
- de ce vanilla BERT nu funcționează pentru similaritate și cum rezolvă SBERT problema
- Cum să alegeți modelul de încorporare potrivit din zeci de opțiuni
- Implementați căutarea semantică cu transformatoare de propoziție și FAISS în Python
- Valori de similaritate vectorială și când să le folosești pe fiecare
- Comparație arhitecturală între principalele motoare de căutare vectoriale
- Cum să reglați fin înglobările pentru anumite domenii
1. Ce este o încorporare
Un încorporarea este o funcție matematică care transformă un obiect discret (un cuvânt, o propoziție, un document, o imagine) într-un vector de numere reale într-unul spațiu continuu cu dimensionalitate fixă. Practic, convertește textul care poate fi citit de om într-o listă de numere ușor de înțeles de mașină, păstrând relațiile semantice printre textele originale.
Ideea fundamentală este că textele cu sens similar trebuie să aibă vectori apropiați în spațiu, în timp ce textele cu semnificații diferite trebuie să aibă vectori îndepărtați. Această proprietate se numeste izomorfism semantic: structura relaţiilor semantice între cuvinte se păstrează în geometria spaţiului vectorial.
1.1 De la codificarea One-Hot la vectori densi
Pentru a înțelege de ce sunt necesare înglobările, să luăm în considerare cea mai simplă alternativă: cel codificare one-hot. Cu un vocabular de 50.000 de cuvinte, fiecare cuvânt este reprezentat de un vector de 50.000 de dimensiuni cu un singur 1 și toate restul zerouri.
ONE-HOT ENCODING (vocabolario di 50.000 parole):
"gatto" = [0, 0, ..., 1, ..., 0, 0] (50.000 dimensioni, un solo 1)
"cane" = [0, 0, ..., 0, ..., 1, 0] (50.000 dimensioni, un solo 1)
Distanza tra "gatto" e "cane" = stessa di "gatto" e "frigorifero"
Nessuna informazione semantica!
DENSE EMBEDDING (es. 384 dimensioni):
"gatto" = [0.23, -0.45, 0.89, ..., 0.12] (384 dimensioni, tutti numeri reali)
"cane" = [0.21, -0.42, 0.91, ..., 0.15] (384 dimensioni, tutti numeri reali)
Distanza tra "gatto" e "cane" = PICCOLA (animali domestici)
Distanza tra "gatto" e "frigorifero" = GRANDE (concetti diversi)
Il significato è catturato nella geometria!
Problemele codificării one-hot sunt evidente: vectorii sunt uriași (dimensionalitate egale cu vocabularul), împrăștiate (aproape toate zerouri) și, mai presus de toate, toate ortogonale între ele. Orice două cuvinte au aceeași distanță, indiferent de sens. Nu există mod de a distinge „pisica” de „felina” versus „pisica” de „economie”.
I înglobări dense rezolva toate cele trei probleme: vectorii sunt compact (câteva sute de dimensiuni), dens (toate valorile sunt semnificative) și ele surprind relaţiile semantice în geometria lor. Cuvintele similare au vectori apropiați, iar direcţiile în spaţiu corespund conceptelor lingvistice.
1.2 Spațiul semantic
O proprietate fascinantă a înglobărilor este că acestea relații semantice da se transformă în relaţii geometrice. Exemplul clasic este aritmetica vector: vectorul „rege” minus „barbat” plus „femeie” produce un vector foarte apropiat la „regina”. Aceasta nu este magie: înseamnă că spațiul a captat conceptul de „gen” ca o direcție și conceptul de „regalitate” ca altă direcție.
Relazioni semantiche come operazioni vettoriali:
vec("re") - vec("uomo") + vec("donna") ~ vec("regina")
vec("Parigi") - vec("Francia") + vec("Italia") ~ vec("Roma")
vec("buono") - vec("migliore") + vec("grande") ~ vec("più grande")
Cluster nello spazio:
[gatto, cane, cavallo, pesce] --> vicini (animali)
[Python, Java, C++, Rust] --> vicini (linguaggi di programmazione)
[felice, contento, gioioso] --> vicinissimi (sinonimi)
Intuiția fundamentală
O încorporare este în esență a adică compresor. Ia sensul unui text, cu toate nuanțele sale, și îl înțelege într-un singur punct în spațiul multidimensional. Poziția acelui punct față de toate celelalte punctele surprinde toate relațiile semantice pe care modelul le-a învățat. Acesta este baza pe care se bazează întreaga cercetare semantică.
2. Înglobare de cuvinte clasice: Word2Vec, GloVe, FastText
Istoria modernă a înglobărilor începe în 2013 cu Word2Old, publicat de Tomas Mikolov și colegii de la Google. Ideea revoluționară era simplă: poți învăța sensul unui cuvânt din contextul în care apare. După cum a spus el lingvistul John Firth în 1957: „Veți cunoaște un cuvânt după compania pe care o păstrează”.
2.1 Word2Vec: CBOW și Skip-gram
Word2Vec propune două arhitecturi neuronale pentru învățarea înglobărilor:
- CBOW (Sacul Continuu de Cuvinte): Având în vedere o fereastră de cuvinte context, preziceți cuvântul central. Exemplu: dat fiind „___ miaună tare”, prezice „pisica”
- Skipgram: Dat un cuvânt central, prezice cuvintele contextului. Exemplu: dat „pisica”, prezice „cea”, „miau”, „tare”
CBOW (Continuous Bag of Words):
Input: parole di contesto ["il", "___", "miagola", "forte"]
Output: parola target "gatto"
Contesto ──> [Embedding Layer] ──> Media vettori ──> [Softmax] ──> "gatto"
Veloce, buono per parole frequenti
SKIP-GRAM:
Input: parola target "gatto"
Output: parole di contesto ["il", "miagola", "forte"]
"gatto" ──> [Embedding Layer] ──> [Softmax] ──> parole contesto
Più lento, migliore per parole rare
Parametri tipici:
- Dimensioni embedding: 100-300
- Finestra di contesto: 5-10 parole
- Vocabolario: 100k-1M parole
- Training: miliardi di parole (Wikipedia, Common Crawl)
2.2 GloVe și FastText
mănușă (Global Vectors for Word Representation, Stanford 2014) adoptă a abordare diferită: construiește o matrice globală de co-ocurență și o factorizează prin obține înglobările. Capturați relațiile globale care Word2Vec, cu fereastra sa local, ar putea pierde.
FastText (Facebook 2016) extinde Word2Vec lucrând la nivelul de subcuvinte (n-grame de caractere). Este reprezentat cuvântul „încorporare”. de asemenea prin componentele sale: „emb”, „mbe”, „bed”, „edd”, etc. Acest lucru vă permite să generați înglobări, de asemenea, pentru cuvintele niciodată văzute în timpul antrenamentului (cuvinte în afara vocabularului), un avantaj crucial pentru limbile cu morfologie bogată precum italiana.
Comparația înglobărilor de cuvinte clasice
| Model | An | Abordare | Vigoare | Limită |
|---|---|---|---|---|
| Word2Old | 2013 | Predicția contextului local | Rapid, eficient | Fără OOV, fără context |
| mănușă | 2014 | Co-ocurență globală | Relații globale | Fără OOV, fără context |
| FastText | 2016 | N-grame de caractere | Gestionează OOV | Un vector pe cuvânt |
2.3 Limita fundamentală: un vector per cuvânt
Toate înglobările de cuvinte clasice au o limitare structurală: produc un singur vector pentru fiecare cuvânt, indiferent de context. Cuvântul „birou” are aceeași încorporare atât în „birou de școală” cât și în „birou al Italiei”. Aceasta este o problemă serioasă, deoarece sensul unui cuvânt depinde aproape întotdeauna de contextul în care apare.
În plus, aceste modele funcționează la nivelul de cuvinte singure: nu poate produce o încorporare pentru o propoziție sau un paragraf. A reprezenta a propoziție, trebuie să recurgem la strategii rudimentare, cum ar fi media vectorilor de cuvintele care o compun, pierzând informații despre ordinea și structura sintactică.
De ce să nu folosiți Word2Vec pentru RAG
Înglobările clasice de cuvinte sunt inadecvate pentru căutarea semantică modernă deoarece: (1) nu captează contextul, (2) nu produc înglobări la nivel de propoziție, (3) media vectorială pierde informații critice. Expresia „câinele mușcă pe om” iar „om mușcă câine” ar avea aceeași încorporare. Pentru RAG ai nevoie de modele care înțeleg sensul întregii propoziții în contextul ei.
3. Înglobări contextuale: Revoluția BERT
În 2018, Google publică BERT (Reprezentări codificatoare bidirecționale din Transformers) și schimbă radical peisajul. BERT produce înglobări contextuale: reprezentarea fiecărui cuvânt depinde din întregul context al propoziţiei în care apare. Cuvântul „birou” în „birou școlii” va avea o încorporare diferită de „il banca d'Italia”.
3.1 Cum funcționează BERT
BERT se bazează pe arhitectură Transformator codificator. Preia intrare o secvență de jetoane și produce, pentru fiecare jetoane, un vector contextualizat de 768 dimensiuni (BERT-bază) sau 1024 dimensiuni (BERT-mare). Puterea BERT constă în mecanism al autoatenție: fiecare jetoane „se uită” la toate celelalte jetoane din secvență, atât la stânga, cât și la dreapta (bidirecțională), pentru a-ți calcula propria ta reprezentare.
Input: [CLS] il banco di scuola [SEP]
Tokenizzazione:
[CLS] il ban ##co di scuo ##la [SEP]
12 layer di Transformer Encoder:
Layer 1: Self-attention + Feed-forward
Layer 2: Self-attention + Feed-forward
...
Layer 12: Self-attention + Feed-forward
Output: un vettore 768-dim per OGNI token
[CLS] --> [0.23, -0.45, ..., 0.12] (vettore "riassunto")
il --> [0.11, -0.32, ..., 0.08]
ban --> [0.56, 0.12, ..., 0.34] (contestualizzato!)
##co --> [0.45, 0.09, ..., 0.29]
di --> [0.03, -0.21, ..., 0.05]
scuo --> [0.67, 0.33, ..., 0.41]
##la --> [0.59, 0.28, ..., 0.38]
[SEP] --> [0.02, -0.05, ..., 0.01]
3.2 Strategii de grupare: de la simbol la expresie
BERT produce un vector pentru fiecare jeton, dar pentru căutarea semantică avem nevoie de ea a un singur vector pentru întreaga propoziție. Cum să-l obții? Sunt mai multe strategii de punerea în comun:
Strategii de punere în comun pentru BERT
| Strategie | Descriere | calitate |
|---|---|---|
| [CLS] Jetoane | Utilizați un transportator de jetoane speciale [CLS] | Mediocru pentru similitudine |
| Mean Pooling | Media tuturor vectorilor token | Bun in general |
| Pooling maxim | Valoarea maximă pentru fiecare dimensiune | Captați caracteristici importante |
| Medie ponderată | Medie ponderată cu ponderi ale atenției | Bun, mai complex |
3.3 de ce BERT Vanilla nu funcționează pentru similaritate
În ciuda puterii BERT, folosește-l direct pentru a calcula asemănarea dintre propoziții este extrem de ineficientă și produce rezultate slabe. Motivul este dublu:
- Ineficiență de calcul: Pentru a compara N propoziții între ele, trebuie să treci fiecare pereche de propoziții prin BERT. Cu 10.000 de sentințe, e nevoie de ele 10.000 x 10.000 / 2 = 50 de milioane de treceri înainte. Cu un timp de aproximativ 65 ms per cuplu, ar dura aproximativ 65 de ore
- Spațiu de încorporare degenerat: Înglobările produse prin pooling pe BERT nu sunt optimizate pentru asemănarea cosinusului. Toate propozițiile tind să ajung într-o regiune îngustă a spațiului, ceea ce face dificilă distingerea propozițiilor asemănătoare din propoziții diferite. Acest fenomen se numește anizotropie
Problema de codificare încrucișată
BERT este conceput ca encoder încrucișat: ia DOUA propoziții ca intrare și produce un scor de similaritate. Pentru fiecare pereche de propoziții trebuie să recalculezi totul de la zero. Cu un corpus de 10.000 de documente și o interogare, aveți nevoie de 10.000 pasă înainte. Pentru a construi un indice al tuturor comparațiilor posibile, costul crește pătratic. Acest lucru face ca BERT să nu fie practic pentru recuperarea în timp real.
4. Transformatori de propoziții: soluția
În 2019, Nils Reimers și Iryna Gurevych publică Sentence-BERT (SBERT), care rezolvă elegant ambele probleme. Ideea cheie este de a transforma BERT din encoder încrucișat a bi-coder: în loc de a transmite perechi de propoziții, fiecare propoziție este codificată independent într-un vector dens. Asemanare da apoi calculează cu o operație vectorială simplă (asemănarea cosinusului).
4.1 Arhitectura siameză și rețele triplet
SBERT este instruit cu o arhitectură a Rețeaua siameză: două exemplare modelele BERT identice (cu ponderi comune) procesează două propoziții separat. Transportatorii rezultate sunt apoi comparate cu o funcție de pierdere care „se apropie” de vectori propoziții similare și „elimină” vectorii diferitelor propoziții.
CROSS-ENCODER (BERT vanilla):
["frase A", "frase B"] ──> [BERT] ──> punteggio similarità
Deve processare OGNI coppia. O(n^2) per n frasi.
BI-ENCODER (SBERT):
"frase A" ──> [BERT + Pooling] ──> vettore_A (384-dim)
"frase B" ──> [BERT + Pooling] ──> vettore_B (384-dim)
similarità = cosine(vettore_A, vettore_B)
Ogni frase viene codificata UNA VOLTA. O(n) per n frasi.
I vettori possono essere pre-calcolati e indicizzati!
TRAINING con Siamese Network:
Frase 1 ──> [BERT] ──> pool ──> u
├──> loss(u, v, label)
Frase 2 ──> [BERT] ──> pool ──> v
Label = 1 se frasi simili, 0 se diverse
Loss: Contrastive Loss o Cosine Similarity Loss
Le rețele triplete extinde abordarea cu trei intrări: o ancoră, un exemplu pozitiv (similar) și un exemplu negativ (diferit). Pierderea (pierderea marjei triplet) împinge modelul să aibă o distanță ancoră-pozitivă mai mică decât distanța ancora-negativ cu cel puțin o marjă predefinită.
4.2 Avantajele SBERT în comparație cu BERT
Comparație de performanță
| Metric | BERT Cross-Encoder | Bi-encoder SBERT |
|---|---|---|
| Similaritate 10.000 de propoziții | ~65 de ore | ~5 secunde |
| Precalcul vectorial | Nu este posibil | Da, o singură dată |
| Indexarea | Nu se aplică | FAISS, HNSW etc. |
| Calitatea de referință STS | ~87 (Spearman) | ~85 (Spearman) |
| Utilizarea în producție | Doar reclasificare | Recuperare + clasare |
SBERT sacrifică un mic procent de acuratețe (aproximativ 2 puncte la STS Benchmark) dar castiga unul Viteză de 10.000 de ori mai mare. În practică, alegerea este obligatoriu: codificatorul încrucișat poate fi folosit ca un re-ranker pe rezultatele top-k ale SBERT, combinând cele mai bune dintre ambele abordări (modelul numit recuperați și re-clasați).
5. Compararea modelelor de încorporare
Ecosistemul modelului de încorporare este vast și evoluează rapid. Alegerea de modelul corect depinde de cazul de utilizare: căutare semantică, grupare, clasificare, RAG multilingv, buget și latență. Iată o comparație detaliată a celor mai relevante modele.
Comparația modelelor de încorporare (2024-2025)
| Model | Furnizorii | Dimensiuni | Max Token | MTEB medie | Note |
|---|---|---|---|---|---|
| toate-MiniLM-L6-v2 | SBERT | 384 | 256 | 56,26 | Foarte rapid, ideal pentru prototipare |
| all-mpnet-base-v2 | SBERT | 768 | 384 | 57,78 | Cel mai bun raport calitate/viteză open-source |
| e5-large-v2 | Microsoft | 1024 | 512 | 62.20 | Calitate înaltă, necesită prefixul „interogare:” / „pasare:” |
| BGE-M3 | BAAI | 1024 | 8192 | 63,55 | Multilingv, dens+rar+colbert |
| text-incorporare-3-mic | OpenAI | 1536 | 8191 | 62,26 | Cloud API, ieftin (0,02 USD/1 milion de token) |
| text-incorporare-3-mare | OpenAI | 3072 | 8191 | 64,59 | Cloud API, calitate superioară (0,13 USD/1 milion de token) |
| embed-v3 (engleză) | Coere | 1024 | 512 | 64,47 | Cloud API, excelent pentru cercetare |
| nomic-embed-text-v1.5 | Nomic | 768 | 8192 | 62,28 | Open-source, context lung, Matryoshka |
Cum să citiți benchmark-urile MTEB
MTEB (Massive Text Embedding Benchmark) este standardul de facto pentru evalua modelele de încorporare. Include peste 56 de seturi de date în 8 categorii (clasificare, grupare, clasificare perechi, reclasificare, regăsire, STS, rezumare). Scorul mediu este media pentru toate categoriile, dar pentru RAG sub-scorul Recuperare este cel mai relevant. Verificați întotdeauna scorul specific pentru cazul dvs. de utilizare.
5.1 Criterii de alegere
- Prototiparea rapidă: all-MiniLM-L6-v2 - rapid, ușor, gratuit
- Productie open-source: all-mpnet-base-v2 sau e5-large-v2
- Cea mai înaltă calitate a norului: text-embedding-3-large (OpenAI) sau embed-v3 (Cohere)
- Multilingv / italiană: BGE-M3 sau multilingv-e5-large
- Context lung (documente lungi): BGE-M3 (8192 de jetoane) sau nomic-embed-text-v1.5
- Buget limitat: text-embedding-3-small (OpenAI) sau șabloane open source auto-găzduite
6. Măsuri de similaritate vectorială
Odată ce aveți vectorii, aveți nevoie de o funcție pentru a măsura cât de mult sunt doi vectori „asemănător”. Alegerea metricii afectează atât rezultatele, cât și performanța.
6.1 Similitudinea cosinusului
La putina asemanare măsoară cosinusul unghiului dintre doi vectori. Variază de la -1 (vectori opuși) la +1 (vectori identici), cu 0 pentru vectorii ortogonali. Ignorați mărimea vectorilor, concentrându-vă doar pe direcție: este metrica cel mai utilizat pentru încorporarea textului.
COSINE SIMILARITY:
A . B sum(a_i * b_i)
cos(A,B) = ─────── = ──────────────────────────────
|A| |B| sqrt(sum(a_i^2)) * sqrt(sum(b_i^2))
Range: [-1, +1]
+1 = identici, 0 = ortogonali, -1 = opposti
Invariante alla scala (normalizzata)
DOT PRODUCT (Prodotto Scalare):
dot(A,B) = sum(a_i * b_i)
Range: (-inf, +inf)
Sensibile alla magnitudine dei vettori
Più veloce (nessuna normalizzazione)
Equivalente a cosine se i vettori sono già normalizzati (norma L2 = 1)
EUCLIDEAN DISTANCE (L2):
d(A,B) = sqrt(sum((a_i - b_i)^2))
Range: [0, +inf)
0 = identici, valori alti = molto diversi
Sensibile alla magnitudine
Nota: DISTANZA, non similarità (valori bassi = più simili)
Când să utilizați ce metrică
| Metric | Când să-l folosești | Performanţă |
|---|---|---|
| Asemănarea cosinusului | Implicit pentru încorporarea textului, șabloane nenormalizate | Medie (necesită normalizare) |
| Produs punct | Embeddings deja normalizate (Sentence Transformers), când este nevoie de viteză | Ridicat (operare mai ușoară) |
| euclidian (L2) | Când magnitudinea este semnificativă, gruparea | Medie |
Un sfat la îndemână: dacă utilizați Sentence Transformers, vectorii pot fi normalizați conform unui standard unitar. În acest caz, asemănarea cosinusului și produsul punctual sunt echivalente, iar produsul punct este mai rapid. Majoritatea bazelor de date vectoriale le acceptă pe toate trei metrici și vă permite să alegeți când creați indexul.
7. Implementare practică cu Python
Să trecem la cod. Vom implementa un sistem complet de căutare semantică folosind
sentence-transformers pentru înglobări e FAISS pentru
indexare și căutare vectorială.
7.1 Configurare și instalare
# Crea ambiente virtuale
python -m venv embedding-env
source embedding-env/bin/activate
# Installa le dipendenze principali
pip install sentence-transformers==3.3.1
pip install faiss-cpu==1.9.0 # Per CPU (usa faiss-gpu per GPU)
pip install numpy==2.1.3
pip install pandas==2.2.3
pip install tqdm==4.67.1
# Opzionale: per visualizzazione
pip install matplotlib==3.9.3
pip install scikit-learn==1.6.0
7.2 Generați înglobări cu transformatoare de propoziții
from sentence_transformers import SentenceTransformer
import numpy as np
# Carica il modello (download automatico al primo utilizzo)
model = SentenceTransformer("all-MiniLM-L6-v2")
# Frasi di esempio
sentences = [
"Il gatto dorme sul divano",
"Il felino riposa sul sofa",
"Python è un linguaggio di programmazione",
"Java è usato per lo sviluppo enterprise",
"La ricerca semantica usa gli embeddings",
]
# Genera embeddings
embeddings = model.encode(sentences, show_progress_bar=True)
# Ogni embedding è un vettore numpy
print(f"Tipo: {type(embeddings)}")
print(f"Shape: {embeddings.shape}") # (5, 384) - 5 frasi, 384 dimensioni
print(f"Norma L2 del primo vettore: {np.linalg.norm(embeddings[0]):.4f}")
# Visualizza i primi 10 valori del primo embedding
print(f"\nPrimi 10 valori: {embeddings[0][:10]}")
7.3 Calculați asemănarea dintre fraze
from sentence_transformers import SentenceTransformer, util
import numpy as np
model = SentenceTransformer("all-MiniLM-L6-v2")
# Frasi da confrontare
sentences = [
"Il gatto dorme sul divano",
"Il felino riposa sul sofa",
"Python è un linguaggio di programmazione",
"La pioggia cade sulla citta",
]
embeddings = model.encode(sentences, convert_to_tensor=True)
# Matrice di similarità completa
cosine_scores = util.cos_sim(embeddings, embeddings)
print("Matrice di Similarità Coseno:")
print("-" * 60)
for i in range(len(sentences)):
for j in range(len(sentences)):
score = cosine_scores[i][j].item()
if i != j:
print(f" {sentences[i][:30]:<30} vs {sentences[j][:30]:<30}")
print(f" Similarità: {score:.4f}")
# Calcolo manuale della cosine similarity per verifica
def cosine_similarity_manual(vec_a, vec_b):
"""Calcola cosine similarity manualmente."""
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)
emb_np = model.encode(sentences)
manual_score = cosine_similarity_manual(emb_np[0], emb_np[1])
print(f"\nVerifica manuale (frase 0 vs 1): {manual_score:.4f}")
Rezultat așteptat
Propozițiile „Pisica doarme pe canapea” și „Felina se odihnește pe canapea” vor avea una similaritate mare (~0,85), deoarece exprimă același concept cu cuvinte diferite. Asemănarea cu „Python este un limbaj de programare” va fi scăzută (~0,10), deoarece conceptele sunt complet diferite. Aceasta este puterea cercetării semantică versus căutarea prin cuvinte cheie.
7.4 Căutare semantică cu FAISS
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
class SemanticSearchEngine:
"""Motore di ricerca semantica basato su FAISS."""
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
self.model = SentenceTransformer(model_name)
self.dimension = self.model.get_sentence_embedding_dimension()
self.index = None
self.documents = []
def build_index(self, documents: list[str], use_gpu: bool = False):
"""Costruisce l'indice FAISS dai documenti."""
self.documents = documents
# Genera embeddings per tutti i documenti
embeddings = self.model.encode(
documents,
show_progress_bar=True,
normalize_embeddings=True, # Normalizza per cosine similarity
batch_size=64,
)
embeddings = embeddings.astype("float32")
# Crea indice FAISS (Inner Product = Cosine per vettori normalizzati)
self.index = faiss.IndexFlatIP(self.dimension)
if use_gpu:
# Trasferisci indice su GPU per ricerca più veloce
res = faiss.StandardGpuResources()
self.index = faiss.index_cpu_to_gpu(res, 0, self.index)
self.index.add(embeddings)
print(f"Indice costruito: {self.index.ntotal} documenti indicizzati")
def search(self, query: str, top_k: int = 5) -> list[dict]:
"""Cerca i documenti più simili alla query."""
# Genera embedding della query
query_embedding = self.model.encode(
[query],
normalize_embeddings=True,
).astype("float32")
# Ricerca nell'indice FAISS
scores, indices = self.index.search(query_embedding, top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
if idx != -1: # -1 indica nessun risultato
results.append({
"document": self.documents[idx],
"score": float(score),
"index": int(idx),
})
return results
def save_index(self, path: str):
"""Salva l'indice su disco."""
faiss.write_index(self.index, path)
def load_index(self, path: str):
"""Carica l'indice da disco."""
self.index = faiss.read_index(path)
# Utilizzo
if __name__ == "__main__":
# Corpus di documenti
documents = [
"Python è un linguaggio di programmazione ad alto livello, interpretato e multiparadigma",
"Java è un linguaggio orientato agli oggetti progettato per essere portabile",
"Il machine learning è un sottoinsieme dell'intelligenza artificiale",
"I database relazionali usano SQL per le query sui dati strutturati",
"Docker permette di containerizzare le applicazioni per il deployment",
"Kubernetes orchestra i container in ambienti di produzione distribuiti",
"React è una libreria JavaScript per costruire interfacce utente",
"Angular è un framework TypeScript per applicazioni web enterprise",
"I transformer sono l'architettura alla base dei modelli linguistici moderni",
"FAISS è una libreria per la ricerca efficiente di similarità tra vettori",
"La sicurezza informatica protegge i sistemi da accessi non autorizzati",
"Il cloud computing fornisce risorse computazionali on-demand via internet",
]
engine = SemanticSearchEngine()
engine.build_index(documents)
# Ricerca semantica
queries = [
"Come funziona l'intelligenza artificiale?",
"Framework per sviluppo web frontend",
"Come distribuire applicazioni in modo scalabile?",
]
for query in queries:
print(f"\nQuery: '{query}'")
print("-" * 50)
results = engine.search(query, top_k=3)
for i, result in enumerate(results, 1):
print(f" {i}. [{result['score']:.4f}] {result['document']}")
7.5 Procesare în loturi pentru seturi mari de date
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
from tqdm import tqdm
import json
from pathlib import Path
class LargeScaleEmbedder:
"""Gestisce embedding e indicizzazione per grandi dataset."""
def __init__(self, model_name: str = "all-mpnet-base-v2"):
self.model = SentenceTransformer(model_name)
self.dimension = self.model.get_sentence_embedding_dimension()
def encode_in_batches(
self,
texts: list[str],
batch_size: int = 256,
output_path: str | None = None,
) -> np.ndarray:
"""Genera embeddings in batch con salvataggio incrementale."""
all_embeddings = []
for start_idx in tqdm(range(0, len(texts), batch_size)):
batch = texts[start_idx : start_idx + batch_size]
batch_embeddings = self.model.encode(
batch,
normalize_embeddings=True,
batch_size=batch_size,
show_progress_bar=False,
)
all_embeddings.append(batch_embeddings)
# Salvataggio incrementale (checkpoint)
if output_path and (start_idx % (batch_size * 10) == 0):
partial = np.vstack(all_embeddings)
np.save(f"{output_path}_partial.npy", partial)
result = np.vstack(all_embeddings).astype("float32")
if output_path:
np.save(f"{output_path}.npy", result)
print(f"Embeddings salvati: {result.shape}")
return result
def build_ivf_index(
self,
embeddings: np.ndarray,
n_clusters: int = 100,
n_probe: int = 10,
) -> faiss.Index:
"""Costruisce un indice IVF per ricerca approssimata veloce."""
# Quantizzatore per assegnare vettori ai cluster
quantizer = faiss.IndexFlatIP(self.dimension)
# Indice IVF con Inner Product
index = faiss.IndexIVFFlat(
quantizer, self.dimension, n_clusters, faiss.METRIC_INNER_PRODUCT
)
# Training: impara la struttura dei cluster
print(f"Training indice IVF con {n_clusters} cluster...")
index.train(embeddings)
# Aggiungi i vettori
index.add(embeddings)
index.nprobe = n_probe # Numero di cluster da esplorare in ricerca
print(f"Indice IVF costruito: {index.ntotal} vettori, {n_clusters} cluster")
return index
# Esempio di utilizzo con un grande corpus
if __name__ == "__main__":
embedder = LargeScaleEmbedder(model_name="all-MiniLM-L6-v2")
# Simula un grande corpus (in produzione: carica da file/database)
corpus = [f"Documento di esempio numero {i} sul tema tecnologia" for i in range(100_000)]
# Genera embeddings in batch
embeddings = embedder.encode_in_batches(
corpus,
batch_size=512,
output_path="corpus_embeddings",
)
# Costruisci indice IVF (più veloce di Flat per grandi corpus)
index = embedder.build_ivf_index(embeddings, n_clusters=256, n_probe=16)
# Ricerca
query_embedding = embedder.model.encode(
["intelligenza artificiale e machine learning"],
normalize_embeddings=True,
).astype("float32")
scores, indices = index.search(query_embedding, 5)
for score, idx in zip(scores[0], indices[0]):
print(f" [{score:.4f}] {corpus[idx]}")
7.6 Încorporarea cu șabloane E5 (pe bază de prefix)
from sentence_transformers import SentenceTransformer
import numpy as np
# I modelli E5 richiedono prefix specifici
model = SentenceTransformer("intfloat/e5-large-v2")
# Per i documenti nel corpus: prefix "passage: "
documents = [
"passage: Python è un linguaggio di programmazione versatile e potente",
"passage: I database NoSQL sono progettati per dati non strutturati",
"passage: Kubernetes gestisce il deployment di applicazioni containerizzate",
"passage: Il deep learning usa reti neurali con molti layer",
]
# Per le query di ricerca: prefix "query: "
queries = [
"query: Come funziona l'apprendimento automatico?",
"query: Strumenti per gestire i container",
]
# Genera embeddings
doc_embeddings = model.encode(documents, normalize_embeddings=True)
query_embeddings = model.encode(queries, normalize_embeddings=True)
# Calcola similarità
for i, query in enumerate(queries):
scores = np.dot(query_embeddings[i], doc_embeddings.T)
best_idx = np.argmax(scores)
print(f"Query: {query}")
print(f" Miglior match: {documents[best_idx]}")
print(f" Score: {scores[best_idx]:.4f}\n")
Acordați atenție prefixelor
Modelele E5 și BGE necesită prefixe specifice pentru a distinge interogările de documente.
Pentru E5: query: e passage: . Pentru BGE:
Represent this sentence: pentru interogări. Uitarea prefixului se degradează
performanta in mod semnificativ. Verificați întotdeauna documentația modelului.
8. Motoare de căutare vectoriale: comparație arhitecturală
FAISS este excelent pentru implementarea la nivel scăzut, dar în producție sunt necesare soluții care gestionează persistența, filtrarea metadatelor, scalabilitatea orizontală și actualizări în timp real. Iată principalele baze de date vectoriale și caracteristicile acestora.
Comparația bazei de date vectoriale
| Baze de date | Tip | Limbă | Indici | Caz de utilizare ideal |
|---|---|---|---|---|
| FAISS | Raft pentru cărți | C++/Python | Flat, FIV, HNSW, PQ | Cercetare pură, încorporare, prototipare |
| Qdrant | Baze de date | Rugini | HNSW | Productie, filtre complexe, performante ridicate |
| Cone de pin | Cloud gestionat | Proprietar | Proprietar | Zero-ops, pornire, autoscaling |
| Milvus | Baze de date | Go/C++ | Flat, FIV, HNSW, DiskANN | Scara întreprinderii, miliarde de vectori |
| pgvector | Extensie | C | IVFFlat, HNSW | Utilizați deja PostgreSQL, seturi de date moderate |
| ChromaDB | Baze de date | Python/Rugina | HNSW | Prototipare, integrare LangChain |
| Weaviate | Baze de date | Go | HNSW | API-ul GraphQL multimodal |
8.1 Exemplu cu Qdrant
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance,
VectorParams,
PointStruct,
Filter,
FieldCondition,
MatchValue,
)
from sentence_transformers import SentenceTransformer
# Inizializza client (in-memory per test, o URL per produzione)
client = QdrantClient(":memory:") # oppure QdrantClient("http://localhost:6333")
model = SentenceTransformer("all-MiniLM-L6-v2")
# Crea collection
client.create_collection(
collection_name="articles",
vectors_config=VectorParams(
size=384, # Dimensione vettore all-MiniLM-L6-v2
distance=Distance.COSINE,
),
)
# Documenti con metadati
documents = [
{"text": "Python è ottimo per il data science", "category": "programming", "year": 2024},
{"text": "React è il framework frontend più popolare", "category": "web", "year": 2024},
{"text": "Kubernetes gestisce i container in produzione", "category": "devops", "year": 2023},
{"text": "TensorFlow è usato per il deep learning", "category": "ai", "year": 2024},
{"text": "PostgreSQL è un database relazionale avanzato", "category": "database", "year": 2023},
]
# Indicizzazione
points = []
for i, doc in enumerate(documents):
embedding = model.encode(doc["text"]).tolist()
points.append(
PointStruct(
id=i,
vector=embedding,
payload={"text": doc["text"], "category": doc["category"], "year": doc["year"]},
)
)
client.upsert(collection_name="articles", points=points)
# Ricerca semantica semplice
query = "machine learning e intelligenza artificiale"
query_vector = model.encode(query).tolist()
results = client.search(
collection_name="articles",
query_vector=query_vector,
limit=3,
)
print(f"Query: '{query}'")
for result in results:
print(f" [{result.score:.4f}] {result.payload['text']} ({result.payload['category']})")
# Ricerca con filtro per metadati
filtered_results = client.search(
collection_name="articles",
query_vector=query_vector,
query_filter=Filter(
must=[FieldCondition(key="year", match=MatchValue(value=2024))]
),
limit=3,
)
print(f"\nRicerca filtrata (solo 2024):")
for result in filtered_results:
print(f" [{result.score:.4f}] {result.payload['text']}")
9. Algoritmi de indexare: Viteză/Recall Trade-off
Căutarea exactă (forță brută) în milioane de vectori este prea lentă. Algoritmii de Cel mai apropiat vecin (ANN) ei sacrifică un mic procent de precizie pentru a câștiga ordine de mărime în viteză. Înțelegeți acești algoritmi este esenţială configurarea corectă a bazei de date vectoriale.
9.1 Index plat (căutare exactă)
Indexul Plat compară interogarea cu fiecare vector din baza de date. Rechemare 100% garantată, dar costul O(n*d) unde n este numărul de transportatori și d este numărul dimensionalitate. Practic doar pentru seturi de date mici (până la ~100k vectori).
9.2 FIV (index de fișier inversat)
FIV partiționează spațiul vectorial în clustere folosind k-medii. Momentan
din căutare, sunt explorate doar clusterele cele mai apropiate de interogare. Parametrul
nprobe controlează câte clustere să exploreze: valorile ridicate măresc reamintirea
dar încetinesc căutarea.
9.3 HNSW (Lumea mică ierarhică navigabilă)
HNSW este cel mai popular algoritm din bazele de date vectoriale moderne. Se construiește un grafic navigabil pe mai multe niveluri în care fiecare nod este un vector conectat la vecinii săi cel mai apropiat. Căutarea începe de la nivelul superior (puține noduri, conexiuni lungi) și coboară la nivelul de bază (toate nodurile, conexiuni scurte), îngustându-se zona de cercetare progresiv.
FLAT (Brute Force):
Costruzione: O(n) | Ricerca: O(n*d) | Recall: 100%
Memoria: n*d*4 bytes | Aggiornamento: O(1)
Ideale per: <100k vettori, quando il recall perfetto e necessario
IVF (Inverted File):
Costruzione: O(n*k) | Ricerca: O(nprobe*n/k*d) | Recall: 90-99%
Memoria: n*d*4 bytes | Aggiornamento: richiede re-training
Parametri: nlist (cluster), nprobe (cluster esplorati)
Ideale per: 100k-10M vettori, quando serve controllo fine
HNSW (Hierarchical Navigable Small World):
Costruzione: O(n*log(n)) | Ricerca: O(log(n)*d) | Recall: 95-99.9%
Memoria: n*d*4 + grafo | Aggiornamento: O(log(n))
Parametri: M (connessioni), ef_construction, ef_search
Ideale per: qualsiasi scala, migliore latenza, più memoria
Product Quantization (PQ):
Comprime vettori 768-dim in ~64 bytes (12x compressione)
Recall inferiore ma enorme risparmio memoria
Combinabile con IVF: IVF-PQ per miliardi di vettori
Ghid practic de alegere
| Scala setul de date | Algoritm recomandat | Rechemare tipică |
|---|---|---|
| <100.000 de transportatori | Plat (forță brută) | 100% |
| 100k - 1M | HNSW | 98-99% |
| 1M - 100M | HNSW sau FIV-HNSW | 95-99% |
| 100M - 1B | IVF-PQ sau DiskANN | 90-95% |
| >1B | FIV-PQ distribuit (Milvus) | 85-95% |
10. Reglarea fină a înglobărilor
Modelele pre-antrenate funcționează bine pentru cazuri de utilizare generale, dar pentru domenii specialist (medicina, juridic, finante, cod) the reglaj fin poate îmbunătăți semnificativ calitatea recuperării. Principiul este simplu: adaptați modelul la datele și vocabularul domeniului dvs.
10.1 Când să reglați fin
- Vocabular de specialitate: Domeniul dvs. folosește terminologie tehnică care nu se găsește în datele de instruire generice
- Diferite relații semantice: În domeniul dvs., cuvintele care în mod normal nu au legătură au relații strânse
- Limbi subreprezentate: Modelul de bază nu a văzut suficient text în limba dvs
- Recuperare slabă: Rezultatele căutării semantice nu sunt satisfăcătoare, în ciuda unor fragmente bune
10.2 Implementarea cu Învățare Contrastivă
from sentence_transformers import (
SentenceTransformer,
InputExample,
losses,
evaluation,
)
from torch.utils.data import DataLoader
# Carica modello base
model = SentenceTransformer("all-MiniLM-L6-v2")
# Prepara i dati di training: coppie (frase_a, frase_b, similarità)
# similarità: 1.0 = semanticamente identiche, 0.0 = completamente diverse
train_examples = [
# Coppie positive (dominio medico)
InputExample(texts=["infarto del miocardio", "attacco cardiaco acuto"], label=0.95),
InputExample(texts=["ipertensione arteriosa", "pressione alta cronica"], label=0.90),
InputExample(texts=["cefalea tensiva", "mal di testa da stress"], label=0.85),
InputExample(texts=["diabete mellito tipo 2", "diabete dell'adulto"], label=0.90),
# Coppie negative
InputExample(texts=["infarto del miocardio", "frattura del femore"], label=0.10),
InputExample(texts=["ipertensione arteriosa", "gastrite cronica"], label=0.05),
# Aggiungi migliaia di coppie per risultati ottimali...
]
# DataLoader
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
# Loss function: Cosine Similarity Loss
train_loss = losses.CosineSimilarityLoss(model)
# Valutazione (opzionale ma consigliata)
eval_examples = [
InputExample(texts=["tachicardia sinusale", "battito cardiaco accelerato"], label=0.85),
InputExample(texts=["polmonite batterica", "infezione polmonare"], label=0.80),
]
evaluator = evaluation.EmbeddingSimilarityEvaluator.from_input_examples(
eval_examples, name="medical-eval"
)
# Fine-tuning
model.fit(
train_objectives=[(train_dataloader, train_loss)],
evaluator=evaluator,
epochs=3,
warmup_steps=100,
evaluation_steps=500,
output_path="models/medical-embedding-model",
)
print("Fine-tuning completato! Modello salvato.")
# Test del modello fine-tuned
finetuned_model = SentenceTransformer("models/medical-embedding-model")
emb1 = finetuned_model.encode("infarto miocardico acuto")
emb2 = finetuned_model.encode("attacco di cuore")
from sentence_transformers import util
score = util.cos_sim([emb1], [emb2])
print(f"Similarità post fine-tuning: {score.item():.4f}")
De câte date aveți nevoie pentru reglaj fin?
Ca regulă generală: minim 1.000 de perechi pentru a vedea o îmbunătățire, 5.000-10.000 de perechi pentru rezultate solide, Peste 50.000 de perechi pentru rezultate optime. Calitatea datelor este mai importantă decât cantitatea: perechi zgomotos sau prost etichetat degradează modelul. Dacă nu aveți suficiente date adnotate, luați în considerare tehnici precum minerit negativ greu sau generație sintetică cu un LLM.
11. Embeddings pentru limba italiana
Majoritatea modelelor de încorporare sunt instruite predominant pe text în limba engleză. Pentru aplicațiile în limba italiană, alegerea modelului este deosebit de critică. Să vedem opțiunile disponibile și performanța acestora.
11.1 Modele multilingve
Modelele multilingve sunt instruite pe zeci de limbi simultan. Calitatea pentru italiană este în general bună, dar mai mică decât performanța pentru engleză. Modelele recomandate pentru italiană sunt:
- multilingv-e5-mare: Calitate multilingvă excelentă, necesită prefixul "interogare:"/"pasaj:"
- BGE-M3: Suportă peste 100 de limbi cu o calitate uniformă, acceptă dens+sparse+colbert
- parafrazați-multilingv-MiniLM-L12-v2: Compromis bun viteză/calitate pentru peste 50 de limbi
- text-embedding-3-small/mare (OpenAI): Performanță multilingvă bună prin API
11.2 Strategii pentru îmbunătățirea calității
- Ajustare fină a datelor italiene: Chiar și câteva mii de perechi adnotate în italiană pot îmbunătăți semnificativ performanța
- Mărirea datelor: Utilizați un LLM pentru a genera parafraze în italiană ale documentelor dvs
- Recuperare hibridă: Combinați căutarea semantică cu BM25 (cuvânt cheie) pentru a compensa slăbiciunile modelului în ceea ce privește limbajul
- Traducere interogare: Pentru modele în limba engleză de înaltă calitate, traduceți interogarea în engleză înainte de încorporare (soluție pragmatică)
Test de practică pentru italiană
Înainte de a alege un șablon pentru un proiect în italiană, efectuați întotdeauna un test manual cu întrebările dumneavoastră și documente reale. Creați un mic set de date de evaluare (50-100 de interogări cu răspunsuri așteptate) și măsoară precision@k și recall@k. Un model cu un scor MTEB mare la engleză, ar putea performa semnificativ mai rău pe italiană. Benchmark-ul MTEB include subsarcini multilingve - verificare mai exact acelea.
12. Înglobări în Contextul RAG
Înglobările sunt fundaţie a fiecărui sistem RAG. Calitatea de recuperarea - și deci a întregii conducte - depinde direct de calitatea reprezentări vectoriale. Acesta este modul în care înglobările se integrează în arhitectură RAG complet.
FASE DI INDICIZZAZIONE (offline):
Documenti ──> Chunking ──> [EMBEDDING MODEL] ──> Vettori ──> Vector DB
|
Stesso modello per query!
FASE DI QUERY (online):
Query utente ──> [EMBEDDING MODEL] ──> Vettore query
|
v
[VECTOR DB: similarity search]
|
v
Top-k chunk rilevanti
|
v
[LLM + Contesto] ──> Risposta con citazioni
REGOLE D'ORO:
1. Usare SEMPRE lo stesso modello per indicizzazione e query
2. La qualità degli embeddings determina la qualità del retrieval
3. Scegliere il modello in base al dominio e alla lingua
4. Normalizzare i vettori per cosine similarity
5. Monitorare retrieval precision e recall in produzione
Cea mai frecventă greșeală în sistemele RAG este subestimarea importanței alegerii model de încorporare. Echipe care investesc săptămâni întregi în optimizarea promptului LLM de multe ori nu au testat niciodată diferite modele de încorporare. În practică, treceți prin all-MiniLM-L6-v2 la un model precum e5-large-v2 sau BGE-M3 poate îmbunătăți calitatea regăsire de 15-25%, cu impact direct asupra calității răspunsurilor finale.
Lista de verificare încorporare pentru RAG
- Ați testat cel puțin 3 modele diferite pe setul dvs. de date?
- Aveți un set de evaluare cu întrebări și răspunsuri așteptate?
- Modelul acceptă limba dvs. cu o calitate suficientă?
- Este dimensionalitatea compatibilă cu bugetul dvs. de stocare?
- Este lungimea maximă a contextului (semnal maxim) adecvată pentru bucățile dvs.?
- Te-ai gândit la ajustarea fină a domeniului tău?
- Monitorizați calitatea recuperării în producție?
13. Costuri și scalare în producție
Alegerea între încorporarea auto-găzduită și API-urile cloud are un impact semnificativ asupra costurilor, asupra latenței și complexității operaționale. Să analizăm factorii cheie pe care trebuie să îi luăm o decizie informată.
Comparația costurilor API de încorporare (2024-2025)
| Furnizorii | Model | Preț pentru 1 M Token | Dimensiuni |
|---|---|---|---|
| OpenAI | text-incorporare-3-mic | 0,02 USD | 1536 |
| OpenAI | text-incorporare-3-mare | 0,13 USD | 3072 |
| Coere | încorporați-v3 | 0,10 USD | 1024 |
| Auto-găzduit | toate-MiniLM-L6-v2 | Costul GPU | 384 |
| Auto-găzduit | BGE-M3 | Costul GPU | 1024 |
13.1 API-ul auto-găzduit vs cloud
- Cloud API (OpenAI, Cohere): Infrastructură zero, plata pe utilizare, latența rețelei, dependența de serviciul extern, confidențialitatea datelor de evaluat
- Auto-găzduit (GPU dedicat): Cost inițial ridicat (GPU ~ 1-3 USD/oră), fără cost pe token, latență minimă, control complet al datelor, complexitate operațională
Regula generală: Pentru mai puțin de 10 milioane de jetoane/lună, API-uri cloud sunt mai ieftine. Peste 100 de milioane de jetoane/lună, auto-găzduirea devine convenabil. Între 10M și 100M, depinde de nevoile de latență și confidențialitate necesare.
13.2 Costul stocării vectoriale
Fiecare transportator ocupă dimensioni * 4 bytes (float32). Cu un model 768
dimensiune și 1 milion de documente: 768 * 4 * 1M = numai ~3 GB memorie vectorială,
la care trebuie adăugate metadate, indexuri și overhead. Cu cuantizarea produsului puteți
comprimați până la 8-16x, dar cu o pierdere a reamintirii.
Estimare rapidă a costurilor
Pentru un sistem RAG cu 1 milion de documente, cu 500 de jetoane pe bucată, Model 768-dim: aveți nevoie de aproximativ 3 GB de RAM pentru vectori + 2-4 GB pentru indexul HNSW. O instanță cloud cu 8 GB de RAM (aproximativ 50-100 USD/lună) este suficientă. Pentru generație încorporare inițială cu API OpenAI (text-embedding-3-small): aproximativ 500 de milioane de jetoane = 10 USD. Cu model auto-găzduit și un GPU T4: aproximativ 2-3 ore de calcul = 3-5 USD.
Concluzii și pașii următori
În acest articol am acoperit întreaga evoluție a înglobărilor textuale: haide înglobări statice de cuvinte ale Word2Vec și GloVe, prin înglobări contextuale ale BERT, până la Sentence Transformers moderne optimizate pentru căutare semantică. Avem am văzut de ce vanilla BERT este nepotrivit pentru recuperare și cum rezolvă SBERT problema cu arhitectură bi-encoder.
Am implementat un motor de căutare semantic complet cu Python, transformatori de propoziții și FAISS, explorând strategii pentru procesarea în loturi și seturi mari de date. Am comparat principalele baze de date vectoriale și algoritmi de indexare, analizând compromisurile între viteză, amintire și memorie.
Puncte cheie de reținut:
- Alegerea modelului de încorporare este cea mai importantă decizie într-un sistem RAG – mai important decât modelul LLM
- Transformatori de propoziții (bi-coder) sunt standardul pentru recuperare; Cross-encoder BERT pentru re-clasificare
- Testează din ce în ce mai multe modele pe setul de date real înainte de a alege
- FAISS și HNSW face căutarea în milioane de vectori eficientă și fezabilă
- Reglaj fin poate îmbunătăți dramatic calitatea pentru domeniile specializate
- Pentru italiană, utilizați modele multilingve (BGE-M3, multilingv-e5) și luați în considerare reglarea fină
În articolul următor
În al treilea articol al seriei vom aprofunda în Baza de date vectorială: arhitectură internă, configurație avansată de index, strategii de sharding și replicare, filtrare pentru metadate și cum să alegeți baza de date potrivită pentru cazul dvs. de utilizare. Vom vedea implementări complete cu Qdrant, ChromaDB și pgvector.







