Единственная задача сегментатора - разбивка текста на предложения, с учетом наличия неделимых токенов, в которых содержатся точки, пробелы и т.д.
Символы, которые являются разделителями предложений, определены параметром SentenceDelimiter в описании языка. Таким образом, для каждого языка можно независимо задать свой набор разделителей предложений. Для европейских языков, включая русский и английский, таковыми символами обычно являются точка, знаки вопроса и восклицания.
Параметр MaxSentenceLen в описании языка задает максимальную длину предложения. Она используется для предотвращения переполнения внутренних буферов и зацикливания при разборе сложноформатированного текста, когда алгоритм не может найти маркер конца предложения.
Если в качестве разделителей используется точка, то она обрабатывается особым образом, в отличие от знаков ? и !. Дело в том, что некоторые слова могут содержать точку, и это не должно вызывать обрыва предложения. Типичный пример - сокращения типа "т.е." или абревиатуры типа "Р.С.Ф.С.Р.". Аналогично особо обрабатываются числа с десятичной точкой "3.141".
Обработка таких исключений - токенов с точкой внутри - опирается на возможность токенизатора распознавать в потоке символов специальные цепочки с разделителями внутри.
Еще один флаг в описании языка, оказывающий влияние на работу сегментатора - SentenceBrokerFlags. Среди элементов этого флага есть значения:
UseLexicon - использовать лексикон для определения полных словоформ. Точка после полной словоформы считается безусловным разделителем предлодений.
CasingSentBroker - список специальных токенов (см. правила unbreakable в Токенизаторе). Если после такого токена идет полная словоформа, начинается она с заглавной буквы и в лексиконе словарная статья отмечена, как не начинающаяся с заглавной буквы, то специальный токен является границей предложения. Например, в тексте "Кошка, собака и т.д. Сокол, Орел и пр." первое предложение будет отсечено после "т.д.", так как следующее слово - Сокол - начинается с заглавной буквы.
MinFormLen=n - минимальная длина полной словоформы.
Эти значения используются в случае, когда точка идет после полного слова. Так как обычно сегментатор смотрит впереди идущие символы и считает границей предложения случай, когда следующее слово начинается с большой буквы, то текст "ночь. улица. фонарь." будет считаться одним предложением. Подфлаг UseLexicon в SentenceBrokerFlags заставляет сегментатор проверить слово перед точкой по лексикону и в случае успеха - считать точку границей предложения независимо от регистра символов следующего слова. Параметр MinFormLen=n позволяет избежать ненужных проверок для случаев "и др. символы" - он задает минимальную длину проверяемого полного слова.
В API движка есть несколько вызовов для использования сегментатора предложений - см. sol_CreateSentenceBroker, sol_CreateSentenceBrokerMem[W,A,8], sol_FetchSentence, sol_GetFetchedSentence[W,A,8], sol_DeleteSentenceBroker. Примеры токенизации для множества особых случаев можно посмотреть в программе-примере TestLexicon.
Они позволяют выполнить разбор текста, заданного строкой в памяти, или содержащегося в дисковом файле. Функции sol_CreateSentenceBroker и sol_CreateSentenceBrokerMem[W,A,8] возвращают объект-перечислитель (итератор), который позволяет продвигаться по тексту с шагом в одно предложение и получать текст каждого извлеченного предложения.
Задача токенизатора - разбить предложение на набор слов с учетом разнообразных случаев использования символов-разделителей.
Существует два типа алгоритма токенизации, доступных в движке: с явно выделенными словами (европейские) и неявной сегментацией (японский, китайский).
Для большинства европейских языков слова так или иначе разделены пробелами и другими символами-разделителями - например запятыми.
Есть также языки, в которых слова не выделяются явно, а текст представляет из себя слитную последовательность символов (иероглифов).
Тип токенизации определен параметром Segmentation в описании языка. Его значение whitespace означает использование пробелов в качестве естественных границ слов. Значение dictionary_lookup означает, что для разбивки на слова используется просмотр по лексикону по принципу "пытаться найти максимально длинное слово по лексикону, затем - следующее максимально длинное".
В последнем случае вместо встроенного в движок алгоритма можно использовать внешний токенизатор, оформленный как динамически загружаемая библиотека. Имя файла библиотеки без расширения .DLL или .SO задается параметром SegmentationEngine в описании языка. Например, для японского языка в файле sg_languages.sol можно увидеть такие строки:
language
Japanese as JAPANESE_LANGUAGE
{
...
Segmentation = dictionary_lookup
SegmentationEngine =
cabocha_segmenter // используем внешний сегментатор в виде
cabocha_segmenter.dll
...
}
Функции, которые внешний токенизатор должен экспортировать, описаны в следующем разделе.
Символы, которые по умолчанию считаются границами слов, перечислены в описании языка в параметре WordBrokers.
Токены, которые нужно считать неделимыми последовательностями символов, не смотря на наличие внутри символов-разделителей, описаны через правила, начинающиеся ключевым словом unbreakable. Для справки смотрите файлы tok_unbreakable.sol, tok_emoticons.sol, rus_sentence_broker.sol, eng_sentence_broker.sol.
Возможны 2 типа токенов-исключений.
Во-первых, можно задать однозначную цепочку символов.
Во-вторых, можно определить регулярное выражение, которое выделит в потоке символов неделимый токен.
Первый стучай тривиален - правило оформляется так:
unbreakable { "и.о." }
В данном примере описано сокращение от "исполняющий обязанности" с точками в теле токена. Ключевое слово unbreakable открывает правило, текст, подлежащий считыванию как единое целое, записывается внутри фигурных скобочек.
Второй случай - регулярные выражения - позволяет одним паттерном задать целый класс токенов. Синтаксис используемых для этого регулярных выражений значительно упрощен в сравнении с обычными регулярными выражениями и содержит следующие синтаксические элементы:
. точка соответствует одному любому символу, включая пробельные.
{n,m} - цепочка символов с заданием минимальной и максимальной длины. Значения m или n можно опустить: {,n} или {m,} в этом случае пропущенный элемент задает соответственно 0 или бесконечность.
* цепочка символов (символ определен слева) любой длины, включая 0; полностью аналогично {0,}
+ цепочка символов (символ определен слева) любой длины, начиная с 1; полностью аналогично {1,}
? один символ (задан слева) или пустая подстрока; полностью аналогично {0,1}
Особый случай {m} задает цепочку символов точно определенной длины.
[abc...yz] - любой один символ из заданного набора, регистр не имеет значения
[<abc...yz>] - любой один символ из заданного набора, с точным соблюдением регистра
[:digit:] - цифра
[:space:] - пробельный символ
[:punct:] - пунктуатор
Пример задания паттерна:
unbreakable { ~"[:digit:][:digit:]-е" }
Он идентифицирует токены типа "30-е". Обратите внимание, что перед строкой с регулярным выражением ставится символ ~ (тильда), заставляющий считать строку именно регулярным выражением, а не буквальной записью неделимого токена.
Хотя токенизатор выделен в отдельный модуль, он очень тесно взаимосвязан с морфологическим анализатором.
Во-первых, делитель текста на предложения использует поиск слова в лексиконе (см. выше описание флага UseLexicon в параметре SentenceBrokerFlags в описании языка).
Во-вторых, окончательная сегментация предложения выполняется именно морфологическим анализатором, а в некоторых случаях - даже синтаксическим анализатором. Например, многолексемные словоформы типа "кто-то" или "сине-зеленый" собираются из частей именно в ходе морфологического анализа, так как дублировать информацию о таких словоформах с учетом их склонений в токенизаторе неразумно. Более того, некоторые конструкции, например "я-то", поступают в разобранном виде, то есть как 3 лексемы, на вход синтаксического анализатора, который и собирает их в единое целое.
Остановимся подробнее на ситуациях, когда разбивка текста на слова может давать не совсем очевидный результат. Как уже было отмечено, в лексиконе есть некоторое количество грамматических форм, состоящих из нескольких слов.
В русском языке таких случаев достаточно мало, но тем не менее их надо учитывать, в противном случае синтаксический анализ для них будет невозможен. Самый простой пример - наречие с кондачка. С одной стороны, это два слова, разделенные пробелом, который в русском языке играет безусловную роль разделителя слов. Но второй элемент кондачка не встречается в русской речи как самостоятельное слово, хотя, конечно, можно искусственно ввести существительное кондачек и задать для него форму родительного падежа. Можно еще заметить, что данная пара слов представляет из себя вполне самостоятельную единицу текста, внутрь которой нельзя вставить другие слова с большого кондачка. Токенизатор должен учитывать такие случаи и возвращать именно пару слов с кондачка как лексему.
Кроме очевидных случаев, бывают и более трудные для решения. Например, наречие с налету вторым элементом имеет слово налету, которое можно с небольшой натяжкой считать грамматической формой партитива для существительного налет. Если токенизатор обнаруживает, что элементы многословной лексемы могут являться самостоятельными словами, то он перекладывает окончательное решение на морфологический анализатор.
Намного чаще ситуация с многоэлементными словами встречается в английском языке, прежде всего из-за фразовых глаголов. Возьмем для демонстрации немного искусственный пример. Два предложения:
I listened in breathless silence.
I listened in to your presentation.
В первом случае имеем форму прошедшего времени глагола to listen, и обстоятельство с предлогом in. Можно еще заметить, что in в английском языке может выступать также в роли наречия, но в данном случае это не играет роли.
Во втором случае имеем форму прошедшего времени фразового глагола to listen in. Вторая часть in тут является послелогом. Но в описании английской морфологии такая часть речи не определена, вместо этого лексикон содержит словарную статью для глагола to listen in:
|
|
В пользу именно такого решения служит то, что значения глаголов to listen in и to listen отличаются. В русском языке аналогичная ситуация регулярно наблюдается для глаголов несовершенного вида и их дериватов совершенного вида, образуемых с помощью различных приставок:
слушать - подслушать
Сложность для токенизации заключается в том, что иногда невозможно выяснить, имеем ли мы дело с фразовым глаголом, не привлекая морфологический анализатор, например сведения о переходности глагола. В таких случаях токенизатор оставляет окончательное решение за морфологическим анализом. В рассматриваемом примере, анализатор приступает к работе с предложением
I listen in to your presentation
и создает 2 варианта токенизации, соответствующие разным интерпретациям слов listen и in, и только применив правила английской грамматики, выберет из них вариант с фразовым глаголом to listen in. Ключевое правило в данном случае - эвристика, которая запрещает соседствовать словам in и to.
Путь к каталогу, откуда будут загружаться модули внешних сегментататоров, указывается в узле segmentation_engines в файле dictionary.xml. Путь по умолчанию должен быть относительным к файлу dictionary.xml. Расширенная конфигурация позволяет задавать в файле dictionary.xml пути к загружаемым библиотекам сегментаторов и файлам данных, а также задавать произвольные инициализирующие параметры. Подробнее об этом читайте на странице о файле конфигурации грамматического словаря.
DLL должна экспортировать несколько функций.
Создание экземпляра сегментатора и его инициализация
void* Constructor( const wchar_t * DataFolder, const wchar_t * Params )
Аргументы:
DataFolder - путь к папке с файлами данных, необходимых сегментатору. Этот путь может указывать на ту же папку, откуда загружена библиотека DLL/SO сегментатора, но в конфигурационном файле словаря в узле segmenter/datapath может быть указан другой путь.
Params - строка дополнительный параметров инициализации, указанная в узле segmenter/params. Интерпретация этой строки отдана на откуп сегментатора и может содержать необходимые настроечные параметры.
Возвращает:
Конструктор должен вернуть указатель на объект, который будет затем передаваться в остальные вызовы API токенизатора как аргумент This. Очень удобно внутри DLL создать в динамической памяти объект со всеми необходимыми ссылками на выделенные ресурсы, чтобы аккуратно освободить их при вызове деструктора Destructor. Кроме того, ничто не запрещает движку создавать одновременно множество токенизаторов с разными настрояками, например для разных вариантов языка. Хранить индивидуальные настройки каждого экземпляра удобно в созданном объекте, указатель на который возвращается конструктором.
Удаление экземпляра сегментатора
void Destructor( void *This )
Функция должна освободить все ресурсы, выделенные для экземпляра, который идентифицируется аргументом This - результатом работы функции Constructor.
Получение свойств сегментатора
const wchar_t* GetSolarixPluginProperty( void *This, int iProp, int iSub )
Возвращает параметры плагина. Аргумент iProp определяет запрашиваемый параметр, для токенизатора имеют значение только четыре параметра: 0 - должно быть возвращена строка "segmenter", 1 - произвольное название плагина, 2 - любое краткое примечание, 10 - морфологические сегментатора, если вернет строку "classes", то значит он может определять для выделенных слов их грамматические классы. В последнем случае можно вызвать процедуру Segmenation с параметром Params=1, что заставит модуль вернуть разобранный текст в виде XML блока с дополнительными пометками для слов (см. далее).
Изменение параметров работы сегментатора
bool SetSolarixPluginProperty( void *This, int iProp, int iSub, const wchar_t *Value )
В текущей версии не используется.
Получение кода последней ошибки
int GetError( void *This )
Возвращает 0, если ошибок не было, либо ненулевой код ошибки.
Копирование текста ошибки в буфер
int GetErrorText( void *This, wchar_t *Buffer, int BufferLen )
Возвращается длина текста ошибки, измеряемая в единицах широких символов. Если задать аргумент Buffer как NULL, то функция должна вернуть необходимую длину буфера, чтобы выделить под него память и вызвать GetErrorText уже для копирования.
Выполнение сегментации текста
wchar_t* Segmentation( void*This, const wchar_t *OriginalText, wchar_t WordDelimiter, int Params )
Выполняет сегментацию текста в OriginalText. Результат возвращается как указатель на строку, в которой токены разделены символом WordDelimiter. Дополнительные флаги передаются через Params. В текущей версии этот аргумент может быть равен 0 или 1. Для 0 возвращается строка в plain text формате, а для 1 - в формате XML с такой структурой, пример для входного предложения 象は動物です。:
void SegmenterFreeString( void *This, wchar_t *Ptr )
Освобождает память, выделенную в Segmentation для возвращаемой строки. Так как DLL токенизатора и прикладной код могут использовать разные менеджеры динамической памяти, либо прикладная программа вообще не поддерживает работу с динамической памятью в C-стиле, то вызов Free - единственный надежный способ избежать утечки памяти при множестве вызовов Segmentation.
Самый простой способ увидеть работу сегментатора текста - вызвать программу Syntax с опциями командной строки -tokenize -language Russian. Вводимые с консоли предложения будут прогонятся через сегментатор и результаты будут напечатаны:

API морфологического анализатора
Внутренний язык грамматической машины
© Elijah Koziev 2010
Поисковая система
SDK Поисковой системы
Экранный переводчик
|
|
изменено 29-Oct-11 | ||||||||||||||||||||||||||||||||||||||