Нотатки з поля · три дні роботи
Чиста реалізація найстарішого трюку Punto Switcher — зроблена за три дні, спостерігаючи, що воно робить, і жодного разу не торкаючись його коду; а потім бенчмарки показали, де я помилявся.
Проблема
Ти сідаєш написати привіт, українська розкладка не ввімкнена — і клавіші виходять як ghbdsn. Або хочеш hello, а отримуєш руддщ. Якщо ти пишеш двома мовами, це трапляється сто разів на день, і щоразу треба зупинитися, стерти, перемкнути, набрати заново.
Punto Switcher — багаторічний інструмент Яндекса — зробив собі ім’я саме на тому, що виправляє це за тебе: стежить за клавішами, впізнає абракадабру, тихо перенабирає її в іншій розкладці. Я хотів те саме для української — але без інструмента, який іде в комплекті. Тож зробив Upyr: упир — це той, хто повернувся з мертвих, а це приблизно те, що застосунок робить із твоїм понівеченим текстом. Rust, чиста реалізація, жодного рядка спільного з Punto.
Поміняти літери — це легка частина: QWERTY та ЙЦУКЕН — це фіксовані позиційні карти, пошук на п’ятдесят рядків. Складне — зрозуміти, коли їх міняти. Кожне завершене слово — це «так» чи «ні»: лишити чи переписати? Перепишеш слово, яке людина справді мала на увазі, — і ти щойно спотворив її текст. Саме через це видаляють застосунки. Пропущене виправлення — це знизане плече; хибне виправлення — це зрада. Тому весь дизайн схиляється до одного правила: сумніваєшся — не роби нічого.
Як воно вийшло
Уся історія — просто в логу комітів. Кожен вузол нижче — реальний коміт, а гранатові — це моменти, коли щось клацнуло, або коли бенчмарк упіймав мене на помилці.
Дизайн, відтворений із поведінки
Punto ніде не каже, як воно вирішує, тож я просто дивився, що воно робить — клавіші на вході, виправлення на виході — доки крізь це не проступила форма. Це два шари, і Upyr копіює обидва. Коротка таблиця жорстких правил має перше слово й може скасувати будь-що. Усе інше йде до статистичної моделі, яка радо каже «не знаю» й мовчить. Ту таблицю правил узято прямо з Punto’вого triggers.dat — мій вихідний файл прямо зізнається в цьому в коментарях.
Два шари роблять різну роботу. Модель обережна й не вгадуватиме. Правила — для тієї жменьки випадків, у яких я вже впевнений, зокрема для коротких частих слів, які модель навмисно пропускає.
Чим було розбирання — а чим ні
Слушне запитання до всього, що зветься «розбиранням»: із чого Punto зроблено і чим його розкрили? Ось що задокументовано публічно — і чесний опис методу.
Punto Switcher — програма для Windows (punto.exe) від Яндекса. Розкладку вона обирає за словником у кілька мільйонів слів плюс правилом «неможливого поєднання» — російське слово не може починатися з певних літер, тож їхня низка викриває не ту розкладку — і перевіряє це на кожен пробіл, Enter чи Tab. Її стан живе в кількох двійкових, не-текстових файлах .dat:
| Файл | Роль |
|---|---|
Data/triggers.dat | правила-тригери перемикання — саме цей файл вихідник проєкту називає своєю моделлю |
Data/ps.dat | основний мовний словник (той список у кілька мільйонів слів) |
Data/translit.dat | таблиця транслітерації |
User Data/replace.dat | автозаміна / розгортання скорочень |
User Data/user.dic | користувацький словник винятків |
diary.dat | щоденник натискань |
Публічна структура файлів, зі сторонніх оглядів — а не з інспекції коду Punto. Файли даних — зашифрований двійковий формат; навіть список автозаміни не відкриється в текстовому редакторі.
diary.dat — чати в месенджерах, пошукові запити, паролі відкритим текстом — а програма тримає власне мережеве з’єднання; саме тому незалежні огляди вважають її кейлогером, а деякі антивіруси позначають збірку як PUA. Не побічний ризик: це поставлена, задокументована функція.
Ті файли .dat — зашифровані двійкові блоби. Upyr їх ніколи не зламував: жодного дизасемблера, жодного розпакованого ps.dat, жодного словника, знятого з Яндекса. Метод був чорноскриньковий: подаєш послідовності клавіш, дивишся на виправлення на виході — і відтворюєш архітектуру, яку ці файли передбачають, а не їхній вміст. У цьому весь сенс «чистої реалізації» — і тому словників Punto ти ніде в цьому проєкті не знайдеш. Відтворено було форму:
triggers.dat → таблиця з 12 правил, написана з нуля;ps.dat на кілька мільйонів слів → зовсім не великий словник, а знаковий n-грам на 2.8 MiB плюс стоп-список із 296 слів — навмисно інша, статистична відповідь на те саме питання;diary.dat → нічого. Свідомо. Та єдина функція Punto, якої Upyr найбільше хотів не мати.Вивчати публічну структуру файлів і видиму поведінку програми — це геть інше, ніж копіювати її код; і саме суворе дотримання першого дає Upyr не мати з Punto нічого спільного, водночас у неї навчаючись. Джерела про внутрішню будову Punto наведені внизу сторінки.
Ідея, що перекроїла дизайн
Мій перший оцінювач ставив очевидне запитання: «на що ghbdsn більше схоже — на англійське чи на українське?» Це вже неправильне запитання, і чотири символи показали мені чому.
Прочитай рядок [ks, символ за символом — і побачиш дужку-літеру-літеру-кому: засмічений пунктуацією мотлох, який ти б нізащо не чіпав. Але це звичайне слово. Тому я перестав оцінювати літери на екрані й почав оцінювати клавіші під ними: Upyr відстежує фізичні позиції (ті самі назви, що браузер дає в KeyboardEvent.code) і питає модель про слово, яке ці клавіші утворили б в іншій розкладці. Саме ця зміна дає змогу [ks, → хліб спрацювати, тоді як справжнє Jkmuf, → Ольга, зберігає свою кому.
Висновки
За поведінкою стежать два бенчмарки, і в обох списки слів заморожені через SHA-256 — тож числа відтворювані, а не «на око». Головні цифри:
Точність (precision) лише каже, що я не ламаю добрий текст. Про повноту (recall) — як часто я справді виправляю зламаний — вона мовчить. Тож другий бенчмарк набирає 1 200 українських і 1 000 англійських слів на неправильній розкладці й проганяє реальний застосунок по них. Ось повнота за замовчуванням, розбита за довжиною слова:
Рівні 0% на коротких словах — і це задум. За min_word_length = 4 найчастіші слова мови (не, як, на, що) не чіпаються взагалі. Це найбільша діра в повноті — і вона навмисна: у трьох символах замало сигналу, щоб подолати планку, поставлену досить високо, аби бути безпечною.
Перемкни на агресивні пороги — без жодної зміни моделі — і повнота підскакує. Тож та чверть українських слів, яку пропускає режим за замовчуванням, — це не стеля, а рішення «точність важливіша за повноту», яке можна викрутити.
92% / 99% лежать напоготові — щойно я вирішу, що ризик того вартий. Слова, які я «пропускав», були не стіною, об яку я вдарився, а налаштуванням, яке я сам обрав. Ручка, яку можна крутнути, щоразу краща за модель, яку довелося б перетреновувати.
На старті я припускав: «більший корпус → краща модель». Історія каже інакше. Виграш дало налаштування ємності та квантування, а не обсяг.
Кинути в неї у 10× більше тексту зробило її гіршою. Я б цього нізащо не впіймав без фіксованого бенчмарку, проти якого можна міняти по одній речі за раз — і саме тому я написав бенчмарк перед «очевидним» апгрейдом, а не після того, як він мене вкусив. Окрема відкладена вибірка з 20 000 слів Вікіпедії (навмисно інший тип тексту, ніж тренувальні дані) тоді повернула 0 хибних виправлень — і виявила чотири справжні українські слова, що зіштовхнулися з англійськими; тепер вони закріплені як регресійні тести, щоб не могли повернутися.
Висновки → впровадження
Бенчмарк, який не змінює код, — це просто гарне число. Кожен із цих таки змінив — ось коміт для кожного.
| Що виявив бенчмарк | Що змінилося і де |
|---|---|
| Символьна модель не читає фізичні клавіші пунктуації як літери ([ks, = хліб) | Оцінювання перейшло в простір фізичних клавіш, незалежний від розкладки; оцінювач міряє задуманий кандидат.fix: recognize mistyped Ukrainian bracket words · fix: honor Ukrainian physical punctuation keys |
| Власні назви з комою в кінці спотворювалися (Jkmuf,) | Спершу оцінюється кандидат зі збереженою пунктуацією, потім звичайне перетворення.fix: handle names and terminal punctuation |
| Повнота за замовч. — рівні 0% на коротких словах, найчастіших у мові | Шар детермінованих тригерів дає коротким певним послідовностям шлях в обхід порога впевненості моделі.perf + trigger layer + recall benchmark |
| Чиста вибірка виявила чотири колізії з українськими словами (зокрема дупу проти фізичного lege) | Пом’якшення пунктуації зробили одностороннім; неоднозначні пари тепер вимагають додаткового пів-запасу. Усі чотири закріплені як регресійні випадки.perf + trigger layer + recall benchmark |
| Наївний корпус у 10× дав гірший результат (88/90) | Відбір налаштував ємність + силу квантування, а не обсяг — 1M / 1.75× / 32 дав 90/90 без жодної помилки на чистій вибірці.feat: train compact language n-gram model |
Чому ця реалізація краща — і в чому саме
Скажу чесно: Punto — двадцятирічний продукт, який уміє куди більше за це, і він випустив цей трюк задовго до мене. Я роблю вузьке твердження: для завдання «англійська ↔ українська», за рядками нижче, моя версія сильніша. І кожну клітинку Upyr можна піти й перевірити самому — у цьому вся суть.
| Вимір | Punto Switcher | Upyr |
|---|---|---|
| Мови | Насамперед російська | EN ↔ UK повноцінно, зокрема український ряд пунктуації [];'\,./ |
| Платформи | Лише Windows | macOS основна; Windows / Linux у превʼю; і той самий рушій у браузері через WASM |
| Приватність під час роботи | Закрита; постачає «щоденник» натискань, що записує набране й вивантажує на Яндекс | Жодної телеметрії, жодного HTTP-клієнта в застосунку — це стереже check_privacy.py у CI |
| Безпека памʼяті | Нативна, доба до Rust | Rust наскрізь |
| Докази коректності | Не опубліковані | Точність 1.000 на 191 межовому випадку; 0 / 20 000 на чистій вибірці; корпуси закріплені через SHA-256, відтворюються однією командою |
| Ланцюг постачання | Пропрієтарний бінарник | MIT; CycloneDX SBOM, безключові підписи Cosign, SHA256SUMS, атестації походження |
| Модель | Непрозора | Знаковий n-грам на 173,964 записи, ~2.8 MiB, пошук двійковим поділом, повністю задокументований |
Чесні межі: це «краще за цими осями для цієї пари мов», а не «кращий продукт». Широта функцій, охоплення мов і два десятиліття загартовування на крайніх випадках усе ще належать Punto.
Що це таке, а що ні