中文翻译暂不可用,显示俄语原文。
Как вы дедуплицируете документы перед индексацией в RAG?
Краткий тезис
Дедупликация документов — обязательный этап предобработки в RAG (Retrieval-Augmented Generation), предотвращающий хранение нескольких копий одного и того же (или почти одинакового) текста в векторной базе данных. Основные подходы: hashing|точное хеширование для полных дубликатов, MinHash и SimHash для текстовых near‑duplicates, а также LSH (Locality‑Sensitive Hashing) на эмбеддингах для семантически близких фрагментов. Выбор метода зависит от требуемой чувствительности к изменениям (опечатки, перефразирование) и масштаба данных.
1. Зачем нужна дедупликация в RAG?
Термин дедупликация — процесс удаления повторяющихся или очень похожих документов (чанков) перед загрузкой в индекс.
Проблемы, вызываемые дубликатами:
- Снижение качества retrieval – при поиске возвращаются почти одинаковые куски, контекст становится однородным, падает recall (полнота) по разным аспектам запроса.
- Увеличение latency – большее количество векторов требует больше времени на поиск и больше памяти.
- Искажение релевантности – LLM может получить несколько одинаковых контекстов и начать их повторять или смешивать, снижая качество ответа.
- Лишние затраты на хранение и индексацию – каждый дубликат занимает место в векторной БД и требует ресурсов на embedding.
2. Типы дубликатов
- Exact duplicates – два документа полностью совпадают символ в символ (с точностью до пробелов/переводов строк или без).
- Near‑duplicates – документы с незначительными отличиями: разные варианты форматирования, опечатки, синонимы, перестановка абзацев, небольшие сокращения.
- Semantic duplicates – тексты, написанные разными словами, но с одинаковым смысловым содержанием (например, новостные заметки об одном событии от разных агентств).
Для каждого типа применяются разные техники.
3. Метод 1: Точное хеширование (Exact duplicate detection)
Самый простой способ – вычислить криптографический хеш (SHA‑256, MD5) от содержимого документа и хранить его в множестве. Перед индексацией проверяем, не встречался ли уже такой хеш.
Плюсы:
- Максимальная скорость.
- Нет ложных срабатываний.
- Подходит для удаления абсолютных копий (например, одинаковые страницы PDF).
Минусы:
- Не ловит near‑duplicates (даже один лишний пробел меняет хеш полностью).
- Бесполезен для текстов с синонимами, перефразированием.
Пример:
import hashlib
def exact_dedup(docs):
seen = set()
unique = []
for doc in docs:
h = hashlib.sha256(doc.encode('utf-8')).hexdigest()
if h not in seen:
seen.add(h)
unique.append(doc)
return unique
4. Метод 2: MinHash и Jaccard similarity
Термин MinHash – вероятностный алгоритм для оценки Jaccard similarity между двумя множествами (например, множествами шинглов текста). Применяется для поиска near‑duplicates на уровне текстовых фрагментов.
Как работает
- Разбиваем документ на шинглы (shingles) – n‑граммы символов или слов.
Пример: для трёхсловных шинглов предложение "Кошка ловит мышь" даст {"Кошка ловит мышь"}. - Строим множество minhash-сигнатур (обычно 128–256 значений). Каждая сигнатура – это минимальный хеш среди всех шинглов после применения нескольких хеш-функций.
- similarity|Jaccard similarity двух документов ≈ доля совпадающих minhash-значений.
- Используем LSH (Locality‑Sensitive Hashing) для группировки документов, у которых совпало определённое количество сигнатур.
Параметры:
- Размер шингла (2–5 слов или 5–10 символов).
- Количество хеш-функций (число сигнатур).
- Порог Jaccard similarity (обычно 0.7–0.9).
Плюсы:
- Устойчив к мелким изменениям (опечаткам, перестановкам слов).
- Обнаруживает почти дословные повторы.
Минусы:
- Не улавливает семантические дубликаты.
- Требует настройки размера шингла.
Библиотека: datasketch (MinHash, MinHashLSH).
from datasketch import MinHash, MinHashLSH
def minhash_dedup(docs, threshold=0.8):
# Создаём LSH-индекс
lsh = MinHashLSH(threshold=threshold, num_perm=128)
minhashes = {}
for i, doc in enumerate(docs):
m = MinHash(num_perm=128)
for shingle in [doc[j:j+4] for j in range(len(doc)-3)]: # символьные 4-граммы
m.update(shingle.encode('utf-8'))
minhashes[i] = m
lsh.insert(i, m)
# Собираем уникальные документы
unique_ids = set()
for i, m in minhashes.items():
neighbors = lsh.query(m)
# Оставляем только первый в каждой группе дубликатов
if min(neighbors) == i:
unique_ids.add(i)
return [docs[i] for i in sorted(unique_ids)]
5. Метод 3: SimHash (Google)
Термин SimHash – алгоритм, создающий фиксированную битовую сигнатуру (например, 64 бита) для документа. Чем больше бит совпадает между сигнатурами, тем ближе тексты. Оценка подобия – по Hamming distance.
Идея
- Вычисляем взвешенный хеш: для каждого признака (слова) получаем 64-битный хеш, затем накапливаем вектор весов (прибавляем +1 для бита 1, -1 для бита 0).
- Итоговый бит: знак суммы.
Применение:
- SimHash хорошо работает для длинных документов (несколько предложений).
- Порог: если Hamming distance ≤ 3–5, документы считаются дубликатами.
Плюсы:
- Быстрое сравнение через XOR и подсчёт бит.
- Используется в реальных системах (Google для дедупликации веб-страниц).
Минусы:
- Чувствителен к длине документа.
- Не подходит для очень коротких текстов.
Библиотека: simhash-py (C++), simhash (Python-реализация).
from simhash import Simhash
def simhash_dedup(docs, distance=3):
sigs = [Simhash(doc) for doc in docs]
unique = []
for i, s in enumerate(sigs):
keep = True
for j in range(i):
if s.distance(sigs[j]) <= distance:
keep = False
break
if keep:
unique.append(docs[i])
return unique
6. Метод 4: LSH на эмбеддингах (Embedding‑based LSH)
Вместо текстовых сигнатур можно использовать эмбеддинги (векторы) документов, полученные от той же модели, что и для индексации в RAG. Для поиска семантических дубликатов применяется LSH на случайных проекциях.
Как работает
- Получаем эмбеддинг каждого документа.
- Создаём несколько случайных гиперплоскостей; для каждой плоскости хеш-бит = знак скалярного произведения с вектором плоскости.
- Комбинируем биты в хеш-значение.
- Документы, у которых совпадают хеши в нескольких таблицах, считаются кандидатами на дубликаты.
- Для пар кандидатов вычисляем косинусную близость (или L2) и удаляем, если превышает порог.
Плюсы:
- Улавливает семантические дубликаты (разные слова, одинаковый смысл).
- Встраивается в пайплайн с тем же энкодером, что и для retrieval.
Минусы:
- Высокая стоимость эмбеддингов (нужен вызов модели).
- Необходимость подбора числа гиперплоскостей и таблиц.
Реализация: через faiss (индекс IndexLSH) или sklearn.neighbors.LSHForest (устаревший).
import faiss
import numpy as np
def embedding_lsh_dedup(embeddings, threshold=0.95):
d = embeddings.shape[1]
# LSH с 128 битами, nbits=4 => 2^4=16 buckets
index = faiss.IndexLSH(d, 128, 4)
index.add(embeddings)
# Поиск похожих (включая самих себя)
D, I = index.search(embeddings, k=min(embeddings.shape[0], 100))
# Удаляем дубликаты: оставляем только документы с меньшим индексом в паре
unique_idx = set(range(len(embeddings)))
for i in range(len(embeddings)):
for j in I[i]:
if i < j and D[i][0] < 1.0 and (1 - D[i][0]) < threshold: # для L2 threshold
unique_idx.discard(j)
return list(unique_idx)
7. Сравнение методов
| Метод | Тип дубликатов | Скорость | Настройка | Чувствительность к изменениям |
|---|---|---|---|---|
| Точное хеширование | Exact | Очень высокая | Нет | Только полное совпадение |
| MinHash + LSH | Near‑duplicate | Высокая | Размер шингла, порог | Опечатки, переформулировки |
| SimHash | Near‑duplicate | Высокая | Длина сигнатуры, порог Hamming | Перестановки, небольшие изменения |
| LSH на эмбеддингах | Semantic duplicate | Средняя (требуется энкодер) | Число проекций, порог similarity | Парафраз, синонимы |
8. Практические рекомендации
- Для огромных корпусов (миллионы документов) лучше комбинировать: сначала точное хеширование, затем MinHash на шинглах, а для оставшихся кандидатов – LSH на эмбеддингах.
- Выбор порога: зависит от жесткости дедупликации. В RAG часто используют Jaccard >0.8 или косинусное сходство >0.95.
- Хранение сигнатур: для MinHash и SimHash можно хранить только сигнатуры, а не весь документ, и сравнивать на лету.
- Chunk‑level дедупликация: если RAG использует чанки, дедуплицировать нужно именно чанки, а не исходные документы (один документ может содержать повторяющиеся абзацы).
- Потоковая дедупликация: в реальном времени можно обновлять LSH‑индекс инкрементально.
9. Пайплайн дедупликации в RAG
Исходные документы
↓
[Этап 1] Извлечение текста (OCR, PDF parsing)
↓
[Этап 2] Чанкинг (если нужно)
↓
[Этап 3] Точное хеширование → удаление exact duplicates
↓
[Этап 4] MinHash (или SimHash) → группировка near‑duplicates → удаление лишних
↓
[Этап 5] (Опционально) LSH на эмбеддингах → удаление семантических дубликатов
↓
[Этап 6] Генерация эмбеддингов для оставшихся уникальных чанков
↓
Индексация в векторную БД
Пет-проект для закрепления
Задача: Реализовать дедупликацию для датасета новостных статей (10k документов), используя MinHash и LSH.
Инструменты: Python, datasketch, pandas, скачать датасет (например, AG News).
Шаги:
- Загрузить датасет и извлечь тексты статей.
- Разбить на чанки по 256 токенов (через tiktoken).
- Для каждого чанка сгенерировать MinHash (шинги по 5 слов, 128 пермутаций).
- Построить MinHashLSH индекс с порогом 0.85.
- Найти все группы дубликатов и оставить только по одному чанку из каждой группы.
- Вывести статистику: число удалённых чанков, примеры пар дубликатов.
Ожидаемый результат: Чёткое понимание, как настраивать параметры MinHash, как интерпретировать результат LSH, как интегрировать дедупликацию в подготовку данных для RAG.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 3 | Стратегии чанкинга (размер шинглов) |
| 4 | Выбор эмбеддинговой модели |
| 5 | Оценка качества retrieval |
| 9 | Обновление документов в существующей RAG |
| 10 | Self‑RAG и релевантность контекста |
| 7 | Уменьшение latency (дедупликация уменьшает количество векторов) |
Навигация
- Предыдущий: 256
- Следующий: 258
- Индекс: 00. Индекс разборов