Anlamsal Benzerlik ve Cümle Yerleştirmeleri: Metinleri Karşılaştırma
İki cümle ne kadar benzer? Sözlüksel anlamda değil (aynı kelimeler), fakat anlamsal anlam (aynı anlam). “Köpek kediyi kovalar” ve “Kedi gelir köpek tarafından kovalanan" ifadeleri anlamsal olarak hemen hemen aynı, ancak sözcüksel olarak çok farklı. Bu soruyu cevaplamak işin zorlu kısmı Anlamsal Benzerlik.
Uygulamalar her yerde: anlamsal arama motorları, öneri sistemleri, içerik tekilleştirme, soru yanıtlama, RAG (Retrieval-Augmented Generation), chatbot ve SSS eşleşmesi. Bu yazıda anlamsal benzerlik sistemleri oluşturacağız sıfırdan: kosinüs benzerliğinden Cümle-BERT ile cümle yerleştirmelere, FAISS ile hızlı vektör aramaya kadar.
Bu serinin dokuzuncu makalesi Modern NLP: BERT'ten Yüksek Lisans'a. Bu konu doğrudan diziyle bağlantılıdır Yapay Zeka Mühendisliği/RAG anlamsal yerleştirmeler yoğun erişimin kalbidir.
Ne Öğreneceksiniz
- Kosinüs benzerliği ve nokta çarpımı: formüller ve bunların ne zaman kullanılacağı
- Anlamsal benzerlik için BERT'in sınırları ve Cümle-BERT'e neden ihtiyaç duyulduğu
- Cümle-BERT (SBERT): Siyam mimarisi ve üçlü kayıp eğitimi
- HuggingFace'teki cümle dönüştürücü modelleri: hangisini seçmeli
- FAISS ile büyük derlemlerde anlamsal arama
- İtalyanca için cümle eklemeleri
- Karşılaştırma: STS-B, SICK ve değerlendirme metrikleri
- Çapraz kodlayıcı ve çift kodlayıcı: kalite/hız dengesi
- Alanınızda bir cümle dönüştürücüye ince ayar yapma
- SSS eşleştirme sisteminin eksiksiz uygulanması
- Önbelleğe alma ve optimizasyon ile üretime hazır ardışık düzen
1. Anlamsal Benzerlik Sorunu
Bu üç cümle grubunu ve bunların zorluklarını ele alalım:
Anlamsal Benzerlik Örnekleri
- Yüksek benzerlik: “Banka faizleri artırdı” / “Bankacılık kurumu faizleri artırdı”
- Düşük benzerlik: "Banka Faiz Arttırdı" / "Kedi Koltukta Uyuyor"
- Aldatıcı (aynı kelimeler, farklı anlamlar): "Okul Masası" / "Marketteki Balık Tezgahı"
- Çapraz dilbilim: "Köpek hızlı koşar" (aynı anlambilim, farklı diller)
Geleneksel ölçümler gibi Jaccard benzerliği veya BM25 sözcüksel örtüşmeye dayanırlar ve eşanlamlılar ve açımlamalar konusunda tamamen başarısız olurlar. Basit TF-IDF bile anlamı yakalayamıyor. Çözüm şu anlamsal yerleştirmeler: Yakınlığın olduğu yoğun vektör gösterimleri geometrik anlamsal yakınlığı yansıtır.
1.1 Kosinüs Benzerliği: Temel Metrik
La az benzerlik uzaydaki iki vektör arasındaki açıyı ölçer yerleştirmelerden oluşur. Dik vektörler için 0 ile -1 (karşı) ile 1 (aynı) arasında değişir. Matematiksel formül şöyledir:
cos(A, B) = (A · B) / (||A|| · ||B||)
Vektörler birim norma normalleştirildiğinde kosinüs benzerliği çakışır GPU donanımında hesaplamayı çok daha verimli hale getiren nokta çarpımı ile.
import numpy as np
import torch
from torch.nn import functional as F
def cosine_similarity(vec1, vec2):
"""Cosine similarity tra due vettori numpy."""
dot_product = np.dot(vec1, vec2)
norm1 = np.linalg.norm(vec1)
norm2 = np.linalg.norm(vec2)
return dot_product / (norm1 * norm2)
# Versione PyTorch (batch-friendly)
def cosine_similarity_batch(emb1, emb2):
"""Cosine similarity tra batch di embedding (normalizzato)."""
# Normalizza a norma unitaria
emb1_norm = F.normalize(emb1, p=2, dim=1)
emb2_norm = F.normalize(emb2, p=2, dim=1)
return (emb1_norm * emb2_norm).sum(dim=1)
# Esempio con vettori semplici
vec_a = np.array([1.0, 0.5, 0.3, 0.8])
vec_b = np.array([0.9, 0.4, 0.4, 0.7]) # simile ad a
vec_c = np.array([-0.2, 0.8, -0.5, 0.1]) # diverso da a
print(f"sim(a, b) = {cosine_similarity(vec_a, vec_b):.4f}") # alta
print(f"sim(a, c) = {cosine_similarity(vec_a, vec_c):.4f}") # bassa
# Matrice di similarità per corpus di frasi
def similarity_matrix(embeddings):
"""Matrice di similarità N x N per un set di embedding."""
# Normalizza
norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
normalized = embeddings / norms
# Prodotto matriciale per tutte le coppie
return normalized @ normalized.T
# Output: matrice (N, N) dove [i,j] = sim(frase_i, frase_j)
1.2 Diğer Mesafe Ölçümleri
Benzerlik/Mesafe Metriklerinin Karşılaştırılması
| Metrik | Formül | Menzil | Kullanım örneği |
|---|---|---|---|
| Kosinüs Benzerliği | çünkü(A, B) | [-1, 1] | Standart anlamsal benzerlik |
| Öklid Mesafesi | ||A - B|| | [0, +inf) | Kümeleme, k-NN |
| Nokta Ürün | A · B | (-inf, +inf) | Normalleştirilmiş vektörlerle = kosinüs |
| Manhattan Mesafesi | toplam(|A-B|) | [0, +inf) | Aykırı değerlere karşı dayanıklılık |
| Pearson Korelasyonu | cov(A,B)/sigma | [-1, 1] | STS kıyaslamasına ilişkin değerlendirme |
2. Standart BERT Benzerlik Açısından Neden İşe Yaramıyor?
Sezgisel olarak cümle yerleştirmelerini çıkarmak ve bunları karşılaştırmak için BERT'i kullanabiliriz. Ancak Reimers & Gurevych (2019) tarafından yapılan araştırma şunu gösterdi: e'ye yaklaşma şaşırtıcı derecede etkisiz.
Asıl sorun, BERT'in Maskeli Dil Modellemesi (MLM) ile önceden eğitilmiş olmasıdır ve
Sonraki Cümle Tahmini (NSP). jeton [CLS] bilgiyi kodlar
Cümle çifti sınıflandırması (NSP) için kullanışlıdır ancak aşağıdakiler için optimize edilmemiştir:
karşılaştırıldığında anlamsal benzerliği yansıtan yerleştirmeler üretir
çok az benzerlikle.
Ayrıca, tüm jetonların ortalama havuzlanması bir yerleştirme alanı oluşturur anizotropik: yönler düzgün bir şekilde dağılmamıştır, ve anlamsal olarak farklı cümle kümeleri örtüşür.
from transformers import BertModel, BertTokenizer
import torch
import numpy as np
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
def bert_mean_pooling(text):
"""Embedding di frase con mean pooling su BERT."""
inputs = tokenizer(text, return_tensors='pt',
truncation=True, max_length=128, padding=True)
with torch.no_grad():
outputs = model(**inputs)
# Mean pooling (esclude padding)
mask = inputs['attention_mask'].unsqueeze(-1)
embeddings = (outputs.last_hidden_state * mask).sum(1) / mask.sum(1)
return embeddings[0].numpy()
# Test: frasi semanticamente simili vs diverse
sent1 = "The weather is lovely today."
sent2 = "It's so beautiful today outside." # simile
sent3 = "My dog bit the mailman." # diversa
emb1 = bert_mean_pooling(sent1)
emb2 = bert_mean_pooling(sent2)
emb3 = bert_mean_pooling(sent3)
sim_1_2 = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
sim_1_3 = np.dot(emb1, emb3) / (np.linalg.norm(emb1) * np.linalg.norm(emb3))
print(f"sim(sent1, sent2) = {sim_1_2:.4f}") # ~0.93 - ok
print(f"sim(sent1, sent3) = {sim_1_3:.4f}") # ~0.87 - troppo alto!
# Problema: BERT tende a produrre embedding simili per tutte le frasi
# perchè il token [CLS] e trainato su NSP, non su similarità semantica
# La soluzione e Sentence-BERT
STS-B'de BERT Performansı (Kıyaslama)
STS-B (Semantik Metin Benzerliği Karşılaştırması) görevinde, ortalama havuzlamalı BERT sadece ulaştı Pearson r = 0,54, yaklaşımların çok altında SBERT (0,87) olarak denetlendi. Yalnızca [CLS] tokenı bile yalnızca 0,20'ye ulaşıyor. Anlamsal benzerlik açısından SBERT doğru seçimdir.
3. Cümle-BERT (SBERT): Çözüm
Cümle-BERT (Reimers & Gurevych, EMNLP 2019) sorunu çözüyor bir mimariye sahip Siyam: BERT paylaşım ağırlıklarının iki örneği, iki cümleyi ayrı ayrı işlerler ve kayıp fonksiyonu temsilleri zorlar anlamsal olarak vektör uzayında yakın olmaya benzer.
3.1 Siyam Mimarisi
Ana fikir, iki "ağın" tamamen aynı ağırlıkları paylaşmasıdır. Bunlar iki ayrı model değil, iki kez adlandırılan aynı modeldir. Kayıp, çıkış çiftinde hesaplanır:
- Regresyon hedefi: Tahmini kosinüs benzerliği ile insan puanı arasındaki MSE (STS için)
- Sınıflandırma hedefi: [u, v, |u-v|] üzerinde çapraz entropi (NLI için)
- Üçlü kayıp: Çapa/pozitif/negatifte marj kaybı (açıklama madenciliği için)
from sentence_transformers import SentenceTransformer, util
import torch
# Carica un modello sentence-transformers
# Modello multilingua (include italiano!)
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
# Modello ottimizzato per inglese (più accurato)
# model = SentenceTransformer('all-MiniLM-L6-v2')
# Encoding di frasi (batch-optimized)
sentences = [
"The weather is lovely today.",
"It's so beautiful today outside.",
"He drove to the stadium.",
"La giornata e bellissima oggi.", # italiano
"Il tempo e meraviglioso questa mattina.", # italiano simile
]
# Encode tutto in una volta (molto più efficiente del loop)
embeddings = model.encode(sentences, batch_size=32, show_progress_bar=False)
print(f"Embedding shape: {embeddings.shape}") # (5, 384)
# Calcola similarità
cos_scores = util.cos_sim(embeddings, embeddings)
print("\nMatrice di similarità:")
for i in range(len(sentences)):
for j in range(i+1, len(sentences)):
score = cos_scores[i][j].item()
if score > 0.6: # mostra solo coppie simili
print(f" {i+1} vs {j+1}: {score:.4f}")
print(f" '{sentences[i][:50]}'")
print(f" '{sentences[j][:50]}'")
# Pairwise similarity per coppie specifiche
sim = util.cos_sim(embeddings[0], embeddings[1]).item()
print(f"\nsim(EN1, EN2) = {sim:.4f}") # ~0.85 (frasi simili)
sim_cross = util.cos_sim(embeddings[0], embeddings[3]).item()
print(f"sim(EN1, IT1) = {sim_cross:.4f}") # ~0.75 (cross-lingual!)
4. Cümle dönüştürücü modeller: Hangisini seçmeli
Ana cümle-dönüştürücü modelleri (2024-2025)
| Modeli | Diller | Loş. | Hız | STS-B Pearson |
|---|---|---|---|---|
| hepsi-MiniLM-L6-v2 | EN | 384 | Çok hızlı | 0,834 |
| tüm-mpnet-base-v2 | EN | 768 | Ortalama | 0,869 |
| açıklama-çok dilli-MiniLM-L12-v2 | 50'den fazla dil | 384 | Hızlı | 0,821 |
| açıklama-çok dilli-mpnet-base-v2 | 50'den fazla dil | 768 | Ortalama | 0,853 |
| intfloat/çok dilli-e5-büyük | 100'den fazla dil | 1024 | Yavaş | 0,892 |
| metin yerleştirme-3-küçük (OpenAI) | Çok dilli | 1536 | Yalnızca API | ~0.90 |
4.1 Model Seçimi: Pratik Kılavuz
Seçim üç ana faktöre bağlıdır: dil, hız ve gerekli kalite.
from sentence_transformers import SentenceTransformer
import time
import numpy as np
def benchmark_model(model_name, sentences, n_runs=3):
"""Benchmark velocità e qualità di un modello sentence-transformer."""
model = SentenceTransformer(model_name)
# Warmup
model.encode(sentences[:2])
# Misura velocità
times = []
for _ in range(n_runs):
start = time.time()
embs = model.encode(sentences)
times.append(time.time() - start)
avg_time = np.mean(times)
dim = embs.shape[1]
print(f"Model: {model_name}")
print(f" Embedding dim: {dim}")
print(f" Avg encoding time ({len(sentences)} sentences): {avg_time*1000:.1f}ms")
print(f" Throughput: {len(sentences)/avg_time:.0f} sentences/sec")
sentences_test = [
"Il sole splende oggi a Milano.",
"Oggi e una bella giornata soleggiata.",
"Roma e la capitale dell'Italia.",
"La Juventus ha vinto il campionato.",
"L'intelligenza artificiale sta cambiando il mondo.",
] * 20 # 100 frasi
# Benchmark modelli multilingua
for model_name in [
'paraphrase-multilingual-MiniLM-L12-v2',
'paraphrase-multilingual-mpnet-base-v2',
'intfloat/multilingual-e5-small',
]:
benchmark_model(model_name, sentences_test)
print()
5. FAISS ile Anlamsal Arama
Büyük bir derleme (milyonlarca belge) için kaba kuvvet araması (tüm belgelerle benzerliği hesaplayın) ve çok yavaş. FAISS (Facebook AI Benzerlik Araması) yaklaşık olarak en yakın komşu aramasına olanak tanır farklı indeks türleriyle alt doğrusal zamanda.
5.1 FAISS Endeksi Türleri
FAISS Endeksleri: Hız/Doğruluk Dengesi
| Dizin | Tip | Kullanım örneği | Hatırlama (%) | Hız |
|---|---|---|---|---|
| DizinDüzL2 | Bire bir aynı | < 100.000 doküman | %100 | Yavaş |
| IndexFlatIP | Kesinlikle (küçük şeyler) | < 100.000 doküman | %100 | Yavaş |
| DizinIVFFlat | Yaklaşık | 100K - 10M | ~%95 | Hızlı |
| DizinHNSW | Yaklaşık | 1 milyonun üzerinde | ~%99 | Çok hızlı |
| DizinIVFPQ | Sıkıştırılmış | 10M+, sınırlı RAM | ~%85 | Çok hızlı |
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
import time
model = SentenceTransformer('all-MiniLM-L6-v2')
# Corpus di esempio: articoli Wikipedia
corpus = [
"The Eiffel Tower is a wrought-iron lattice tower on the Champ de Mars in Paris.",
"Apple Inc. is an American multinational technology company founded by Steve Jobs.",
"Python is a high-level, general-purpose programming language.",
"The Mediterranean diet is based on traditional foods from countries bordering the sea.",
"Quantum computing uses quantum-mechanical phenomena such as superposition.",
"The Amazon River is the largest river by discharge volume in the world.",
"Artificial neural networks are computing systems inspired by biological neural networks.",
"The Sistine Chapel ceiling was painted by Michelangelo between 1508 and 1512.",
"Machine learning is a subset of artificial intelligence focused on algorithms.",
"The Colosseum is an oval amphitheatre in the centre of Rome, Italy.",
]
# Encode il corpus (offline, una volta sola)
print("Encoding corpus...")
start = time.time()
corpus_embeddings = model.encode(corpus, convert_to_numpy=True, show_progress_bar=False)
print(f"Encoded {len(corpus)} docs in {time.time()-start:.2f}s")
print(f"Embeddings shape: {corpus_embeddings.shape}") # (10, 384)
# Costruisci indice FAISS
dim = corpus_embeddings.shape[1] # 384
# IndexFlatIP: esatta, cosine similarity su vettori normalizzati
index_ip = faiss.IndexFlatIP(dim)
# Normalizza per usare cosine similarity (dot product su vettori unit-norm)
faiss.normalize_L2(corpus_embeddings)
index_ip.add(corpus_embeddings)
print(f"Index size: {index_ip.ntotal} vettori")
# IndexHNSW: approssimato ma molto veloce, buona per produzione
# M = numero di connessioni per nodo (16-64 in produzione)
index_hnsw = faiss.IndexHNSWFlat(dim, 32, faiss.METRIC_INNER_PRODUCT)
index_hnsw.hnsw.efConstruction = 200 # più alto = migliore recall in build
index_hnsw.hnsw.efSearch = 128 # più alto = migliore recall in search
# Ricerca semantica
def semantic_search(query, index, corpus, model, k=3):
"""Ricerca semantica: restituisce i k documenti più simili alla query."""
query_emb = model.encode([query], convert_to_numpy=True)
faiss.normalize_L2(query_emb)
start = time.time()
distances, indices = index.search(query_emb, k)
search_time = (time.time() - start) * 1000
print(f"\nQuery: '{query}'")
print(f"Search time: {search_time:.2f}ms")
for rank, (dist, idx) in enumerate(zip(distances[0], indices[0]), 1):
print(f" {rank}. [{dist:.4f}] {corpus[idx][:80]}")
return [(corpus[i], float(d)) for i, d in zip(indices[0], distances[0])]
# Test
semantic_search("ancient Roman architecture", index_ip, corpus, model)
semantic_search("programming language features", index_ip, corpus, model)
semantic_search("painting and art in Italy", index_ip, corpus, model)
5.2 İndeks Kalıcılığı ve Yükleme
import faiss
import numpy as np
import json
import os
def build_and_save_index(corpus, model, index_path="faiss_index.bin",
corpus_path="corpus.json"):
"""Costruisce e salva un indice FAISS su disco."""
# Encode
embeddings = model.encode(corpus, convert_to_numpy=True, show_progress_bar=True)
faiss.normalize_L2(embeddings)
dim = embeddings.shape[1]
index = faiss.IndexFlatIP(dim)
index.add(embeddings)
# Salva indice FAISS
faiss.write_index(index, index_path)
# Salva corpus (per recuperare testi)
with open(corpus_path, 'w', encoding='utf-8') as f:
json.dump(corpus, f, ensure_ascii=False, indent=2)
print(f"Index saved: {index.ntotal} vettori -> {index_path}")
return index
def load_index(index_path="faiss_index.bin", corpus_path="corpus.json"):
"""Carica indice FAISS e corpus da disco."""
if not os.path.exists(index_path):
raise FileNotFoundError(f"Index not found: {index_path}")
index = faiss.read_index(index_path)
with open(corpus_path, 'r', encoding='utf-8') as f:
corpus = json.load(f)
print(f"Index loaded: {index.ntotal} vettori")
return index, corpus
# Utilizzo
# Prima volta: costruisce e salva
# index = build_and_save_index(my_corpus, model)
# Riavvii successivi: carica direttamente (molto più veloce)
# index, corpus = load_index()
6. SSS Eşleştirme: Tam Kullanım Örneği
Anlamsal benzerliğin pratik bir uygulaması: otomatik soru eşleştirme mevcut SSS'ye sahip kullanıcı. Bu model birçok chatbot ve sistemin temelini oluşturur müşteri desteği.
from sentence_transformers import SentenceTransformer, util
import torch
import json
class FAQMatcher:
"""Sistema di FAQ matching semantico con caching e persistenza."""
def __init__(self, model_name='paraphrase-multilingual-MiniLM-L12-v2',
threshold=0.7):
self.model = SentenceTransformer(model_name)
self.threshold = threshold
self.faqs = []
self.faq_embeddings = None
def load_faqs(self, faqs: list):
"""
faqs: lista di dizionari con 'question', 'answer', 'category'
"""
self.faqs = faqs
questions = [faq['question'] for faq in faqs]
print(f"Encoding {len(questions)} FAQ...")
self.faq_embeddings = self.model.encode(
questions,
convert_to_tensor=True,
show_progress_bar=False
)
print("FAQ pronte per la ricerca!")
def match(self, user_query: str, top_k: int = 3) -> list:
"""Trova le FAQ più simili alla domanda utente."""
if self.faq_embeddings is None:
raise ValueError("Carica prima le FAQ con load_faqs()")
query_emb = self.model.encode(user_query, convert_to_tensor=True)
scores = util.cos_sim(query_emb, self.faq_embeddings)[0]
top_k_indices = torch.topk(scores, k=min(top_k, len(self.faqs))).indices
results = []
for idx in top_k_indices:
score = scores[idx].item()
if score >= self.threshold:
results.append({
'question': self.faqs[idx]['question'],
'answer': self.faqs[idx]['answer'],
'category': self.faqs[idx].get('category', 'N/A'),
'score': round(score, 4)
})
return results
def respond(self, user_query: str) -> str:
"""Risposta automatica alla domanda utente."""
matches = self.match(user_query, top_k=1)
if not matches:
return f"Mi dispiace, non ho trovato una risposta per '{user_query}'. Contatta il supporto."
best = matches[0]
return f"[{best['category']}] {best['answer']} (Confidenza: {best['score']:.2f})"
# Esempio di utilizzo
faqs_ecommerce = [
{
"question": "Come posso restituire un prodotto?",
"answer": "Puoi restituire il prodotto entro 30 giorni dall'acquisto contattando il supporto.",
"category": "Resi"
},
{
"question": "Quanto tempo impiega la spedizione?",
"answer": "La consegna standard impiega 3-5 giorni lavorativi, l'express 24 ore.",
"category": "Spedizioni"
},
{
"question": "Come posso pagare?",
"answer": "Accettiamo carte di credito, PayPal, bonifico bancario e contrassegno.",
"category": "Pagamenti"
},
{
"question": "Il prodotto e in garanzia?",
"answer": "Tutti i prodotti hanno 2 anni di garanzia legale del consumatore.",
"category": "Garanzia"
},
{
"question": "Posso tracciare il mio ordine?",
"answer": "Si, riceverai un'email con il numero di tracking dopo la spedizione.",
"category": "Ordini"
},
]
matcher = FAQMatcher()
matcher.load_faqs(faqs_ecommerce)
test_queries = [
"Voglio rimandare indietro la merce",
"Quando arriva il pacco?",
"Accettate il bonifico?",
"Ho bisogno del codice di tracciamento",
"L'articolo si e rotto, cosa faccio?",
]
print("\n=== FAQ Matching ===")
for query in test_queries:
response = matcher.respond(query)
print(f"\nDomanda: {query}")
print(f"Risposta: {response}")
7. Çapraz kodlayıcı ve Bi-kodlayıcı
Semantik benzerliğe yönelik farklı değiş tokuşlar sunan iki yaklaşım vardır. kalite/hız. Bunları anlamak doğru mimariyi seçmek için çok önemlidir.
Çift kodlayıcı ve Çapraz kodlayıcı
| bekliyorum | Çift kodlayıcı (SBERT) | Çapraz kodlayıcı |
|---|---|---|
| Mimarlık | İki ayrı BERT, gömmeler üretir | Çifti işleyen bir BERT |
| Hız | Çok hızlı (hesaplama öncesi yerleştirme) | Yavaş (her çifti işleyin) |
| Ölçeklenebilirlik | Milyonlarca belge | Sadece birkaç yüz çift |
| kalite | İyi (KH-B'de ~0,87 Pearson) | Mükemmel (~0,92 Pearson) |
| Kullanım örneği | Alma, anlamsal arama | Sonuçların yeniden sıralanması |
| Karmaşıklık O(n) | Sorgular için O(1) (önceden hesaplanmış yerleştirmeler) | Her sorgu için O(n) |
from sentence_transformers import SentenceTransformer, CrossEncoder, util
# Bi-encoder per il retrieval iniziale (veloce)
bi_encoder = SentenceTransformer('all-MiniLM-L6-v2')
# Cross-encoder per il reranking (accurato)
cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
# Pipeline a due stadi (best of both worlds)
def retrieval_and_rerank(query, corpus, corpus_embeddings, top_k=100, final_k=5):
"""
Stage 1: Bi-encoder retrieval (veloce, ritorna top 100)
Stage 2: Cross-encoder reranking (accurato, sui top 100)
"""
# Stage 1: Bi-encoder retrieval
query_emb = bi_encoder.encode(query, convert_to_tensor=True)
hits = util.semantic_search(query_emb, corpus_embeddings, top_k=top_k)[0]
# Stage 2: Cross-encoder reranking
cross_inp = [[query, corpus[hit['corpus_id']]] for hit in hits]
cross_scores = cross_encoder.predict(cross_inp)
# Combina e riordina
for hit, score in zip(hits, cross_scores):
hit['cross_score'] = score
hits = sorted(hits, key=lambda x: x['cross_score'], reverse=True)[:final_k]
print(f"\nQuery: '{query}'")
for rank, hit in enumerate(hits, 1):
bi_score = hit['score']
cross_score = hit['cross_score']
doc = corpus[hit['corpus_id']][:80]
print(f" {rank}. [bi={bi_score:.3f}, cross={cross_score:.3f}] {doc}")
return hits
# Encode corpus una sola volta
corpus_embs = bi_encoder.encode(corpus, convert_to_tensor=True)
retrieval_and_rerank("ancient Roman buildings", corpus, corpus_embs)
8. Değerlendirme: STS-B ve Metrikler
Anlamsal benzerlik sisteminin doğru değerlendirilmesi şunları gerektirir: standartlaştırılmış kıyaslama veri kümeleri. STS-B İngilizce için ana referanstır, STS-IT İtalyanca için mevcuttur.
from sentence_transformers import SentenceTransformer
from sentence_transformers.evaluation import EmbeddingSimilarityEvaluator
from datasets import load_dataset
import numpy as np
from scipy.stats import pearsonr, spearmanr
# Carica STS-B per l'evaluazione
stsb = load_dataset("mteb/stsbenchmark-sts")
val_data = stsb['validation']
sentences1 = val_data['sentence1']
sentences2 = val_data['sentence2']
scores = [s / 5.0 for s in val_data['score']] # normalizza 0-5 a 0-1
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
# Evaluation automatica
evaluator = EmbeddingSimilarityEvaluator(
sentences1=sentences1,
sentences2=sentences2,
scores=scores,
name="sts-val"
)
pearson = model.evaluate(evaluator)
print(f"STS-B validation - Pearson: {pearson:.4f}")
# Valutazione manuale con correlazione di Pearson e Spearman
emb1 = model.encode(sentences1, show_progress_bar=False)
emb2 = model.encode(sentences2, show_progress_bar=False)
from numpy.linalg import norm
cos_sims = [
np.dot(e1, e2) / (norm(e1) * norm(e2))
for e1, e2 in zip(emb1, emb2)
]
pearson_r, _ = pearsonr(cos_sims, scores)
spearman_r, _ = spearmanr(cos_sims, scores)
print(f"Pearson: {pearson_r:.4f}")
print(f"Spearman: {spearman_r:.4f}")
# Analisi degli errori: trova le coppie più sbagliate
errors = [(abs(p - t), s1, s2, p, t)
for p, t, s1, s2 in zip(cos_sims, scores, sentences1, sentences2)]
errors.sort(reverse=True)
print("\n=== Top 3 Errori ===")
for err, s1, s2, pred, true in errors[:3]:
print(f" Errore: {err:.3f} | Pred: {pred:.3f} | True: {true:.3f}")
print(f" '{s1[:60]}'")
print(f" '{s2[:60]}'")
9. Alanınızda Bir Cümle Dönüştürücüsüne İnce Ayar Yapmak
Önceden eğitilmiş modeller genel metinler için mükemmeldir ancak belirli alanlar için idealdir (tıbbi, hukuki, teknik) açıklamalı cümle çiftleriyle ince ayar yapmak daha iyidir.
from sentence_transformers import (
SentenceTransformer,
InputExample,
losses,
evaluation
)
from torch.utils.data import DataLoader
# Dati di training: coppie (frase1, frase2, score)
# Score: 0.0 = totalmente diverse, 1.0 = identiche
train_examples = [
InputExample(texts=["Diagnosi di diabete tipo 2", "Paziente con iperglicemia cronica"], label=0.85),
InputExample(texts=["Prescrizione antibiotico", "Terapia con amoxicillina"], label=0.80),
InputExample(texts=["Intervento chirurgico al ginocchio", "Artroscopia del menisco"], label=0.75),
InputExample(texts=["Pressione arteriosa elevata", "Ipertensione arteriosa"], label=0.95),
InputExample(texts=["Dolore toracico", "Bruciore di stomaco"], label=0.30),
InputExample(texts=["Frattura del femore", "Infarto del miocardio"], label=0.05),
]
# Carica modello base
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
# DataLoader
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
# Loss: CosineSimilarityLoss per regressione su score continuo
train_loss = losses.CosineSimilarityLoss(model)
# Valutazione su dati di test
test_examples = [
InputExample(texts=["Cefalea tensiva", "Mal di testa da stress"], label=0.88),
InputExample(texts=["Diabete gestazionale", "Diabete in gravidanza"], label=0.92),
]
evaluator_sentences1 = [e.texts[0] for e in test_examples]
evaluator_sentences2 = [e.texts[1] for e in test_examples]
evaluator_scores = [e.label for e in test_examples]
val_evaluator = evaluation.EmbeddingSimilarityEvaluator(
evaluator_sentences1, evaluator_sentences2, evaluator_scores
)
# Fine-tuning
model.fit(
train_objectives=[(train_dataloader, train_loss)],
evaluator=val_evaluator,
epochs=10,
evaluation_steps=50,
warmup_steps=100,
output_path='./medical-sentence-transformer',
save_best_model=True
)
print("Fine-tuning completato!")
print("Modello salvato in './medical-sentence-transformer'")
10. Boru Hattı Üretimine Hazır
Üretimdeki bir anlamsal benzerlik sistemi, gömme önbelleğe almayı yönetmeli, derlemin aşamalı olarak güncellenmesi ve kalitenin izlenmesi.
import faiss
import numpy as np
import json
import hashlib
from sentence_transformers import SentenceTransformer, util
from pathlib import Path
from typing import List, Dict, Optional
class SemanticSearchEngine:
"""
Motore di ricerca semantica production-ready con:
- Caching degli embedding su disco
- Aggiornamento incrementale
- Threshold configurabile
- Logging delle query
"""
def __init__(
self,
model_name: str = 'paraphrase-multilingual-MiniLM-L12-v2',
cache_dir: str = './search_cache',
similarity_threshold: float = 0.5
):
self.model = SentenceTransformer(model_name)
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(exist_ok=True)
self.threshold = similarity_threshold
self.documents: List[Dict] = []
self.embeddings: Optional[np.ndarray] = None
self.index: Optional[faiss.Index] = None
def _doc_hash(self, doc: Dict) -> str:
"""Hash del documento per il caching."""
content = json.dumps(doc, sort_keys=True, ensure_ascii=False)
return hashlib.md5(content.encode()).hexdigest()
def add_documents(self, documents: List[Dict], text_field: str = 'text'):
"""Aggiunge documenti al corpus con caching."""
texts = [doc[text_field] for doc in documents]
new_embeddings = self.model.encode(texts, convert_to_numpy=True, show_progress_bar=True)
if self.embeddings is None:
self.embeddings = new_embeddings
else:
self.embeddings = np.vstack([self.embeddings, new_embeddings])
self.documents.extend(documents)
self._rebuild_index()
print(f"Corpus: {len(self.documents)} documenti")
def _rebuild_index(self):
"""Ricostruisce l'indice FAISS."""
dim = self.embeddings.shape[1]
self.index = faiss.IndexFlatIP(dim)
embs_normalized = self.embeddings.copy()
faiss.normalize_L2(embs_normalized)
self.index.add(embs_normalized)
def search(self, query: str, k: int = 5, text_field: str = 'text') -> List[Dict]:
"""Cerca i documenti più rilevanti per la query."""
if self.index is None or len(self.documents) == 0:
return []
query_emb = self.model.encode([query], convert_to_numpy=True)
faiss.normalize_L2(query_emb)
distances, indices = self.index.search(query_emb, min(k, len(self.documents)))
results = []
for dist, idx in zip(distances[0], indices[0]):
if dist >= self.threshold:
result = dict(self.documents[idx])
result['score'] = float(dist)
results.append(result)
return results
def save(self):
"""Persiste il motore di ricerca su disco."""
faiss.write_index(self.index, str(self.cache_dir / 'index.faiss'))
np.save(str(self.cache_dir / 'embeddings.npy'), self.embeddings)
with open(self.cache_dir / 'documents.json', 'w', encoding='utf-8') as f:
json.dump(self.documents, f, ensure_ascii=False, indent=2)
print(f"Engine saved to {self.cache_dir}")
# Utilizzo
engine = SemanticSearchEngine(similarity_threshold=0.6)
# Aggiungi documenti
docs = [
{"text": "Come configurare un ambiente Python con virtualenv.", "id": "py001", "category": "python"},
{"text": "Installazione e configurazione di Docker su Ubuntu.", "id": "docker001", "category": "devops"},
{"text": "Introduzione alle reti neurali con PyTorch.", "id": "ml001", "category": "ml"},
{"text": "Best practices per la sicurezza delle API REST.", "id": "api001", "category": "security"},
{"text": "Ottimizzazione delle query SQL con indici.", "id": "db001", "category": "database"},
]
engine.add_documents(docs)
# Ricerca
results = engine.search("come creare un ambiente virtuale Python")
for r in results:
print(f"[{r['score']:.3f}] {r['text']}")
11. İtalyanca için Cümle Eklemeleri
İtalyanca için çok dilli modelden ince ayara kadar çeşitli seçenekler mevcuttur İtalyan cesetlerine özgü. İşte pratik bir rehber.
from sentence_transformers import SentenceTransformer, util
# Opzione 1: Modello multilingua (più pratico, supporta 50+ lingue)
model_multi = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')
# Opzione 2: E5 multilingual (stato dell'arte per retrieval)
model_e5 = SentenceTransformer('intfloat/multilingual-e5-large')
# Frasi italiane di test
frasi_it = [
"Il governo italiano ha approvato la nuova legge sul lavoro.",
"Il parlamento ha votato la riforma del mercato del lavoro.", # simile
"La Serie A e il campionato di calcio italiano.", # diversa
"Juventus e Inter si sfideranno nel prossimo derby.", # correlata (calcio)
"Il PIL italiano e cresciuto del 2% nel 2024.", # diversa
]
# Test con modello multilingua
embeddings = model_multi.encode(frasi_it)
sim_matrix = util.cos_sim(embeddings, embeddings)
print("=== Similarità tra frasi italiane (multilingua mpnet) ===")
for i in range(len(frasi_it)):
for j in range(i+1, len(frasi_it)):
score = sim_matrix[i][j].item()
if score > 0.5:
print(f" {score:.3f} | '{frasi_it[i][:50]}'")
print(f" | '{frasi_it[j][:50]}'")
# Per E5: aggiungere prefisso "query: " o "passage: " per retrieval
query = "query: come va l'economia italiana?"
passages = [f"passage: {f}" for f in frasi_it]
q_emb = model_e5.encode(query)
p_embs = model_e5.encode(passages)
scores = util.cos_sim(q_emb, p_embs)
top3 = scores[0].topk(3)
print("\n=== Top 3 risultati con E5 ===")
for score, idx in zip(top3.values, top3.indices):
print(f" {score:.3f} | {frasi_it[idx]}")
12. Yaygın Hatalar ve Anti-Kalıplar
Anti-Desen: Doğrudan BERT [CLS] kullanın
jeton [CLS] BERT anlamsal benzerlik için optimize edilmemiştir.
Doğrudan kullanmak (benzerlik görevlerinde ince ayar yapmadan) sonuç verir
SBERT'ten çok daha kötü. Her zaman özel bir cümle dönüştürücü şablonu kullanın.
Anti-Desen: Farklı desenlerin yerleştirmelerini karşılaştırın
Yerleştirmeler all-MiniLM-L6-v2 ve
paraphrase-multilingual-mpnet-base-v2 vektör uzaylarında yaşıyorlar
tamamen farklı. Farklı modellerin ürettiği gömmeleri karşılaştıramazsınız.
Derleminizdeki tüm cümleler için daima aynı kalıbı kullanın.
Anti-Desen: Normalleştirmeyi yoksay
FAISS'ı birlikte kullanıyorsanız IndexFlatIP kosinüs benzerliği için,
vektörleri birim norma göre normalleştirmeniz gerekir. faiss.normalize_L2()
hem indeksleme hem de arama sırasında. Bu adımı unut
açık hatalar olmadan yanlış sonuçlar üretir.
En İyi Uygulamalar: Kontrol Listesi
- Amerika cümle dönüştürücüler anlamsal benzerlik için ham BERT yerine
- İtalyanca veya diller arası içerik için çok dilli şablonları seçin
- IndexFlatIP ile FAISS indekslemeden önce vektörleri daima normalleştirin
- Her yeniden başlatmada yeniden kodlamayı önlemek için diskteki yerleştirmelere devam edin
- Ölçeklenebilir + yüksek kaliteli erişim için çift kodlayıcı + çapraz kodlayıcı hattı
- Dağıtımdan önce STS-B'yi veya alanınızdaki bir veri kümesini değerlendirin
- Sapmayı tespit etmek için üretimdeki benzerlik puanlarının dağılımını izleyin
- İlgisiz eşleşmeleri filtrelemek için minimum güven eşiğini belirleyin
Sonuçlar ve Sonraki Adımlar
Cümle yerleştirmelerle anlamsal benzerlik temel bir bileşendir birçok modern NLP uygulamasından biridir: anlamsal arama, SSS eşleştirme, tekilleştirme, tavsiye ve RAG sistemleri. SBERT ve cümle dönüştürücü modelleri FAISS bu yetenekleri yalnızca birkaç satır kodla erişilebilir hale getirdi. Milyonlarca belgeye ölçeklendirme yaparken gecikmeleri milisaniye düzeyinde tutmanıza olanak tanır.
İtalyanca gibi çok dilli modeller için paraphrase-multilingual-mpnet-base-v2
e intfloat/multilingual-e5-large mükemmel performans sunuyorlar
diller arası bağlamlarda bile.
Önemli Noktalar
- Amerika SBERT anlamsal benzerlik için standart BERT yerine (Pearson 0,87 vs 0,54)
- FAISS ve geniş kapsamlı araştırmalar için gereklidir
- Çift kodlayıcı + çapraz kodlayıcı ardışık düzeni: alma hızı + yeniden sıralama kalitesi
- İtalyanca için çok dilli şablonlar:
paraphrase-multilingual-mpnet-base-v2omultilingual-e5-large - Her zaman puan verin STS-B veya alanınızdan bir veri kümesi
- Etki alanına özel ince ayar yapma
CosineSimilarityLossmaksimum kalite için
Seri devam ediyor
- Madde 10: Üretimde NLP İzleme — sürüklenme tespiti ve otomatik yeniden eğitim
- Madde 8: Yerel LoRA İnce Ayarı — Yüksek Lisans'ı tüketici GPU'larıyla alanınıza uyarlayın
- İlgili seri: Yapay Zeka Mühendisliği/RAG - yoğun erişimin kalbi olarak anlamsal benzerlik
- İlgili seri: Gelişmiş Derin Öğrenme — üçlü kayıp, metrik öğrenme ve karşılaştırmalı öğrenme







