04-03-02 — Similaridade: cosine, dot product, euclidean
TL;DR
Para RAG, use cosine similarity por padrão — mede o ângulo entre vetores, ignorando magnitude, o que funciona bem para texto. Use dot product quando os embeddings já estão normalizados (mesma coisa que cosine, mas mais rápido). Evite distância euclidiana para texto de alta dimensão — sofre com a maldição da dimensionalidade. Normalização importa: sempre normalize seus vetores ou use cosine.
O problema: como medir "proximidade" de vetores?
Após transformar texto em vetores de 1536 dimensões, precisamos de uma função matemática que diga "quão parecidos são esses dois vetores". Existem três candidatos principais, cada um com lógica diferente.
Cosine Similarity
Mede o ângulo entre dois vetores. O resultado vai de -1 (opostos) a +1 (idênticos). Score 0 significa ortogonais (sem relação).
Fórmula: cos(θ) = (A · B) / (|A| × |B|)
Intuição geométrica: imagine dois ponteiros saindo da origem. Cosine mede o ângulo entre eles — não importa o comprimento dos ponteiros, só a direção.
Por que funciona bem para texto: Dois documentos sobre o mesmo assunto, mesmo que um seja muito longo e outro curto, apontam na mesma direção no espaço vetorial. A magnitude (tamanho do vetor) reflete comprimento do texto, não relevância semântica.
import numpy as np
def cosine_similarity(a: list[float], b: list[float]) -> float:
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Score próximo de 1.0 = muito similar
# Score próximo de 0.0 = sem relação semântica
score = cosine_similarity(embedding_query, embedding_chunk)
Dot Product (Produto Escalar)
Mede o produto escalar direto entre dois vetores. Resultado é qualquer número real — sem limite superior.
Fórmula: A · B = Σ(aᵢ × bᵢ)
Quando usar: Quando os vetores estão normalizados (norma = 1). Nesse caso, dot product == cosine similarity, mas é computacionalmente mais barato (sem divisão pela norma).
OpenAI e Cohere já retornam embeddings normalizados (norma L2 = 1). Isso significa que para esses modelos, dot product e cosine similarity são equivalentes. Azure AI Search usa dotProduct como padrão quando os vetores são normalizados — é ligeiramente mais rápido.
def dot_product_similarity(a: list[float], b: list[float]) -> float:
a, b = np.array(a), np.array(b)
return float(np.dot(a, b))
# Válido como métrica de similaridade APENAS se ||a|| = ||b|| = 1
def is_normalized(v: list[float], tol: float = 1e-6) -> bool:
return abs(np.linalg.norm(np.array(v)) - 1.0) < tol
Euclidean Distance (L2)
Mede a distância em linha reta entre dois pontos no espaço vetorial. Ao contrário das outras, menor = mais similar.
Fórmula: d(A,B) = √Σ(aᵢ - bᵢ)²
Problema em alta dimensão: Em espaços de 1000+ dimensões, a distância euclidiana entre pontos tende a convergir para o mesmo valor — fenômeno chamado de "curse of dimensionality". A diferença entre o vizinho mais próximo e o mais distante fica cada vez menor, tornando a métrica pouco discriminativa.
def euclidean_distance(a: list[float], b: list[float]) -> float:
a, b = np.array(a), np.array(b)
return float(np.linalg.norm(a - b))
# MENOR = mais similar (ao contrário de cosine e dot)
Em 1536 dimensões, euclidean distance sofre com a maldição da dimensionalidade. Use cosine. Exceção: se você trabalha com embeddings de imagens ou modelos específicos que recomendam L2 (ex: FAISS com L2).
Comparativo prático
| Métrica | Range | Mais similar | Normalização necessária? | Quando usar |
|---|---|---|---|---|
| Cosine | [-1, 1] | Próximo de 1 | Não | Padrão para texto — robusto |
| Dot Product | (-∞, ∞) | Maior valor | Sim (ou explícita) | Vetores normalizados, performance |
| Euclidean | [0, ∞) | Próximo de 0 | Recomendada | Imagens, baixa dimensão |
Normalização importa
Se seus embeddings não vêm normalizados (ex: modelos locais como BGE), normalize antes de indexar:
def normalize(embedding: list[float]) -> list[float]:
v = np.array(embedding)
norm = np.linalg.norm(v)
if norm == 0:
return embedding # vetor zero — edge case
return (v / norm).tolist()
# Normalizar antes de indexar E antes de buscar
chunk_vector = normalize(raw_embedding)
query_vector = normalize(raw_query_embedding)
Cada vector database tem sua nomenclatura: Qdrant usa Distance.COSINE, pgvector usa o operador <=> para cosine, Azure AI Search usa "metric": "cosine". A matemática é a mesma — o que muda é apenas a configuração do índice.
Como isso se conecta
- 04-02-01 — Modelos de embedding — o modelo determina se os vetores vêm normalizados
- 04-03-03 — Hybrid search — como combinar score vetorial com BM25 (escalas diferentes)
- 04-04-01 — Re-ranking — por que score vetorial sozinho não é suficiente para ordenação final
Fontes
- Aggarwal, C. et al. (2001). On the surprising behavior of distance metrics in high dimensional space. ICDT. springer.com
- OpenAI. Embeddings — Which distance function should I use?. platform.openai.com
- Microsoft. Vector search — similarity metrics in Azure AI Search. learn.microsoft.com