Модуль морфологического анализа, проверки орфографии и лемматизации русского языка

Модель описания словоизменения

Физическое устройство словаря

Физическое устройство страницы словаря

Сканирование словаря

Особенности реализации

"Классический" программный интерфейс

Структуры данных и терминология

Проверка правописания

Лемматизация

Построение словоформы по ее идентификатору

Построение словоформы по ее грамматическому описанию

Итератор по идентификаторам лексем

Низкоуровневая подсказка

Извлечение информации о лексеме

CXX-интерфейс

Структуры данных и терминология

Преобразование регистра

Проверка правописания

Лемматизация

Построение формы слова

Построение форм возможных лексем

Орфографическая подсказка

Интерфейс вероятностного морфологического анализатора

Структуры данных и терминология

Доступ к интерфейсам

Лемматизация

Построение формы слова

PHP-интерфейс

Библиотека расширения

Проверка правописания

Лемматизация

Построение словоформы по ее идентификатору

Модель описания словоизменения.

Для описания словоизменения слово, точнее, его нормальная форма,представленная в словнике, разбивается на 4 формальных фрагмента: Окончание при генерации словаря отщепляется автоматически в соответствии с грамматической информацией, содержащейся в таблице окончаний, приписанной данному слову.

В случае, если в основе слова есть чередования, то определяется,какой фрагмент чередуется с каким (подбирается таблица чередований в основе), а от основы отщепляется фрагмент, соответствующий нормальной форме слова.

Псевдосуффиксы генерируются автоматически и используются лишь для уменьшения физического размера словаря, поэтому на них подробнее останавливаться не имеет смысла.

В перечисленных ниже примерах чередующийся фрагмент основы выделен жирным шрифтом, а окончание - косым:

Для каждого слова после отщепления всех изменяемых частей в словарь заносится часть речи, ссылка на таблицу чередований в основе (если есть) и ссылка на таблицу окончаний.

При работе анализатора нужная ступень чередования выбирается по жестким формальным правилам, которые в этом документе не приводятся.

Физическое устройство словаря

Словарь разбит на страницы средним объемом 0xF000 байт. Такой размер обусловлен, с одной стороны, тем, что ссылки между таблицами в этом случае можно делать шестнадцатибитными, с другой строны - стремлением увеличить до минимума размер одной страницы словаря, что положительно сказывается на производительности программы в целом. Существует индекс по этим страницам, но на нем подробнее останавливаться смысла нет, так как и интересного там ничего нет.

Физическое устройство страницы словаря

Каждая страница словаря представляет собою дерево, в каждом узле которого, можно считать, находится таблица, ставящая в соответствие каждому символу смещение аналогичной таблицы следующего уровня.

Пусть словарь содержит лишь слова "стол", "столешница", "стоять", "стоить", "стрелять", "судить" и "ты". Тогда, после отщепления всех изменчивых фрагментов, мы получим (в порядке следования) графические псевдоосновы "стол", "столешниц", "сто", "сто", "стрел", "су" и "т". Дерево, строящееся из них, можно представить следующим образом:

Под "грамматической информацией" следует понимать в данном случае часть речи, ссылку на таблицу чередований в основе, ссылку на таблицу окончаний и список чередующихся приставок.

Сканирование словаря

При сканировании устроенного таким образом словаря фактически идет перебор элементов таблицы очередного уровня (0 - для первой буквы слова, 1 - для второй и т. д).

Рассмотрим этот процесс на примере изображенного выше словаря. Пусть на обработку поступило слово "суд". Тогда, находясь в корне дерева, мы можем утверждать, что если такое слово в словаре есть, то оно находится на ветви, начинающейся от буквы "с".

Переходим на второй уровень. Теперь текущая буква слова - "у". Допустимые буквы после "с" в нашем словаре - "т" и "у". Если искомое слово в словаре есть, оно лежит на ветви дерева, на которую ссылается буква "у".

