Pular para o conteúdo

Trilha de RAG · parte 4 de 12

Similaridade entre vetores: quando dois vetores são parecidos

Como o computador decide que dois vetores são parecidos, com as três medidas usadas (euclidiana, produto escalar e cosseno) e quando cada uma serve.

Publicada em · 47 min de leitura

Tamanho da fonte:
Largura da coluna:

Esta aula explica como medir se dois vetores estão perto: as três medidas mais usadas (distância euclidiana, produto escalar e cosseno), quando cada uma faz sentido e os erros comuns ao escolher uma. Os vetores daqui são listas de números, como as que um modelo de embedding devolve para um texto.

A aula parte do zero: para acompanhar as contas basta saber somar, multiplicar e tirar uma raiz quadrada. Toda fórmula vem depois de uma explicação em palavras e de um exemplo numérico pequeno, com vetores de 2 ou 3 números, que dá para refazer no papel. Os trechos de código estão em Python e foram executados por mim (a seção de fontes detalha o ambiente). Tudo o que muda com o tempo (nomes de operadores, métricas dos bancos, recomendações dos provedores) vem da documentação oficial consultada em 15 de outubro de 2026. Os números de similaridade e de tempo que aparecem foram medidos por mim ou estão marcados como ilustrativos. As figuras são animadas e foram geradas com o ArchFlow.

Mapa da aula

ParteSeçõesPergunta que responde
1. Fundamentos1 e 2O que é “perto” e o que é um vetor, para esta aula?
2. As três medidas3 a 6Como se calcula cada medida, o que ela enxerga e onde elas discordam?
3. Normalização7Por que dividir pelo tamanho faz tudo ficar equivalente?
4. Na prática8 a 12Qual medida escolher, como ler o score, o que muitas dimensões fazem, quanto custa e como se faz em código?
5. Fechamento e prática13 a 15Que erros evitar, que dúvidas ficam em aberto, como treinar e que palavras guardar?

Pré-requisito: Embeddings. Os diagramas seguem um mapa de cores fixo: laranja para o que a pessoa escreve ou pergunta, bronze escuro (o hexágono) para modelos, verde para documentos e resultados bons, azul para vetores, números e contas, cinza para o que é neutro e rosa para o que dá errado.

Parte 1: Fundamentos

1. Perto, de vários jeitos

Pergunte a alguém se São Paulo está perto de Campinas e a resposta virá em quilômetros: a medida é a distância em linha reta entre dois pontos de um mapa, o que se chama de régua. Agora pergunte se duas pessoas têm o mesmo gosto de cinema e a resposta será outra, sem quilômetro nenhum: o que importa é se elas apontam para o mesmo lado, isto é, se gostam das mesmas coisas, mesmo que uma goste com mais entusiasmo que a outra. Existe ainda um terceiro jeito. Duas pessoas empurrando o mesmo carro ajudam mais quanto mais fortes forem e quanto mais alinhadas estiverem com a direção em que o carro anda. “Perto” é uma palavra de vários sentidos, e cada um desses sentidos vira uma medida diferente.

Precisamos escolher uma porque o computador não tem intuição: ele compara números. Se a busca por significado vai devolver “os trechos mais parecidos com a pergunta”, cada trecho precisa receber um número que diga o quanto ele se parece com a pergunta, e esse número precisa servir para ordenar. Mais: precisa ser comparável entre os trechos de uma mesma consulta (é isso que permite montar uma fila do mais parecido para o menos parecido) e calculado do mesmo jeito para toda consulta nova. Sem um número, a “vizinhança” continua sendo uma boa metáfora que ninguém consegue executar.

perto de varios jeitos
figura 1 · perto de varios jeitos

A figura reúne os três sentidos. Em cima, a régua: duas cidades ligadas por uma linha reta. No meio, a direção: Ana e Bia apontam para o mesmo farol, e o que interessa é o ângulo entre as duas setas. Embaixo, o empurrão: duas pessoas e um carro, onde a seta grossa indica a pessoa que empurra com mais força. Cada grupo entrega o seu resultado ao quadro azul “Um número por par”, e o rótulo da seta diz qual medida corresponde ao sentido (euclidiana para a régua, cosseno para a direção, produto escalar para o empurrão). Do número sai a seta grossa e verde do ranking. As três analogias são só um mapa de leitura, e as seções 3 a 5 mostram as contas.

Com as frases de exemplo, a pergunta “Quero encerrar meu plano” precisa ser colocada ao lado de “Como cancelar a assinatura?” (mesmo assunto, nenhuma palavra em comum), de “Misture a farinha com os ovos” (outro assunto) e de “A taxa de juros subiu” (outro assunto ainda). Uma boa medida dá à primeira um número alto, e às outras números baixos. Escolher a medida e saber lê-la é o que esta aula faz.

2. Um vetor é uma seta com tamanho e direção

O vetor foi apresentado como uma lista de números. Para esta aula, vale um segundo jeito de olhar a mesma coisa. Desenhe um plano com um eixo horizontal (x) e um vertical (y), que se cruzam num ponto chamado origem, onde os dois valores são zero. A lista [3, 4] pode ser lida como uma instrução: ande 3 passos para a direita e 4 para cima. O ponto onde você chega é (3, 4). Se você desenhar uma seta da origem até esse ponto, a seta é o vetor. Ela tem duas propriedades que importam daqui em diante: o tamanho (quanto ela mede) e a direção (para onde ela aponta).

vetor e seta
figura 2 · vetor e seta

À esquerda, o plano com os dois eixos. A seta laranja vai da origem até A = (3, 4), e o traço pontilhado marca os 3 passos horizontais. A seta verde vai até B = (4, 3). Repare que as duas têm o mesmo comprimento, mas apontam para lados ligeiramente diferentes, e essa diferença é o que as medidas vão capturar. À direita, o mesmo vetor lido como lista de números, como coordenadas, como tamanho (a seta mede 5, e a conta aparece na seção 3) e como direção (o ângulo em relação ao eixo x). Os dois lados da figura são a mesma coisa vista de dois ângulos, e os quatro retângulos formam uma cadeia porque cada leitura prepara a seguinte.

Um embedding real tem 384 ou mais números em vez de 2, e não dá para desenhar 384 eixos. A boa notícia é que as contas não mudam: tudo o que se faz com 2 números se faz do mesmo jeito com 3, com 384 ou com 3.072, só com mais parcelas na soma. Por isso a aula usa vetores de 2 e de 3 números, que cabem no papel, e o que valer neles vale nos reais. A única coisa que se perde é a imagem: ninguém consegue visualizar o ângulo entre duas setas de 384 dimensões, mas o computador calcula esse ângulo com a mesma fórmula.

Parte 2: As três medidas

3. Distância euclidiana: a régua

A distância euclidiana é a medida que todo mundo já conhece sem saber o nome: é a distância em linha reta entre dois pontos, a que uma régua daria. Para calculá-la, você subtrai os dois vetores posição a posição, eleva cada diferença ao quadrado (para que diferenças negativas não cancelem as positivas), soma tudo e tira a raiz quadrada do total. Em símbolos, para dois vetores de dois números, distância(A, B) = raiz((a1 - b1)² + (a2 - b2)²), onde a1 e a2 são os números de A, e b1 e b2 são os de B. Menor distância quer dizer mais perto, e zero quer dizer vetores idênticos.

