Через 2 недели после выхода ChatGPT первое, что мне захотелось попробовать, это вытащить моделью сумму из платёжного документа. Я хотел увидеть, как выглядит ошибка внутри конвейера, потому что там ответы никто не читает и видят их только проверки. У ChatGPT пока нет API. Его токенов я не вижу. Поэтому я взял Flan-T5, модель Google, которую можно запустить на своей машине.
Стенд нарочно скучный. Модель обёрнута в маленький HTTP-сервер, который возвращает ответ в поле generated_text, как Hugging Face Inference API. Код 500 он возвращает, только если Python бросил исключение. Мой клиент отправляет документ и вопрос, а в конце строку “Answer with the amount only”. Потом проверяет то, что я проверил бы у любого внутреннего сервиса: статус 200, тело это JSON, ответ разбирается как сумма. Эти 3 проверки я написал первыми, не задумываясь. Только потом понял, что неверное число проходит все 3.
Версии, потому что от них зависят числа: Python 3.9, torch 1.13.1 на CPU, transformers 4.25.1, жадное декодирование. У Flan-T5 base 250M параметров, у large 780M, веса у обеих те, что выложены 21 октября. Документов 600, они синтетические и сделаны скриптом с фиксированным сидом, так что правильный ответ я знаю для каждого. Видов 6, по 100 каждого. Это счёт, счёт с задолженностью за прошлый период, подтверждение перевода, чек, письмо о возврате средств и напоминание об оплате. В половине документов числа записаны как PLN 1,284.50, в другой половине по-европейски, 1 284,50 PLN.
Дашборд
Base получила 2000 вызовов. По каждому документу я задал основной вопрос и тот же вопрос другими словами. Для 4 видов документов было ещё по 2 вопроса о других полях, они нужны для одной проверки ниже. Все 2000 вызовов вернули 200, каждый ответ удалось разобрать. Панель в Grafana для такого сервиса показала бы 0 ошибок.
Неверной оказалась 161 сумма из 600.
Первый неверный ответ, который я открыл, был по чеку с TOTAL 437,78 EUR, а модель ответила 43778 EUR. Сначала я решил, что запятую съел мой парсер, а потом, что её теряет токенизатор при декодировании. Проверил и то и другое, прежде чем поверил, что это модель. Токенизатор режет вход на ▁4, 37, ,, 78, ▁EUR, а модель сгенерировала ▁4, 37, 78, ▁EUR. Запятая на этом шаге была у неё вторым по вероятности токеном, с вероятностью 0,12 против 0,79 у 78. В итоге сумма становится в 100 раз больше. Так вышло 52 раза с правильным числом и ещё 10 раз с другим числом из документа. Все 52 случая приходятся на запись с десятичной запятой.
Класс побольше заметить труднее. 68 неверных ответов это другая сумма из того же документа, записанная как нормальная сумма, просто не та, о которой я спрашивал. В напоминании написано “Invoice 52081 dated 2022-05-02 for PLN 2,501.80 is overdue. We received PLN 500.36 from you on 2022-09-21. The remaining PLN 2,001.44 is due…” Base назвала исходную сумму счёта в 73 напоминаниях из 100, причём в 10 из них ещё и потеряла запятую. Ещё в 1 напоминании она ошиблась по-другому. Я думал, что она просто берёт первую сумму. Но в счёте с задолженностью за прошлый период сумма к оплате стоит последней строкой. Там base была права 87 раз из 100. Так что я не знаю, от чего это зависит. 27 ответов это скопированный из документа текст, чаще всего строки чека, из которых мой парсер взял первое число. В последних 4 цифры искажены как-то иначе.
Проверки, которые можно добавить без разметки
До прогона я записал, чего жду, чтобы потом не подгонять ожидания под результат. Я ждал, что проверка “есть ли эта сумма в документе” поймает меньше половины неверных ответов. Ещё я ждал, что вопрос, заданный дважды разными словами, поймает больше. Обе идеи не мои. Согласованность ответов использовали Wang et al. в марте, а в июле Kadavath et al. изучали, насколько хорошо модель знает, когда она права.
В определении моей первой проверки по документу была ошибка. Она сравнивала с любой цепочкой цифр, поэтому количество товара из строки чека проходило. Поймала она 66 из 161. Когда я оставил только суммы с 2 знаками после разделителя, она поймала 91, больше половины, так что для base моё ожидание не сбылось. Ложной тревоги эта проверка дать не может, потому что правильный ответ всегда есть в документе. Строгая проверка формата требует одно число с группами по 3 цифры и ровно 2 знака после разделителя. Она ловит почти те же ответы, 91 из 161, при 2 ложных тревогах на 439 правильных.
Двойной вопрос сработал хуже, чем я ждал. Он поймал 53 неверных ответа и пометил 105 правильных из 439. Base часто даёт 2 разных ответа, один из которых правильный. Уверенность самой модели сработала лучше, но не настолько, чтобы полагаться только на неё. Я взял средний логарифм вероятности токенов ответа и пометил 20% наименее уверенных ответов. Попались 67 неверных и 53 правильных. AUROC равен 0,76, то есть в случайном неверном ответе модель уверена меньше, чем в случайном правильном, примерно в 3 случаях из 4.
Арифметика сначала выглядела победителем. В 4 видах документов поля складываются: сумма без НДС плюс НДС даёт итог, сумма счёта плюс прошлая задолженность даёт сумму к оплате, возврат плюс удержанный сбор даёт исходное списание, оплаченное плюс остаток даёт сумму счёта. Я спрашиваю у модели значения 2 других полей и складываю их. Там, где это применимо, проверка поймала 103 неверные суммы из 103 и пометила 130 правильных из 297. Потом я посмотрел на счета. На вопрос про НДС base ни разу не дала правильного числа и 67 раз вернула сумму без НДС, так что на счетах проверка помечает всё подряд. Без счетов она поймала 87 неверных сумм из 87 при 46 ложных тревогах на 213 правильных.
Модель побольше
Large намного лучше, но в 3 раза медленнее. На 4 ядрах виртуалки с AMD EPYC 7713 у неё 1,6 секунды на вызов против 0,5. Она ошиблась в 35 суммах из 600. Снова все вызовы вернули 200 OK. Все ответы удалось разобрать. Я ждал меньше ошибок, но не ждал, что дешёвые проверки почти перестанут работать. Проверка по документу поймала 7 из 35, строгий формат 8. Двойной вопрос тоже 7, при 41 ложной тревоге. Отсечка по 20% наименее уверенных поймала 14 при 106 ложных. 28 неверных ответов из 35 это другие суммы из документа. Выглядят они в точности как правильные.
Арифметика там, где применима, работает и здесь: 21 из 21 при 8 ложных тревогах на 379, потому что large правильно ответила на 791 вопрос о полях из 800. Вместе с арифметикой проверка по документу и строгий формат ловят 25 из 35.
Остальные 10 это одна и та же ошибка. Base её ни разу не допускала. В подтверждении перевода large вместо суммы перевода называла остаток на счёте из строки “Balance after transfer”. Так было 11 раз, строгая проверка отбросила только 1 из них. У base на переводах тоже было 5 ошибок, но это были потерянные запятые и искажённые цифры, а такие ошибки проверки ловят. В моём шаблоне перевода нет остатка до перевода, поэтому никакое равенство не связывает сумму перевода с другими числами. Двойной вопрос пометил 5 из 10, уверенность 2. Для остальных нужен второй источник, например банковская выписка по этой операции.
Чего я не проверял
- ChatGPT и
text-davinci-003. Я мерил только 2 небольшие открытые модели и не знаю, теряет ли запятые намного более крупная модель. Думаю, что нет. Но нормально записанная сумма не из той строки, думаю, останется. - Настоящие документы. Мои 6 шаблонов короткие и чистые, поэтому доля ошибок по шаблону говорит о моих шаблонах. То, что base ошиблась в 74 напоминаниях из 100, скорее говорит о том, как я написал напоминание, чем о модели.
- Промпты. Одна формулировка инструкции и одна переформулировка каждого вопроса. Другая инструкция могла бы сильно поменять результат на напоминаниях.
- Повторные прогоны. Только жадное декодирование. Каждое число здесь из одного прогона. Я повторно прогнал 2 документа вне сервера и получил те же токены, но весь прогон не повторял.