На третьем уровне таблица пуста, что означает, что возможно лишь отождествление по окончаниям. Понятно, что на таблице чередований "д/ж/ж" (судить - сужу - суженный) и таблице окончаний глагола "су-д-ить" слово "суд" отождествиться не может.

Особенности реализации

Морфологический анализатор реализован в виде динамической библиотеки с экспортируемыми функциями в стиле "C". Размер библиотеки со словарем - немногим более двух Mb. Двоичный словарь включен в виде данных, что позволяет операционным системам оптимальным образом подгружать и выгружать его страницы по мере надобности. Последнее (вкупе со способом организации словаря - см. выше) обеспечивает высокую производительность анализатора - более двадцати тысяч слов в секунду в режиме лемматизации с построением текстов нормальных форм слов и грамматических описаний отождествлений.

Генерация словаря происходит полностью автоматически, процесс этот занимает не более трех минут.

Процесс отождествления последовательности символов идет слева направо - то есть сначала сканируется словарь основ, затем происходит отождествление остатка слова по отсортированным лексикографически таблицам окончаний, что позволяет обрывать сканирование, не проходя по всей таблице.

Основы в пределах страницы словаря разложены в таблицу переходов конечного автомата.

"Классический" программный интерфейс

Структуры данных и терминология

Морфологический анализатор использует метафору аддитивности грамматической информации о форме слова. Полное грамматическое описание формы слова занимает 16 бит и разбито на следующие области: Формально введены 2 дополнительных "времени" (помимо настоящего, прошедшего и будущего) - инфинитив и императив.

Константы, позволяющие конструировать грамматические описания простым сложением, приведены в заголовочном файле mlma1049.h.

Для полного грамматического описания формы слова используется структура

struct SGramInfo { unsigned char wInfo; unsigned char iForm; unsigned short gInfo; unsigned char other; },
где wInfo - информация о лексеме - часть речи и признаки флективности, iForm - идентификатор формы слова, однозначно соответствующий грамматическому описанию для данной части речи, gInfo - собственно грамматическое описание, other - некоторые дополнительные признаки, из следует обратить внимание лишь на 2 - одушевленность (0x01) и неодушевленность (0x02).

Также в заголовочном файле mlma1049.h декларированы коды ошибок, возвращаемые функциями морфологического анализатора, и флаги его настройки.

Коды ошибок морфологического анализатора
Код ошибкиЗначениеТолкование
LEMMBUFF_FAILED-1Недостаточно места в массиве нормальных форм
LIDSBUFF_FAILED-2Недостаточно места в массиве лексем
GRAMBUFF_FAILED-3Недостаточно места для грамматических описаний
WORDBUFF_FAILED-4Слишком длинное слово или плохой указатель

Настройки анализатора
Флаг настройкиЗначениеТолкование
sfStopAfterFirst0x0001Сканировать словарь до первого отождествления
sfIgnoreCapitals0x0002Игнорировать неправильные схемы капитализации
sfHardForms 0x0004Разрешать затрудненные формы слов
nfAdjVerbs 0x0100Нормализовать причастия, как прилагательные

