Практика · RAG · качество данных
Как оценивать влияние OCR-ошибок в русскоязычных документах на качество RAG
Ошибки распознавания таблиц, сокращений, букв и реквизитов ухудшают поиск, но их легко спутать с недостатками эмбеддингов, разбиения на фрагменты или генерации. Ниже — методика, которая разделяет эти причины экспериментально.
Что именно нужно измерять
OCR преобразует изображение документа в текст. В русскоязычных материалах результат может выглядеть правдоподобно для человека, но содержать ошибки, критичные для точного поиска: «О» вместо нуля, латинскую C вместо кириллической С, потерянную точку в сокращении или переставленные ячейки таблицы.
Одна итоговая оценка RAG не показывает, где возникла потеря. Для диагностики полезно рассматривать систему как три последовательных этапа:
- OCR: сохранился ли искомый факт в распознанном тексте?
- Индекс и поиск: попал ли содержащий факт фрагмент в результаты поиска?
- Ответ модели: извлекла ли модель правильное значение из найденного контекста?
Главный принцип эксперимента — менять только один этап за раз. Если одновременно заменить OCR, модель эмбеддингов и промпт, объяснить изменение результата будет невозможно.
1. Соберите небольшой, но диагностический корпус
Начните не со случайной выборки, а с документов, где встречаются разные режимы отказа. Для первого прохода достаточно такого объёма, который можно проверить вручную. Размер зависит от ваших документов; универсального минимального числа нет.
| Категория | Что включить | Типичный риск |
|---|---|---|
| Таблицы | Многострочные ячейки, объединённые столбцы, суммы | Значение связывается не с той строкой |
| Реквизиты | ИНН, КПП, номера договоров, даты | Замена или пропуск одного символа |
| Сокращения | ООО, г., обл., ст., п/п | Потеря точек, пробелов или косой черты |
| Похожие знаки | О/0, З/3, Ч/4, В/8, кириллица/латиница | Нарушение точного совпадения |
| Макет | Колонтитулы, печати, две колонки, сноски | Неверный порядок чтения |
Для каждого исходного документа сохраните три представления:
scan— исходная страница или PDF;ocr— текст, полученный проверяемым OCR-конвейером;gold— вручную проверенная эталонная расшифровка только тех фрагментов, которые участвуют в тестах.
Полная ручная расшифровка всего архива обычно не нужна. Важно проверить предложения, строки таблиц и реквизиты, на которых основаны контрольные вопросы.
2. Опишите контрольные примеры
Храните вопросы в JSONL: одна строка — один тест. Не помещайте в публичный репозиторий реальные персональные данные, банковские реквизиты или конфиденциальные договоры. Для шаблона используйте синтетические значения, явно обозначенные как примеры.
{
"id": "example-requisite-001",
"document_id": "example-contract-01",
"category": "requisite",
"question": "Какой номер указан у договора?",
"gold_answer": "АБ-042/25",
"gold_evidence": "Договор № АБ-042/25",
"page": 1,
"match_mode": "normalized_exact"
}
{
"id": "example-table-001",
"document_id": "example-invoice-01",
"category": "table",
"question": "Какова сумма для позиции «Кабель»?",
"gold_answer": "12 450,00 руб.",
"gold_evidence": "Кабель | 10 | 1 245,00 | 12 450,00",
"page": 2,
"match_mode": "numeric"
}
Это формат примера, а не готовый тестовый набор и не данные реальной организации. Поле gold_evidence должно содержать минимальный фрагмент, достаточный для подтверждения ответа.
Добавьте к каждому примеру категорию ошибки. Это позволит увидеть, что среднее качество приемлемо, а поиск по номерам договоров фактически не работает.
3. Постройте две версии одного индекса
Создайте контрольный индекс из эталонного текста и экспериментальный индекс из OCR-текста. Все остальные параметры должны совпадать:
experiment:
control_index:
source: gold
ocr_index:
source: ocr
chunking:
strategy: paragraph
max_characters: 1800
overlap_characters: 180
retrieval:
top_k: 5
filters:
enabled: false
generation:
temperature: 0
require_evidence: true
Значения в конфигурации приведены как воспроизводимый пример, а не как универсально оптимальные настройки. Для вашего корпуса их следует зафиксировать заранее и не менять в середине сравнения.
Если система поддерживает гибридный поиск, сначала протестируйте тот режим, который используется в рабочем контуре. Затем можно отдельно сравнить векторный, лексический и гибридный варианты — с одинаковыми фрагментами и вопросами.
4. Проверьте OCR до запуска поиска
Сначала ответьте на простой вопрос: присутствует ли эталонное свидетельство в OCR-тексте хотя бы после безопасной нормализации? Такая нормализация может приводить пробелы к единому виду и Unicode к форме NFC, но не должна исправлять предполагаемые ошибки распознавания.
python - <<'PY'
import json
import unicodedata
from pathlib import Path
def normalize(text):
text = unicodedata.normalize("NFC", text)
return " ".join(text.split()).casefold()
ocr_dir = Path("data/ocr")
tests_path = Path("data/tests.jsonl")
found = 0
total = 0
with tests_path.open(encoding="utf-8") as source:
for line in source:
case = json.loads(line)
text = (ocr_dir / f"{case['document_id']}.txt").read_text(encoding="utf-8")
present = normalize(case["gold_evidence"]) in normalize(text)
print(json.dumps({
"id": case["id"],
"evidence_present": present
}, ensure_ascii=False))
found += int(present)
total += 1
print(json.dumps({
"evidence_recall": found / total if total else None,
"found": found,
"total": total
}, ensure_ascii=False))
PY
Команда только читает локальные файлы и печатает результат. Она не отправляет документы во внешние сервисы и ничего не перезаписывает.
Метрика evidence recall здесь равна доле тестов, для которых эталонный фрагмент сохранился в OCR-тексте. Она строгая: изменение одного символа даст отрицательный результат. Поэтому рядом полезно хранить символьное расстояние или ручную метку причины, но не подменять ими основной факт наличия свидетельства.
5. Измерьте поиск независимо от генерации
Для каждого вопроса сохраните идентификаторы и текст фрагментов из top_k. Не запускайте модель ответа на этом этапе. Фрагмент считается релевантным, если содержит размеченное свидетельство или ссылается на вручную отмеченный участок документа.
Минимальный набор метрик:
- Recall@k: доля вопросов, для которых релевантный фрагмент найден среди первых
kрезультатов; - MRR: учитывает позицию первого релевантного результата;
- document recall: найден ли хотя бы правильный документ, даже если выбран неверный фрагмент;
- evidence recall: найден ли именно фрагмент с ответом.
Сравните четыре величины для одного и того же вопроса:
| Условие | Что показывает |
|---|---|
| Свидетельство отсутствует в OCR | Потеря возникла до индексации |
В OCR есть, в top_k нет |
Проблема разбиения, индекса или поискового запроса |
В top_k найден документ, но не свидетельство |
Слишком крупные, мелкие или неверно сформированные фрагменты |
| Свидетельство в контексте, ответ неверен | Проблема извлечения или генерации ответа |
Разница Recall@k между эталонным и OCR-индексом при неизменных настройках — практическая оценка потери поиска, связанной с OCR. Это не доказывает, что виноват только OCR-движок: причиной может быть и преобразование его вывода в строки, Markdown или фрагменты. Поэтому сохраняйте промежуточные представления.
6. Оцените ответ модели при фиксированном контексте
Для отделения генерации от поиска выполните два прогона:
- передайте модели эталонное свидетельство напрямую;
- передайте лучший фрагмент, реально найденный в OCR-индексе.
Ответь только по переданному контексту.
Если значения нет или оно неоднозначно, ответь: «Недостаточно данных».
Сохраняй номера, даты, сокращения и денежные суммы без исправлений.
Вопрос: {question}
Контекст:
{context}
Температуру и версию модели следует зафиксировать. Для точных реквизитов сравнивайте нормализованные строки, для сумм — разобранные числовые значения, а для развёрнутых ответов применяйте ручную шкалу с заранее описанными критериями.
Не используйте одну модель как безусловного судью другой модели. Автоматическая проверка удобна для предварительной сортировки, но номера, даты и табличные связи лучше сверять детерминированно или вручную.
Матрица локализации потерь
Объедините результаты в одну таблицу на уровне каждого вопроса:
id,category,ocr_evidence,gold_hit_5,ocr_hit_5,answer_with_gold,answer_with_ocr
example-requisite-001,requisite,0,1,0,1,0
example-table-001,table,1,1,1,1,0
В этом синтетическом примере первая строка указывает на потерю реквизита при OCR. Во второй строке свидетельство распознано и найдено, но модель не извлекла правильное значение: исследовать нужно генерацию, формат таблицы в контексте или критерий сравнения ответа.
Для отчёта посчитайте показатели отдельно по категориям и добавьте абсолютное число примеров. Процент без знаменателя вводит в заблуждение, особенно в маленьких подгруппах.
Как проверить, что эксперимент получился
Перед интерпретацией результатов пройдите контрольный список:
- вопросы, ответы и свидетельства размечены до просмотра результатов сравнения;
- эталонный и OCR-индексы используют одинаковое разбиение и настройки поиска;
- идентификаторы документов и страниц сохраняются после OCR и индексации;
- поиск оценивается без участия генератора;
- генератор проверяется на фиксированном эталонном контексте;
- метрики представлены по категориям ошибок, а не только одним средним;
- каждый неуспешный пример можно открыть и проверить вручную;
- конфигурация, версии компонентов и хеш тестового файла сохранены вместе с отчётом.
Хеш позволяет убедиться, что два запуска использовали один тестовый файл:
sha256sum data/tests.jsonl
Команда безопасна: она читает файл и выводит контрольную сумму.
Типовые ошибки эксперимента
Оценивать только итоговый ответ
Неверный ответ не говорит, потерян ли факт при OCR, не найден индексом или проигнорирован моделью. Сохраняйте результаты каждого этапа.
Исправлять OCR до базового замера
Замены вроде О → 0 могут улучшить одни реквизиты и испортить обычные слова. Сначала измерьте исходный вариант, затем добавляйте коррекцию как отдельную экспериментальную ветку.
Использовать только семантические вопросы
Общие вопросы могут скрывать повреждение точных значений. В наборе должны быть запросы по номерам, датам, сокращениям, строкам таблиц и близким по написанию сущностям.
Считать правильным любой фрагмент нужного документа
Это завышает качество поиска. Разделяйте попадание документа и попадание свидетельства.
Менять несколько компонентов одновременно
Одновременная смена OCR, эмбеддингов, размера фрагмента и модели ответа превращает диагностику в сравнение двух непрозрачных систем.
Нормализовать слишком агрессивно
Удаление пунктуации и замена похожих символов могут скрыть именно те ошибки, которые требуется измерить. Храните сырой текст, мягко нормализованный текст и журнал преобразований.
Ограничения методики
Точное совпадение свидетельства хорошо обнаруживает повреждение реквизитов, но слишком строго для свободного текста. Символьные метрики оценивают сходство строк, однако не всегда отражают смысловую потерю. Например, один неверный знак в ИНН критичнее нескольких ошибок в служебной фразе.
Небольшой ручной набор помогает локализовать дефекты, но не доказывает качество на всём архиве. Распределение сканов, макетов, языков и годов может отличаться от тестовой выборки. После первой диагностики расширяйте набор примерами из обнаруженных классов ошибок и сохраняйте старые случаи как регрессионные тесты.
Наконец, эталонная расшифровка тоже может содержать ошибки. Для критичных чисел и реквизитов полезна независимая повторная проверка по изображению страницы.
Практический итог
Рабочий тест RAG на OCR-документах состоит не из одной оценки ответа, а из связанной цепочки: наличие эталонного факта в распознанном тексте, попадание свидетельства в top_k и корректное извлечение ответа из фиксированного контекста.
Если хранить эти признаки для каждого вопроса, причина потери становится наблюдаемой. Тогда улучшения можно проверять адресно: новый OCR — по сохранности свидетельств, новое разбиение или эмбеддинги — по Recall@k, новый промпт или модель — по ответам при одинаковом контексте.
Дополнительные схемы экспериментов собраны в разделе практических руководств, а определения терминов — в глоссарии.