Phase 11 - Lesson 04

Embeddings e Representações Vetoriais

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

O texto é discreto. A matemática é contínua. Toda vez que você pede a um LLM para encontrar documentos "semelhantes", comparar significados ou pesquisar além de palavras-chave, você está confiando em uma ponte entre esses dois mundos. Essa ponte é um embedding. Se você não entende embeddings, você não entende a IA moderna. Você apenas a usa.

Tipo: Build Linguagens: Python Pré-requisitos: Fase 11, Lição 01 (Engenharia de Prompt) Tempo: ~75 minutos Relacionado: A Fase 5 · 22 (Aprofundamento em Modelos de Embedding) cobre denso vs esparso vs multi-vetor, truncamento Matryoshka e seleção de modelo por eixo. Esta lição foca no pipeline de produção (bancos de dados vetoriais, HNSW, matemática de similaridade). Leia a Fase 5 · 22 antes de escolher um modelo.

Objetivos de Aprendizagem

  • Gerar embeddings de texto usando provedores de API e modelos de código aberto, e calcular a similaridade de cosseno entre eles
  • Explicar por que os embeddings resolvem o problema de incompatibilidade de vocabulário que a busca por palavras-chave não consegue lidar
  • Construir um índice de busca semântica que recupera documentos pelo significado em vez de correspondência exata de palavras-chave
  • Avaliar a qualidade do embedding usando benchmarks de recuperação (precisão@k, recall) e escolher o modelo de embedding correto para sua tarefa

O Problema

Você tem 10.000 chamados de suporte. Um cliente escreve "meu pagamento não foi aprovado". Você precisa encontrar chamados anteriores semelhantes. A busca por palavras-chave encontra chamados contendo "pagamento" e "não foi aprovado". Ela deixa de fora "falha na transação", "cobrança recusada" e "erro de faturamento". Esses chamados descrevem exatamente o mesmo problema com palavras completamente diferentes.

Este é o problema da incompatibilidade de vocabulário (vocabulary mismatch). A linguagem humana tem dezenas de maneiras de dizer a mesma coisa. A busca por palavras-chave trata cada palavra como um símbolo independente sem significado. Ela não pode saber que "recusada" e "não foi aprovado" se referem ao mesmo conceito.

Você precisa de uma representação de texto onde o significado, e não a grafia, determine a similaridade. Você precisa de uma maneira de colocar "meu pagamento não foi aprovado" e "a transação foi recusada" próximos em algum espaço matemático, enquanto afasta "meu pagamento chegou a tempo", apesar de compartilharem a palavra "pagamento".

Essa representação é um embedding.

O Conceito

O que é um Embedding?

Um embedding é um vetor denso de números de ponto flutuante que representa o significado de um texto. A palavra "denso" importa — cada dimensão carrega informação, ao contrário de representações esparsas (bag-of-words, TF-IDF) onde a maioria das dimensões é zero.

"The cat sat on the mat" torna-se algo como [0.023, -0.041, 0.087, ..., 0.012] — uma lista de 768 a 3072 números, dependendo do modelo. Esses números codificam o significado. Você nunca os inspeciona diretamente. Você os compara.

A Revolução do Word2Vec

Em 2013, Tomas Mikolov e seus colegas no Google publicaram o Word2Vec. O insight principal: treinar uma rede neural para prever uma palavra a partir de suas vizinhas (ou as vizinhas a partir de uma palavra), e os pesos da camada oculta se tornam representações vetoriais significativas.

O resultado famoso:

king - man + woman = queen

A aritmética vetorial em embeddings de palavras captura relações semânticas. A direção de "man" para "woman" é aproximadamente a mesma direção de "king" para "queen". Este foi o momento em que a área percebeu que a geometria poderia codificar o significado.

O Word2Vec produzia vetores de 300 dimensões. Cada palavra recebia um único vetor, independentemente do contexto. "Bank" em "river bank" (margem do rio) e "bank account" (conta bancária) tinha o mesmo embedding. Essa limitação impulsionou a próxima década de pesquisa.