Com A = (3, 4) e B = (4, 3), passo a passo: as diferenças posição a posição são 3 - 4 = -1 e 4 - 3 = 1. Elevadas ao quadrado, viram 1 e 1. A soma é 2, e a raiz de 2 é cerca de 1,41. Em três dimensões a conta é a mesma com uma parcela a mais. Para A = (2, 1, 2) e B = (1, 2, 2), as diferenças são 1, -1 e 0, os quadrados somam 1 + 1 + 0 = 2, e a distância também é raiz de 2, cerca de 1,41. O tamanho de um vetor, que usaremos adiante, é a mesma conta aplicada à distância entre o vetor e a origem: o tamanho de (3, 4) é raiz de (3² + 4²) = raiz de 25 = 5.

euclidiana
figura 3 · euclidiana

À esquerda, os pontos A e B no plano, longe da origem de propósito, para mostrar que a conta só depende da diferença entre eles. Os dois traços pontilhados que vão ao “canto (4, 4)” são os catetos de um triângulo retângulo: 1 para a direita e 1 para baixo. A seta azul e grossa, com a etiqueta “régua: 1,41”, é a distância, a hipotenusa desse triângulo. É o teorema de Pitágoras, o mesmo que você viu na escola: o quadrado da hipotenusa é a soma dos quadrados dos catetos. À direita, os três passos da conta numa cadeia: subtrair, elevar ao quadrado e somar, tirar a raiz. O retângulo de baixo lembra a leitura: quanto menor, mais perto. O quadro “Em 3 dimensões” no canto avisa que nada muda além da quantidade de parcelas.

O que a euclidiana enxerga é a diferença total entre os vetores, e ela mistura dois fatores: a direção e o tamanho. Dois vetores que apontam exatamente para o mesmo lado, mas têm tamanhos diferentes, ficam longe um do outro. Isso atrapalha quando o tamanho do vetor não carrega o significado que interessa (um texto longo e um curto sobre o mesmo assunto podem render vetores de tamanhos diferentes, dependendo do modelo). A seção 6 mostra um exemplo em que isso muda o ranking.

4. Produto escalar: multiplicar e somar

O produto escalar (em inglês, dot product) tem uma conta ainda mais simples que a da distância: você multiplica os dois vetores posição a posição e soma os resultados. Em símbolos, A · B = a1 × b1 + a2 × b2. Não há raiz, não há subtração. O resultado é um único número, e quanto maior, mais parecidos os vetores se consideram.

Com A = (3, 4) e B = (4, 3): 3 × 4 = 12 e 4 × 3 = 12, e a soma é 24. Com os vetores de 3 posições A = (2, 1, 2) e B = (1, 2, 2), os produtos são 2 × 1 = 2, 1 × 2 = 2 e 2 × 2 = 4, e a soma é 8. Dois casos extremos ajudam a entender o que o número diz. Para (1, 0) e (0, 1), que são perpendiculares (formam um ângulo de 90 graus), o produto é 1 × 0 + 0 × 1 = 0: sem alinhamento nenhum, zero. Para dois vetores que apontam para lados opostos, como (2, 1) e (-2, -1), o produto é 2 × (-2) + 1 × (-1) = -5: negativo.

produto escalar
figura 4 · produto escalar

Os dois grupos à esquerda são as duas posições. Em cada um, os números de A (laranja) e de B (verde) entram numa caixa azul “Multiplicar” (3 × 4 = 12 na posição 1, 4 × 3 = 12 na posição 2). As setas grossas levam os dois produtos para “Somar tudo”, que dá 24. Dali saem dois casos em pontilhado: o de cima, com pacotes rosas, leva a “B dobrado”, que a seção 6 explora, e o de baixo leva ao caso dos vetores perpendiculares, em que o produto vale 0.

O que o produto escalar enxerga é o alinhamento ponderado pelo tamanho. Multiplicar posição a posição premia as posições em que os dois vetores têm o mesmo sinal e números grandes, e castiga as posições em que os sinais são opostos. Por isso ele cresce quando os vetores apontam para o mesmo lado e também quando os vetores são longos. É o terceiro sentido da seção 1: o empurrão vale mais quando a pessoa é forte e está alinhada. Se você dobrar B para (8, 6), o produto com A = (3, 4) passa a ser 3 × 8 + 4 × 6 = 48, o dobro de antes, sem que o alinhamento tenha mudado em nada. Guarde esse detalhe, porque ele separa o produto escalar da próxima medida.

5. Similaridade do cosseno: só o ângulo

A similaridade do cosseno quer ignorar o tamanho e olhar só para a direção. Ela faz isso dividindo o produto escalar pelo produto dos tamanhos dos dois vetores: cosseno(A, B) = (A · B) / (|A| × |B|), onde |A| é o tamanho de A (a raiz da soma dos quadrados dos números de A, como na seção 3). O nome vem da trigonometria: o resultado é o cosseno do ângulo entre as duas setas, e você não precisa de trigonometria para usá-lo, só de saber que esse valor é 1 quando as setas apontam para o mesmo lado (ângulo de 0 grau), 0 quando são perpendiculares (90 graus) e -1 quando apontam para lados opostos (180 graus).

Com A = (3, 4) e B = (4, 3): o produto escalar é 24 (seção 4), o tamanho de cada um é 5, o produto dos tamanhos é 25, e o cosseno é 24 ÷ 25 = 0,96. O ângulo correspondente é cerca de 16,3 graus, uma abertura pequena. Com A = (2, 1, 2) e B = (1, 2, 2): o produto escalar é 8, cada tamanho é raiz de (4 + 1 + 4) = 3, o produto dos tamanhos é 9, e o cosseno é 8 ÷ 9, cerca de 0,89 (ângulo próximo de 27 graus). A divisão é o que apaga o tamanho: se você multiplicar um dos vetores por 2, o produto escalar dobra e o tamanho dele também, e os dois efeitos se cancelam.

cosseno
figura 5 · cosseno

À esquerda, a consulta q = (2, 1), em laranja, e quatro vetores candidatos. d1 = (4, 2) aponta exatamente para o mesmo lado que q (as duas setas se sobrepõem, por isso a verde parece sair da laranja): cosseno 1,00, ângulo de 0 grau, embora d1 seja o dobro de q em tamanho. d2 = (1, 2), em azul, abre 37 graus e rende 0,80. d3 = (-1, 2), em cinza, forma 90 graus com q e rende 0,00. d4 = (-2, -1), em rosa, aponta para o lado oposto e rende -1,00. À direita, a conta do exemplo A = (3, 4) e B = (4, 3): produto escalar, tamanhos, divisão. A seta grossa e verde que chega ao “Cosseno” é a conclusão.

Para texto, o cosseno é a escolha mais comum, pelo que as documentações mostram na seção 8 (as palavras exatas vêm lá). A razão é que, nos modelos de embedding, o que carrega o significado é a direção do vetor, e o tamanho costuma ser um subproduto do texto de entrada ou do modelo, que não interessa para saber se dois textos falam da mesma coisa. Há uma ressalva importante: alguns modelos são treinados com o produto escalar, e para eles o tamanho pode carregar informação. Isso volta na seção 8.

