English translation is not available yet. Showing Russian content.

Как вы дедуплицируете документы перед индексацией в 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 на уровне текстовых фрагментов.

Как работает

  1. Разбиваем документ на шинглы (shingles) – n‑граммы символов или слов.
    Пример: для трёхсловных шинглов предложение "Кошка ловит мышь" даст {"Кошка ловит мышь"}.
  2. Строим множество minhash-сигнатур (обычно 128–256 значений). Каждая сигнатура – это минимальный хеш среди всех шинглов после применения нескольких хеш-функций.
  3. similarity|Jaccard similarity двух документов ≈ доля совпадающих minhash-значений.
  4. Используем 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 на случайных проекциях.

Как работает

  1. Получаем эмбеддинг каждого документа.
  2. Создаём несколько случайных гиперплоскостей; для каждой плоскости хеш-бит = знак скалярного произведения с вектором плоскости.
  3. Комбинируем биты в хеш-значение.
  4. Документы, у которых совпадают хеши в нескольких таблицах, считаются кандидатами на дубликаты.
  5. Для пар кандидатов вычисляем косинусную близость (или 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 + LSHNear‑duplicateВысокаяРазмер шингла, порогОпечатки, переформулировки
SimHashNear‑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).

Шаги:

  1. Загрузить датасет и извлечь тексты статей.
  2. Разбить на чанки по 256 токенов (через tiktoken).
  3. Для каждого чанка сгенерировать MinHash (шинги по 5 слов, 128 пермутаций).
  4. Построить MinHashLSH индекс с порогом 0.85.
  5. Найти все группы дубликатов и оставить только по одному чанку из каждой группы.
  6. Вывести статистику: число удалённых чанков, примеры пар дубликатов.

Ожидаемый результат: Чёткое понимание, как настраивать параметры MinHash, как интерпретировать результат LSH, как интегрировать дедупликацию в подготовку данных для RAG.


Связь с другими вопросами

ВопросТема
3Стратегии чанкинга (размер шинглов)
4Выбор эмбеддинговой модели
5Оценка качества retrieval
9Обновление документов в существующей RAG
10Self‑RAG и релевантность контекста
7Уменьшение latency (дедупликация уменьшает количество векторов)

Навигация