Когда внутренних заказчиков становится слишком много — каждый со своими хотелками и типом документов — поддерживать кучу отдельных сервисов уже нереально.

Отдел кадров приносит документы о приёме на работу, бухгалтерия хочет сверять финансовую отчётность, другие подразделения — своё. Нужен был единый сервис, который умеет распознавать документы и гибко настраивать извлечение данных под любой шаблон.

Ситуацию усложнил уход западных вендоров — тот же ABBYY стал недоступен. Просто взять и купить готовый зарубежный софт мы не могли — ни по соображениям безопасности, ни по факту: рынок сам закрыл эту возможность. 

Нужно было решение, которое работает строго внутри защищённого контура. С открытым API, через который можно интегрироваться с практически любой внутренней системой.

Так в Гринатоме (ИТ-интеграторе Росатома) мы начали разработку Атом.Око. Это система, которая не просто видит текст на скане, а понимает структуру документа. Вытаскивает реквизиты, проверяет наличие подписей и печатей.

Почему не подошли коробочные решения

К 2022 году на рынке были коробочные решения, но они были заточены под конкретные типы документов. Зачастую это первичка: паспорт, СНИЛС, ну, может, пара бухгалтерских форм вроде ТОРГ-12. Они отлично вытаскивали данные из 20 типов документов, но как только нам приносили внутреннюю заявку Росатома на логины и пароли или специфическую финансовую отчётность — коробка пасовала.

Нам это не подходило, потому что документов у нас было намного больше, чем в любом таком перечне. И даже если бы мы купили эти коробочные решения для распознавания, вторую половину пришлось бы писать самим. Нужен был гибкий инструмент, который помогает извлекать конкретные данные из документов.

В половинчатом решении смысла не было, поэтому решили делать всё сами. 

От Tesseract к своему зоопарку моделей

Начинали мы с Tesseract. Это был самый очевидный бесплатный старт — взять готовую библиотеку, прогнать через неё документы и посмотреть, что получается. Получалось, честно говоря, не очень.

Tesseract — это добротный инструмент для аккуратно отсканированных документов с чистым белым фоном и чёрным шрифтом. Но если документ немного помят, пожелтел, имеет нестандартный шрифт или фон с узором — качество распознавания резко падает. А у нас таких документов было больше половины.

Поэтому с самого начала стояла задача: сделать своё OCR-решение, которое работает как минимум не хуже Tesseract на стандартных документах, но при этом справляется там, где Tesseract буксует. И делать это нужно было на CPU — без видеокарт, потому что дорого и не всегда доступно.

Первым делом занялись сбором датасета. Исходные данные для обучения OCR — это пары «картинка строки + текст на ней». То есть вырезаете из документа прямоугольник с одной строчкой текста, а рядом кладёте файл, в котором написано, что на этой картинке написано. Потом ещё одна строчка. Потом ещё. Таких пар нужны миллионы.

У нас сначала было 300 тысяч, и это не строк, а отдельных слов. Это мало по современным меркам. Мы прогнали несколько документов через Tesseract, нарезали распознанное на отдельные слова-картинки и отдали разметчикам. Разметчиков было двое. Это дата-сайентисты, которые занимались этим в перерывах между основной работой. Никакого специального инструмента тогда ещё не было. Просто открывали картинки прямо в проводнике и писали в текстовый файл, что на них изображено.

Чтобы как-то ускорить процесс и не ждать, пока люди вручную разметят миллионы примеров, начали генерировать синтетику. Мы брали какой-нибудь текст, рисовали поверх случайного фона и добавляли искажения. Например, где-то строчка чуть пошла вверх, буквы немного растянуты или размыты. Именно эти искажения учат модель распознавать реальные документы, которые приносят люди.

Параллельно подключили авторазметку. Прогоняли новые документы через уже обученную (пусть пока слабую) модель, смотрели, где разметка вышла достаточно точной, отбирали хорошие примеры и отбрасывали плохие. Так датасет рос постепенно. Сейчас в нём порядка 7 миллионов строк — то есть вырос примерно в 25 раз по сравнению со стартом, и это уже строки, а не слова.

Получить устойчивую модель, которая работает лучше Tesseract на наших документах, заняло у нас полтора года. Тут учитываем, что этим занимались всего два человека и не на фулл-тайм.

По нынешним временам, когда есть опенсорс, современные LLM и инструменты для разметки, это можно было бы сделать гораздо быстрее. Но на дворе был 2022 год, и о LLM в прикладном смысле у нас тогда особо не говорили.

Архитектурно система с самого начала делилась на две части. Первая — это ядро: OCR-движок, который просто видит текст и возвращает его вместе с координатами на странице. ML-ядро мы выделили в отдельную библиотеку, к которой обращаются все остальные сервисы по мере необходимости.

Вторая — шаблонизатор: модуль, который понимает бизнес-логику и извлекает из распознанного текста именно те поля, которые нужны конкретному заказчику. Это принципиальное разделение: ядро не знает, что такое «ИНН» или «дата выдачи лицензии», оно просто видит символы. А шаблонизатор уже работает с конкретными сущностями и правилами.