Части речи
Часть речи (hex) Значение
10x01Глагол несовершенного вида
20x02Непереходный глагол несовершенного вида
30x03Глагол совершенного вида
40x04Непереходный глагол совершенного вида
50x05Двувидовой глагол
60x06Непереходный двувидовой глагол
70x07Неодуш. существительное мужского рода
80x08Одуш. существительное мужского рода
90x09Одуш.-неодуш. существительное мужского рода
100x0aНеодуш. существительное мужского рода, склоняющееся по схеме среднего
110x0bОдуш. существительное мужского рода, склоняющееся по схеме женского
120x0cОдуш. существительное мужского рода, склоняющееся по схеме среднего
130x0dНеодуш. существительное женского рода
140x0eОдуш. существительное женского рода
150x0fОдуш.-неодуш. существительное женского рода
160x10Неодуш. существительное среднего рода
170x11Одуш. существительное среднего рода
180x12Одуш.-неодуш. существительное среднего рода
190x13Неодуш. существительное общего рода
200x14Одуш. существительное общего рода
210x15Неодуш. существительное мужского/среднего рода
220x16Одуш. существительное мужского/среднего рода
230x17Неодуш. существительное женского/среднего рода
240x18Неодуш. существительное множественного числа
250x19Прилагательное
260x1aПрилагательное, образованное от географического названия
270x1bПритяжательное местоимение
280x1cМестоименное прилагательное
290x1dМестоимение множественного числа
300x1eМестоимение мужского рода
310x1fМестоимение женского рода
320x20Местоимение среднего рода
330x21Числительное
340x22Числительное "два"
350x23Собирательное числительное
360x24Порядковое числительное
370x25Имя собственное
380x26Имя мужского рода
390x27Имя женского рода
400x28Отчество мужского рода
410x29Отчество женского рода
420x2aФамилия
430x2bНеизменяемое географическое название
440x2cГеографическое название мужского рожа
450x2dГеографическое название женского рода
460x2eГеографическое название среднего рода
470x2fГеографическое название множественного числа
480x30Вводное слово
490x31Междометье
500x32Предикатив
510x33Предлог
520x34Союз
530x35Частица
540x36Наречие
590x3bАббревиатура, пишущаяся прописными буквами
600x3cАббревиатура, пишущаяся строчными буквами

Проверка правописания

short WINAPI mlmaruCheckWord( const char* lpWord, word16 options );
Функция выполняет проверку правописания слова по словарю и возвращает Аргументы:

Лемматизация

short WINAPI mlmaruLemmatize( const char* lpWord, word16 dwSets, char* lpLemm, word32* lpLids, char* lpGram, word16 ccLemm, word16 cdwLid, word16 cbGram );
Функция выполняет отождествление поданной строки по морфологическому словарю и возвращает Аргументы: Формат формируемого массива грамматических описаний: Например, при лемматизации слова "клавиатуры" функция mlmaruLemmatize возвращает 1. Это означает, что отождествление произошло с формами одной лексемы, в lpLemm будет восстановлено слово "клавиатура", в lpLids - идентификатор лексемы, а в lpGram будет помещен один блок грамматических описаний (количество отождествлений - 3, Р.ед., И.мн., В.мн.).

Построение словоформы по ее идентификатору

short WINAPI mlmaruBuildForm( const char* lpWord, word32 dwLxID, word16 dwsets, byte08 idForm, char* lpDest, word16 ccDest );
Функция строит формы указанного слова, соответствующие идентификатору, и возвращает Аргументы:

Построение словоформы по ее грамматическому описанию

short WINAPI mlmaruBuildFormGI( const char* lpWord, word32 dwLxID, word16 dwSets, word16 grInfo, byte08 bFlags, char* lpDest, word16 ccDest );
Функция строит формы указанного слова, соответствующие переданному грамматическому описанию, и возвращает Аргументы:

Итератор по идентификаторам лексем

short WINAPI mlmaruEnumWords( TEnumWords enumproc, void *lpv );
Функция - итератор по всем лексемам словаря. Возвращает Аргументы: Функция enumproc должна соответствовать следующему прототипу:
typedef short (* TEnumWords)( unsigned long lid, void* lpv );

Низкоуровневая подсказка

short WINAPI mlmaruCheckHelp( const char* lpWord, char* lpList );
Функция низкоуровневого обслуживания подсказки для проверки орфографии и подобных нужд. В ответ на строку-шаблон, содержащую один символ ? или * строит список символов, которые могли бы стоять на его месте. При этом * служит для усечения справа, а ? обозначает один символ. Возвращает Аргументы:

Извлечение информации о лексеме

short WINAPI mlmaruGetWordInfo( word32 dwLxId, byte08* lpInfo );
Функция извлекает из словаря информацию о лексеме dwLxId - часть речи и некоторые признаки словоизменения. Возвращает: Аргументы:

CXX-интерфейс