De Palavras para Sentenças

Os embeddings de palavras representam tokens individuais. Sistemas em produção precisam incorporar sentenças inteiras, parágrafos ou documentos. Quatro abordagens surgiram:

Média (Averaging): tira a média de todos os vetores de palavras na sentença. Barato, com perdas, mas surpreendentemente decente para textos curtos. Perde totalmente a ordem das palavras — "dog bites man" (cão morde homem) e "man bites dog" (homem morde cão) obtêm embeddings idênticos.

Token CLS: modelos baseados em transformer (BERT, 2018) geram um embedding de token especial [CLS] que representa toda a entrada. Melhor do que a média, mas o token [CLS] foi treinado para previsão da próxima sentença, não para similaridade.

Aprendizado contrastivo (Contrastive learning): treina o modelo explicitamente para aproximar pares semelhantes e afastar pares diferentes. O Sentence-BERT (Reimers & Gurevych, 2019) usou essa abordagem e se tornou a base dos modelos de embedding modernos. Dados "How do I reset my password?" e "I need to change my password", o modelo aprende que estes devem ter vetores quase idênticos.

Embeddings ajustados por instruções (Instruction-tuned embeddings): a abordagem mais recente. Modelos como E5 e GTE aceitam um prefixo de tarefa ("search_query:", "search_document:") que diz ao modelo que tipo de embedding produzir. Isso permite que um único modelo atenda a múltiplas tarefas.

graph LR
    subgraph "2013: Word2Vec"
        W1["king"] --> V1["[0.2, -0.1, ...]"]
        W2["queen"] --> V2["[0.3, -0.2, ...]"]
    end

    subgraph "2019: Sentence-BERT"
        S1["Como faço para redefinir minha senha?"] --> E1["[0.04, 0.12, ...]"]
        S2["Preciso alterar minha senha"] --> E2["[0.05, 0.11, ...]"]
    end

    subgraph "2024: Ajustado por Instruções"
        I1["search_query: redefinição de senha"] --> T1["[0.08, 0.09, ...]"]
        I2["search_document: Para redefinir sua senha, clique..."] --> T2["[0.07, 0.10, ...]"]
    end

Modelos de Embedding Modernos

O mercado se consolidou em algumas opções de nível de produção (pontuações MTEB do início de 2026, MTEB v2):

Modelo Provedor Dimensões MTEB Contexto Custo / 1M tokens
Gemini Embedding 2 Google 3072 (Matryoshka) 67.7 (recuperação) 8192 $0.15
embed-v4 Cohere 1024 (Matryoshka) 65.2 128K $0.12
voyage-4 Voyage AI 1024/2048 (Matryoshka) 66.8 32K $0.12
text-embedding-3-large OpenAI 3072 (Matryoshka) 64.6 8192 $0.13
text-embedding-3-small OpenAI 1536 (Matryoshka) 62.3 8192 $0.02
BGE-M3 BAAI 1024 (dense+sparse+ColBERT) 63.0 multilíngue 8192 Pesos abertos
Qwen3-Embedding Alibaba 4096 (Matryoshka) 66.9 32K Pesos abertos
Nomic-embed-v2 Nomic 768 (Matryoshka) 63.1 8192 Pesos abertos

O MTEB (Massive Text Embedding Benchmark) v2 cobre mais de 100 tarefas em recuperação, classificação, agrupamento (clustering), reordenamento (reranking) e sumarização. Quanto maior, melhor. Em 2026, modelos de pesos abertos (Qwen3-Embedding, BGE-M3) igualam ou superam os modelos proprietários hospedados na maioria dos eixos. O Gemini Embedding 2 lidera em recuperação pura; Voyage/Cohere lideram em dominios específicos (finanças, direito, código). Sempre faça benchmarking em suas próprias consultas antes de se comprometer.

Métricas de Similaridade