Есть ещё классификатор: отдельный сервис, которому сначала показывают несколько примеров каждого типа документа, чтобы он понял, как тот выглядит. Дальше он сам определяет, что перед ним, — и только потом запускает нужный шаблон.

Помимо самого OCR, вокруг него сразу появился целый набор вспомогательных задач: поворот страницы и выравнивание изображения, детекция самой страницы в кадре (потому что люди присылают фотографии, где документ лежит на столе), детекция штрихкодов и QR-кодов, детекция таблиц, печатей и подписей. Сейчас всё это делает одна модель.

Стек — Python. Всё развёрнуто в контейнерах, горизонтально масштабируется и без проприетарных зависимостей.

Улучшение качества картинки

Часто бизнес приходит с запросом: «Сделайте нам картинку чёткой, красивой, и чтобы всё читалось». По сути, это классическая задача Super Resolution, когда из исходного изображения нужно сгенерировать такое же или с большим разрешением, но более высокого качества.

К решению подходили в два этапа. Сначала взяли модель RealESRGAN, которая увеличивает разрешение в 4 раза или оставляет исходный размер, но подтягивает детали. Чтобы обучить её, написали собственный «генератор грязи». Он искусственно портил чистые документы: накладывал случайные линии, удалял пиксели и размывал текст. Оригиналы мы сохраняли в формате PNG, а испорченные версии — в JPG, чтобы модель училась справляться ещё и с артефактами сжатия. Результат получился неплохим, но обработка одной страницы занимала несколько секунд, и, как все GAN-модели, она периодически выдавала артефакты. Мы попробовали альтернативы вроде SwinSR, но они оказались ещё массивнее и медленнее. В итоге эту задачу тогда отложили.

Чуть позже вышла модель DocDiff, как раз для улучшения качества документов. Она состоит из обычного unet’а + диффузионной модели, которая дополнительно учится предсказывать высокочастотную информацию (это как раз текст). Мы также обучили её на нашем датасете + датасете, который генерировался на лету из чистых картинок (было сделано очень много дополнительных видов аугментаций для генерации грязных страниц), и она показала хорошие результаты. Но даже с конвертацией в OpenVINO и квантованием при обучении только Unet-часть требует порядка 8 секунд на картинку 300 dpi.

Ещё выяснили, что, если учить модели совместно, а потом затюнить отдельно Unet, этого тоже хватает для очистки изображения.

Как боролись с бликами

Страшный сон любой распознавалки — это документы с защитными элементами. Паспорт, СНИЛС, дипломы, грамоты — все они имеют специфический фон, который для обычного OCR выглядит как шум.

Если сфотографировать паспорт, то на голограмме появляются блики, которые буквально перекрывают часть текста. Или СНИЛС: он пластиковый, зелёный, с текстурой. Классический OCR, заточенный под чёрный текст на белом фоне, просто не понимает, где тут буквы, а где узор защитной сетки. Эти защитные элементы называются гильоширными — такие мелкие переплетённые линии, которые красиво смотрятся, но безжалостно ломают распознавание.

Чтобы научить нашу модель справляться с этим, пришлось использовать тот же принцип синтетики, но уже прицельно. Генерировали фоны с гильоширными узорами, накладывали на них текст, добавляли имитацию бликов и голографических эффектов. Настоящих паспортных данных у нас, понятное дело, не было. Это персональные данные, которые так просто не получишь. Зашли с другой стороны: взяли датасеты сгенерированных водительских прав (такие есть в открытом доступе), разметили то, что дали заказчики, и сгенерировали свою синтетику.

В итоге пришли к отдельным детекторам под конкретные документы — паспорт, СНИЛС, ИНН, дипломы. Детектор не читает весь документ целиком, он сразу знает, где на паспорте находится фамилия, номер и дата рождения. Вырезает только нужный прямоугольник и распознаёт три-четыре строчки. Это быстрее и точнее, чем обрабатывать страницу полностью.

Была ещё одна неочевидная проблема — порядок чтения. Если у вас документ в две колонки, обычный софт прочитает его слева направо по всей ширине страницы. В итоге получится каша: кусок первой колонки перемешается с куском второй. Мы решили это через кластеризацию: система сама считает расстояние между блоками текста и группирует их в логические абзацы. Теперь программа понимает, где заканчивается одна колонка и начинается другая.

Отдельно занимались печатями. Задача оказалась шире, чем просто «есть печать или нет». Нужно было определить форму, цвет и ещё вытащить текст из самой печати, если он читается. Это полезно для проверки цифровых подписей в документах, которые пришли в виде скана. Если на документе распечатана картинка с QR-кодом или текстом подписи — мы можем её найти и прочитать. Понятно, что это не замена криптографической проверке, но для быстрого первичного контроля работает.

Кстати, о грязных документах. Мы научили Атом.Око работать с архивами 90-х годов: пожелтевшая бумага и покоцанные края — для нас не проблема. Даже старые ГОСТы со специфическими шрифтами печатных машинок 40–60-х годов мы щёлкаем вполне уверенно, хотя с таблицами из тех времён (которые рисовали палочками) бывает непросто.

Ещё работаем над тем, чтобы система умела извлекать данные даже из рукописного текста (HTR). Первыми результатами на тестах с заявлениями от руки мы уже довольны.

50 000 страниц в сутки

Когда к нам пришёл один федеральный орган исполнительной власти, масштаб задачи изменился. У них было два потока: пользователи, которые в реальном времени загружают документы через сайт, и огромный архив. Оба потока нужно обрабатывать параллельно, и при этом пользовательские запросы не должны ждать в хвосте за архивными.

Приоритизацию решили через систему очередей: пользовательские задания идут с высоким приоритетом, архивные — в фоновую. ML-сервисы горизонтально масштабируются — добавляешь инстансы под нагрузку. Итог: 25 серверов, всё на CPU, около 50 000 страниц в сутки.

Настройка под каждого заказчика — это ручная работа. У одних — простой OCR с парой регулярок на документ, и тут узкое место на этапе распознавания: нужен максимальный параллелизм. У других — 20 страниц, из которых надо извлечь буквально каждое слово как отдельный атрибут. Тут шаблонизатор нагружен сильнее OCR.

Определяем через анализ логов: смотрим, где время теряется, балансируем количество инстансов разных сервисов. У себя мы не можем воспроизвести 25 серверов для тестирования, поэтому рисуем карту нагрузки, прикидываем пропорции, потом смотрим реальные метрики в продакшене и подкручиваем. Не элегантно, но работает.

Развёртывание — on-premise или SaaS. Для госструктур почти всегда on-premise, потому что документы чувствительные. В облачном варианте идёт разграничение доступа через ролевую модель на уровне организаций. То есть пользователь из компании А не видит документы компании Б, а внутри компании роли разделены до уровня «только отправлять на распознавание» или «только настраивать шаблоны».

Low-code и будущее

Один из главных уроков — документы, которые кажутся стандартными, стандартными не являются. Один и тот же тип в разных регионах и годах может иметь разный порядок полей.

Поначалу решали через разные классы: один тип документа, один шаблон. Когда вариаций стало слишком много, это превратилось в комбинаторный взрыв. Так появился шаблонизатор с бизнес-логикой. По духу это что-то вроде Атом.РИТА или n8n: блоками рисуешь связи, подаёшь переменные на вход, получаешь результат. Только заточено именно под работу с документами.

Нужно извлечь ИНН? Сначала ищем регуляркой — 10 или 12 цифр подряд. Не нашли — режем документ по bounding box-у: у некоторых форм ИНН всегда на одном и том же месте. Не получилось — ищем по контексту: берём всё между словами «присвоен номер» и следующим ключевым словом. Что-то вытащили — это и есть ИНН.

Стратегии настраиваются через веб-интерфейс без кода. Аналитик заходит, размечает поля на документе, сохраняет шаблон — и новый тип документа в системе за пару часов, а не за месяц разработки.

Бизнес-логика делает больше, чем просто вытаскивает значения. Нужно посчитать полную сумму аренды по договору — извлекаем платёж, умножаем на количество месяцев, переводим в нужную единицу, округляем. Нужно привести ФИО к именительному падежу — постобработка с лемматизацией: «Пушкину Александру Сергеевичу» превращается в «Пушкин Александр Сергеевич». Нужно проверить, что дата выдачи не в будущем — это тоже бизнес-логика.

Для сложных случаев подключаем NER-модели — они хорошо работают с русскоязычными именованными сущностями.

Когда заказчику нужны совсем нестандартные классы сущностей, например, «отношение ГОСТа к изделию» или «кем сформирован документ», — используем модели с поддержкой произвольных классов, где класс задаётся текстовым описанием прямо в запросе.

Иногда подключаем экстрактивный question-answering. Модель не генерирует ответ, а указывает span в тексте. Для максимальной гибкости можно подключить любую OpenAI-совместимую модель — GigaChat или локально развёрнутую свою. Но LLM — не дефолт. Они дороже, медленнее, и гонять большую языковую модель ради извлечения ИНН — это микроскоп вместо молотка.

Был показательный кейс. Заказчик, полтора года работающий с системой, пришёл с запросом «хотим конвертировать PDF в Word». Выяснилось, что система это уже умеет. То есть функционал был с самого начала, просто заказчик не знал. Никакой разработки не потребовалось.

Сейчас Атом.Око вырос в полноценного ассистента. Мы продолжаем внедрять LLM. Например, для суммаризации писем, чтобы человек не читал всю простыню текста, а сразу видел суть и понимал, в какую папку отложить документ.

P.S. В разработке Атом.Око принимала участие команда из десяти человек.

Комментарии (0)