Универсальный грамматический анализатор естественных языков с нуля. Выпуск 1
Компиляторы, интерпретаторы… Сколько им посвещено книг и проектов! Баста, надоело! А вот сунешся в область анализа естественных языков, и никакой информации! А все что есть как-то очень сложно, непонятно и не универсально. Была у меня идея создать средневековую лингвистическую новеллу. Чтобы можно было разговаривать с персонажами на каком нибудь древнем естественном или вымышленном языке. На Латыни например? И на Квенья. И чтобы они понимали. А почему бы и нет? Для этого всего то нужно:
1) Разработать формат описания грамматики произвольного языка. 2) Написать грамматики для Квенья и Латыни. 3) Разработать универсальный грамматический анализатор и синтаксический анализатор. 4) Сделать связь между поведением персонажей и синтаксическим анализатором.
Например, фраза «Леголас, подойди к дереву» интерпретировалась бы так:
Запускается скрипт глагола «идти», в качестве субьекта действия передается «Леголас» (поиском по тегу находим игровой объект), указывается время (императив), что без дополнительных условий ожидания заставляет субьекта действия идти в позицию объекта действия.
В этом цикле статей мы займемся разработкой грамматического анализатора с нуля до полностью стабильной версии, уже находящейся на гитхабе (ссылка в конце статьи):
1) Спроектируем архитектуру анализатора 2) Разработаем язык описания сводок грамматик (чтобы на нем могли писать обычные лингвисты) 3) Научим наш анализатор читать сводки 4) Научим анализатор на основе сводки грамматики анализировать текст
Код анализатора будет: 1) Качественный 2) Расширяемый 3) Легко поддерживаемый 4) Приятный для чтения
Анализатором можно будет пользоваться: 1) Из командной строки 2) Удаленно или локально, через RPC
Думаете будет нереально много кода? Если бы мы писали это на С++ мы бы действительно мало что успели, но в следующем выпуске, по секрету, я расскажу вам об очень приятном, кратком и лаконичном языке, на котором написать такой проект можно за 2 месяца (по вечерам). Итак, начнем!
Для начала подумаем, как вообще должен выглядеть язык описания сводок грамматик? Следуя замечательным принципам SOLID мы полностью перейдем на абстракции. Какие абстракции можно выделить в лингвистике? Я выделил целый 'лист':
L) Алфавит L (множество символов) E) Сущность E (глагол, существительное и т.д.) A) Атрибуты A (время, наклонение и т.д.) F) Правило сопоставления F (как по слову получить его характеристики)
Следовательно наш анализатор будет оперировать этими абстракциями.
Спецификация языка «Ололо».В репозитории имеются сводки для языков Quenya (хорошо проработанная) и для Lingua Latina (самая малость). Но мы будем писать сводку для простого вымышленного инопланетного языка «Ололо». Найти можно там же. Открываем спецификацию etc/al/tpl:
0) Алфавит. 0.1) Глассные (a, o, u). 0.2) Согласные (l).
1) У нас есть два глагола (ololoo, olalaa). 1.1) Настоящее время образуется сокращением последней гласной (ololo, olala). 1.2) Прошедшее время образуется заменой начальной гласной на любую другую (ulolloo, alallaa).
2) У нас одно существительное (ll, lol, lool, luol, . ) без склонений.
3) У нас два предлога (ao, oa). 3.1) Смежные предлоги в тексте склеиваются (ao oa -> aooa).
4) У нас два прилагательных (lo, ol) разных склонений. 4.1) Множественное число (lo -> lola, ol -> ola) соответственно. 4.2) Суперлатив (lo -> allo, ol -> alol). 4.3) Компаратив только для одного склонения (lo -> alo, ulo, olo). 4.4) Множественное число только для одного склонения (lo -> lololo).
Конкурс. Учимся поэзии инопланетян.ll ololo — Простое предложение (1 грамматическая основа). lol ulalaa oa lul olala allololo — Сложное предложение (2 грамматических основ).
Конкурс: придумайте начальный перевод приведенным словарным формам и напишите самое красивое и звучное восьмеростишье на языке «Ололо» (соблюдая все правила описанной выше грамматики). Предложение действует в течение 2х недель с момента публикации статьи. Ответы пишите в комментариях и обязательно дублируйте на адрес apborezkiy@gmail.com, указывая тему «ACC #1». Победитель будет объявлен в последующих статьях и получит бесплатную Skype консультацию на любую из тем в которых я могу быть чем-то полезен.
Разработка через тестирование.Используя принцип TDD сначала подготовим тестовый материал для нашего анализатора.
Забегая вперед, скажу, что мы научим наш анализатор на выходе выдавать вот такой вот подробный анализ:
Числа означают цепочку правил (горизонтальных и вертикальных). По ней очень удобно отследить последовательность анализа.
Пишем нашу первую сводку. АлфавитТак что будет содержать язык описания сводок? Во-первых, алфавит в стиле ООП. Зарезервированными лексемами являются: 1) .alphabet — Указывает на начало описания алфавита 2) .base — Указывает на родительский алфавит 3) = ""
Иерархия лингвистических сущностейВо-вторых, описание сущностей (частей речи) и их аттрибутов. Зарезервированными лексемами являются: 1) .attribute — Указывает на начало описания атрибута. 2) .class — Указывает на начало описание сущности. 3) = ""
Цифра после имени атрибута будет обозначать порядок вывода атрибута в выходном файле (для наглядности).
СловарьПора ввести словарик:
Зарезервированными лексемами являются: 1) .vocabulary — Указывает на начало описания словаря. 2)
Здесь каждой словарной записи сопоставляется сущность и набор атрибутов. Помните нашу волшебную формулу?
Подстановочные знакиЕще нужно ввести подстановочные символы (алиасы), которые будут использоваться в мутациях и правилах сопоставления.
Здесь мы задаем подстановочные знаки для наших алфавитов, которые мы описали выше. Теперь нам нужно описать преобразования (мутации), возникающие в словах.
Зарезервированными лексемами являются: 1) .mutation — Указывает на начало описания преобразования. 2) 3) =
Слева в мутациях можно записывать как символы алфавита, так и подстановочные знаки. Ну а теперь сами подстановочные знаки для наших мутаций:
Правила сопоставленияОсталось самое сложное. Правила сопоставления. Идея похожая на словарь, но вместо словарных форм используется маска с подстановочными символами и символами алфавита. Правила сопоставления существительных.
Зарезервированными лексемами являются: 1) .match — Указывает на начало описания правила сопоставления. 2) .backward — Маска начинает сопоставляться с конца слова (удобно для суффиксов и окончаний). 2) .forward — Маска начинает сопоставляться с начала слова (удобно для префиксов). 3) .inward-void — Сначала с конца, потом сначала, и так до словарной основы. 4) | — После вертикальной черты начинаются соответствующие характеристики слова, подходящего под маску. 5) 6) + — =
Слева указывается маска слова, справа — аттрибуты или сущность, которой соответствует данная маска. Маска состоит из последовательности правил, которые могут быть либо самостоятельным правилом, либо комбинацией подстановочных символов с символами алфавита и со специальными знаками "+", "-", " =##". Вначале стоит указатель на банальный режим посимвольного сравнения "=". За ней два наших вышеописанных подстановочных символа "#", означающих две согласных. Значит, в конце слова должно быть два согласных, на этом правило заканчивается. Рассмотрим альтернативный вариант развития событий «mn_vowel_right +*=#». Это правило рекурсивное. Оно означает что в конце стоит согласная. После этого перед этой согласной мы должны отщепить одну гласную и записать ее как результат этого правила. И делать так до тех пор пока не наткнемся на единственно возможный вариант "=##". Т.е. все глассные которые мы удалим будут результатом «mn_vowel_right», что мы должны увидеть в результатах анализа.
Поэтапно правило «mn_vowel_right». Возьмем к примеру слово «loolool». Остаток слова Шаблон сопоставления Результат loolool # в режиме сравнения loolol * в режиме отщепления o loolol # в режиме сравнения o looll * в режиме отщепления oo looll ## в режиме сравнения oo К концу мы получили «looll». Оно и пойдет дальше в «mn_vowel_left». Аналогично к концу мы получим «lll». Оно и пойдет дальше в «mn_stem» и будет искаться в словаре. Поскольку в словаре такого слова нет, эта цепочка правил сочтется неподходящей. А вот если бы мы взяли «looool», мы бы получили нашу словарную форму существительного «ll». C существительным разобрались. Фууух. Вы еще не устали? Чуток отдохнем и пора браться за глагол.
Разбираем правила сопоставления глаголаЗдесь намного интереснее, мы используем вышеописанные мутации для восстановления последствий фузий. Впервые используется режим удержания "-", который вместо отщепления "+" сохраняет символ на месте как "=", но записывает его в результат правила как "+". Поехали с начала. Возьмем слово «ulolloo». Проанализируем на правила прошедшего и настоящего времен.
Правило «mvb_time_past». Остаток слова Шаблон сопоставления Результат ulolloo ololloo (*>o) в режиме удержания u ololloo не «o» в режиме сравнения u Анализатором рассматриваются всевозможные цепочки событий, но если встречаются противоречивые характеристики, например V.inf. и V.p. то подобный вариант развития событий прекращается как невозможный.
Правило «mvb_time_present». Возьмем слово «olollo». Остаток слова Шаблон сопоставления Результат olollo ololloo (a>aa,o>oo) в режиме удержания o
Разбираем правила сопоставления прилагательныхA.no." значит в будущем допускается либо A.sup., либо A.cmp., либо ничего.
Возьмем слово allololo. Рассмотрим более крупными шагами. Исходное слово Правило Подходящий шаблон сопоставления Результат Остаток слова allololo ma_number "=*" либо "=*+lolo" нет либо «lolo» allololo либо allo allololo, allo ma_degree "+al" либо "+@-#" либо "=." «al» либо «al» либо нет lololo, lo либо llololo, llo либо allololo, allo lololo, lo либо llololo, llo либо allololo, allo ma_stem словарь нет нет Подходит только lo. Таким образом, мы получили единственно подходящую цепочку сопоставлений характеристик: «A.pl. AD.V
A.no. A.sup. adj. adjective». Следовательно, слово allololo однозначно представляет собой «plural superlative adjective vowel declension». Если будет интересно, для языка описания грамматик мы посветим отдельный цикл статей. А пока, пока!
В следующем выпуске мы перейдем непосредственно к проектированию и кодированию нашего анализатора.