Dados dois vetores de embedding, existem três maneiras de medir o quão semelhantes eles são:

Similaridade de cosseno (Cosine similarity): o cosseno do ângulo entre dois vetores. Varia de -1 (oposto) a 1 (direção idêntica). Ignora a magnitude — uma frase de 10 palavras e um documento de 500 palavras podem obter uma pontuação de 1.0 se apontarem para a mesma direção. Este é o padrão para 90% dos casos de uso.

cosine_sim(a, b) = dot(a, b) / (||a|| * ||b||)

Produto escalar (Dot product): o produto interno bruto de dois vetores. Idêntico à similaridade de cosseno quando os vetores são normalizados (comprimento unitário). Mais rápido de calcular. Os embeddings da OpenAI são normalizados, portanto, o produto escalar e o cosseno fornecem a mesma classificação.

dot(a, b) = sum(a_i * b_i)

Distância Euclidiana (L2): distância em linha reta no espaço vetorial. Menor = mais semelhante. Sensível a diferenças de magnitude. Use quando a posição absoluta no espaço importar, não apenas a direção.

L2(a, b) = sqrt(sum((a_i - b_i)^2))

Quando usar cada uma:

Métrica Usar quando Evitar quando
Similaridade de cosseno Comparando textos de comprimentos diferentes; a maioria das tarefas de recuperação A magnitude carrega informação
Produto escalar Os embeddings já estão normalizados; velocidade máxima Os vetores têm magnitudes variadas
Distância Euclidiana Agrupamento (clustering); problemas de vizinhos mais próximos espaciais Comparando documentos com comprimentos muito diferentes

Bancos de Dados Vetoriais e HNSW

Uma busca por similaridade por força bruta compara a consulta com cada vetor armazenado. Para 1 milhão de vetores com 1536 dimensões, isso representa 1,5 bilhão de operações de multiplicação e soma por consulta. Lento demais.

Os bancos de dados vetoriais resolvem isso com algoritmos de Vizinho Mais Próximo Aproximado (ANN - Approximate Nearest Neighbor). O algoritmo dominante é o HNSW (Hierarchical Navigable Small World):

  1. Constrói um grafo de vetores de várias camadas
  2. As camadas superiores são esparsas — conexões de longo alcance entre clusters distantes
  3. As camadas inferiores são densas — conexões refinadas entre vetores próximos
  4. A busca começa na camada superior, descendo de forma gananciosa (greedy) para refinar os resultados
  5. Retorna resultados top-k aproximados em tempo O(log n) em vez de O(n)

O HNSW troca uma pequena perda de precisão (geralmente 95-99% de recall) por ganhos massivos de velocidade. Para 10 milhões de vetores, a força bruta leva segundos. O HNSW leva milissegundos.

graph TD
    subgraph "Camadas HNSW"
        L2["Camada 2 (esparsa)"] -->|"saltos longos"| L1["Camada 1 (média)"]
        L1 -->|"saltos mais curtos"| L0["Camada 0 (densa, todos os vetores)"]
    end

    Q["Vetor de consulta"] -->|"entrar no topo"| L2
    L0 -->|"vizinhos mais próximos"| R["Resultados top-k"]

Opções de produção:

Banco de dados Tipo Melhor para Escala máxima
Pinecone SaaS Gerenciado Produção zero-ops Bilhões
Weaviate Código aberto Auto-hospedado, busca híbrida 100M+
Qdrant Código aberto Alto desempenho, filtragem 100M+
ChromaDB Embarcado Prototipagem, desenvolvimento local 1M
pgvector Extensão do Postgres Já utiliza Postgres 10M
FAISS Biblioteca Em processo, pesquisa 1B+

Estratégias de Divisão em Blocos (Chunking)

Os documentos são longos demais para serem incorporados como vetores únicos. Um PDF de 50 páginas cobre dezenas de tópicos — seu embedding se torna uma média de tudo, não se parecendo com nada específico. Você divide os documentos em blocos (chunks) e incorpora cada um deles.

Divisão de tamanho fixo (Fixed-size chunking): divide a cada N tokens com uma sobreposição de M tokens. Simples e previsível. Funciona bem quando os documentos não têm uma estrutura clara. Um bloco de 512 tokens com sobreposição de 50 tokens: o bloco 1 contém os tokens 0-511, o bloco 2 contém os tokens 462-973.

Divisão baseada em sentenças (Sentence-based chunking): divide nos limites das sentenças, agrupando-as até atingir o limite de tokens. Cada bloco contém pelo menos uma sentença completa. Melhor que o tamanho fixo porque você nunca corta um pensamento ao meio.

Divisão recursiva (Recursive chunking): tenta dividir primeiro no maior limite (cabeçalhos de seção). Se ainda for muito grande, tenta os limites de parágrafo. Depois, limites de sentenças. Por fim, limites de caracteres. Este é o funcionamento do RecursiveCharacterTextSplitter do LangChain e funciona bem para corpora de formatos mistos.

Divisão semântica (Semantic chunking): incorpora cada sentença e depois agrupa sentenças consecutivas cujos embeddings são semelhantes. Quando a similaridade do embedding cai abaixo de um limiar, inicia-se um novo bloco. Caro (requer incorporar cada sentença individualmente), mas produz os blocos mais coerentes.

Estratégia Complexidade Qualidade Melhor para
Tamanho fixo Baixa Razoável Texto não estruturado, logs
Baseada em sentenças Baixa Boa Artigos, e-mails
Recursiva Média Boa Markdown, HTML, documentos mistos
Semântica Alta Excelente Qualidade de recuperação crítica

O ponto ideal para a maioria dos sistemas: blocos de 256 a 512 tokens com sobreposição de 50 tokens.

Bi-Encoders vs Cross-Encoders

Um bi-encoder incorpora a consulta e os documentos de forma independente e, em seguida, compara os vetores. Rápido — você incorpora a consulta uma única vez e a compara com os embeddings de documentos pré-calculados. É isso que você usa para recuperação.

Um cross-encoder recebe a consulta e um documento como uma única entrada e gera uma pontuação de relevância. Lento — ele processa cada par consulta-documento por meio do modelo completo. No entanto, é muito mais preciso porque consegue prestar atenção (attend) aos tokens da consulta e do documento simultaneamente.

O padrão de produção: o bi-encoder recupera os top-100 candidatos, e o cross-encoder os reordena para os top-10. Este é o pipeline de recuperar e depois reordenar (retrieve-then-rerank).

graph LR
    Q["Consulta"] --> BE["Bi-Encoder: incorporar consulta"]
    BE --> VS["Busca vetorial: top 100"]
    VS --> CE["Cross-Encoder: reordenar"]
    CE --> R["Resultados top 10"]

Modelos de reordenamento: Cohere Rerank 3.5 (

por 1.000 consultas), BGE-reranker-v2 (gratuito, código aberto), Jina Reranker v2 (gratuito, código aberto).

Embeddings Matryoshka

Os embeddings tradicionais funcionam no estilo tudo-ou-nada. Um vetor de 1536 dimensões usa 1536 números de ponto flutuante. Você não pode truncá-lo para 256 dimensões sem realizar um novo treinamento.

O Aprendizado de Representação Matryoshka (Matryoshka Representation Learning - Kusupati et al., 2022) resolve isso. O modelo é treinado de forma que as primeiras N dimensões capturem as informações mais importantes, como uma boneca russa matryoshka. Truncar um embedding Matryoshka de 1536 dimensões para 256 dimensões perde um pouco de precisão, mas continua funcional.

Os modelos text-embedding-3-small e text-embedding-3-large da OpenAI suportam o truncamento Matryoshka por meio do parâmetro dimensions. Solicitar 256 dimensões em vez de 1536 reduz o armazenamento em 6x com uma perda de precisão de aproximadamente 3-5% nos benchmarks MTEB.

