Привет, Хабр! Меня зовут Эдуард. В прошлой статье я рассказывал про локальный переводчик и синтез голоса на кабардинском. С тех пор проект вырос: я делаю приложение на iOS для чтения с синтезом и распознаванием речи на кабардинском и ещё паре десятков «обделённых» языков. Чтобы приложение было умным (ранжировало важные предложения, искало по смыслу, вело чат по книге, читало с выразительной интонацией), ему нужен полный набор языковых сигналов: лемма, часть речи, именованные сущности, тональность и эмбеддинги.
Для английского, немецкого и испанского всё это Apple даёт из коробки. Для кабардинского — ноль по всем пяти пунктам.
Прежде чем лезть в дебри, я решил спросить у Gemini Pro, насколько это вообще реально. Ответ меня обескуражил.
Приговор
Дословно (сокращаю, но дух передаю точно):
Кабардинский язык — это «финальный босс» мировой лингвистики. Написать для него «наивный» стеммер или POS-таггер на правилах абсолютно невозможно.
И три аргумента, каждый из которых чистая правда.
1. Полисинтетизм на стероидах. Кабардинский глагол похож на поезд, где вагоны цепляются и спереди, и сзади, и в середине. Одно слово умыгъэпӏащӏэ означает «не заставляй его торопиться»: внутри и «ты», и отрицание «не», и каузатив «заставлять», и корень «торопиться».
2. Однобуквенные корни (консонантный кошмар). В кабардинском около 48 согласных и почти нет гласных в корнях. Многие корни — это одна согласная: п варить, т давать, щ делать или продавать. Gemini рисует апокалипсис:
Если вы напишете наивный стеммер, который «разденет» слово до корня, он оставит вам токен
п. Ваш TF-IDF решит, что половина базы посвящена кулинарии.
3. Эргативный строй. Окончание существительного меняется в зависимости от переходности глагола.
Вывод Gemini: никакой наивности, только сложнейшие конечные автоматы (FST) в профессиональных лабораториях. «Кабардинский оставляем нейросетям».
Красиво. Убедительно. И, как выяснилось, обжалуемо по каждому пункту. Вызов принят.
Козырь в рукаве: словари
К этому моменту у меня уже был отработанный на латышском, украинском и белорусском подход: словарь как основа плюс наивный стеммер плюс мосты через глоссы. Идея простая: если у языка есть словарь, половину NLP можно собрать без единого GPU, на хеш-таблицах и правилах.
А для кабардинского у меня, носителя, лежали сокровища, которых нет в открытых датасетах:
-
kabrudict— кабардино-русский словарь, 32 700 статей; -
kaborfodict— орфографический словарь, 138 000 словоформ; -
kabmorfodict— морфемный словарь (приставки, суффиксы, корни).
В машинном Wiktionary (Kaikki) кабардинского жалкие 1.8 МБ. А тут целая языковая база. Осталось её оживить.
Пункт обвинения №1: «наивный стеммер сойдёт с ума»
Gemini прав: если раздевать слово до корня, получишь п и кулинарный апокалипсис. Поэтому мы не раздеваем до корня.
Ключевая идея: лемма это не голый корень, а словарный инфинитив на -н, и превербы (те-, дэ-, къэ-, щӏэ- и прочие) в нём уже сидят. тесын, къищтэн — это цельные словарные слова, а не буква. Дальше по шагам:
-
собираем инвентарь из 81 312 лемм (kaborfodict плюс kabrudict), из них 38 442 глагольных инфинитива;
-
у словоформы снимаем только словоизменение (падеж, число, финитные суффиксы
-щ/-ащ/-т/-къым) и личные/динамические префиксы (сы-, у-, е-, йо-, о-, що-); -
а превербы не трогаем и достраиваем инфинитив обратно, проверяя по словарю.
# упрощённо: восстановление глаголаdef as_verb(stem, verbs): if stem in verbs: return stem for inf in ("ын", "эн", "ун", "н"): if stem + inf in verbs: # тхы + н -> тхын return stem + inf for p in PERSONAL_PREFIXES: # снять е-/йо-/о-/що- if stem.startswith(p): b = stem[len(p):] for inf in ("ын", "эн", "ун", "н"): if b + inf in verbs: return b + inf return None
Токен-лемма получается настоящим словом, а не согласной. тӏысыж, къищтащ, щоджэгу схлопываются в тӏысыжын, къищтэн, джэгуэн. Кулинарный апокалипсис отменяется.
Часть речи вышла почти даром: в кабардино-русском словаре у слов есть русские пометы (гл., прил., нареч.), инфинитив на -н это глагол, всё остальное по умолчанию существительное.
Я ставил себе честную планку в 75%. Прогнал на двух реальных народных сказках:
-
«Бдзэжьеящэмрэ бдзэжьеймрэ» (Рыбак и рыбка), 669 слов — 77% слов получили лемму и часть речи;
-
«Шырыкъу зыщыгъ джэду» (Кот в сапогах), 1132 слова — 73%.
В среднем около 75%, ровно вокруг планки. И это с оговоркой в мою же сторону: сказки я записывал на слух, и сам текст не идеально грамотный. По вычитанному тексту было бы выше.
Что осталось в непокрытых? Две вещи. Первая — сложные глаголы: къыфӏэрыхьэнущ, къыретыж, сыӏутӏыпщыжыну. Тот самый полисинтетический хвост с гроздью префиксов и суффиксов вокруг корня. Именные слова разбираются почти полностью, а глагол остаётся последним рубежом. Вторая — имена собственные: во втором тексте верхушку «промахов» заняли маркиз и Къэрэбас, персонажи сказки. Их в словаре нет и быть не должно, это отдельная задача (о ней ниже). Но 77% и 73% против «абсолютно невозможно» это, согласитесь, другой разговор.
Детективная деталь: палочка, которая ломала всё
Отдельная засада, на которую я потратил вечер. В кабардинском есть буква «палочка» (Ӏ). В моём словаре она записана латинской I, а в реальных текстах люди печатают кириллической ӏ (U+04CF), потому что стандартной кабардинской раскладки в России до сих пор нет, и каждый выкручивается как может (I, l, ӏ, |).
Результат: фӏы из текста и фIы из словаря это разные строки, совпадений ноль. Пока не нормализуешь всё к одной палочке, весь стек молча выдаёт пустоту. Одна строчка нормализации, и покрытие взлетает.
И тут же вылез приятный побочный эффект. Когда я прогнал сказку, словарь начал ловить мои ошибки, которых я, записывая на слух, даже не замечал. В тексте я писал бдзэжэй, а словарная форма бдзэжьей (рыбка) — с мягким знаком. Получился дефолтный спелл-чекер для языка, у которого спелл-чекера нет: слово не нашлось точно, но нашлось при снятии выпавшего ь и вариации э/е, значит вот правильное написание.
Правда, тут же выяснилось, где эта наивность опасна. Слепо править нельзя: хы (море) и хьы это разные слова, тхэ и Тхьэ (Бог) тоже. Автокоррекция запросто превратит «Бога» в опечатку. Поэтому словарь у меня работает как ассистент вычитки: предлагает исправление, а решает носитель. Для языка без единого автокорректора даже это уже полезный инструмент.
Пункт обвинения №2: тональность языка без модели тональности
Sentiment-модели для кабардинского не существует и в ближайшее время наверно не будет. Но она и не нужна, если построить мост.
Логика такая. Есть английский словарь валентности Warriner et al., 14 000 слов с оценкой «плохо — хорошо» по шкале 1-9. Дальше двухходовка:
-
Английский в русский. Беру русские слова из Wiktionary, у каждого есть английское определение (глосс). Оцениваю слова глосса по Warriner и получаю валентность русской леммы. Так родилась таблица
ru-sentна 40 000 русских слов. -
Русский в кабардинский. Беру кабардинское слово, нахожу его русский перевод в
kabrudict, оцениваю поru-sent. Получаю валентность кабардинского слова.
Итоговый перенос весов: английский, затем русский, затем кабардинский. Чистая арифметика по хеш-таблицам. Ни одной нейросети, ни одного вызова переводчика в рантайме.
Проверяем на живых словах:
зауэ (война) -2.91уз (боль, болезнь) -1.96шынэ (страх) -2.64лъагъуныгъэ (любовь) +2.35насып (счастье) +1.81узыншагъэ (здоровье) +1.25
Знаки на месте. 81% статей словаря получили тональность.
Этот тональный словарь я выложил в открытый доступ: 26 663 леммы, лицензия CC BY-SA 4.0, с примером использования — github.com/kubataba/kbd-sentiment. Насколько знаю, это первый открытый sentiment-ресурс для кабардинского. Забирайте, сравнивайте тон предложений, дополняйте. Вот как это выглядит на предложении:
$ python3 example.py "лъагъуныгъэрэ мамырыгъэрэ" лъагъуныгъэрэ 2.35 мамырыгъэрэ 1.22средняя валентность = +1.79 -> позитив (2 слова оценено)
Пункт обвинения №3: смысловой поиск без эмбеддингов
Последний рубеж — эмбеддинги (векторы смысла) для семантического поиска и сегментации. У Apple есть встроенная модель NLContextualEmbedding, но кабардинского в ней нет.
Хитрость в том, что модель скрипт-зависимая, а не язык-зависимая. Кириллица это русская модель. Что если скормить ей кабардинский текст напрямую? Проверил — не работает (то же самое было с белорусским). По дороге, кстати, вскрылись два неочевидных факта:
-
Сырой косинус бесполезен. У контекстных эмбеддингов анизотропия: всё похоже на всё с косинусом около 0.9. Судить можно только по центрированному косинусу (вычесть общий вектор). Это переворачивает результаты с ног на голову.
-
NLContextualEmbeddingигнорирует подсказку языка внутри одного скрипта. Три разных варианта дали бит-в-бит одинаковые числа.
Раз напрямую нельзя, снова мост через русский. Кабардинское предложение, затем русские глоссы каждого слова, затем эмбеддим русский текст русской моделью Apple. Тест на 6 тройках предложений (похожее против несвязанного):
Kabardian (Russian gloss pivot): CENTERED 5/6 pass margin +0.270 -> PASS
+0.270 это на уровне украинского (+0.321) и латышского (+0.221), которые работают нативно. И это с грубым стеммером и 75% покрытия, то есть пол, а не потолок.
Что получилось
Язык, который ИИ назвал открытым космосом, за несколько вечеров получил полноценную обработку, причём на данных, которые у меня уже были:
|
Сигнал |
Кабардинский |
Как |
|---|---|---|
|
Лемма и часть речи |
75% (две сказки ~2К слов) |
словарь 81k лемм плюс стеммер на инфинитив |
|
Тональность |
81% статей |
мост англ, рус, кбд по Warriner |
|
Эмбеддинги |
+0.270 (PASS) |
пивот через русские глоссы плюс модель кириллицы Apple |
Ни обучения, ни GPU, ни FST-лабораторий. Только эмбеддинги кириллицы от Apple, английский словарь валентности, переброшенный в русский и кабардинский, и старые добрые словари морфем.
Мораль, которая работает не только для кабардинского: малый язык можно дотянуть через высокоресурсного родственника, на которого есть двуязычный словарь. Русский дал и таблицу тональности, и модель эмбеддингов. Тот же приём применим к десяткам языков со словарями, но без своих моделей.
Чего пока нет: NER
Честный клиффхэнгер. Один сигнал из пяти ещё не сдался — распознавание имён (NER). Я его уже увидел вживую: в «Коте в сапогах» верхние строчки «непонятых» слов заняли маркиз и Къэрэбас. Это не ошибка морфологии, а именно имена, которых нет ни в одном словаре и быть не должно, но на них завязаны и поиск, и «важность» предложений. План простой: наивный газеттир, где слово с большой буквы, которого нет среди обычных слов, это кандидат в имя, а имена собственные из kabrudict дадут позитивный список. Это тема следующей серии.
Вместо заключения
Я не лингвист и не обучал нейросетей для этой задачи. Я просто не поверил, что мой родной язык «нерешаемый», и проверил каждый аргумент руками. Оказалось, «невозможно» часто значит «никто пока не приложил словарь».
Если вы носитель северокавказского языка и у вас есть словари (морфемные, двуязычные, орфографические), напишите, буду рад помочь оживить и ваш язык тем же методом. И, как в прошлый раз, зову энтузиастов кабардинского в проект записи Common Voice: без данных даже самый элегантный стек мёртв.
P.S. Отдельное спасибо Gemini Pro за приговор. Без него не было бы такого азарта его обжаловать.
ссылка на оригинал статьи https://habr.com/ru/articles/1060378/