Вчера ночью закончил свою библиотеку для морфологического анализа. Вот что пока получилось: Требования: >= php 7 Зависимости: нет Возможности: 1. Привести слово к нормальной форме. 2. Показать грамматическую информацию о слове и всех его формах Устройство: Библиотека построена на базе другой моей библиотеки Yatrie, реализующей префиксное дерево trie. Словарь русского языка в библиотеке использован из проекта OpenCorpora.org Концепция работы словаря почерпнута из статьи И. Сегаловича "A fast morphological algorithm with unknown word guessing induced by a dictionary for a web search engine" (http://download.yandex.ru/company/iseg-las-vegas.pdf). По мотивам этой статьи (или наоборот) И.Сегалович и В.Титов написали программу mystem. Проект по прежнему развивается в Яндексе (https://tech.yandex.ru/mystem/) Насколько я понимаю данная технология по-прежнему используется в Яндексе. Это я к тому, что концепция очень крутая. Я не очень хорошо знаю английский и еще совсем слабо программирую, поэтому мне пришлось незначительно изменить принцип поиска изложенный в вышеуказанной статье И.Сегаловича. Вот что у меня получилось: Всего в действующей редакции словаря openCorpora (http://opencorpora.org/dict.php) 114 граммем, 389360 лемм, 5100150 форм. Граммема - это род, число, падеж, часть речи и т.д. Лемма как пишут в интернетах нормализованная, основная форма слова, вместе с информацией о построении других форм. Но почему-то у слова любить и полюбить 2 разные леммы в словаре OpenCorpora. Насколько я понял формы одной леммы это всегда одно понятие, но 1 понятие может быть представлено сразу 2 леммами. Например, любить и полюбить это 2 разные леммы. Экскурс в историю: В 1965 году некто А.А.Зализяк в будущем академик и светило отечественной науки, тогда еще соискатель звания кандидат наук защитил кандидатскую диссертацию по теме «Классификация и синтез именных парадигм современного русского языка». Именно эта идея и легла в основу mystem. Суть ее в следующем: Практически все словоформы одной леммы (в последней редакции словаря было всего 12 исключений) имеют некоторую общую часть, И.Сегалович называет эту общую часть "стемма", от слова stemmata, что в переводе с латыни означает венок из ветвей. Я называю эту общую часть просто корень root. Пример: Код (Text): ┌------------┬---------------------------------------┐ │ form │ props │ │------------┼---------------------------------------│ │скот │имя существительное, одушевлённое, мужс│ │ │кой род │ │------------┼---------------------------------------│ │скот │единственное число, именительный падеж │ │------------┼---------------------------------------│ │скота │единственное число, родительный падеж │ │------------┼---------------------------------------│ │скота │единственное число, винительный падеж │ │------------┼---------------------------------------│ │скоту │единственное число, дательный падеж │ │------------┼---------------------------------------│ │скотом │единственное число, творительный падеж │ │------------┼---------------------------------------│ │скоте │единственное число, предложный падеж │ │------------┼---------------------------------------│ │скотам │множественное число, дательный падеж │ │------------┼---------------------------------------│ │скотами │множественное число, творительный падеж│ │------------┼---------------------------------------│ │скотах │множественное число, предложный падеж │ │------------┼---------------------------------------│ │скотов │множественное число, родительный падеж │ │------------┼---------------------------------------│ │скотов │множественное число, винительный падеж │ │------------┼---------------------------------------│ │скоты │множественное число, именительный падеж│ └------------┴---------------------------------------┘
а можно мне пример как сделать на запрос "укладка камня на даче" сделать ответ "уложим камень на твоей даче"?
Таким образом часть слова до корня - префикс, а после корня - суффикс. Получается, что для получения любой словоформы нужно иметь. префикс, корень, окончание и грамматическую информацию. Например, словоформа "скоты" PHP: $prefix = ''; $root = 'скот'; $suffix = 'ы'; $props = 'множественное число, именительный падеж'; Совокупность всех суффиксов, префиксов и грамматической информации одной леммы образуют парадигму. Т.е. шаблон того, как из корня получить любую словоформу. При том, что в языке > 5 млн. слов, уникальных корней ~284 тыс., а парадигм всего 3.5 тыс.. --- Добавлено --- Краткое описание того, как из словаря openCorpora в формате xml получается словарь для моей библиотеки. Образец одной леммы из словаря openCorpora Код (Text): <lemma id="323408" rev="323408"><l t="скот"><g v="NOUN"/><g v="anim"/><g v="masc"/></l><f t="скот"><g v="sing"/><g v="nomn"/></f><f t="скота"><g v="sing"/><g v="gent"/></f><f t="скоту"><g v="sing"/><g v="datv"/></f><f t="скота"><g v="sing"/><g v="accs"/></f><f t="скотом"><g v="sing"/><g v="ablt"/></f><f t="скоте"><g v="sing"/><g v="loct"/></f><f t="скоты"><g v="plur"/><g v="nomn"/></f><f t="скотов"><g v="plur"/><g v="gent"/></f><f t="скотам"><g v="plur"/><g v="datv"/></f><f t="скотов"><g v="plur"/><g v="accs"/></f><f t="скотами"><g v="plur"/><g v="ablt"/></f><f t="скотах"><g v="plur"/><g v="loct"/></f></lemma>
1. Сначала весь словарь практически в неизменном виде переносится из xml в mysql. Вот какие таблицы: forms - lemma_id, word_id, props_id words - id, name lemmata id, word_id, props_id grammemes id, name, parent, alias, desc props id, name (в поле name в виде строки через разделитель записаны id граммем) 2. Обрабатываем все словоформы для каждой леммы и получаем prefix, root, suffix Записываем все в отдельные таблицы. В таблицу forms и lemmata записываем соответствующие prefix_id, root_id, suffix_id. В таблице lemmata содержится нормализованная форма слова и у некоторых лемм, например, наречий, есть только нормализованная форма. Т.е. записи в таблицы forms вообще нет, поэтому prefix_id, root_id, suffix_id есть и в этой таблице. 3. Берем все словоформы 1 слова, сортируем их в алфавитном порядке по полям: suffix_id, prefix_id, props_id по порядку все суффиксы, префиксы и свойства через |. Например, 1|2|3. Все полученные комбинации записываем через разделитель -. Например, 1|2|3-4|5|6-3|23|5 Это и есть парадигма леммы. Все парадигмы записываем в отдельную таблицу paradigms, а в таблицу lemmata добавляем соответствующие paradigm_id. 4. Берем все суффиксы из таблицы суффиксов и переворачиваем их. Например словоформа человеком имеет префикс "че" корень "л" и суффикс "овеком". Перевернутый суффикс будет "мокево". Записываем все эти перевернутые суффиксы в дерево trie. 5. Переворачиваем все корни и записываем их, добавляя id парадигмы этого корня через специальный разделитель $. Например, корень слова человек «л» записан в дереве вот так: Код (Text): Array ( [0] => л$1852 [1] => л$1984 [2] => л$1986 [3] => л$1999 [4] => л$2001 [5] => л$2002 [6] => л$2022 [7] => л$2023 [8] => л$2833 [9] => л$2834 [10] => л$2835 [11] => л$2874 [12] => л$3107 [13] => л$3212 [14] => л$3222 [15] => л$3223 [16] => л$3254 [17] => л$411 [18] => л$532 [19] => л$534 [20] => л$968 ) 6. Аналогичным образом записываем еще одно дерево trie со всеми id суффиксов и соответствующие этим суффиксам парадигмы. Например, 12$32 На этом структуры данных закончены. Логика работы поиска: 1. Ищем в дереве суффиксов, получаем все возможные суффиксы для нашего слова. 2. Теперь начиная с самого «глубокого» (длинного) суффикса ищем «хвост» в дереве корней. Получаем все возможные корни и их парадигмы, которые записаны на концах ветвей нашего дерева. 3. Преобразуем каждый найденный на шаге 1 суффикс в id и ищем его в дереве суффиксов и парадигм. Накладываем найденный массив парадигм из этого дерева с массивом парадигм найденном на 2 шаге. Если есть хотя бы 1 совпадение, значит мы нашли наше слово. Иногда совпадений бывает 2. Т.е. есть 2 разных слова с одинаковым написанием. В слове человек пересечение будет только по 1 парадигме 3107. А вот в слове "скот" ,будет пересечение по 2 парадигмам 54, 75. У неодушевленного слова "скот" есть одушевленный омоним "скот". 4. Берем полученный на этапе 2 корень и номер парадигмы слова и генерируем лемму слова по массивам «ключ-значение» prefix, suffix, root, props.
Сейчас функционал, который бы позволил получить нужную форму еще не готов. Но будет примерно вот так: PHP: $y = new Yamorphy(); $str = "укладка камня на даче"; $a = explode(' ', $str); //тут мы должны получить что-то вроде "укладка камень на дача" foreach($a as &$w){ $w = $y->morph($w, ['n']); //n это нормальная форма } //Чтобы первое слово сделать из существительного "укладка" глаголом "уложим" $a[0] = $y->morph($w, ['verb', '1per', 'futr', 'indc', 'plur']); //получаем предложный падеж $last = array_key_last($a); $a[$last] = $y->morph($a[$last], ['loct']); $str2 = implode(' ' , $a); echo $str2; //получаем "уложим камень на даче" --- Добавлено --- Сейчас это все как-то упакую поприличнее, чтобы на гит можно было выложить и выложу тогда ссылку.