Uma confusão frequente envolve as palavras. Similaridade sobe quando os vetores se parecem mais: cosseno 1 é o máximo. Distância desce quando eles se parecem mais: distância 0 é o máximo. A distância do cosseno é definida como 1 - cosseno, e vale 0 para vetores na mesma direção, 1 para perpendiculares e 2 para opostos. Quando um banco vetorial devolve “distância”, menor é melhor. Quando devolve “similaridade” ou “score”, maior é melhor. Misturar as duas leituras inverte o ranking sem dar erro algum, e o erro 6 da seção 13 volta a isso.

6. Dobrar um vetor e o mesmo par medido três vezes

Duas experiências curtas mostram a diferença entre as três medidas. A primeira é dobrar um dos vetores sem mudar para onde ele aponta. Com A = (3, 4) fixo e B passando de (4, 3) para (8, 6), a euclidiana vai de 1,41 para a raiz de (3 - 8)² + (4 - 6)² = raiz de (25 + 4) = raiz de 29, cerca de 5,39. O produto escalar vai de 24 para 48. O cosseno continua em 48 ÷ (5 × 10) = 0,96. Dobrar B não muda a direção nem o ângulo, e só o cosseno percebe isso.

dobrar o vetor
figura 6 · dobrar o vetor

O grupo da esquerda tem os três valores para B = (4, 3), e o da direita, para B = (8, 6). As setas ligam cada medida ao seu valor depois da dobra. As duas setas rosas e grossas (euclidiana e produto escalar) indicam que o valor mudou, a primeira dizendo “parece mais longe” e a segunda dizendo “dobra”. A seta verde fina do cosseno, “não muda”, indica o valor que permaneceu em 0,96. A cor dos retângulos da direita repete a ideia: rosa onde a medida foi afetada pelo tamanho, verde onde ela ficou intacta.

Isso não é só curiosidade. Imagine que uma consulta tenha q = (2, 1) e que existam dois documentos: d1 = (4, 2), com a mesma direção da consulta e tamanho dobrado, e d2 = (1, 2), com direção diferente e o mesmo tamanho da consulta. As contas são rápidas. Distâncias: q a d1 é raiz de (4 + 1) = 2,24, e q a d2 é raiz de (1 + 1) = 1,41. Produtos escalares: q · d1 = 8 + 2 = 10, e q · d2 = 2 + 2 = 4. Cossenos: 1,00 para d1 e 4 ÷ (2,24 × 2,24) = 0,80 para d2. Os três rankings:

tres rankings
figura 7 · tres rankings

À esquerda, as três setas partem da origem: laranja para q, verde para d1 e azul para d2. Os dois traços pontilhados, com pacotes lentos, mostram as distâncias entre a consulta e cada candidato. À direita, um grupo por medida, com o primeiro e o segundo colocados. A euclidiana escolhe d2 (distância 1,41 contra 2,24), porque d1 está mais longe em linha reta, ainda que aponte exatamente para o mesmo lado de q. O produto escalar e o cosseno escolhem d1, o primeiro porque d1 é longo e alinhado, o segundo porque o ângulo é zero. Três réguas, dois vencedores: a mesma pergunta, feita com medidas diferentes, devolveu documentos diferentes.

Quando os vetores têm tamanhos parecidos, as três medidas tendem a concordar, e quando os tamanhos variam elas podem discordar. Por isso o próximo passo, a normalização, é o que faz a discordância desaparecer.

Parte 3: Normalização

7. Normalizar: todo mundo no círculo de raio 1

Normalizar um vetor é dividir cada número dele pelo tamanho dele. O resultado aponta exatamente para o mesmo lado e tem tamanho 1. Para A = (3, 4), o tamanho é 5 e o vetor normalizado é (3 ÷ 5, 4 ÷ 5) = (0,6, 0,8). Conferindo: raiz de (0,6² + 0,8²) = raiz de (0,36 + 0,64) = raiz de 1 = 1. Para B = (4, 3), o resultado é (0,8, 0,6), também de tamanho 1. Normalizar não muda a direção, só descarta o tamanho.

normalizacao
figura 8 · normalizacao

O círculo de bolinhas azuis é o círculo unitário, o conjunto de todos os pontos a distância 1 da origem. A seta laranja vai da origem até A normalizado, (0,6, 0,8), que está sobre o círculo, e a verde vai até B normalizado, (0,8, 0,6), também sobre o círculo. As setas pontilhadas de fora, rotuladas ”÷ 5”, vêm dos vetores originais A e B, que estão fora do círculo (o desenho não respeita a escala: com a escala do círculo, A ficaria bem mais longe). Os pacotes caminham para dentro, que é o que dividir pelo tamanho faz. À direita, a conta em três passos, e o retângulo de baixo, “Círculo de raio 1”, resume: todo vetor normalizado mora ali.

Com todos os vetores no círculo (ou, em muitas dimensões, na esfera de raio 1), duas coisas acontecem. A primeira é que o produto escalar e o cosseno passam a ser o mesmo número, porque o denominador do cosseno, o produto dos tamanhos, vale 1 × 1 = 1. Com A e B normalizados, o produto escalar é 0,6 × 0,8 + 0,8 × 0,6 = 0,48 + 0,48 = 0,96, igual ao cosseno de antes. A segunda é que a distância euclidiana passa a ser uma função do cosseno. A conta geral é que o quadrado da distância entre dois vetores é o quadrado do tamanho de um, mais o quadrado do tamanho do outro, menos duas vezes o produto escalar: distância² = |A|² + |B|² - 2 × (A · B). Com os dois tamanhos iguais a 1, fica distância² = 2 - 2 × cosseno. Conferindo com os mesmos vetores: a distância entre (0,6, 0,8) e (0,8, 0,6) é a raiz de (0,04 + 0,04) = raiz de 0,08, e o quadrado disso é 0,08, enquanto 2 - 2 × 0,96 = 0,08. A relação é exata.

Daí vem uma pista importante: com vetores normalizados, produto escalar e cosseno dão o mesmo valor, e portanto o mesmo ranking. A euclidiana dá o mesmo ranking também, lido ao contrário: quanto maior o cosseno, menor 2 - 2 × cosseno, então “mais similar” e “menos distante” apontam para a mesma ordem.

normalizados mesmo ranking
figura 9 · normalizados mesmo ranking

O grupo de cima tem os três valores para vetores de tamanho 1: produto escalar 0,96, cosseno 0,96 e a euclidiana ao quadrado 0,08 = 2 - 2 × 0,96. As três setas grossas se juntam em “Mesmo ranking”. O grupo de baixo repete o exemplo da seção 6, sem normalizar: a euclidiana elege d2, e o produto escalar e o cosseno elegem d1, e as setas rosas chegam a “Rankings que divergem”. O contraste é o ponto da figura: a mesma consulta e os mesmos candidatos produzem um acordo ou uma divergência conforme os vetores estejam normalizados ou não.

Na prática isso tem três consequências, todas apoiadas na documentação consultada em 15 de outubro de 2026. Primeiro, as documentações da OpenAI e da Voyage dizem que os embeddings que oferecem já vêm normalizados para tamanho 1. A da OpenAI explica que, por isso, o cosseno pode ser calculado “um pouco mais rápido” com um simples produto escalar e que cosseno e euclidiana dão rankings idênticos, e a da Voyage diz que o cosseno equivale ao produto escalar, “que pode ser calculado mais rápido”. Segundo, os bancos vetoriais costumam tratar a normalização para você. O Qdrant diz que implementa o cosseno como produto escalar sobre vetores normalizados e que os vetores são normalizados automaticamente no envio. O pgvector, no trecho que li, não diz que normaliza sozinho: ele aconselha usar o produto interno (<#>) quando os vetores já têm tamanho 1, para o melhor desempenho. Terceiro, a documentação do sentence-transformers diz que, se o modelo termina com um módulo Normalize, é sensato usar a métrica dot em vez de cosine, porque o produto escalar de embeddings normalizados equivale ao cosseno e é mais rápido (o cosseno renormaliza por dentro). Para os modelos que não normalizam, o parâmetro normalize_embeddings=True de encode faz a normalização na saída.

Um aviso que vale repetir: não suponha, meça. O modelo paraphrase-multilingual-MiniLM-L12-v2 que uso nos exemplos devolve vetores com tamanhos entre 3,6 e 5,6 nas oito frases da seção 12 (medi). Se você usar produto escalar com ele sem normalizar, os tamanhos entram na conta, e um trecho cujo vetor sai mais longo ganha pontos que não têm relação com o assunto.

Parte 4: Na prática

8. Qual medida escolher

A regra que resolve quase todos os casos é usar a medida com que o modelo foi treinado, ou a que a documentação dele recomenda. O modelo aprendeu a organizar o espaço de modo que “perto”, na medida do treino, signifique “parecido em significado”. Trocar de régua depois às vezes não muda nada (se os vetores são normalizados) e às vezes piora o ranking, sem nenhuma mensagem de erro, porque todas as medidas devolvem números e todos os rankings parecem bem ordenados.

qual metrica
figura 10 · qual metrica

Leia a figura de cima para baixo. O grupo cinza reúne o que as documentações dizem (todas consultadas em 15 de outubro de 2026). A da OpenAI recomenda o cosseno e informa que os vetores têm tamanho 1, acrescentando que a escolha da função de distância “tipicamente não importa muito”. A da Voyage lista produto escalar, cosseno ou euclidiana como usos possíveis, com vetores de tamanho 1. A do sentence-transformers traz o cosseno como padrão da função similarity e sugere dot quando o modelo normaliza. O cartão do intfloat/multilingual-e5-small mostra um exemplo que normaliza os vetores e usa o produto escalar. Todos desembocam no hexágono “Seu modelo de embedding”, cuja documentação manda, e dali sai o losango “Vetores normalizados?”. Com “sim”, a seta grossa leva ao produto escalar, que dá o mesmo ranking do cosseno e é mais rápido. Com “não”, leva à métrica do modelo, em geral o cosseno. As duas saídas chegam ao banco vetorial, que precisa ser configurado com a mesma métrica. O ramo rosa e tracejado de baixo é o desvio silencioso: se a métrica ignorar a do modelo, o programa roda sem erro e o ranking piora sem aviso.

A página do sentence-transformers sobre modelos pré-treinados traz um exemplo de que a escolha tem consequências. Ela separa os modelos “ajustados para o produto escalar” (os multi-qa-*-dot-v1, por exemplo) dos que “produzem vetores normalizados de tamanho 1” (os multi-qa-*-cos-v1, que servem para produto escalar, cosseno ou euclidiana). Para os primeiros, o tamanho do vetor faz parte do que o treino otimizou. Pelo que sei do conhecimento geral da área (esta parte não vem de uma fonte consultada hoje), em modelos assim o tamanho pode funcionar como um sinal adicional, e por isso normalizar ou trocar para cosseno pode alterar o ranking. Para os modelos de vetores normalizados, as três medidas são intercambiáveis em ranking, e a escolha passa a ser de desempenho.

Os bancos vetoriais expõem as medidas com nomes e convenções próprios, e vale conhecer os que consultei. No pgvector (README consultado em 15 de outubro de 2026), os operadores são <-> para a distância L2 (a euclidiana), <#> para o produto interno negativo, <=> para a distância do cosseno, <+> para a distância L1 (a de Manhattan) e <~> e <%> para as distâncias de Hamming e de Jaccard em vetores binários. O produto interno vem negativo porque o Postgres só faz varreduras de índice em ordem crescente: a documentação manda multiplicar por -1 para recuperar o valor. A similaridade do cosseno se obtém com 1 - (embedding <=> consulta), e a classe de operadores do índice precisa combinar com o operador da consulta (vector_l2_ops, vector_ip_ops ou vector_cosine_ops). No Qdrant (páginas de coleções e de busca, consultadas na mesma data), as métricas se chamam Dot, Cosine, Euclid e Manhattan, e a documentação diz que a escolha depende de como os vetores foram obtidos e do método de treino do codificador. A página de busca diz, sobre o parâmetro score_threshold, que ele exclui resultados com score pior que o dado e que isso pode excluir scores menores ou maiores conforme a métrica: na euclidiana, scores maiores são considerados mais distantes e são os excluídos. Não li, na página consultada, o que o Qdrant devolve como score para o cosseno; confira antes de definir um limiar.

Três nomes aparecem de relance nessas listas, e cabem em uma frase cada. A distância de Manhattan (ou L1) soma o valor absoluto da diferença em cada posição, em vez de elevar ao quadrado (a distância de quem anda por quarteirões, sem cortar caminho). A de Hamming conta em quantas posições dois vetores de bits (a quantização binária) diferem. A de Jaccard compara dois conjuntos de bits pela proporção do que têm em comum. Todas existem porque alguns índices e alguns formatos de vetor funcionam melhor com elas, e a aula Busca vetorial mostra onde isso entra.

9. Ler o score

Um score de similaridade de 0,82 não quer dizer 82% de certeza, nem 82% de chance de o trecho responder à pergunta. É apenas o cosseno (ou o produto escalar, ou a distância) calculado para aquele par de vetores. Ele serve para ordenar: o trecho com 0,82 está mais perto da consulta que o trecho com 0,61. Dele não se pode concluir que o primeiro está certo. A escala também varia de modelo para modelo e, em alguns casos, de idioma para idioma, de modo que o mesmo número pode ser excelente num modelo e medíocre noutro.

Medi isso com dois modelos de 384 dimensões, com as mesmas cinco frases como trechos e a pergunta “Como cancelar a assinatura?”. No paraphrase-multilingual-MiniLM-L12-v2, os cossenos foram 0,51 para “Cancelamento de pedidos”, 0,44 para “Descontos na assinatura anual”, 0,40 para “Para encerrar seu plano, acesse Conta e escolha Encerrar”, 0,11 para o horário da loja e -0,03 para a receita de bolo. Com uma pergunta fora do assunto (“Qual a capital da Austrália?”), os scores dos cinco trechos ficaram entre 0,02 e 0,14. Com o intfloat/multilingual-e5-small (com os prefixos “query: ” e “passage: ” e vetores normalizados), a mesma pergunta de cancelamento rendeu de 0,775 a 0,873, e a pergunta sobre a Austrália, que não tem nada a ver com os trechos, rendeu de 0,709 a 0,769. O cartão do modelo explica isso: o cosseno se distribui entre 0,7 e 1,0 por causa da temperatura baixa (0,01) usada no treino por contraste, e o que importa é a ordem relativa, não o valor absoluto.

escalas diferentes
figura 11 · escalas diferentes

As duas réguas horizontais vão de 0,0 a 1,0. A de cima é o MiniLM e a de baixo é o E5. Os pontos verdes são os scores dos trechos que tratam do tema da pergunta de cancelamento, e os rosas são os de trechos de outros assuntos (somando as duas perguntas). No MiniLM, os verdes ficam em torno de 0,4 a 0,5, e os rosas, entre -0,03 e 0,14: há um vão grande no meio. No E5, os verdes ficam em 0,85 a 0,87, e os rosas, em 0,71 a 0,82, tudo comprimido na ponta direita. A linha tracejada rosa vertical é um limiar fixo de 0,30. No MiniLM ela separa os dois grupos. No E5, deixa passar tudo, inclusive a pergunta sobre a Austrália (o aviso rosa de baixo diz isso). O mesmo limiar, em dois modelos, faz coisas opostas.

Daí as regras de leitura. Compare scores só dentro da mesma consulta e do mesmo modelo. Não compare o 0,82 de uma consulta com o 0,82 de outra como se fossem a mesma qualidade. Não leve um limiar de um modelo para outro, nem de um idioma para outro sem testar.

Para usar os scores num sistema, há duas ferramentas. O top-k é pegar os k trechos de maior score (ou de menor distância) e descartar o resto, com k escolhido conforme a janela de contexto e a tolerância a ruído (a aula Prompt e contexto mostrou que contexto demais também prejudica). O limiar é um corte no score: só entra quem passa de um valor mínimo. Eles se combinam: pega-se o top-k e, entre eles, só os que passam do limiar. Há duas razões para não parar no top-1. Uma é que o melhor score nem sempre é o trecho certo: no exemplo acima, o trecho que de fato responde a “Como cancelar a assinatura?” ficou em terceiro lugar (0,40), atrás de “Cancelamento de pedidos” (0,51) e de “Descontos na assinatura anual” (0,44). Com k = 1 o sistema o perderia, e com k = 3 ele entra no prompt. A outra razão é que o Reranking precisa de candidatos para reordenar.

topk e limiar
figura 12 · topk e limiar

A figura percorre o caminho completo. A consulta laranja passa pelo modelo de embedding (o mesmo dos trechos) e vira um vetor. Do vetor saem cinco setas, com atraso crescente, uma para cada trecho do grupo “Pontuação de cada trecho, em ordem”, e cada trecho mostra o cosseno medido. Os três primeiros (verdes) seguem em setas grossas para o retângulo “top-k, com k = 3”. Os dois últimos (cinzas) vão para “Fora do top-k” com pacotes barrados pelo ✕ (o corte do k). O top-k segue para o losango “Melhor score ≥ 0,30?”. Com “sim”, a seta grossa leva os três trechos ao prompt, com a fonte, como recomenda Prompt e contexto. Com “não”, a seta rosa leva ao documento “Não sei”, cujo exemplo é uma pergunta que fiz ao mesmo modelo (“Posso devolver o produto?”, sem nenhum trecho sobre devolução), que teve o melhor score de 0,23.

Dois pontos de atenção sobre o limiar. O primeiro é que um limiar fixo é frágil: o 0,30 da figura foi escolhido olhando poucos exemplos de um modelo, e vale só para ele (a figura anterior mostrou isso). Por isso o erro 5 da seção 13 recomenda calibrar o limiar com perguntas e trechos do seu próprio acervo, e o exercício 7 propõe fazer uma versão pequena. O segundo é o caso de empate: dois trechos com o mesmo score (por exemplo, um documento duplicado) ficam na mesma posição, e a ordem entre eles costuma ser arbitrária, então não construa nada que dependa de qual vem primeiro. Remover duplicatas antes de indexar evita o problema (a aula Ingestão de documentos trata disso).

O ramo “não” do losango leva à recusa. Quando nenhum trecho passa do limiar, a resposta honesta do sistema é “não encontrei isso no material”, e o prompt precisa estar preparado para isso (Prompt e contexto mostra como instruir o modelo a dizer que não sabe em vez de inventar). Enviar ao LLM os três “melhores” trechos de uma busca em que o melhor tem 0,23 é entregar-lhe material irrelevante com cara de relevante, e o modelo tende a tentar usá-lo. Em Agents, o mesmo score pode virar uma decisão: com scores baixos, o agente reformula a pergunta e busca de novo, em vez de responder. E, se a busca for uma ferramenta exposta por um servidor MCP, vale que ela devolva o score junto do texto e da fonte, para que o agente (ou você, depois) possa avaliar o quanto confiar no resultado. Isso é um padrão de projeto, e não algo que um produto específico faça por você.

10. Muitas dimensões: tudo fica quase à mesma distância

Há um efeito conhecido de dados com muitas dimensões que atrapalha a intuição de quem pensa em 2 ou 3: as distâncias entre pontos tendem a se concentrar. Em poucas dimensões, o ponto mais próximo de uma consulta pode estar bem mais perto que o mais distante. À medida que as dimensões crescem, a razão entre o mais distante e o mais perto vai se aproximando de 1, e “o vizinho mais próximo” e “um ponto qualquer” passam a diferir só um pouco. A razão, pelo que sei do conhecimento geral da área (sem fonte consultada hoje), é que o quadrado da distância é uma soma de uma parcela por dimensão. Com poucas parcelas, uma parcela grande ou pequena decide o total. Com milhares, as parcelas se compensam, e os totais de pontos diferentes ficam todos perto de “dimensões × parcela típica”.

Medi isso num experimento pequeno. Para cada número de dimensões, gerei 1.000 pontos com números aleatórios (distribuição normal, semente 42 do numpy) e uma consulta também aleatória, e calculei a distância euclidiana da consulta a cada ponto e o desvio-padrão dos cossenos.

import numpy as np

rng = np.random.default_rng(42)
N = 1000
for d in [2, 10, 100, 1000, 10000]:
    X = rng.standard_normal((N, d))
    q = rng.standard_normal(d)
    dist = np.linalg.norm(X - q, axis=1)
    cos = X @ q / (np.linalg.norm(X, axis=1) * np.linalg.norm(q))
    print(d, dist.min(), dist.max(), dist.max() / dist.min(), cos.std())

Os resultados que obtive (arredondados) estão na tabela.

DimensõesMais pertoMais longeMais longe ÷ mais pertoDesvio-padrão dos cossenos
20,034,34132,690,717
101,866,943,720,309
10010,6716,641,560,100
1.00041,2447,371,150,031
10.000137,41144,281,050,010
concentracao
figura 13 · concentracao

Cada linha da figura é um número de dimensões, e a escala é a do próprio mais longe (por isso o ponto rosa sempre fica na ponta direita). O ponto verde é o mais perto, e o pontilhado entre os dois mostra a folga. Com 2 dimensões, o mais perto está quase colado na origem da escala (0,03 contra 4,34). Com 10, fica a cerca de um quarto do caminho. Com 100, cerca de dois terços. Com 1.000 e com 10.000, os dois pontos quase se tocam, e o retângulo azul de cada linha traz a razão: de 132,69 em 2 dimensões para 1,05 em 10.000, isto é, o ponto mais longe está só 5% mais distante que o mais perto.

Duas ressalvas para ler o experimento direito. A primeira: os pontos eram aleatórios, o que é o pior caso do efeito. Embeddings reais têm estrutura (textos sobre o mesmo assunto se agrupam), e não é por acaso que as similaridades reais da seção 9 ainda separam bem: com 384 dimensões e o MiniLM, medi cossenos de 0,40 a 0,51 para trechos do tema e de -0,03 a 0,14 para os outros. O efeito existe, mas a estrutura dos dados dá folga. A segunda: o cosseno não escapa do fenômeno. A última coluna da tabela mostra que o desvio-padrão dos cossenos também encolhe, de 0,717 para 0,010. O que a normalização e o cosseno resolvem é outro problema, o do tamanho (seções 6 e 7), e o que o encolhimento causa é que diferenças pequenas de score passam a decidir o ranking, então o ruído pesa mais.

Isso importa para a busca em grandes acervos, por um motivo simples: a busca aproximada tenta evitar comparar a consulta com todos os vetores e se contenta com vizinhos “bons o bastante”. Quando as distâncias são todas parecidas, a diferença entre o vizinho exato e um vizinho aproximado fica pequena em números absolutos, o que ajuda a aproximar, mas também significa que a ordem fina entre os melhores candidatos é mais sensível a ruído. A aula Busca vetorial mostra como os índices lidam com isso.

11. O custo de comparar com todos

Calcular a similaridade entre uma consulta e um trecho de 384 dimensões custa 384 multiplicações (mais as somas). Com N trechos, o custo é N × dimensões multiplicações por consulta. Para 10 mil trechos, são 3,84 milhões de multiplicações. Para um milhão, 384 milhões. É a “busca exata” ou “força bruta”: compara-se a consulta com todos, e se pega o top-k.

Medi o tempo disso em um MacBook com chip M1 Max, com numpy e vetores float32 de 384 dimensões (aleatórios, só para medir), com uma consulta por vez, multiplicando a matriz de vetores pelo vetor da consulta:

import numpy as np, time

rng = np.random.default_rng(0)
for N in (10_000, 100_000, 1_000_000):
    X = rng.standard_normal((N, 384), dtype=np.float32)
    q = rng.standard_normal(384, dtype=np.float32)
    X @ q                                    # aquecimento
    t = time.perf_counter()
    for _ in range(5):
        scores = X @ q                       # N × 384 multiplicações
    ms = (time.perf_counter() - t) / 5 * 1000
    t = time.perf_counter()
    np.argpartition(-scores, 10)[:10]        # os 10 maiores, sem ordenar tudo
    ms_top = (time.perf_counter() - t) * 1000
    print(N, round(ms, 1), "ms; top-10:", round(ms_top, 1), "ms;", X.nbytes / 1e6, "MB")

Em duas execuções do código acima, a multiplicação (média de 5 repetições depois de um aquecimento) levou 0,2 a 0,3 ms para 10 mil vetores, 3,9 ms para 100 mil e 39,6 a 41,3 ms para 1 milhão, que ocupam 1,536 GB de memória. Escolher os 10 maiores do 1 milhão com argpartition levou 5,3 a 5,4 ms. A primeira chamada, sem aquecimento, é mais lenta: numa versão curta do mesmo teste, a multiplicação mais a seleção levaram cerca de 0,1 s para 1 milhão de vetores. O custo cresce de forma linear: 10 vezes mais vetores, cerca de 10 vezes mais tempo. Um milhão de trechos, numa máquina moderna, ainda cabe numa busca exata (e os números dependem do hardware, do tipo de número e da biblioteca; os meus são uma ilustração de ordem de grandeza).

custo e pergunta
figura 14 · custo e pergunta

À esquerda, a consulta laranja, com o custo escrito: N × 384 multiplicações. As quatro setas, na cor laranja da consulta, vão para acervos crescentes, e a espessura e a velocidade dos pacotes mudam junto: 10 mil e 100 mil vetores em verde (0,3 ms e 3,9 ms medidos), 1 milhão em azul (cerca de 41 ms medidos) e 100 milhões em rosa, em estado degradado. Os números do último retângulo são contas minhas e ilustrativas: 100 milhões × 384 = 38,4 bilhões de multiplicações, que, extrapolando linearmente os 41 ms de 1 milhão, dariam cerca de 4 segundos por consulta, e os vetores ocupariam 100.000.000 × 384 × 4 bytes = 153,6 GB, bem acima da memória de uma máquina comum. A seta rosa leva ao losango que pulsa, “E quando N é milhões?”, e a seta pontilhada ao tema de Busca vetorial.

A pergunta se desdobra em duas. Uma é o tamanho do acervo: com centenas de milhões de vetores, a varredura completa deixa de caber na memória e no orçamento de tempo. A outra é o volume de consultas: mesmo com 1 milhão de vetores, mil consultas por segundo exigiriam quarenta segundos de cálculo a cada segundo de relógio, numa máquina só. Ambas as perguntas levam à mesma ideia, a de evitar comparar a consulta com todo mundo, e é o assunto da aula Busca vetorial. O preço dessa ideia é aceitar vizinhos aproximados, e a seção 10 já deu uma pista de por que isso é possível.

12. Em código

O código a seguir calcula as três medidas à mão com numpy, escolhe o top-3 de cada uma e confere o resultado com a função similarity do sentence-transformers. Foi executado no ambiente descrito nas fontes (sentence-transformers 6.1.0, numpy 2.5.4), com o modelo multilíngue pequeno de Embeddings.

import numpy as np
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")

consulta = "Quero encerrar meu plano."
frases = [
    "Como cancelar a assinatura?",
    "Para encerrar seu plano, acesse Conta e escolha Encerrar.",
    "Misture a farinha com os ovos.",
    "Refogue a cebola no azeite.",
    "A taxa de juros subiu.",
    "O fundo rendeu mais que a poupança.",
    "O laço for percorre o array.",
    "Qual o horário de funcionamento da loja?",
]

q = model.encode(consulta)
D = model.encode(frases)
print(D.shape, "normas:", np.round(np.linalg.norm(D, axis=1), 2))

euclid = np.linalg.norm(D - q, axis=1)
dot = D @ q
cos = dot / (np.linalg.norm(D, axis=1) * np.linalg.norm(q))

def top(score, k=3, menor=False):
    ordem = np.argsort(score if menor else -score)[:k]
    return [(int(i), round(float(score[i]), 3)) for i in ordem]

print("euclidiana (menor):", top(euclid, menor=True))
print("produto escalar  :", top(dot))
print("cosseno          :", top(cos))

# conferindo com a biblioteca
print("model.similarity :", np.round(model.similarity(q, D).numpy()[0], 3))

# normalizando
qn = q / np.linalg.norm(q)
Dn = D / np.linalg.norm(D, axis=1, keepdims=True)
dot_n = Dn @ qn
print("dot normalizado == cosseno:", np.allclose(dot_n, cos))
euc_n = np.linalg.norm(Dn - qn, axis=1)
print("eucl^2 == 2-2cos:", np.allclose(euc_n**2, 2 - 2*cos, atol=1e-5))
print("mesmo ranking:", list(np.argsort(-cos)) == list(np.argsort(euc_n)) == list(np.argsort(-dot_n)))
print("todos os scores cos:", np.round(cos, 3))

A saída que obtive, em ordem. Os tamanhos dos oito vetores foram [4.55 3.63 5.57 3.76 4.65 3.67 3.61 4.85], nada perto de 1, como a seção 7 avisou. O top-3 da euclidiana (menor ganha) foi o trecho 1 (distância 2,718, “Para encerrar seu plano…”), o 0 (4,516, “Como cancelar a assinatura?”) e o 5 (5,357, “O fundo rendeu mais que a poupança.”). O do produto escalar foi o 1 (11,786), o 0 (9,04) e o 7 (3,838, “Qual o horário de funcionamento da loja?”). O do cosseno foi o 1 (0,77), o 0 (0,471) e o 7 (0,188). A linha de model.similarity bateu com o cosseno que calculei à mão: [0.471 0.77 0.016 0.078 -0.131 0.083 0.049 0.188]. Depois de normalizar, as três verificações deram True: o produto escalar dos normalizados é igual ao cosseno, a distância ao quadrado é igual a 2 - 2 × cosseno (tolerância de 1e-5, por causa dos números de ponto flutuante) e os três rankings coincidem.

A comparação mostra três coisas. Os dois primeiros lugares são os mesmos nas três medidas: o trecho sobre encerrar o plano e o sobre cancelar a assinatura, que são mesmo os dois relevantes. O terceiro lugar muda: a euclidiana escolhe a frase sobre fundos de investimento, e as outras duas escolhem a do horário da loja. Nenhuma das duas é relevante, e os valores de cosseno (0,188 e 0,083) mostram que são trechos fracos, mas a divergência é um exemplo real, em vetores com tamanhos entre 3,6 e 5,6, de medidas que concordam no topo e discordam na cauda. Por fim, os scores do produto escalar (até 11,8) e do cosseno (até 0,77) estão em escalas completamente diferentes, e só a ordem é comparável.

A função similarity do sentence-transformers aceita trocar a medida, que a documentação chama de similarity_fn_name. Testei com a mesma consulta e duas frases (a primeira e a terceira da lista, índices 0 e 2):

for nome in ["cosine", "dot", "euclidean", "manhattan"]:
    model.similarity_fn_name = nome
    print(nome, np.round(model.similarity(q, D[[0, 2]]).numpy()[0], 3))

O padrão desse modelo foi cosine, e as saídas foram [0.471 0.016] para o cosseno, [9.04 0.388] para o produto escalar, [-4.516 -6.932] para a euclidiana e [-68.561 -106.699] para a de Manhattan. As duas últimas vêm com sinal negativo porque a biblioteca devolve o negativo da distância, para que “maior é melhor” valha para as quatro (a documentação lista “Negative Euclidean Distance” e “Negative Manhattan Distance”). Essa convenção é útil e perigosa ao mesmo tempo: o número que sai é sempre “maior é melhor”, mas não é a distância que você mediria à mão.

Parte 5: Fechamento e prática

13. Erros comuns

O primeiro erro é comparar vetores de modelos diferentes, ou de versões diferentes. Como visto em Embeddings, as coordenadas de um modelo não significam nada para outro, e que o código roda sem reclamar quando os tamanhos coincidem. A correção é um modelo por índice, com o nome e a versão guardados junto de cada vetor.

O segundo é usar a métrica errada. Se o modelo foi treinado para o cosseno e você usa produto escalar com vetores não normalizados, os tamanhos entram no ranking e trechos de vetor longo ganham vantagem que não vem do assunto. Nada trava, e o ranking só piora. A correção é ler a documentação do modelo, escolher a métrica recomendada e testar com perguntas reais.

O terceiro é esquecer de normalizar quando a sua conta supõe vetores de tamanho 1. Se você usa o produto escalar como se fosse o cosseno, e os vetores não estão normalizados (como os do MiniLM da seção 12, que medem entre 3,6 e 5,6), o resultado fica distorcido. A correção é conferir np.linalg.norm em um vetor, e normalizar na saída (normalize_embeddings=True) ou usar o cosseno.

O quarto é interpretar o score como probabilidade ou como porcentagem de acerto. Um 0,82 não é 82% de nada, e a seção 9 mostrou o mesmo valor com significados opostos em dois modelos. A correção é tratar o score como ordenação, e só dar significado absoluto a ele depois de calibrá-lo com dados seus.

O quinto é fixar um limiar sem testar nos seus dados. Um limiar que funciona numa demonstração com cinco trechos pode cortar respostas boas ou deixar passar lixo num acervo real, e não vale de um modelo para outro. A correção é montar um conjunto pequeno de perguntas com e sem resposta no acervo, olhar os scores do melhor trecho nos dois casos e escolher o corte com base nisso (exercício 7), revisando quando trocar de modelo.

O sexto é usar distância onde o banco espera similaridade, ou o contrário, e com isso inverter o ranking. Se você ordena em ordem decrescente uma coluna de distâncias, o pior resultado vem primeiro. No pgvector, <=> devolve distância do cosseno (menor é melhor), e <#> devolve o produto interno negativo (menor também é melhor, por isso o sinal), então ORDER BY ... ASC é o certo. O mesmo vale para score_threshold no Qdrant, que depende da métrica, como a seção 8 citou. A correção é conferir, antes de ordenar ou de aplicar um limiar, o que o banco devolve e para que lado “melhor” aponta.

14. Exercícios

Os exercícios 1 a 3 são contas à mão. Os demais rodam em CPU, sem chave de API, num ambiente com numpy e sentence-transformers (o primeiro uso de um modelo baixa os pesos).

  1. Faça as três medidas à mão para A = (1, 2, 2) e B = (2, 0, 1). Calcule a distância euclidiana, o produto escalar, os dois tamanhos e o cosseno. Confira com numpy. (Resposta que obtive: distância 2,449, produto escalar 4, tamanhos 3 e 2,236, cosseno 0,596.)

  2. Normalize C = (6, 8) e depois D = (3, 4) e mostre, com contas, que os dois resultados são iguais e têm tamanho 1. Em seguida, para E = (0, 3, 4) e F = (0, 6, 8), calcule a distância, o produto escalar e o cosseno e explique por que o cosseno dá 1 e a distância não dá 0. (Resposta que obtive: cosseno 1,0, distância 5,0, produto escalar 50.)

  3. Verifique a relação distância² = 2 - 2 × cosseno para dois vetores normalizados que você escolher (por exemplo, (1, 0) e (0,6, 0,8)). Depois tente com um vetor não normalizado e veja a igualdade quebrar.

  4. Implemente as três medidas em numpy, sem usar funções prontas de distância, e um top_k que devolva os k melhores índices. Rode com 10 frases suas em português, usando o modelo da seção 12, e compare os rankings das três medidas com e sem normalize_embeddings=True. Anote em que posições eles discordam.

  5. Teste o efeito de dobrar um vetor com embeddings reais. Gere os vetores de “Quero encerrar meu plano.” e “Como cancelar a assinatura?” e multiplique o segundo por 2. Calcule as três medidas antes e depois. (Resultado que obtive com o MiniLM: distância de 4,516 para 8,019, produto escalar de 9,04 para 18,081, cosseno 0,471 nos dois casos.) Depois repita com normalize_embeddings=True e explique o que mudou.

  6. Meça a concentração de distâncias, repetindo o experimento da seção 10 com outras dimensões (3, 30, 300, 3.000) e outra semente. Faça um gráfico simples (ou uma tabela) de “mais longe ÷ mais perto” contra as dimensões e verifique se a razão cai como na tabela. Como extensão, repita com 1.000 embeddings reais de frases (podem ser variações de frases suas) e compare a razão com a dos pontos aleatórios de 384 dimensões.

  7. Ache um limiar razoável num conjunto pequeno. Escreva 8 trechos curtos sobre um assunto (por exemplo, atendimento de uma loja) e 10 perguntas, 6 delas com resposta nos trechos e 4 sem. Para cada pergunta, calcule o cosseno do melhor trecho. Ordene os 10 melhores scores marcando “tem resposta” ou “não tem” e escolha um limiar que separe os dois grupos o melhor possível. Anote quantos erros restam, e repita com o intfloat/multilingual-e5-small (com os prefixos) para ver o limiar mudar de lugar.

15. Glossário

TermoSignificado
VetorLista de números, vista como uma seta que sai da origem, com tamanho e direção
OrigemO ponto onde todas as coordenadas valem zero
Tamanho (norma)O comprimento da seta: a raiz da soma dos quadrados dos números
Distância euclidianaA distância em linha reta entre dois pontos; menor é mais perto
Produto escalar (dot product)Multiplicar posição a posição e somar; cresce com o alinhamento e com o tamanho
Similaridade do cossenoO produto escalar dividido pelo produto dos tamanhos; só o ângulo, de -1 a 1
Distância do cosseno1 - cosseno; 0 para a mesma direção, 2 para direções opostas
NormalizarDividir o vetor pelo seu tamanho, para que ele fique com tamanho 1
Círculo unitárioO conjunto dos pontos a distância 1 da origem, onde moram os vetores normalizados
ScoreO número que uma medida devolve para um par; serve para ordenar, e não é probabilidade
Top-kOs k resultados de melhor score
LimiarScore mínimo para um resultado entrar
Concentração de distânciasEm muitas dimensões, as distâncias de uma consulta a vários pontos ficam quase iguais
Busca exata (força bruta)Comparar a consulta com todos os vetores
Manhattan (L1)Soma das diferenças absolutas, posição a posição
HammingNúmero de posições diferentes entre dois vetores de bits

Fontes

Consultadas em 15 de outubro de 2026.

  • pgvector (README): operadores <-> (L2), <#> (produto interno negativo), <=> (distância do cosseno), <+> (L1), <~> (Hamming) e <%> (Jaccard); 1 - distância para a similaridade do cosseno; classes de operadores vector_l2_ops, vector_ip_ops e vector_cosine_ops; conselho de usar produto interno com vetores de tamanho 1.
  • Qdrant: coleções: métricas Dot, Cosine, Euclid e Manhattan; a escolha depende de como os vetores foram obtidos e do treino do codificador; cosseno implementado como produto escalar sobre vetores normalizados, que são normalizados automaticamente no envio.
  • Qdrant: busca: score_threshold exclui resultados piores que o valor dado, conforme a métrica (na euclidiana, scores maiores são mais distantes e são os excluídos). Li só os primeiros 100.000 caracteres da página.
  • OpenAI: embeddings (FAQ sobre a função de distância): recomenda o cosseno, diz que a escolha tipicamente não importa muito, que os embeddings têm tamanho 1, que o cosseno pode ser calculado como produto escalar e que cosseno e euclidiana dão rankings idênticos.
  • Voyage AI: FAQ: os embeddings podem ser usados com produto escalar, cosseno ou euclidiana; têm tamanho 1, e por isso cosseno equivale ao produto escalar (mais rápido) e cosseno e euclidiana dão o mesmo ranking.
  • Sentence Transformers: similaridade textual semântica: model.similarity, similarity_fn_name, as funções cosine (padrão), dot, euclidean e manhattan (as duas últimas em versão negativa) e o conselho de usar dot quando o modelo termina com Normalize.
  • Sentence Transformers: modelos pré-treinados: modelos ajustados para produto escalar (multi-qa-*-dot-v1) e modelos de vetores normalizados (multi-qa-*-cos-v1). Li só os primeiros 100.000 dos cerca de 101.900 caracteres da página.
  • Cartão do modelo intfloat/multilingual-e5-small: prefixos “query: ” e “passage: ” (sem eles há queda de desempenho), exemplo que normaliza os vetores e usa produto escalar, e a explicação de que os cossenos se concentram entre 0,7 e 1,0 por causa da temperatura de 0,01 e de que importa a ordem relativa.
  • Embeddings, Prompt e contexto e LLMs: as frases-exemplo, a janela de contexto e o tratamento de “não sei”.

Medido por mim (ambiente descartável no scratchpad da sessão, Python 3.12, sentence-transformers 6.1.0 e numpy 2.5.4, nada instalado fora dele nesta aula, com os modelos já em cache): todas as similaridades, normas, rankings e verificações de normalização da seção 12, os scores do MiniLM e do E5 da seção 9, o efeito de dobrar um vetor (exercício 5), a tabela de concentração da seção 10 (pontos gaussianos, semente 42) e os tempos da seção 11 (M1 Max, float32, 384 dimensões). Os trechos de código das seções 10, 11 e 12 foram executados e as saídas descritas são as que obtive. Valores de similaridade são específicos de modelo e de versão.

Sem execução ou sem fonte: não li, na documentação do Qdrant consultada, o que o Cosine devolve como score; não vi nas páginas lidas que o pgvector normalize vetores sozinho (disse só o que li); não consultei os guias do Gemini e do Cohere para esta aula. A frase sobre o tamanho do vetor como sinal em modelos treinados para produto escalar, e a de que dados reais têm menos concentração que dados aleatórios, são conhecimento geral meu, sem fonte consultada hoje. O cartão do E5 mostra o uso de cosseno e de produto escalar normalizado, mas não diz em termos explícitos com qual métrica o modelo foi treinado. Os números de 100 milhões de vetores (4 s e 153,6 GB) são contas ilustrativas, não medições. Os vetores e os pontos das figuras 2 a 9 são exemplos de contas à mão. Os 14 diagramas foram renderizados e revisados.

Relacionados

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?