Сегодня хотим поделиться опытом, который мы накопили при попытке автоматизировать анализ коммунальных платежей для нашей сети магазинов, состоящей более чем из 1200 объектов.
За каждым магазином стоят свои коммунальные расходы. Каждый месяц арендодатель выставляет нам пакет документов, причем они приходят в самых разных форматах: одни присылают УПД, другие — неформализованные счета, а третьи — сканы актов. Например, такие:

Такие:

И даже такое:

При этом в УПД (формализованном) может быть указано что-то вроде «Переменная часть арендной платы за февраль 2023», чего явно недостаточно для детального анализа. Мы хотели проанализировать данные по потреблению каждого ресурса (электроэнергия, водоснабжение, теплоснабжение, кондиционирование и т. д.) в разрезе конкретного магазина с учётом сезонности.
Помимо этого, часть наших магазинов подключена к системе «Умный магазин»: она даёт температуру внутри помещения и снимает данные потребления электроэнергии непосредственно со счётчиков.
Какие пути решения мы испробовали?
-
OCR (Оптическое распознавание символов)
Мы начали с классического подхода, подключив вендора, который специализируется на распознавании актов и счетов. Однако, из-за того, что большинство документов приходят в разных формах, система смогла выдать хоть какой-то результат только лишь в 30% случаев.
-
GPT-4o-mini для обработки сканов
Тогда мы решили попробовать GPT-4o-mini, которая обладает возможностями мультимодальной обработки (то есть, работает с картинками и текстами). Мы попробовали перевести сканы в JSON. И тут результаты нас удивили: даже при плохих сканах модель выдаёт довольно точные данные.
Нас удивила способность GPT работать даже с плохими изображениями. Например тут мы попробовали прочитать данные со скана и предоставить нам JSON табличной части, GPT выдал следующее:

Конечно, были и ошибки в распознавании (выделены в JSON красным), но это уже было большим шагом вперёд.
Ограничения и вопросы безопасности
После консультации с ИБ выяснилось, что многие документы содержат персональные данные, которые нельзя передавать в зарубежные сервисы без разрешения регуляторов. Чтобы протестировать модель, мы деперсонифицировали небольшой набор данных (630 страниц), что позволило нам продолжить эксперименты.
Однако для реального использования таких данных нам нужно либо обезличивать всё, либо разворачивать модели локально, чтобы работать без внешних сервисов. Поэтому мы решили попробовать локальные модели Llama. Как раз в тот момент была выпущена серия моделей Llama 3.2 vision (11 и 70 млрд. параметров).
Подход к решению
Чтобы сравнить разные мультимодальные модели (VLM), мы сформировали контрольную выборку. Для этого сначала воспользовались GPT-4o, загрузили в него деперсонализированную выборку (630 страниц) и вручную верифицировали полученные результаты. В итоге сформировали 210 документов с подтверждёнными JSON — на этом объёме и начали тесты.
Задачи для модели:
-
Определение типа документа: является ли документ актом или счётом? Были случаи, когда нам присылали не документы, а расчёты, которые мы не должны были обрабатывать.
-
Проверка принадлежности документа компании «МВМ»: арендодатели могут присылать документы от поставщиков услуг, и важно отсеивать их.
-
Определение итоговой суммы по документу.
-
Анализ табличной части документа.
Также у нас были попытки заставить модель сразу же оценить насколько она уверена в точности распознавания. От этого параметра мы отказались, т.к. он даже на небольшом количестве запусков был непоказателен.
Как мы организовали эксперименты?
Мы предполагали разбить процесс на несколько этапов:
-
Single-shot подход: Использовали модели без обучения для анализа документов.
-
Few-shot: Пробовали обучать модели на небольшом количестве примеров.
Fine-tuning: Настройка модели на конкретные данные для улучшения качества.
Промпт для GPT-4o-mini:
В результате многочисленных тестов мы составили следующий промпт для GPT-4o-mini.
Analyze the provided <|image|> and answer the following questions:
1. Is the <|image|> a bill or invoice containing services?
2. Does the bill or invoice belong to МВМ company? If you find this word on a page it means this bill belongs to МВМ company
3. Extract a table from the <|image|> with the following columns:
– Service Name. In this field write only service name.
– Service Type.
– Consumption Volume. In this filed leave only digits without unit of measure.
– Unit of Measure. Here leave only unit of measure.
– Price.
– Amount Due.
4. For each line, immediately determine the type of service and write the result in the service_type field.
Everything related to electricity. This can be determined by the unit of measurement “kWh”, “kW”, etc. or by the name of the service. In the name of the service, in this case, there will be the word “Electricity”, “EE”, “E/E” or other combinations. For such lines in Service_type write “electricity”
Everything related to heat energy. This can be determined by the unit of measurement “Gcal” or by the name of the service. In the name of the service, in this case, there will be the word “Heat”, “Heat Supply”, “TE”, “TEP/E” or other combinations. For such lines in Service_type write “HeatSupply”
Everything related to water supply. This can be determined by the unit of measurement “m3”, “m^3” and by the name of the service (take into account that there is wastewater disposal, which is measured in the same unit of measurement). In the name of the service, in this case, there will be the word “Water Supply”, “Water”, “vod.” or other combinations. For such lines in Service_type write “WaterSupply”
5. Verify that the units of measure conform to the SI system. Use english language.
Present your findings in JSON format with the following structure:
{
“is_bill”: true/false,
“is_mvm”: true/false,
“table”: [
{
“service_name”: “string”,
“sevice_type” : “string”,
“consumption_volume”: “string”,
“unit_of_measure”: “string”,
“price” : “string”,
“amount_due”: “string”
}
]
}
6. Features of invoices in Russian
– One number can be separated by spaces for ease of reading. For example, the number 74 523,57 should be read as 74523.5
– units of measurement should always refer to the international system of units of measurement. For example, if it seems that the unit of measurement is “523”, it means that you made a mistake in scanning. In fact, most units of measurement in invoices are rubles, kWh, m ^ 3, Gcal
– if unit of measure is empty, leave it empty in JSON to
Write all numbers in the format accepted in Python
**Only return the JSON output. Do not include any additional text. The output should be accepted by json.loads.**
**If the <|image|> is not a bill or invoice containing services, or if it does not contain a table with services, return an empty JSON object: {}**
Для себя мы определили следующий порядок проведения экспериментов:
-
single-shot
-
few-shot
-
fine tuning (для single-shot и few-shot)
-
применение каскада моделей: локальная для извлечения данных из картинки (и деперсонализации) и GPT(или другая модель) для дальнейшей обработки.
Для документа мы составили confusion matrix для параметров is_bill, is_mvm. Матрица показывает 4 значения:
истинно отрицательные |
ложно положительные |
ложно отрицательные |
истинно положительные |
Для таблицы анализировали насколько верно были определены:
-
service_name — название услуги
-
service_type — тип услуги (классификация услуг, например, electricity)
-
consumption_volume — объем услуг
-
unit_of_measure — единица измерения, бывает электричество в кВт*Ч, бывает в МВт*ч, а бывает и просто мощность в кВт
-
price —цена за единицу измерения
-
amount_due — общая стоимость
-
совпадения по количеству строк с данными в таблице
Параметры 2 и 4 по большому счету — значения справочников, поэтому LLM либо подставила верно, либо ошиблась.
Параметры 1, 3, 5, 6 – литеральные, тут мы считали соответствует на 100% или не соответствует. В примере выше были хорошо распознаны данные, но была ошибка в 1 цифру. Такой вариант считался неверно распознанным.
Параметр 7 важен т.к. показывает в каком количестве документов мы упустили какие-то строки (в автоматическом режиме мы сможем только косвенно понять что что-то упустили по конечной сумме (сравнив с суммой строк).
Эксперименты
Мы начали с подхода single-shot. Проверили модели Llama 3.1-11b-vision-instruct, Llama 3.1-70b-vision-instruct, GPT 4o-mini
GPT 4o-mini показал следующие результаты:
is_bill
8,79% |
8,33% |
2,31% |
80,55% |
is_mvm
34,72% |
16,66% |
2,31% |
46,29% |
Поля таблиц:
service_name |
62,80% |
service_type |
84,54% |
consumption_volume |
80,19% |
unit_of_measure |
84,54% |
price |
78,26% |
amount_due |
64,73% |
совпадения по количеству строк с данными в таблице |
74,29% |
Данные выглядели многообещающе, т.к мы верно определяли характер документа примерно в 85% случаев, примерно в такой же доле верно определяли сервис, единицу измерения, поставленный объем и цену.
При этом значительный провал был по двум параметрам:
-
сумму по строке мы определяли верно только в 64,73% случаев
-
подтягивали все строки в 25,71% случаев.
И если с суммой по строке можно было бы жить, то неправильное определение количества строк в 75% случаев ставило крест на инициативе. Т.е. примерно для 900 магазинов пришлось бы проводить анализ документа вручную и вводить данные.
Llama 3.2-11b-vision-instruct показал следующие результаты:
is_bill
0% |
17,12% |
0% |
82.88% |
is_mvm
51,39% |
0% |
48,61% |
0% |
Поля таблиц:
service_name |
4,20% |
service_type |
9,91% |
consumption_volume |
13,81% |
unit_of_measure |
11,11% |
price |
11,11% |
amount_due |
11,11% |
совпадения по количеству строк с данными в таблице |
73,33% |
Результаты Llama 3.2 11b нас разочаровали, фактически, они означали что модель скорее работает неправильно чем правильно. Мы пришли к выводу что дело в том, что Llama не обучается на корпусе русских слов.
Llama 3.2-90b-vision-instruct показал следующие результаты:
is_bill
0% |
17,12% |
0% |
82,88% |
is_mvm
51,39% |
0% |
48,61% |
0% |
Поля таблиц:
service_name |
24,62% |
service_type |
64,62% |
consumption_volume |
30,77% |
unit_of_measure |
3,08% |
price |
10,77% |
amount_due |
13,85% |
совпадения по количеству строк с данными в таблице |
34,29% |
Тут нас удивило количество неточных единиц измерения и только 10% точность по числам.
Few-Shot
Этот подход мы опробовали только на Llama и он дал обескураживающий результат. При работе на нескольких примерах, Llama ни разу не выдала нам JSON с данными.
Обучение
Также мы пытались провести исключительно на Llama и выяснили что арендованных нами ресурсов недостаточно для такого обучения. Доступные онлайн ресурсы (выдаются бесплатно на 12 часов) также не позволили нам провести обучение, уложившись в тайминг.
Попытка каскада
Последней нашей идеей было заставить Llama вытащить данные из документа и отправить в GPT.
is_bill
6,02% |
11,11% |
19,91% |
62,96% |
is_mvm
41,20% |
10,18% |
34,72% |
13.89% |
Поля таблиц:
service_name |
8,11% |
service_type |
50,45% |
consumption_volume |
45,05% |
unit_of_measure |
36,64% |
price |
46,25% |
amount_due |
43,24% |
совпадения по количеству строк с данными в таблице |
65,71% |
Наблюдения и выводы

-
При анализе таблиц с количеством ячеек больше 6×6 Llama теряет структуру, пропускает столбцы или данные.
-
Если документ сканирован под углом, данные съезжают, что снижает точность распознавания.
-
Повороты на 90 или 180 градусов модель переносит лучше, серьезных ошибок в таких случаях не наблюдалось.
-
Важно отметить, что Llama крайне редко выдает чистый JSON — зачастую приходится вручную извлекать данные из текста.
-
Модели с 90B параметрами работают очень медленно, до нескольких минут на обработку одного скана (на одном T4). Модели поменьше показывают лучшую скорость.
-
Проблема с бинарными параметрами. Модели часто выдают «Да» или «Нет» независимо от контента запроса (в таблице помечены “*”), что делает результаты с таким ответом ненадёжными.
Все это пока не позволяет говорить о том что VLM может заменить качественный OCR, однако, трудозатраты на создание чего-то подобного OCR на базе VLM значительно ниже, осталось только дождаться когда появятся достойные локальные модели.
К моменту окончания наших тестов уже появилась Llama 3.3 (LLM-вариант), однако мультимодальная линейка обновлений пока не получила. Другие VLM в открытом доступе часто основаны на Llama и показывают ещё более слабые результаты.
Из всего этого можно сделать вывод, что профильные OCR-системы (пусть и со своими ограничениями) пока дают более предсказуемое качество распознавания актов, счетов и УПД, чем VLM-модели, особенно в single-shot режиме без обучения.
Тем не менее, мы продолжаем следить за развитием VLM и не исключаем, что в будущем мультимодальные модели станут удобным и универсальным решением для задач OCR, обеспечивая более высокую точность и гибкость в обработке документов.
Автор: mvideo