Quantização Binária

Um embedding de 1536 dimensões armazenado como float32 utiliza 6.144 bytes. Multiplique por 10 milhões de documentos: são 61 GB apenas para os vetores.

A quantização binária converte cada número de ponto flutuante em um único bit: valores positivos tornam-se 1, valores negativos tornam-se 0. O armazenamento cai de 6.144 bytes para 192 bytes — uma redução de 32x. A similaridade é calculada usando a distância de Hamming (contagem de bits diferentes), o que as CPUs podem fazer em uma única instrução.

A perda de precisão é de cerca de 5-10% no recall da recuperação. O padrão comum: quantização binária para a busca de primeira passagem em milhões de vetores e, em seguida, reclassificação dos top-1000 com vetores de precisão total. Isso oferece mais de 95% da precisão original com 32x menos uso de memória.

Construa

Construiremos um mecanismo de busca semântica do zero. Sem banco de dados vetorial. Sem API de embedding externa. Apenas Python puro com numpy para a matemática.

Passo 1: Divisão de Texto em Blocos (Chunking)

def chunk_text(text, chunk_size=200, overlap=50):
    words = text.split()
    chunks = []
    start = 0
    while start < len(words):
        end = start + chunk_size
        chunk = " ".join(words[start:end])
        chunks.append(chunk)
        start += chunk_size - overlap
    return chunks


def chunk_by_sentences(text, max_chunk_tokens=200):
    sentences = text.replace("\n", " ").split(".")
    sentences = [s.strip() + "." for s in sentences if s.strip()]
    chunks = []
    current_chunk = []
    current_length = 0
    for sentence in sentences:
        sentence_length = len(sentence.split())
        if current_length + sentence_length > max_chunk_tokens and current_chunk:
            chunks.append(" ".join(current_chunk))
            current_chunk = []
            current_length = 0
        current_chunk.append(sentence)
        current_length += sentence_length
    if current_chunk:
        chunks.append(" ".join(current_chunk))
    return chunks

Passo 2: Construindo Embeddings do Zero

Implementamos um embedding denso simples usando TF-IDF com normalização L2. Este não é um embedding neural, mas segue o mesmo contrato: entra texto, sai um vetor de tamanho fixo, textos semelhantes produzem vetores semelhantes.

import math
import numpy as np
from collections import Counter

class SimpleEmbedder:
    def __init__(self):
        self.vocab = []
        self.idf = []
        self.word_to_idx = {}

    def fit(self, documents):
        vocab_set = set()
        for doc in documents:
            vocab_set.update(doc.lower().split())
        self.vocab = sorted(vocab_set)
        self.word_to_idx = {w: i for i, w in enumerate(self.vocab)}
        n = len(documents)
        self.idf = np.zeros(len(self.vocab))
        for i, word in enumerate(self.vocab):
            doc_count = sum(1 for doc in documents if word in doc.lower().split())
            self.idf[i] = math.log((n + 1) / (doc_count + 1)) + 1

    def embed(self, text):
        words = text.lower().split()
        count = Counter(words)
        total = len(words) if words else 1
        vec = np.zeros(len(self.vocab))
        for word, freq in count.items():
            if word in self.word_to_idx:
                tf = freq / total
                vec[self.word_to_idx[word]] = tf * self.idf[self.word_to_idx[word]]
        norm = np.linalg.norm(vec)
        if norm > 0:
            vec = vec / norm
        return vec

Passo 3: Funções de Similaridade

def cosine_similarity(a, b):
    dot = np.dot(a, b)
    norm_a = np.linalg.norm(a)
    norm_b = np.linalg.norm(b)
    if norm_a == 0 or norm_b == 0:
        return 0.0
    return float(dot / (norm_a * norm_b))


def dot_product(a, b):
    return float(np.dot(a, b))


def euclidean_distance(a, b):
    return float(np.linalg.norm(a - b))

Passo 4: Índice Vetorial com Busca por Força Bruta

class VectorIndex:
    def __init__(self):
        self.vectors = []
        self.texts = []
        self.metadata = []

    def add(self, vector, text, meta=None):
        self.vectors.append(vector)
        self.texts.append(text)
        self.metadata.append(meta or {})

    def search(self, query_vector, top_k=5, metric="cosine"):
        scores = []
        for i, vec in enumerate(self.vectors):
            if metric == "cosine":
                score = cosine_similarity(query_vector, vec)
            elif metric == "dot":
                score = dot_product(query_vector, vec)
            elif metric == "euclidean":
                score = -euclidean_distance(query_vector, vec)
            else:
                raise ValueError(f"Unknown metric: {metric}")
            scores.append((i, score))
        scores.sort(key=lambda x: x[1], reverse=True)
        results = []
        for idx, score in scores[:top_k]:
            results.append({
                "text": self.texts[idx],
                "score": score,
                "metadata": self.metadata[idx],
                "index": idx
            })
        return results

    def size(self):
        return len(self.vectors)

Passo 5: O Mecanismo de Busca Semântica

class SemanticSearchEngine:
    def __init__(self, chunk_size=200, overlap=50):
        self.embedder = SimpleEmbedder()
        self.index = VectorIndex()
        self.chunk_size = chunk_size
        self.overlap = overlap

    def index_documents(self, documents, source_names=None):
        all_chunks = []
        all_sources = []
        for i, doc in enumerate(documents):
            chunks = chunk_text(doc, self.chunk_size, self.overlap)
            all_chunks.extend(chunks)
            name = source_names[i] if source_names else f"doc_{i}"
            all_sources.extend([name] * len(chunks))
        self.embedder.fit(all_chunks)
        for chunk, source in zip(all_chunks, all_sources):
            vec = self.embedder.embed(chunk)
            self.index.add(vec, chunk, {"source": source})
        return len(all_chunks)

    def search(self, query, top_k=5, metric="cosine"):
        query_vec = self.embedder.embed(query)
        return self.index.search(query_vec, top_k, metric)

    def search_with_scores(self, query, top_k=5):
        results = self.search(query, top_k)
        return [
            {
                "text": r["text"][:200],
                "source": r["metadata"].get("source", "unknown"),
                "score": round(r["score"], 4)
            }
            for r in results
        ]

Passo 6: Comparando Métricas de Similaridade

def compare_metrics(engine, query, top_k=3):
    results = {}
    for metric in ["cosine", "dot", "euclidean"]:
        hits = engine.search(query, top_k=top_k, metric=metric)
        results[metric] = [
            {"score": round(h["score"], 4), "preview": h["text"][:80]}
            for h in hits
        ]
    return results

Use

Com uma API de embedding de produção, a arquitetura permanece idêntica. Apenas o gerador de embeddings (embedder) muda:

from openai import OpenAI

client = OpenAI()

def openai_embed(texts, model="text-embedding-3-small", dimensions=None):
    kwargs = {"model": model, "input": texts}
    if dimensions:
        kwargs["dimensions"] = dimensions
    response = client.embeddings.create(**kwargs)
    return [item.embedding for item in response.data]

Truncamento Matryoshka com a OpenAI — mesmo modelo, menos dimensões, menos armazenamento:

full = openai_embed(["semantic search query"], dimensions=1536)
compact = openai_embed(["semantic search query"], dimensions=256)

O vetor de 256 dimensões usa 6x menos armazenamento. Para 10 milhões de documentos, isso representa 10 GB contra 61 GB. A perda de precisão é de cerca de 3-5% nos benchmarks padrão.

Para reordenamento com a Cohere:

import cohere

co = cohere.ClientV2()

results = co.rerank(
    model="rerank-v3.5",
    query="What is the refund policy?",
    documents=["Full refund within 30 days...", "No refunds after 90 days..."],
    top_n=3
)

