Мне трэба было, каб моўная мадэль чытала лісты з рахункамі і аддавала 5 палёў у JSON, ад адпраўніка да тэрміну аплаты. У GPT-2 large з 3 прыкладамі ў промпце каля паловы адказаў не праходзіла схему. Відавочнае рашэнне было не даваць мадэлі пісаць нічога, чаго схема не дапускае, як гэта робіць PICARD для SQL. План быў просты: калі адказ праходзіць jsonschema, ён ідзе ў табліцу. Што гэты план насамрэч прапускае, я не мераў, таму памераў.
Пастаноўка
Я згенераваў скрыптам 150 кароткіх лістоў, seed 2022, так што кожны правільны адказ вядомы. Частка палёў прапушчана наўмысна. У 53 лістах няма нумара рахунку да аплаты, але ёсць нумар заказу. У 51 няма тэрміну аплаты, але ёсць дата выстаўлення рахунку. Яшчэ ў 40 не пазначана валюта. Сума запісана ў 4 выглядах:
Amount to pay: USD 11,113.79.
Amount to pay: 1 951,01 USD.
Amount to pay: €15060.75.
Amount to pay: 16 128,82 EUR.
У схеме 5 абавязковых ключоў, сума гэта лік, валюта гэта адзін з 3 кодаў (PLN, EUR або USD), тэрмін аплаты супадае з ^\d{4}-\d{2}-\d{2}$. Нумар рахунку, валюта і тэрмін аплаты могуць быць null. Промпт кажа “Use null when a field is not in the text”, а ў адным з 3 прыкладаў null стаіць у 3 палях.
Я ўзяў GPT-2 large (774M параметраў) і запускаў яе на 4 ядрах працэсара віртуальнай машыны: Python 3.9, torch 1.11, transformers 4.18, jsonschema 4.4, прагнае дэкадаванне. transformers 4.18 загружае ўвесь файл вагаў у памяць побач з мадэллю. Мой ліміт у 5 ГіБ гэтага не дазваляў, таму вагі я загружаю часткамі. На лічбы гэта ніяк не ўплывае, але каштавала мне 2 забітыя прагоны, пакуль я не зразумеў, у чым справа.
Ліст лічыцца правільным, толькі калі правільныя ўсе 5 палёў. Поле, якога няма, правільнае толькі як null.
Вольная генерацыя
Тут мадэль піша JSON сама, потым json.loads і праверка схемай. Схему праходзяць 74 адказы са 150. Усе 5 палёў правільныя ў 12 з іх.
58 з 76 зламаных адказаў падаюць з адной прычыны. Мадэль капіюе суму так, як яна надрукаваная: "amount": 11,113.79 або "amount": 1 951,01. JSON спыняецца на косцы або на прабеле. Астатнія 18 пішуць тэрмін аплаты як "10.04.2022", а такое шаблон не прапускае. Абедзве адмовы гучныя: разбор спыняецца і я ведаю, што ліст трэба паглядзець.
Што да null, указанне амаль нічога не дало. На 150 адказаў мадэль напісала null адзін раз.
Дэкадаванне па схеме
Другі прагон ідзе на той самай мадэлі і з тым самым промптам. Цяпер ключы, коскі і дужкі піша мой код, а мадэль выбірае толькі самі значэнні. На кожным кроку я сартую токены па ацэнцы мадэлі. Потым бяру першы, пасля якога значэнне ўсё яшчэ застаецца дапушчальным пачаткам свайго тыпу. У ліку могуць быць лічбы і адна кропка. Дата можа расці толькі да выгляду 2022-04-10. Для валюты застаюцца толькі 3 коды.
Першая версія заканчвала лік толькі на косцы або на закрывальнай дужцы. У лісце пасля сумы ідзе прабел або кропка, там тое і другое было забаронена, таму мадэль дапісвала лічбы да майго ліміту ў 40 токенаў: 15060.750000100000000080000... на кожным з 32 лістоў пробнага прагону на GPT-2 medium. JSON дапускае прабел пасля значэння, так што цяпер лік заканчваецца і на прабеле. Я даволі доўга думаў, што гэта мадэль. Гэта была мая граматыка.
З выпраўленым дэкодэрам схему праходзяць усе 150 адказаў. Усе 5 палёў правільныя ў 11, на адзін менш, чым без яго.
Куды падзеліся гучныя адмовы
Усе 76 лістоў, якія ў вольнай генерацыі падалі гучна, цяпер праходзяць схему і няправільныя. Ніводзін з іх не стаў правільным.
Значную частку тлумачаць сумы. GPT-2 пачынае капіяваць 11,113.79 гэтак жа, як раней. Пасля 11 лепшы токен, які прапускае граматыка, пачынаецца з коскі. У JSON коска заканчвае лік. Значэнне атрымліваецца 11. З 1 951,01 лік заканчвае прабел, значэнне 1. Няправільных сум 62. У 56 з іх стаяць лічбы да першага прабелу або коскі. Вольная генерацыя давала на 55 з гэтых 56 лістоў зламаны JSON. У астатнім адным яна напісала 668.29, гэта правільна. Дэкодэр зрабіў з гэтага 668.
З датамі тое самае. У 25 лістах тэрмін аплаты надрукаваны як 10.04.2022. Мадэль пачынае з 10, як у лісце, шаблону да злучка трэба яшчэ 2 лічбы і адказ атрымліваецца 1034-04-20. Ніводная з гэтых 25 дат не правільная. 21 з іх у гадах накшталт 1034, 2603 і 0501, астатнія 4 гэта даты з прыкладаў у промпце. У jsonschema ёсць праверка format: date, па змаўчанні выключаная. Я яе ўключыў. Яна адхіліла 4 адказы са 150, з месяцам 17, 20, 24 або 26. 2603-04-16 гэта сапраўдная дата, таму яна праходзіць.
Што трапляе ў поле, якога няма
Кожны ключ мусіць быць на месцы, а гэты дэкодэр ніколі не прапануе null, так што поле, якога няма, чымсьці запаўняецца. І запаўняецца яно не выпадкова:
- нумар рахунку: нумар заказу ў 51 лісце з 53, нумар заказу з прэфіксам у астатніх 2;
- валюта: EUR ва ўсіх 40;
- тэрмін аплаты: дата з прыкладаў у промпце ў 17 з 51, дата выстаўлення ў 14, дата не ў 2022 годзе ў 15, нейкая іншая дата ў 5.
Кожнае з гэтых значэнняў праходзіць схему, большасць выглядае правільна. Нумар заказу ў полі нумара рахунку гэта лік з 4 або 5 лічбаў з таго самага ліста. Мая праверка схемай яго б не злавіла.
Дазволіць null
Трэці прагон гэта той самы дэкодэр, але 3 палі, якіх можа не быць, могуць яшчэ і быць null. Мадэль выбрала null 9 разоў з 450 палёў, дзе магла. 4 разы на палях, якіх сапраўды няма, са 144. Астатнія 5 на палях, якія ў лісце былі. Усе 5 палёў правільныя ў 10 са 150, на адзін менш, чым без null.
Мадэль меншая
Тыя ж 3 прагоны я зрабіў на GPT-2 medium (355M параметраў). У вольнай генерацыі схему праходзяць 52 адказы са 150, усе 5 палёў правільныя ў 1. Дэкадаванне па схеме праходзіць у 149, а апошняя адмова на маім сумленні: нумар рахунку ў ім пайшоў KOW-S-S-S-... да майго ліміту ў 40 токенаў. Правільных лістоў 2. З 98 гучных адмоў 95 сталі ціхімі і 2 сталі правільнымі, так што гэтай мадэлі дэкодэр дапамог, на 1 ліст. Калі null дазволены, яна не выбрала яго ніводнага разу і дала тыя самыя 150 адказаў, што і без яго. Нумар рахунку, якога няма, у яе часцей за ўсё гэта нумар заказу з літарамі з імя адпраўніка наперадзе, накшталт ORZ-34592, у 36 лістах з 53.
Што я з гэтага вынес
Да гэтага праверка схемай была для мяне апошнім крокам перад табліцай. Яна правярае форму значэння. Адкуль значэнне ўзялося, яна не ведае. З GPT-2 large дэкодэр зрабіў горш: выпадкі, дзе разбор спыняўся і я б паглядзеў вачыма, ён ператварыў у радкі, якія выглядаюць нармальна.
Для гэтай задачы я б хутчэй пакінуў вольную генерацыю з яе гучнымі адмовамі і потым звяраў кожнае значэнне з самім лістом, але такую праверку я яшчэ не мераў.
Чаго я не правяраў
- Большыя мадэлі. GPT-2 large для такой задачы малая. Як падзяліліся б гэтыя 76 у GPT-3, я не ведаю, яе я не выклікаў.
- Beam search, сэмплінг або промпт без прыкладаў.
- Граматыка, якая дапускае коску ўнутры ліку і потым яе выпраўляе. Яна выратавала б
11,113.79, але нумар заказу ўсё роўна не выратавала б. - Кошт. Дэкадаванне па схеме тут не было павольнейшым, у сярэднім 16 секунд на ліст супраць 19 у вольнай генерацыі, але гэта час аднаго прагону на агульнай машыне.
- Сапраўдныя рахункі. Гэта 150 згенераваных лістоў па адным шаблоне, з 4 фарматамі сумы і 3 фарматамі даты.