Como usar embeddings para deduplicação semântica de conteúdo
1. Fundamentos da Deduplicação Semântica com Embeddings
A deduplicação semântica é o processo de identificar e remover conteúdo que transmite o mesmo significado, mesmo quando expresso com palavras diferentes. Métodos tradicionais baseados em hash (como SHA-256) ou similaridade exata de strings falham porque consideram "gato comeu o rato" e "o felino devorou o roedor" como textos completamente distintos, quando semanticamente são equivalentes.
Embeddings vetoriais resolvem esse problema ao mapear textos para vetores numéricos em um espaço multidimensional onde a proximidade geométrica reflete similaridade semântica. Diferentemente de abordagens léxicas como TF-IDF ou similaridade de Jaccard (que contam palavras em comum), embeddings capturam relações contextuais, sinônimos e paráfrases.
A métrica mais comum para comparar embeddings é a similaridade por cosseno, que mede o ângulo entre dois vetores. Valores próximos de 1 indicam alta similaridade semântica, enquanto valores próximos de 0 ou negativos indicam baixa ou nenhuma relação.
2. Escolhendo o Modelo de Embedding Ideal para o Domínio
Para o contexto de "Temas — Lista Final (1200 temas)", onde trabalhamos com categorias hierárquicas de conteúdo, a escolha do modelo de embedding é crítica. Modelos da família sentence-transformers oferecem um excelente equilíbrio entre desempenho e custo computacional.
Modelos recomendados para deduplicação semântica:
- all-MiniLM-L6-v2: 384 dimensões, rápido, ideal para inglês
- paraphrase-multilingual-MiniLM-L12-v2: suporte a 50+ idiomas
- OpenAI text-embedding-3-small: 1536 dimensões, alta acurácia
- Cohere embed-multilingual-v3: otimizado para recuperação semântica
O trade-off principal envolve dimensionalidade versus velocidade. Modelos com 384 dimensões processam 10x mais rápido que modelos com 1536 dimensões, mas podem perder nuances em domínios especializados. Para "Temas — Lista Final (1200 temas)", recomenda-se começar com all-MiniLM-L6-v2 e validar com amostras do seu corpus.
O pré-processamento deve incluir:
- Tokenização preservando pontuação relevante (vírgulas, ponto final)
- Normalização de maiúsculas/minúsculas
- Remoção de HTML tags e caracteres especiais sem perder contexto semântico
3. Pipeline de Geração de Embeddings para Conteúdo Textual
O pipeline começa com a extração e chunking inteligente do conteúdo. Para artigos longos, divida em parágrafos ou seções de 256-512 tokens, mantendo contexto suficiente para capturar o tema central.
Exemplo de estrutura de dados para conteúdo com embedding:
{
"id": "tema_0452",
"titulo": "Inteligência Artificial na Educação",
"conteudo": "A IA está transformando o aprendizado personalizado...",
"embedding": [0.0234, -0.1567, ..., 0.0891], # vetor 384-dim
"metadata": {
"fonte": "artigo_academico",
"data": "2024-03-15",
"categoria": "tecnologia_educacional"
}
}
Para processamento em lote, utilize batch processing:
def gerar_embeddings_lote(textos, modelo, batch_size=32):
embeddings = []
for i in range(0, len(textos), batch_size):
batch = textos[i:i+batch_size]
batch_emb = modelo.encode(batch, show_progress_bar=True)
embeddings.extend(batch_emb)
return np.array(embeddings)
O armazenamento pode ser feito em bancos vetoriais como FAISS (para busca em memória) ou Chroma (para persistência em disco). Para datasets menores (<100k itens), arrays NumPy em memória são suficientes.
4. Métricas de Similaridade e Thresholds de Deduplicação
A similaridade por cosseno é a métrica padrão para embeddings normalizados, pois é invariante à escala dos vetores. A distância euclidiana pode ser usada quando a magnitude do embedding carrega informação semântica relevante.
def similaridade_cosseno(vetor_a, vetor_b):
produto_interno = np.dot(vetor_a, vetor_b)
norma_a = np.linalg.norm(vetor_a)
norma_b = np.linalg.norm(vetor_b)
return produto_interno / (norma_a * norma_b)
Para definir thresholds dinâmicos, analise a distribuição das similaridades no dataset:
# Calcula matriz de similaridade e encontra threshold ótimo
similaridades = cosine_similarity(matriz_embeddings)
threshold = np.percentile(similaridades[similaridades < 1], 95)
# threshold típico: 0.85-0.92 para deduplicação semântica
5. Algoritmos de Agrupamento para Deduplicação em Escala
Para grandes volumes, uma abordagem two-pass é eficiente:
- Passo rápido: LSH (Locality-Sensitive Hashing) reduz o espaço de busca, agrupando embeddings em buckets hash
- Passo fino: Similaridade por cosseno dentro de cada bucket para identificar duplicatas exatas
from sklearn.cluster import HDBSCAN
def deduplicar_hdbscan(embeddings, min_cluster_size=2):
clusterer = HDBSCAN(min_cluster_size=min_cluster_size, metric='euclidean')
labels = clusterer.fit_predict(embeddings)
return labels # -1 indica outlier (conteúdo único)
HDBSCAN é particularmente útil porque não requer especificar o número de clusters e identifica automaticamente outliers, evitando falsos positivos em conteúdos similares mas distintos.
6. Implementação Prática com Exemplos de Código
Abaixo, um pipeline completo de deduplicação semântica:
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
# Carrega modelo
modelo = SentenceTransformer('all-MiniLM-L6-v2')
# Conteúdos de exemplo (1200 temas)
conteudos = [
"Inteligência Artificial na medicina diagnóstica",
"IA aplicada ao diagnóstico médico",
"Machine learning para diagnósticos clínicos",
"Energia solar fotovoltaica residencial",
"Painéis solares para casas"
]
# Gera embeddings
embeddings = modelo.encode(conteudos)
# Matriz de similaridade
matriz_sim = cosine_similarity(embeddings)
# Encontra pares duplicados (threshold 0.85)
threshold = 0.85
duplicatas = []
for i in range(len(conteudos)):
for j in range(i+1, len(conteudos)):
if matriz_sim[i][j] > threshold:
duplicatas.append((i, j, matriz_sim[i][j]))
# Remove duplicatas preservando o melhor representante
indices_manter = set(range(len(conteudos)))
for i, j, sim in duplicatas:
# Mantém o conteúdo mais longo (assumindo mais informativo)
if len(conteudos[i]) >= len(conteudos[j]):
indices_manter.discard(j)
else:
indices_manter.discard(i)
conteudos_deduplicados = [conteudos[i] for i in sorted(indices_manter)]
print(f"Removidas {len(conteudos) - len(conteudos_deduplicados)} duplicatas")
7. Métricas de Avaliação e Otimização do Sistema
Para validar o sistema, crie um dataset anotado com pares de duplicatas semânticas e não-duplicatas:
# Exemplo de métricas
from sklearn.metrics import precision_score, recall_score, f1_score
y_true = [1, 1, 0, 0, 1] # 1 = duplicata, 0 = não duplicata
y_pred = [1, 0, 0, 1, 1] # predições do modelo
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)
print(f"Precisão: {precision:.2f}, Recall: {recall:.2f}, F1: {f1:.2f}")
Ajuste fino do threshold pode ser feito usando curva ROC para maximizar o F1-score. Monitore também o drift semântico: se a distribuição dos embeddings mudar significativamente ao longo do tempo, pode ser necessário re-treinar ou trocar o modelo.
8. Considerações de Performance e Casos Reais
Para grandes volumes (1M+ itens), utilize indexação vetorial com HNSW (Hierarchical Navigable Small World) e quantização escalar para reduzir memória:
import faiss
dim = 384 # dimensionalidade do embedding
index = faiss.IndexHNSWFlat(dim, 32) # 32 conexões por nó
index.hnsw.efConstruction = 200
index.add(embeddings_np)
# Busca os 10 vizinhos mais próximos
distancias, indices = index.search(query_embedding, k=10)
Para conteúdo multilingue, modelos como paraphrase-multilingual-MiniLM-L12-v2 alinham embeddings de diferentes idiomas no mesmo espaço vetorial. Após a deduplicação, implemente estratégias de merge que preservem o histórico de versões, mantendo metadados como data de criação, fonte e autor do conteúdo original.
A deduplicação semântica com embeddings não é apenas uma técnica de limpeza de dados — é uma estratégia fundamental para manter a qualidade e relevância em bases de conhecimento como "Temas — Lista Final (1200 temas)", garantindo que cada tema único seja representado sem redundância semântica.
Referências
- Sentence-Transformers Documentation — Documentação oficial da biblioteca sentence-transformers com modelos pré-treinados para embeddings semânticos
- FAISS: A Library for Efficient Similarity Search — Repositório oficial do Facebook Research com implementações de indexação vetorial em larga escala
- OpenAI Embeddings Guide — Guia oficial da OpenAI sobre como usar embeddings para similaridade semântica e deduplicação
- HDBSCAN Clustering Documentation — Documentação completa do algoritmo HDBSCAN para agrupamento hierárquico baseado em densidade
- Chroma Vector Database — Banco vetorial open-source para armazenamento e busca de embeddings com suporte a metadados
- Locality-Sensitive Hashing for Similarity Search — Tutorial da Pinecone sobre LSH para aproximação de busca de similaridade em alta dimensão