Para embeddings locais sem dependência de API:

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-small-en-v1.5")
embeddings = model.encode(["semantic search query", "another document"])

A classe VectorIndex da nossa construção funciona com qualquer uma dessas opções. Basta trocar a função de embedding e manter a lógica de busca.

Envie

Esta lição produz:

Exercícios

  1. Comparação de métricas: execute as mesmas 5 consultas em documentos de amostra usando similaridade de cosseno, produto escalar e distância euclidiana. Registre os top-3 resultados de cada uma. Em quais consultas as métricas discordam? Por quê?

  2. Experimento de tamanho de bloco: indexe os documentos de amostra com tamanhos de bloco de 50, 100, 200 e 500 palavras. Para cada um, execute 5 consultas e registre a pontuação de similaridade mais alta (top-1). Faça um gráfico da relação entre o tamanho do bloco e a qualidade da recuperação. Encontre o ponto onde blocos maiores começam a prejudicar o desempenho.

  3. Simulação do Matryoshka: crie um SimpleEmbedder que produza vetores de 500 dimensões. Trunque para 50, 100, 200 e 500 dimensões. Meça como o recall da recuperação se degrada em cada truncamento. Isso simula o comportamento do Matryoshka sem precisar do truque de treinamento real.

  4. Quantização binária: pegue os embeddings do mecanismo de busca, converta-os para binário (1 se positivo, 0 se negativo) e implemente a busca por distância de Hamming. Compare os top-10 resultados com a similaridade de cosseno de precisão total. Meça o percentual de sobreposição.

  5. Divisão baseada em sentenças: substitua a divisão de tamanho fixo por chunk_by_sentences. Execute as mesmas consultas e compare as pontuações de recuperação. Respeitar os limites das sentenças melhora os resultados?

Termos-Chave

Termo O que as pessoas dizem O que realmente significa
Embedding "Texto em números" Um vetor denso onde a proximidade geométrica codifica a similaridade semântica
Word2Vec "O embedding original" Modelo de 2013 que aprendeu vetores de palavras prevendo palavras de contexto; provou que a aritmética vetorial codifica o significado
Similaridade de cosseno "Quão semelhantes são dois vetores" Cosseno do ângulo entre os vetores; 1 = direção idêntica, 0 = ortogonal, -1 = oposto
HNSW "Busca vetorial rápida" Grafo Hierarchical Navigable Small World — uma estrutura de múltiplas camadas que permite busca de vizinhos mais próximos aproximada em O(log n)
Bi-encoder "Incorpore separadamente, compare rápido" Codifica a consulta e o documento de forma independente em vetores; permite o pré-cálculo e recuperação rápida
Cross-encoder "Reordenador lento, mas preciso" Processa o par consulta-documento conjuntamente através do modelo completo; maior precisão, sem pré-cálculo
Embeddings Matryoshka "Vetores truncáveis" Embeddings treinados para que as primeiras N dimensões capturem as informações mais importantes, permitindo armazenamento de tamanho variável
Quantização binária "Embeddings de 1 bit" Conversão de vetores de ponto flutuante para binário (apenas o bit de sinal) para redução de 32x no armazenamento com busca por distância de Hamming
Chunking (Divisão em blocos) "Dividir documentos para embedding" Divisão de documentos em segmentos de 256-512 tokens para que cada um possa ser incorporado e recuperado de forma independente
Banco de dados vetorial "Mecanismo de busca para embeddings" Armazenamento de dados otimizado para guardar vetores e realizar busca de vizinhos mais próximos aproximada em escala
Aprendizado contrastivo "Treinar por comparação" Abordagem de treinamento que aproxima embeddings de pares semelhantes e afasta embeddings de pares diferentes
MTEB "O benchmark de embeddings" Massive Text Embedding Benchmark — 56 conjuntos de dados em 8 tarefas; o padrão para comparação de modelos de embedding

Leituras Adicionais