В настоящее время в рамках развития модуля реализован также расширенный интерфейс доступа к модулю, более удобный в использовании и реализованный в духе стандарта COM. Он НЕ является истинным COM-интерфейсом, к нему нет и не может быть type library, однако доступ и обращения к нему выполняются практически так же.
Реально доступны два интерфейса взаимодействия - multibyte, использующий кодировку 1251 Windows Cyrillic, и widechar, опирающийся на unicode.

Структуры данных и терминология

В дополнение к структуре SGramInfo, описанной выше, используются еще две структуры, похожих одна на другую:
typedef struct { lexeme_t nlexid; const char* plemma; SGramInfo* pgrams; unsigned ngrams; } SLemmInfoA; typedef struct { lexeme_t nlexid; const unsigned short* plemma; SGramInfo* pgrams; unsigned ngrams; } SLemmInfoW;
Обе они описывают отождествления одной лексемы при лемматизации, однако первая из них используется в multibyte-, а вторая - в unicode-интерфейсе при вызове функции лемматизации.
В этих структурах: Сами интерфейсы декларированы в заголовочном файле mlma1049.h так, чтобы обращение к ним было возможным не только из C++, но и из C-приложений с помощью приведенных там же макроопределений.

Для большей читаемости и наглядности здесь они приводятся так, как если бы они были декларированы явно в программном коде C++.

struct IMlmaMb { virtual int SetLoCase( char* pszstr, size_t cchstr ); virtual int SetUpCase( char* pszstr, size_t cchstr ); virtual int CheckWord( const char* pszstr, size_t cchstr, unsigned dwsets ); virtual int Lemmatize( const char* pszstr, size_t cchstr, SLemmInfoA* plexid, size_t clexid, char* plemma, size_t clemma, SGramInfo* pgrams, size_t ngrams, unsigned dwsets ); virtual int BuildForm( char* output, size_t cchout, lexeme_t nlexid, formid_t idform ); virtual int FindForms( char* output, size_t cchout, const char* pszstr, size_t cchstr, formid_t idform ); virtual int CheckHelp( char* output, size_t cchout, const char* pszstr, size_t cchstr ); virtual int GetWdInfo( unsigned char* pwinfo, lexeme_t nlexid ); virtual int EnumWords( IMlmaEnum* pienum, const char* pszstr, size_t cchstr ); }; struct IMlmaWc { virtual int SetLoCase( widechar* pszstr, size_t cchstr ); virtual int SetUpCase( widechar* pszstr, size_t cchstr ); virtual int CheckWord( const widechar* pszstr, size_t cchstr, unsigned dwsets ); virtual int Lemmatize( const widechar* pszstr, size_t cchstr, SLemmInfoW* plexid, size_t clexid, widechar* plemma, size_t clemma, SGramInfo* pgrams, size_t ngrams, unsigned dwsets ); virtual int BuildForm( widechar* output, size_t cchout, lexeme_t nlexid, formid_t idform ); virtual int FindForms( widechar* output, size_t cchout, const widechar* pszstr, size_t cchstr, formid_t idform ); virtual int CheckHelp( widechar* output, size_t cchout, const widechar* pszstr, size_t cchstr ); virtual int GetWdInfo( unsigned char* pwinfo, lexeme_t nlexid ); virtual int EnumWords( IMlmaEnum* pienum, const widechar* pszstr, size_t cchstr ); };
Для доступа к интерфейсам библиотека экспортирует три метода:

int MLMAPROC mlmaruLoadMbAPI( IMlmaMb** ); int MLMAPROC mlmaruLoadCpAPI( IMlmaMb**, const char* codepage ); int MLMAPROC mlmaruLoadWcAPI( IMlmaWc** );
Эти методы ожидают указатель на указатель на интерфейс в качестве параметра, восстанавливают по этому адресу запрошенный интерфейс доступа и возвращают либо 0 в случае успеха, либо код ошибки: Никаких иных причин отказа в инициализации быть не может, так как иные операции, которые могут быть неуспешными, при этом не производятся.

mlmaruLoadCpAPI(...) дополнительно получает параметр codepage, который может принимать следующие значения:

Преобразование регистра

int SetLoCase( pszstr, cchstr ); int SetUpCase( pszstr, cchstr );
Функции преобразовывают поданную строку указанной длины в нижний и верхний регистр соответственно в соответствии с правилами языка. В частности, в случае русского языка это преобразование - очевидное, и не требует каких-либо особых действий. Возвращает 0 в случае успеха или -1 при возникновении каких-либо ошибок, в данном случае - всегда 0. Аргументы:

Проверка правописания

int CheckWord( pszstr, cchstr, dwsets );
Функция выполняет проверку правописания слова по словарю и возвращает Аргументы:

Лемматизация

int Lemmatize( pszstr, cchstr, plexid, clexid, plemma, clemma, pgrams, ngrams, dwsets );
Функция выполняет отождествление поданной строки по морфологическому словарю и возвращает Аргументы:

Построение формы слова

int BuildForm( output, cchout, nlexid, idform );
Функция выполняет построение указанной формы слова для заданной лексемы, восстанавливает графические варианты начертания этой формы в выходной массив, разделяя их нулевым символом, и возвращает Аргументы:

Построение форм возможных лексем

int FindForms( output, cchout, pszstr, cchstr, idform );
Функция выполняет построение указанной формы слова для всех лексем, с которыми произойдет отождествление поданной словоформы. Данная функция полезна для использования в системах, которые не сохраняют идентификаторы лексем. Восстанавливает графические варианты начертания формы в выходной массив, разделяя их нулевым символом, и возвращает Аргументы:

Орфографическая подсказка

int CheckHelp( output, cchout, pszstr, cchstr );
Функция низкоуровневого обслуживания подсказки для проверки орфографии и подобных нужд. В ответ на строку-шаблон, содержащую один символ ? или * строит список символов, которые могли бы стоять на его месте. При этом * служит для усечения справа, а ? обозначает один символ. Функция возвращает Аргументы:

Интерфейс вероятностного морфологического анализатора

Вероятностный морфологический анализатор предназначен для использования в связке со словарным анализатором, использует практически такой же программный интерфейс и оперирует теми же категориями, что и словарный анализатор. Отличие состоит лишь в том, что в структурах и параметрах используется длина графической основы слова и идентификатор грамматического класса вместо идентификатора лексемы. В остальном структуры данных интерфейса аналогичны описанным выше.

Стандартная схема использования вероятностного анализатора - вызов его для тех слов, которые не были опознаны словарным морфологическим анализатором. Исходя из такой постановки задачи, при построении словарной базы экзотические модели словоизменения, свойственные древним словам языка и не использующиеся при образовании неологизмов, были исключены. Поэтому не следует ожидать корректных результатов анализа слов типа идти или быть.

Структуры данных и терминология

typedef struct { unsigned ccstem; unsigned nclass; const char* plemma; SGramInfo* pgrams; unsigned ngrams; float frange; } SStemInfoA; typedef struct { unsigned ccstem; unsigned nclass; const unsigned short* plemma; SGramInfo* pgrams; unsigned ngrams; float frange; } SStemInfoW;
Приведённые структуры описывают отождествления одной лексемы при лемматизации, однако первая из них используется в multibyte-, а вторая - в unicode-интерфейсе при вызове функции лемматизации. Отличия структур от аналогичных, используемых в строгом анализе, выделены цветом.

В этих структурах:

Интерфейсы декларированы в заголовочном файле mlfa1049.h.

Для большей читаемости и наглядности здесь они приводятся так, как если бы они были декларированы явно в программном коде C++.

struct IMlfaMb { virtual int Lemmatize( const char* pszstr, unsigned cchstr, SStemInfoA* plexid, unsigned clexid, char* plemma, unsigned clemma, SGramInfo* pgrams, unsigned ngrams, unsigned dwsets ); virtual int BuildForm( char* output, unsigned cchout, const char* lpstem, unsigned ccstem, unsigned nclass, unsigned char idform ); };

Версия интерфейса для работы с unicode:

struct IMlfaWc { virtual int Lemmatize( const unsigned short* pszstr, unsigned cchstr, SStemInfoW* plexid, unsigned clexid, unsigned short* plemma, unsigned clemma, SGramInfo* pgrams, unsigned ngrams, unsigned dwsets ); virtual int BuildForm( unsigned short* output, unsigned cchout, const unsigned short* lpstem, unsigned ccstem, unsigned nclass, unsigned char idform ); };

Доступ к интерфейсам

Для доступа к интерфейсам библиотека доступны два метода:
int MLMAPROC mlfaruLoadMbAPI( IMlfaMb** ); int MLMAPROC mlfaruLoadWcAPI( IMlfaWc** );

Оба метода ожидают указатель на указатель на интерфейс в качестве параметра и возвращают либо 0, либо код ошибки в случае некорректного параметра. Никаких иных причин отказа в инициализации не предусмотрено, так как резервирование памяти или иные операции, которые могут быть неуспешными, при этом не производятся.

Лемматизация

int Lemmatize( pszstr, cchstr, plexid, clexid, plemma, clemma, pgrams, ngrams, dwsets );
Функция подбирает возможные модели словоизменения для переданной словоформы, ранжирует их по вероятности соответствия и возвращает Аргументы:

Построение формы слова

int BuildForm( output, cchout, lpstem, ccstem, nclass, idform );
Функция выполняет построение указанной формы слова для заданных основы и грамматического класса, восстанавливает графические варианты начертания этой формы в выходной массив, разделяя их нулевым символом, и возвращает Аргументы:

PHP-интерфейс

Версия библиотеки, собранная в виде PHP-расширения, позволяет использовать морфологический анализатор русского языка непосредственно из PHP-скриптов.

Библиотека расширения

Чтобы начать использование его в PHP-приложениях, следует либо упомянуть библиотеку в списке загружаемых расширений, либо явным образом указать на необходимость её загрузки в начале скрипта:
<?php dl( "phpmorphrus_demo.dll" ); ?>

Проверка правописания

function mlmaruCheckWord( $string, $dwsets );
Функция выполняет проверку правописания слова по словарю и возвращает результат типа boolean Аргументы:
<?php dl( "phpmorphrus_demo.dll" ); function CheckWord( $word, $ignore_capitals = 0 ) { if ( $ignore_capitals ) return mlmaruCheckWord( $word, sfIgnoreCapitals ); else return mlmaruCheckWord( $word, 0 ); } function ReportCheckWord( $word ) { printf( "Checking the word '%s'...", $word ); if ( CheckWord( $word ) ) printf( "\tOK\n" ); else printf( "\tfalse\n" ); } ReportCheckWord( "слово" ); ReportCheckWord( "слоав" ); ?>

Лемматизация

function mlmaruLemmatize( $string, $dwsets );
Функция выполняет отождествление поданной строки по морфологическому словарю и возвращает массив (PHP-структуру), описывающий варианты отождествления слова. Ниже приводится распечатка такой структуры с комментариями для результатов разбора слова простой - одного из самых любимых примеров омонимии.
Array ( /* всего в массиве 3 элемента */ [0] => Array ( /* описание первого отождествления */ [0] => простоять /* текст нормальной формы слова */ [1] => 16500 /* идентификатор лексемы */ [2] => Array ( /* массив грамматических описаний */ [0] => Array ( /* отождествление с одной формой */ [0] => 3 /* часть речи 3 (wInfo=св_нп) */ [1] => 2 /* грамматическое описание (gInfo) */ [2] => 3 /* other */ [3] => 2 /* идентификатор формы */ ) ) ) [1] => Array ( [0] => простой [1] => 45384 [2] => Array ( ... ) ) [2] => Array ( [0] => простой [1] => 136174 [2] => Array ( ... ) ) )
Аргументы:

Построение словоформы по ее идентификатору

function mlmaruBuildForm( $string|$lexeme, $formid );
Функция строит массив форм указанного слова, соответствующих переданному идентификатору. Аргументы: