中文翻译暂不可用,显示俄语原文。
Как вы дедуплицируете документы перед индексацией в RAG?
Краткий тезис
Дедупликация документов — это этап очистки данных перед индексацией, который удаляет или помечает дубликаты (точные и семантические). В RAG дубликаты могут искажать retrieval (много одинаковых чанков снижают разнообразие контекста) и увеличивать размер индекса. Используются четыре подхода: точное хэширование (SHA-256) для exact duplicates, MinHash + LSH для near-duplicates, векторная кластеризация на основе эмбеддингов и LLM-проверка для семантических рерайтов. Лучшая практика — не удалять дубликаты полностью, а помечать их в метаданных, чтобы RAG-система могла учитывать множественные источники.
1. Термин: Дедупликация документов
Дедупликация (deduplication) — процесс поиска и устранения повторяющихся или почти повторяющихся документов в корпусе. В контексте RAG дедупликация выполняется перед этапом chunking и индексации, чтобы:
- Уменьшить размер векторной базы данных (экономия памяти и ускорение поиска).
- Избежать «зашумления» контекста: если LLM получает несколько одинаковых чанков, она может повторять одну и ту же информацию, упуская другие релевантные фрагменты.
- Повысить качество retrieval: дубликаты могут искусственно завышать метрики (например, recall@k), но не добавляют новой информации.
Важно различать точные дубликаты (exact duplicates) — документы, идентичные по содержанию, и почти дубликаты (near-duplicates) — документы, которые отличаются незначительно (например, перефразирование, разные форматы, опечатки).
2. Точные дубликаты (Exact duplicates)
Самый простой и быстрый метод — вычислить криптографический хэш содержимого документа (например, SHA-256) и сравнить хэши. Если хэши совпадают — документы идентичны.
Почему SHA-256
- Детерминированность: одинаковый текст → одинаковый хэш.
- Высокая скорость: хэширование даже больших документов занимает микросекунды.
- Коллизионная стойкость: вероятность случайного совпадения хэшей разных документов пренебрежимо мала.
Пример реализации на Python
import hashlib
def compute_hash(text: str) -> str:
return hashlib.sha256(text.encode('utf-8')).hexdigest()
def deduplicate_exact(documents: list[dict]) -> list[dict]:
seen_hashes = set()
unique_docs = []
for doc in documents:
doc_hash = compute_hash(doc['content'])
if doc_hash not in seen_hashes:
seen_hashes.add(doc_hash)
unique_docs.append(doc)
return unique_docs
Ограничения
- Не обнаруживает документы, отличающиеся пробелами, знаками препинания или регистром (можно предварительно нормализовать текст).
- Не работает для перефразированных версий.
3. Почти дубликаты (Near-duplicates) с MinHash + LSH
Для обнаружения документов, которые не идентичны, но очень похожи (например, одна новость с небольшими изменениями), используется MinHash (Minwise Hashing) в сочетании с LSH (Locality Sensitive Hashing).
3.1 Jaccard similarity
Мера сходства между двумя множествами (например, множествами шинглов — n-грамм слов или символов):
[ J(A, B) = \frac{|A \cap B|}{|A \cup B|} ]
Для текстов шинглы — это последовательности из k слов (обычно k=3..5). Два документа считаются near-duplicates, если Jaccard similarity превышает порог (например, 0.8).
3.2 MinHash
MinHash — это техника, которая позволяет оценить Jaccard similarity без попарного сравнения всех шинглов. Идея:
- Представить каждый документ как множество шинглов.
- Применить h хэш-функций к каждому шинглу и для каждой функции записать минимальное хэш-значение. Получится сигнатура документа из h чисел.
- Вероятность того, что сигнатуры двух документов совпадут по позиции, равна Jaccard similarity.
3.3 LSH (Locality Sensitive Hashing)
LSH группирует похожие сигнатуры в «бакеты» (buckets). Для этого сигнатура разбивается на b полос (bands) по r строк в каждой. Если хотя бы в одной полосе сигнатуры двух документов полностью совпадают, они помещаются в один бакет и считаются кандидатами на near-duplicate.
Параметры
- Порог Jaccard similarity (обычно 0.7–0.9).
- Количество полос b и строк r: чем больше b, тем выше чувствительность, но больше ложных срабатываний.
Пример с библиотекой datasketch
from datasketch import MinHash, MinHashLSH
def create_minhash(text: str, num_perm=128):
m = MinHash(num_perm=num_perm)
for shingle in [text[i:i+3] for i in range(len(text)-2)]: # 3-граммы символов
m.update(shingle.encode('utf-8'))
return m
lsh = MinHashLSH(threshold=0.8, num_perm=128)
for i, doc in enumerate(documents):
m = create_minhash(doc['content'])
lsh.insert(f"doc_{i}", m)
# Поиск дубликатов для нового документа
candidates = lsh.query(create_minhash(new_doc))
Преимущества
- Масштабируемость: работает для миллионов документов.
- Не требует попарного сравнения.
Недостатки
- Чувствителен к выбору шинглов и порога.
- Может пропускать семантически похожие, но лексически разные тексты.
4. Векторная дедупликация (эмбеддинги + кластеризация)
Этот метод использует эмбеддинги документов (векторные представления, полученные из языковой модели, например, text-embedding-3-small). Документы, близкие в векторном пространстве (по косинусной близости), считаются дубликатами.
Алгоритм
- Получить эмбеддинг для каждого документа.
- Применить алгоритм кластеризации (например, DBSCAN или Agglomerative Clustering) с порогом расстояния.
- В каждом кластере оставить один документ — ближайший к центроиду кластера (среднему вектору).
Пример:
from sklearn.cluster import DBSCAN
from sklearn.metrics.pairwise import cosine_distances
import numpy as np
embeddings = get_embeddings(documents) # shape (N, D)
dist_matrix = cosine_distances(embeddings)
clustering = DBSCAN(eps=0.2, min_samples=2, metric='precomputed')
labels = clustering.fit_predict(dist_matrix)
unique_docs = []
for label in set(labels):
if label == -1: # шум (уникальные документы)
continue
cluster_indices = np.where(labels == label)[0]
# Найти центроид
centroid = embeddings[cluster_indices].mean(axis=0)
closest_idx = cluster_indices[np.argmin(cosine_distances([centroid], embeddings[cluster_indices]))]
unique_docs.append(documents[closest_idx])
Преимущества
- Улавливает семантическую близость (например, «кошка» и «кот»).
- Не требует ручного выбора шинглов.
Недостатки
- Вычислительно дорого (эмбеддинги + кластеризация).
- Порог расстояния подбирается эмпирически.
- Может ошибочно объединять документы на разные темы, если эмбеддинги плохие.
5. Семантическая дедупликация с помощью LLM
Самый точный, но и самый дорогой метод — использовать LLM (например, GPT-4) для проверки, является ли документ рерайтом другого.
Промпт (пример):
Определи, является ли следующий документ рерайтом (перефразированием) первого документа. Ответь только "да" или "нет".
Документ 1: {text1}
Документ 2: {text2}
Применение
- После грубой фильтрации (MinHash или векторной) для финальной верификации.
- Для небольших корпусов или критически важных данных (юридические, медицинские).
Недостатки
- Высокая стоимость (токены).
- Задержка (latency).
- Зависимость от качества LLM (галлюцинации).
6. Сравнение методов
| Метод | Скорость | Точность | Обнаруживает | Когда использовать |
|---|---|---|---|---|
| SHA-256 | ⚡⚡⚡⚡⚡ | ⚡⚡⚡⚡⚡ | Точные копии | Всегда как первый этап |
| MinHash + LSH | ⚡⚡⚡⚡ | ⚡⚡⚡ | Лексически похожие (near-duplicates) | Большие корпуса (миллионы документов) |
| Векторная кластеризация | ⚡⚡ | ⚡⚡⚡⚡ | Семантически похожие | Средние корпуса, когда важна семантика |
| LLM-проверка | ⚡ | ⚡⚡⚡⚡⚡ | Рерайты, сложные перефразирования | Финальная верификация, малые корпуса |
7. Компромиссы и best practices
Почему не стоит удалять все дубликаты?
В RAG дубликаты могут быть полезны:
- Один и тот же факт из разных источников повышает faithfulness (LLM видит подтверждение).
- Если удалить все дубликаты, можно потерять контекст (например, разные формулировки одного закона).
Рекомендация
- Помечать дубликаты в метаданных (поле
is_duplicate,duplicate_group_id). - При retrieval можно либо исключать помеченные документы, либо оставлять, но передавать LLM с пометкой «этот факт подтверждён несколькими источниками».
- Для агентных RAG (Agentic RAG) агент может динамически решать, какие дубликаты включить в контекст, анализируя их полезность.
Влияние на метрики retrieval
- Удаление дубликатов может снизить recall@k (если дубликаты были релевантны), но повысить precision и разнообразие контекста.
- Рекомендуется тестировать оба варианта (с удалением и без) на валидационном наборе.
8. Пет-проект для закрепления
Задача Реализовать пайплайн дедупликации для корпуса новостных статей (1000 документов) с использованием двух методов: MinHash + LSH и векторной кластеризации. Сравнить результаты.
Инструменты
- Python,
datasketch,sentence-transformers(для эмбеддингов),scikit-learn. - Датасет:
ag_news(или любой набор текстов).
Шаги:
- Загрузить датасет, нормализовать текст (нижний регистр, удаление пунктуации).
- Вычислить MinHash-сигнатуры (шинглы по 3 символа) и построить LSH-индекс с порогом 0.8.
- Для каждого документа найти кандидатов в дубликаты.
- Получить эмбеддинги (например,
all-MiniLM-L6-v2) и выполнить DBSCAN (eps=0.3). - Сравнить множества дубликатов, найденных двумя методами.
- Визуализировать распределение размеров кластеров.
- Выбрать стратегию: удалить или пометить дубликаты, и оценить влияние на retrieval (например, hit rate@5) на тестовых запросах.
Ожидаемый результат
- Код пайплайна с комментариями.
- Таблица сравнения: количество найденных дубликатов, время выполнения, пересечение методов.
- Вывод: какой метод лучше подходит для данного корпуса и почему.
9. Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 509. Как вы выбираете стратегию chunking для RAG | Предобработка документов |
| 510. Какие метаданные вы добавляете к чанкам | Обогащение индекса |
| 511. Как вы обрабатываете PDF и другие форматы | Извлечение текста |
| 513. Как вы обновляете документы в существующей RAG-системе | Инкрементальная индексация |
| 514. Как вы оцениваете качество retrieval'а в RAG-системе | Метрики поиска |
| 520. Что такое Agentic RAG и как он отличается от обычного | Архитектура агентов |
10. Навигация
- Предыдущий: 511
- Следующий: 513
- Индекс: 00. Индекс разборов
Навигация
- Предыдущий: 511
- Следующий: 513
- Индекс: 00. Индекс разборов