Поразительная история про режиссера Быкова

15.10.2017 - 1:48
Поразительная история про режиссера Быкова | Русская весна
Известный публицист Дмитрий Ольшанский — о режиссере Юрии Быкове, снявшем прекрасный патриотический сериал о российских чекистах «Спящие» и решившим вдруг покаяться перед «прогрессивной либеральной общественностью»

Режиссер Быков снял сериал — что-то, говорят, древнесоветское, про американских шпионов и хороших чекистов, а когда «честные люди» принялись его за это травить, — он вдруг сломался и начал каяться.

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

Но нет, оказывается, не игра.

Быков написал о себе:

«Я слабый, сомневающийся человек, чья мягкость и дезориентированность в моменте привела к тому, что лучшие умы страны от меня окончательно отвернулись».

А еще он написал:

«Я предал всё прогрессивное поколение, которое что-то хотело изменить в этой стране.
Боюсь, что после совершенного я больше не могу быть публичной фигурой и объектом следования. Мне придётся уйти надолго в тень и даже не для того, чтобы мои преступления забыли, а для того, чтобы не раздражать собой окружающий мир».

Удивительные слова.

Такие знакомые-знакомые.

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

А теперь так разоружаются перед «честными людьми».

Эта история, на самом деле, про то, что идеи во времени живут отдельно от тех символов, которые эти идеи должны представлять.

И сталинский СССР в двадцать первом веке — это не пенсионеры с красными флагами, не заваленная цветами могила Иосифа Виссарионыча, не книжки с названиями вроде «Эффективный нарком и попаданцы — вместе ударим по США!», не сериалы про шпионов с чекистами, и не современные кремлевские кабинеты, где можно встретить скорее расслабленный цинизм, чем единомыслие и страх.

А подлинный сталинский СССР, продленный в будущее, — это «честные люди», те самые лучшие умы.

Только они умеют навязывать вроде бы самостоятельным и образованным людям, как и о чем им надо думать.

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

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

И хочется сказать ему, этому несчастному Быкову:

Дорогой Юрий!

Да не бойтесь вы их так!

Они, конечно, верные сталинцы — именно они, — но уже совсем не такие страшные, как их предки в 1937-м.

Они уже — вырожденцы, говоря прямо.

Так что вы просто откройте глаза и скажите себе:
— Больше не боюсь прогрессивного поколения!
— Больше не боюсь лучших умов!

У вас получится, я в вас верю.

 

 

 

 

Кузявые ли бутявки, т.е. пишем морфологический анализатор на Python

Морфологический анализатор для русского языка — это что-то заумное? Программа, которая приводит слово к начальной форме, определяет падеж, находит словоформы — непонятно, как и подступиться? А на самом деле все не так и сложно. В статье — как я писал аналог mystem, lemmatizer и phpmorphy на Python, и что из этого получилось. 

Сразу скажу, получилась библиотека для морфологического анализа на Python, которую Вы можете использовать и дорабатывать согласно лицензии MIT.

 

Первый вопрос — зачем все это?


Потихоньку делаю один хобби-проект, назрело решение писать его на Python. А для проекта был жизненно необходим более-менее качественный морфологический анализатор. Вариант с mystem не подошел из-за лицензии и отсутствием возможности поковыряться, lemmatizer и phpmorphy меня слегка напрягали своими словарями не в юникоде, а приличного аналога на python я почему-то не нашел. Вообщем причин много, все, если честно, не очень серьезные, на самом деле просто захотелось. Решил я, в итоге, изобрести велосипед и написать свою реализацию.

За основу взял наработки с сайта aot.ru. Словари (LGPL) для русского и английского, а также идеи — оттуда. Там были описаны и конкретные алгоритмы реализации, но в терминах теории автоматов. Я знаком с теорией автоматов (даже диплом по формальным грамматикам защитил), но мне показалось, что тут можно прекрасно обойтись без нее. Поэтому реализация — независимая, что имеет как плюсы, так и минусы. 

Для начала, библиотека должна, как минимум, уметь:
1. Приводить слово к нормальной форме (например, в ед.ч., И.п. для существительных) — для слова «ЛЮДЕЙ» она должна вернуть «ЧЕЛОВЕК»
2. Определять форму слова (или формы). Например, для слова «ЛЮДЕЙ» она должна как-то дать знать, что это существительное, во множественном числе, может оказаться в родительном или винительном падежах.

 

Разбираемся со словарями


Словари с сайта aot.ru содержат следующую информацию:
1. парадигмы слов и конкретные правила образования;
2. ударения;
3. пользовательские сессии;
4. набор префиксов (продуктивных приставок);
5. леммы (незменяемые части слова, основы);
и еще есть 6. грамматическая информация — в отдельном файле.

Из этого всего нам интересны правила образования слов, префиксы, леммы и грамматическая информация.

Все слова образуются по одному принципу:
[префикс]+[приставка]+[основа]+[окончание]

Префиксы — это всякие «мега», «супер» и т.д. Набор префиксов хранится просто списком.

Приставки — имеются в виду приставки, присущие грамматической форме, но не присущие неизменяемой части слова («по»,«наи»). Например, «наи» в слове «наикрасивейший», т.к. без превосходной степени будет «красивый».

Правила образования слов — это то, что надо приписать спереди и сзади основы, чтобы получить какую-то форму. В словаре хранятся пары «приставка — окончание», + «номер» записи о грамматической информации (которая хранится отдельно). 

Правила образования слов объединены в парадигмы. Например, для какого-нибудь класса существительных может быть описано, как слово выглядит во всех падежах и родах. Зная, что существительное принадлежит к этому классу, мы сможем правильно получить любую его форму. Такой класс — это и есть парадигма. Первой в парадигме всегда идет нормальная форма слова (если честно, вывод эмпирический))

Леммы — это неизменяемые части слов. В словаре хранится информация о том, какой лемме соответствуют какие парадигмы (какой набор правил для образования грамматических форм слова). Одной лемме может соответствовать несколько парадигм.

Грамматическая информация — просто пары («номер» записи, грам. информация). «Номер» в кавычках, т.к. это 2 буквы, просто от балды, но все разные.

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

Поняв структуру словаря, уже несложно написать первую версию морфологического анализатора. 

 

Пишем морфологический анализатор


По сути, нам дано слово, и его надо найти среди всех разумных комбинаций вида
<префикс>+<приставка>+<лемма>+<окончание> и
<приставка>+<лемма>+<окончание>

Дело упрощает то, что оказалось (как показала пара строчек на питоне), что «приставок» у нас в языке (да и в английском вроде тоже) всего 2. А префиксов в словаре — порядка 20 для русского языка. Поэтому искать можно среди комбинаций
<префикс>+<лемма>+<окончание>, объединив в уме список приставок и префиксов, а затем выполнив небольшую проверочку.

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

В итоге получается, что задача сводится к поиску среди комбинаций
<лемма>+<окончание>, что еще лучше. Ищем подходящие леммы, потом смотрим, есть ли для них подходящие окончания*.

Тут я решил сильно упростить себе жизнь, и задействовать стандартный питоновский ассоциативный массив, в который поместил все леммы. Получился словарь вида lemmas: {base -> [rule_id]}, т.е. ключ — это лемма, а значение — список номеров допустимых парадигм. А дальше поехали — сначала считаем, что лемма — это первая буква слова, потом, что это 2 первых буквы и т.д. По лемме пытаемся получить список парадигм. Если получили, то в каждой допустимой парадигме пробегаем по всем правилам и смотрим, получится ли наше слово, если правило применить. Получается — добавляем его в список найденных форм. 

*Еще был вариант — составить сразу словарь всех возможных слов вида <лемма>+<окончание>, получалось в итоге где-то миллионов 5 слов, не так и много, но вариант, вообщем, мне не очень понравился.

 

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


По сути — все готово, мы написали морфологический анализатор, за исключением некоторых деталей, которые заняли у меня 90% времени)

 

Деталь первая

Если вспомнить пример, который был в начале, про «ЛЮДЕЙ» — «ЧЕЛОВЕК», то станет понятно, что есть слова, у которых неизменяемая часть отсутствует. И где тогда искать формы этих слов — непонятно. Пробовал искать среди всех окончаний (точно так же, как и среди лемм), ведь для таких слов и «ЛЮДЕЙ», и «ЧЕЛОВЕКУ» в словаре будут храниться как окончания. Для некоторых слов работало, для некоторых выдавало, кроме правильного результата, еще и кучу мусора. В итоге, после долгих экспериментов выяснилось, что есть в словаре такая хитрая магическая лемма "#", которая и соответствует всем пустым леммам. Ура, задача решена, ищем каждый раз еще и там.

 

Деталь вторая

Хорошо бы иметь «предсказатель», который смог бы работать и со словами, которых нет в словаре. Это не только неизвестные науке редкие слова, но и просто описки, например. 

Тут есть 2 несложных, но вполне работающих подхода:
1. Если слова отличаются только тем, что к одному из них приписано что-то спереди, то, скорее всего, склоняться они будут однаково 
2. Если 2 слова оканчиваются одинаково, то и склоняться они, скорее всего, будут одинаково.

Первый подход — это угадывание префикса. Реализуется очень просто: пробуем считать сначала одну первую букву слова префиксом, потом 2 первых буквы и т.д. А то, что осталось, передаем морфологическому анализатору. Ну и делаем это только для не очень длинных префиксов и не очень коротких остатков.

Второй (предсказание по концу слова) подход чуть сложнее в реализации (так-то сильно сложнее, если нужна хорошая реализация)) и «поумнее» в плане предсказаний. 
Первая сложность связана с тем, что конец слова может состоять не только из окончания, но и из части леммы. Тут я тоже решил «срезать углы» и задействовал опять ассоциативный массив с предварительно подготовленными всеми возмоными окончаниями слов (до 5 букв). Не так и много их получилось, несколько сот тысяч. Ключ массива — конец слова, значение — список возможных правил. Дальше — все как при поиске подходящей леммы, только у слова берем не начало, а 1, 2, 3, 4, 5-буквенные концы, а вместо лемм у нас теперь новый монстромассив. 
Вторая сложность — получается много заведомого мусора. Мусор этот отсекается, если учесть, что полученные слова могут быть только существительными, прилагательными, наречиями или глаголами. 
Даже после этого у нас остается слишком много не-мусорных правил. Для определенности, для каждой части речи оставляем только самое распространенное правило. 
По идее, если слово не было предсказано как существительное, хорошо бы добавить вариант с неизменяемым существительным в ед.ч. и.п., но это в TODO.

Идеальный текст для проверки работы предсказателя — конечно же, «Сяпала Калуша по напушке», про то, как она там увазила бутявку и что из этого вышло:

 
Сяпала Калуша по напушке и увазила бутявку. И волит:
— Калушата, калушаточки! Бутявка!
Калушата присяпали и бутявку стрямкали. И подудонились.
А Калуша волит:
— Оее, оее! Бутявка-то некузявая!
Калушата бутявку вычучили.
Бутявка вздребезнулась, сопритюкнулась и усяпала с напушки.
А Калуша волит:
— Бутявок не трямкают. Бутявки дюбые и зюмо-зюмо некузявые. От бутявок дудонятся.
А бутявка волит за напушкой:
— Калушата подудонились! Калушата подудонились! Зюмо некузявые! Пуськи бятые!
 

Из текста предсказатель не справился с именем собственным Калуша, с «Калушата»(они стали мужиками «Калуш» и «Калушат»), с междометием «Оее», загадочным «зюмо-зюмо», и вместо «Пуська» опять выдал мужика «Пусек», остальное все, на мой взгляд, определил правильно. Вообщем есть куда стремиться, но уже неплохо. 

О ужас, «хабрахабр» предсказывается тоже никак. А вот «хабрахабра» — уже понимает, что «хабрахабр».

Тут можно было бы, в принципе, подвести итог, если бы компьютеры работали мгновенно. Но это не так, поэтому есть

 

Деталь №3: ресурсы процессора и оперативной памяти

Скорость работы первого варианта меня вполне устроила. Получалось, по прикидкам, тыщ до 10 слов в секунду для простых русских слов, около тыщи для навороченных. Для английского — еще быстрее. Но было 2 очевидных (еще до начала разработки) «но», связанных с тем, что все данные грузились в оперативную память (через pickle/cPickle). 
1. первоначальная загрузка занимала 3-4 секунды
2. кушалось порядка 150 мегабайт оперативной памяти с psyco и порядка 100 без ( +удалось немного сократить, когда привел всякие там питоновские set и list к tulpe, где возможно)

Не долго думая, провел небольшой рефакторинг и вынес все данные в отдельную сущность. А дальше мне на помощь пришла магия Python и duck typing. Вкратце — в алгоритмах использовались данные в виде ассоциативных и простых массивов. И все будет работать без переписывания алгоритмов, если вместо «настоящих» массивов подсунуть что-нибудь, что ведет себя как массив, а конкретнее, для нашей задачи, — поддерживает [] и in. Все) В стандартной поставке питона обнаружились такие «массивные» интерфейсы к нескольким нереляционным базам данных. Эти базы (bsddb, ndbm, gdbm), по сути, и есть ассоциативные массивы на диске. Как раз то, что нужно. Еще там обнаружилась пара высокоуровневых надстроек над этим хозяйством (anydbm и shelve). Остановился на том, что унаследовался от DbfilenameShelf и добавил поддержку ключей в юникоде и ключей-целых чисел. А, еще добавил кеширование результата, которое почему-то есть в Shelf, но убито в DbfilenameShelf. 

Данные по скорости на тестовых текстах (995 слов, русский словарь, macbook):
get_pickle_morph('ru', predict_by_prefix = False): 0.214 CPU seconds
get_pickle_morph('ru'): 0.262 CPU seconds
get_shelve_morph('ru', predict_by_prefix = False): 0.726 CPU seconds
get_shelve_morph('ru'): 0.874 CPU seconds

Памяти вариант shelve, можно сказать, не кушал совсем.

Варианты shelve похоже, работали, когда словари уже сидели в дисковом кеше. Без этого время может быть и 20 секунд с включенным предсказателем. Еще замечал, что медленнее всего работает предсказание по префиксу: до того, как прикрутил кеширование к своим наследникам от DbfilenameShelf, тормозило это предсказание раз в 5 больше, чем все остальное вместе взятое. А в этих тестах вроде не сильно уже.

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

 

Пример использования


import pymorphy
morph = pymorphy.get_shelve_morph('ru')
#слова должны быть в юникоде и ЗАГЛАВНЫМИ
info = morph.get_graminfo(unicode('Вася').upper()) 


 

Так все-таки, зачем?


В самом начале я уже объяснил, зачем стал писать морфологический анализатор.
Теперь время объяснить, почему я стал писать эту статью. Я ее написал в надежде на то, что такая библиотека интересна и полезна, и вместе мы эту библиотеку улучшим. Дело в том, что функционал, который уже реализован, вполне достаточен для моих нужд. Я буду ее поддерживать и исправлять, но не очень активно. Поэтому эта статья — не подробная инструкция по применению, а попытка описать, как все работает. 

 

В поставке


pymorphy.py — сама библиотека
shelve_addons.py — наследники от shelf, может кому пригодится
encode_dicts.py — утилита, которая преобразовывает словари из формата AOT в форматы pymorphy. Без параметров, работает долго, ест метров 200 памяти, запускается 1 раз. Сконвертированные словари не распространяю из-за возможной бинарной несовместимости и большого объема.
test.py — юнит-тесты для pymorphy
example.py — небольшой пример и тексты с теми 995 словами
dicts/src — папка с исходными словарями, скачанными с aot.ru
dicts/converted — папка, куда encode_dicts.py будет складывать сконвертированные словари

 

Напоследок


ссылка:

bitbucket.org/kmike/pymorphy

Лицензия — MIT.

проверял только под Python 2.5.

Замечания, предложения, вопросы по делу — приветствуются. Если интересно — подключайтесь к разработке.

 

 

 

 

19 марта 2013 в 06:10Разработка

Чья морфология лучше? Яндекс vs Google

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


Эта статья адаптация моей статьи на SeoNews для Хабра

 
Русская морфология

В русском языке несколько сотен тысяч слов, причем каждое из них может быть во множестве словоформ. Например, прилагательное может быть в 100 словоформах:



В итоге, если сохранять морфологический словарь «в лоб» нам нужно около 500 мб. 500.000(число слов) * 75(ср. число словоформ) * (10 (ср. длина слова) + 4 байта (на сохранение номера слова + 2 байта на сохранение номера словоформы)). Для ускорения необходимо держать все эти данные в памяти, а скорость критична в случае поисковой системы. 

Существует «сжатый» вид. Многие слова имеют одинаковые окончания в одинаковой форме. Например, «великий» и «могучий». Нам нужно сохранить лишь начало слова («велик» и «могуч») и номер группы. В итоге нам понадобиться около 5мб. 500.000 * (8(ср. длина начала)+ 2(номер группы)). Однако, в этом случае база будет содержать артефакты.

 
Артефакты

Правил преобразования глаголов(делать) в причастия(делающий) не много. Поэтому в сжатой базе деепричастия и причастия считаются словоформами глагола, а не отдельными словами.
А вот правил преобразования глаголов в совершенный вид (делать->сделать, купить->покупать, искать->найти) бессчетное множество, поэтому для сжатой базы глаголы совершенного и несовершенного вида — разные слова.
Эти артефакты критичны только для поиска, в котором морфология используется, чтобы объединять словоформы. 

 
Яндекс

Яндекс подсвечивает не только словоформы, но еще и синонимы. Однако, подсветку синонимов можно отключить при помощи оператора "+". 


Связь совершенного и несовершенного вида глаголов в Яндексе организована через синонимы, а не через морфологию. 
А вот связь глаголов и причастий — реализована через морфологию.

На этой картинке четко видны артефакты сжатия морфологического словаря. Другими словами Яндекс использует сжатие.

 
Разница в выдаче

Быть может подсветка просто «отстает от мозгов». Однако, для высокочастотных запросов подсветка синонимом сама отключается. Это показывает, то что подсветка связана с мозгами в случае синонимов — не может же она просто так отключаться. Единственное объяснение этому — результатов в выдаче итак хватает и Яндекс экономит ресурсы не подключая поиск по синонимам.

Разница в выдаче хорошо наблюдается в запросах содержащих глагол в обоих видах и причастие. Например, «сделать клизму», «делать клизму» и «сделавший клизму», если набрать их в Яндекс и в Google.

 
Влияние на качество выдачи

Мы показали наличие артефактов морфологии Яндекс и то что они влияют на ранжирование, хотя, они могут не влиять на качество выдачи. Однако, мне удалось довольно быстро найти несколько исключений в Яндексе: купить и покупать, выщипывать и выщипать, отправлять и отправить склеены на уровне морфологии. Единственная гипотеза почему эти исключения появились — их добавили для улучшения выдачи. Следовательно, артефакты, как минимум, в частных случаях ухудшают выдачу.

 
Google

Google использует несжатую морфологию. По крайней мере, «артефакты сжатия» мне не удалось найти.

Единственное несоответствие формальной модели русского языка в Google — обычная (хороший) и превосходная (лучший) степени прилагательных разделены в морфологии. Вероятно они соединены как синонимы, однако, Google не подсвечивает синонимы.

Это не объясняется как артефакт сжатия, поскольку правил преобразования преобразования форм прилагательных не так много (красивый->красивейший, умный->умнейший) и ни база AOT.ru и ни словарь Зализняка не разделяет формы прилагательных.

Разделение прилагательных по степени, объясняется оптимизацией качества выдачи. Степень прилагательных изменяет их «окраску», делая их смысловую связь более похожей на синонимы, чем на словоформы. Например, запрос «прекрасные фото» по смыслу намного ближе к «красивые фото» чем «красивейшие фото». 

Это совпадает с интуитивным представлением о языке. Я несколько раз встречался с тем, что «хороший» и «лучше» приводили в пример того, что Яндекс понимает синонимы.

 
Почему так произошло

Морфология в Яндексе писалась лет 10 назад, а тогда 500 мб. памяти для нескольких сотен серверов могли обойтись в копеечку. С тех пор память подешевела, но изменение морфологии привело бы к целому каскаду изменений в БД Яндекса. Поэтому, в Яндексе используется сжатый вид морфологии. 
Google изначально английская поисковая система. В английском языке слова имеют всего несколько словоформ и в сжатии морфологии нет смысла. Видимо, поэтому, в русской морфологии Google не используется сжатие. 

 
Итого

Морфология у Google организована «правильнее» и немного лучше чем у Яндекса. Ирония в том, что причина этому в английском происхождении Google. 
Однако, морфология это лишь один из многих аспектов в выдаче. Сказать, что у Google лучше выдача чем у Яндекса только на основе морфологии, тоже самое что оценивать интеллект по высоте лба. Цель статьи была в развеивании убеждения о том, что морфология в Google организована хуже чем в Яндексе.

 

 

 

Морфология и компьютерная лингвистика для самых маленьких

На Хабре уже был пост о Технопарке, и даже рассказы о курсах (12), которые в нем проходят. Сегодня мы публикуем первую часть мастер-класса, который для студентов Технопарка провел Андрей Андрианов из ABBYY.
 
В цикле будет 4 поста

Для начала не лишним будет вспомнить, что такое морфология, а также какое отношение она имеет к лингвистике. За этим предлагаю пройти под кат к содержимому первого поста серии.

Многим из вас со школы знакомо предложение «Глокая куздра штеко будланула бокра и курдячит бокрёнка». Хотя мы не знаем, что скрывается за всеми словами этого предложения (за исключением союза «и»), мы можем предположить, что главное действующее лицо здесь куздра. Причем это не какая-то обычная куздра, а глокая. Что она сделала? Будланула. Как она это сделала? Штеко. Кого она будланула? Бокра. Кроме того, она совершает какие-то действия над бокрёнком.

Эту фразу придумал академик Лев Щерба, а академик Александр Потебня на примере этой фразы демонстрировал своим студентам, как определенную часть семантики мы можем извлечь из морфологии слова, из его словоизменения, из окончаний. Мы не знаем лексического значения слов — не понимаем, какие объекты названы — но мы можем уловить их грамматическое значение. Именно о грамматических значениях я бы хотел рассказать в этой статье.

 
Морфология – это раздел лингвистики, который изучает 4 вещи

 
Части речи

Как только вы прочитали предложение: «Глокая куздра штеко будланула бокра и курдячит бокрёнка», вы сразу выловили подлежащее и два сказуемых — «будланула» и «курдячит». Разные части речи в разных языках могут по-разному образовывать предложения. 

 
Словоизменение

Увидев слово «будланула», даже не зная, что оно означает, вы уже можете его просклонять, проспрягать. Вам понятно, что инфинитив этого слова — «будлануть». Можно изменить род (будланул, будлануло), а можно поменять время (будланёт, будланёшь). То, как слова изменяются, в каких формах определяют то или иное грамматическое значение, изучает второй подраздел морфологии — словоизменение.

 
Словообразование


Встретив бокра и бокрёнка в одном предложении, вы сразу же себе представили, что бокрёнок – это детеныш бокра, как слонёнок и слон. Может быть, это просто мелкая копия большого бокра — ну, не выдался персонаж, например, ростом.

Мы часто образуем новые слова с помощью суффиксов (например, уменьшительно-ласкательных), для того чтобы поменять какие-то свойства объекта; можно даже изменить часть речи. Например, есть слово «лопата». От этого слова при желании можно образовать глагол: – лопатить. Носители языка быстро поймут его значение, а вот те, кто изучает русский язык как иностранный, будут долго разгадывать, что же это за слово, и почему его нет в словаре. Довольно часто мы образуем глаголы от свойств разных животных и наделяем эти глаголы какими-то свойствами. 

 
Грамматическое значение

Я уже упоминал, что у слова есть два значения – лексическое (что слово означает по словарю), и грамматическое (что слово означает в предложении). Какую-то семантику можно вынести и из грамматического значения. Например, слово «будланула». Очевидно, что это глагол. Из этого следует, что слово «будланула» означает действие. Кроме того, мы можем сказать, что это глагол в прошедшем времени, единственном числе, женского рода, совершенного вида. Все это дает вам дополнительную информацию. Например, в русском языке часто женский род ассоциируется с женским полом. Мы не можем объяснить, почему вилка женского рода, а стакан мужского, но почему девушка поднялась, а мальчик поднялся, нам понятно. И нам будет резать слух, если кто-то ошибется в выборе рода.

Ещё со школьной парты мы представляем грамматическое значение в виде набора граммем. Родительный падеж, прошедшее время, единственное число – все это разные граммемы. Граммемы можно сгруппировать по категориям. Именительный, родительный, дательный, винительный и предложный – это категория падежа. Одна и та же форма не может иметь две граммемы одной категории. Если мы говорим «будланула», то мы имеем в виду только граммему единственное число. В одной и той же форме «будланула» мы не можем зашифровать одновременно две формы глагола. Не может быть существительного одновременно и в именительном и в дательном падеже. Формы могут совпадать, как они часто совпадают у именительного и винительного падежа, однако их нужно различать. Это еще одна из задач морфологии.

 
Прикладные задачи лингвистики

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

Области применения компьютерной лингвистики: 
 
  • Обработка естественного языка
    1. Словари и автоматический перевод
    2. Извлечение фактов из текста и автореферирование
    3. Системы управления знаниями, экспертные системы
    4. Вопросно-ответные системы
  • Распознавание текста (OCR)
  • Распознавание речи (ASR)
  • Синтез речи

 
Обработка естественного языка

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

 
Распознавание текста (OCR)

При распознавании текста применяются другие технологии. А в данном случае нас интересует, является слово словарным или нет. Когда распознается текст, мы часто имеем дело с нечеткостью изображения, и алгоритмы бинаризации, которая происходит перед распознаванием текста, не могут дать результат 100%. В связи с этим генерируется масса гипотез о том, что же там все-таки написано. Иногда невозможно отличить букву «н» от «м» или «н» от «к», и тогда в дело вступает компьютерная лингвистика, а если точнее – морфология. Морфология подсказывает, есть такое слово в языке или нет. 

 
Распознавание речи (ASR)

Распознавание речи работает схожим образом. Из набора звуков строятся гипотезы относительно конкретных букв, которые произносит человек. Возьмем слово «корова». Мы говорим «карова», а пишем «корова». Здесь важно понять, есть ли в русском языке слово «карова» или нет.

 
Синтез речи

Синтез речи – еще одна интересная технология, которая может использоваться как самостоятельно, так и в рамках автоматического перевода. Это уже синтетическая задача: нам необходимо проанализировать текст на одном естественном языке, определить его смысл, и, исходя из полученного результата, сгенерировать текст на другом естественном языке.

На этом вводная часть закончена. В следующем посте поговорим о роли морфологии в компьютерной лингвистике.

 

 

 

6 августа 2013 в 13:19Разное

Роль морфологии в компьютерной лингвистике

Содержание цикла статей про морфологию

Раньше автоматический перевод работал следующим образом:
 
  1. Анализировал формы слов в исходном предложении;
  2. Пытался подобрать одну из синтаксических схем исходного языка, в которую подошло бы предложение с найденными формами;
  3. Находил соответствующую синтаксическую схему для целевого языка;
  4. Находил перевод для каждой из словоформ в исходном предложении;
  5. Слова-переводы ставил в форму, необходимую для целевой синтаксической схемы.

Современные технологии пытаются пойти дальше. 
Они пытаются объяснить значение каждого слова в предложении, поднимаясь в пирамидке на рисунке на уровень семантики. Это помогает уточнить перевод, так как отметаются гипотезы, в которых не согласуется семантика отдельных слов. Кроме того, из семантических соображений некоторые синтаксические правила могут быть применимы не для всех слов языка. А также семантика в некоторой мере позволяет избавиться от омонимии. 

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

Прагматика языка – это то, как человек относится к тому, что он говорит. Классический пример: «Может ли двойное утверждение означать отрицание?» — «Ну да, конечно!». Здесь за двойным утверждением прячется прагматический слой понимания языка. 
Но не будем забегать вперёд, пройдёмся снизу вверх по пирамиде.

 
Морфология

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

 
Синтаксис

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

Так происходит перевод. Нам дано предложение на русском языке. Сначала мы его анализируем морфологически, потом синтаксически, потом семантически. После этого семантика определяет синтаксическую форму предложения на целевом языке (к примеру, на английском), синтаксис выстраивает все лексемы в нужные формы, и от морфологии мы получаем непосредственно конкретные формы этих слов.

 
Семантика

На семантическом уровне задача существенно усложняется. Нам нужно понять, что значат те или иные слова. Морфология работает со словами по отдельности, она берет отдельное слово, – к примеру, «бокра», – и говорит: «Это существительное мужского рода в родительном падеже». Синтаксис смотрит на связь «бокра» с другими словами в предложении. «Будланула бокра» – «бокр» относится к глаголу «будланула» как дополнение.

Но на уровне семантики мы бессильны: мы не знаем, кто такой «бокр», и мы не знаем, что значит «будланула». До недавних пор технология автоматического перевода текста была бессильна даже в тех случаях, которые могут показаться нам очевидными. Она не знает, что прячется за фразой «Do you want a cup of tea?» Может быть, это кубок чая (cup как спортивный кубок), а может быть, всего лишь чашка.

 
Прагматика

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

 

Задачи морфологии


Компьютерную лингвистику можно рассматривать как большое здание, а морфологию – как фундамент здания, решающий конкретные задачи. Ведь в программе это отдельная dll'ка, предоставляющая определенное API, от которой мы хотим получить конкретный результат. Табличка ниже упорядочивает задачи, которые возложены на модуль морфологии. 

 
 Задача  Как используется
 Получение начальной формы — Лемматизация Поиск
 Постановка слова в заданную форму Автоматический перевод
Синтез речи
 Получение всех форм слова Поиск
Базы знаний
Текстовый редактор
OCR
 Грамматическое значение Автоматический перевод
OCR
Распознавание речи
 Словарность слова OCR
 Исправление слова Поиск
Текстовый редактор


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

Начнем по порядку: лемматизация – это получение начальной формы слова, или, по-другому, леммы. Если нам нужно восстановить начальную форму слова «будланула», у нас в голове сразу возникает слово «будлануть». Однако если попробовать провернуть подобный эксперимент со словом на неизвестном для нас языке, задача быстро перестанет казаться тривиальной.

Когда я учил немецкий язык в институте, для меня было ужасно искать в бумажном словаре слово «gemacht» и не находить это слово. Или найти не то слово и пытаться понять, как оно связанно с другими словами в предложении. А причина в том, что это форма глагола «machen», а не существительное «Gemächt».

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

 
Постановка слова в заданную форму

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

 
Получение всех форм слова

Следующая задача — получения всех форм слова — чаще всего используется при поиске. Существует два подхода к индексации текста: первый – сопоставление слова из запроса со всеми формами этого слова в индексе, второй – получение леммы запрашиваемого слова и последующее сопоставление с начальными формами в индексе. В данном случае реализация зависит от специфики решаемой задачи. Может быть, вы пытаетесь проиндексировать весь интернет, а может быть, наоборот, какую-нибудь локальную базу знаний, но вам необходима высокая точность результатов.

 
Получение грамматического значения слова

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

 
Словарность слова

На этом пункте мы уже останавливались раньше, поэтому просто закрепим: для распознавания текста очень важна словарность слова — принадлежит слово языку или не принадлежит.

 
Исправление слова

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

Кроме того, исправление слова используется при индексировании интернета. В случае с вебом производительность становится особенно важной, потому что слова с ошибками встречаются очень часто. Мы не можем найти их в словаре в том виде, в котором их ввел пользователь, но перевести должны, потому что человек, прочитав это слово, понял бы, что имел в виду собеседник. Как говорится, «виласипед» или «велосипед», мопедом он от этого не станет, как ни напиши (кстати, об одной из реализаций «понятливого» спеллчекера можно почитать в этой статье).

 

Решения задач. Начнем с простого: лемматизация



Вернемся к нашим куздрам. Как нам получить начальную форму от слова «будланула»? Первое, что приходит на ум: меняется только окончание. Почему бы просто не заменять одни окончания на другие с помощью старых добрых регулярных выражений? Кстати, очень много морфологий, которые можно найти в интернете в открытом доступе, основаны именно на регулярных выражениях. Замена -ла в конце на -ть в нашем случае прекрасно работает: «будланула» на «будлануть». Однако стоит копнуть чуть глубже, и окажется, что работает далеко не всегда.

 
Подводные камни: омонимия


Омонимия – это совпадение форм разных слов. Более того, бывает так, что совпадают две формы одного и того же слова, а бывает – разных слов, как в нашем примере. «Стали» – это глагол прошедшего времени множественного числа или это множественное число от слова «сталь». Какая начальная форма: «стать» или «сталь»? Эта проблема не может быть решена на уровне морфологии: морфология рассматривает отдельное слово вне контекста, поэтому, какие методы ни применяй, омонимию не разрешишь. Но самое интересное, что даже на следующих уровнях, на синтаксическом, а порой и на семантическом, не всегда возможно разрешить омонимию. Приведенный выше расхожий пример – яркое тому подтверждение.

 
Как будто этого мало


Есть и другое возражение: не все слова употребляются во всех формах. Если мы из перехода «кружиться» – «кружить» вынесем такое правило, что -ться следует заменять на -ть, то слово «казаться» мы должны будем заменить на слово «казать». Но такого слова в русском языке нет.

 
Все сложно

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

 
В активном поиске

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

 

 

 

 

Морфология. Задачи и подходы к их решению

 
Содержание цикла статей про морфологию

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

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

Во-первых, мы можем проверить, есть ли слово в словаре. С помощью правил, основанных на регулярных выражениях, мы не выясним, есть слово «мымымымыться» в русском языке. Окончание вполне подчиняется правилам русского языка, повторение слогов – тоже не исключительный случай. Регулярное выражение это слово пропустит, но на самом деле никакого «мымымымыться» в русском языке не существует.

Другая задача, которую решает словарь, хранимый в морфологии, – это исправление ошибок. Как только мы не находим в словаре какого-то слова, зато обнаруживаем там другое слово на коротком расстоянии Левенштейна до искомого, мы принимаем решение об исправлении.

Для решения этих задач нам нужно хранить не только все слова, но и все их формы. Все правила образования форм для группы слов, похожих друг на друга, называются парадигмой словоизменения. Возьмем глагол «будлануть»: будланул, будланешь, будланет, будланул, будланула, будланули. Можно найти массу глаголов, которые будут изменяться по тем же правилам; с другой стороны, огромное количество глаголов будет изменяться по иным правилам. Значит, эти глаголы принадлежат разным парадигмам.

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

 
Хранение словаря

Таким образом, словарь, хранимый в системе, решает массу задач, что не может не радовать. Но как же хранить этот словарь? В русском языке несколько сотен тысяч слов. В среднем на одно слово приходится 15 форм. Откуда мы берем это число? Рассмотрим падежи: их 6 для единственного числа и 6 для множественного – уже 12. И это не говоря о том, что в русском языке не 6 падежей, как учат в школе, а 8.

Во-первых, в предложном падеже прячется падеж, который называется локатив. Если мы поставим в предложный падеж слово «лес», у нас получится два варианта: «о лесе» и «в лесу». В лесу – это локативный падеж, а не предложный. Для многих существительных формы этих падежей совпадают, потому что мы не различаем «в столе», «о столе», и «на столе». Все потому, что локации «в столе» нет, а вот локация «в лесу» есть.

Восьмой падеж носит название партитив. Этот падеж показывает на часть от целого. Если у нас закончился чай, вставать лень, а рядом ходит кто-то незанятый, мы ему говорим: «Налей мне, пожалуйста, чаю». Как правило, форма слов в партитиве совпадает с родительным или винительным падежом. Википедия приводит нам прекрасный пример слова, которое не имеет никаких других форм, кроме партитива множественного числа – «щец» («Щец не желаете?»; «Налей-ка мне щец!») 

Итого, 8 падежей умножить на 2 формы (единственное и множественное число) — уже 16. У глаголов же есть время, число и род. Путем нехитрых арифметических вычислений получаем, что 15 форм – довольно скромная оценка снизу.

Среднее эмпирическое число символов в русском слове – 9. Умножаем полученные результаты, не забывая учесть, что на 1 символ приходится 2 байта, и выясняется, что для словаря всех форм русского языка необходимо 50 мегабайт. На практике же наши идеальные 50 мегабайт увеличиваются еще в полтора раза: в итоге текстовый файл со всеми формами слов русского языка занимает более 75 мегабайт.

Кроме того, даже если мы запишем все эти слова в файл, нам необходимо будет проводить по нему поиск (иначе зачем он нужен?). То есть мы должны упорядочить слова по алфавиту и дополнительно к каждому слову добавить указатель на начальную форму слова. Указатели, хоть и не тяжелые по одному, все же ощутимо добавят веса нашему словарю. При этом скорость поиска составит



И это только на русский язык, а еще есть английский, французский, испанский, итальянский и масса других прекрасных и богатых языков. Если мы хотим, чтобы, например, FineReader распознавал и поддерживал много языков, нам одних морфологических словарей нужно будет уместить в него на гигабайт. Что говорить об эталонах для распознавания и других dll'ках? Пользователь нас с такой тяжеловесной технологией не поймет — и будет прав. Надо быть скромнее и хранить оптимальнее.

 
Префиксное дерево

Нам на помощь приходит префиксное дерево. Как оно устроено? Все начинается с вершины – пустого символа, в котором хранится алфавит. Для первой буквы там хранится весь алфавит, точнее, массив из 33 указателей. Каждый указатель соответствует своему символу. Первый указатель – символу А, второй – символу Б, некий К-тый указатель – символу К и некий П-тый – символу П. По указателям мы можем получить следующие узлы этого дерева, которые соответствуют буквам русского алфавита.

В следующих узлах снова хранятся массивы. Причем для быстроты поиска мы пренебрегаем тем, что букв, на которые нам нужны указатели, будет уже меньше 33 (вряд ли нам потребуется сочетание «аы», например). Мы оставляем размерность массива неизменной, вставляя для отсутствующих символов нулевые указатели. 


На рисунке изображен пример префиксного дерева для четырех слов: «кот», «кошка», «кит» и «пес». Розовым закрашены финальные вершины.

Преимущество такого подхода в том, что мы не храним огромные цепочки повторяющихся символов для одного и того же слова, стоящего в разных формах. Таким образом, мы уменьшаем размер файла со словарем. К примеру, для русского языка это дерево вместе с дополнительными данными занимает порядка 2 мегабайт. Во-первых, по сравнению с 75 мегабайтами это, безусловно, успех. Во-вторых, скорость поиска по такому дереву значительно выше, чем по отсортированному файлу. Теперь она зависит не от размера всего словаря, а только от того, что мы ищем. Если мы ищем союз «а», мы найдем его за один шаг, а если мы ищем длинное слово, например, «вертолетостроительный», поискать придется немножко подольше.

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

 
Что хранить в словаре



Теоретически мы действительно можем хранить все формы слов в дереве, сохраняя при этом указатели на начальную форму (синие линии на рисунке). Однако обратите внимание: несмотря на то что «кот» и «котик» относятся к разным словообразовательным парадигмам, часть флексий (так мы называем окончания) у них совпадают. Что если эти хвосты слов хранить отдельно, а в дереве оставить начальные формы котов, котиков и китов? Так будет эффективнее.

 
Основательный подход

В школе слово делят на составные части следующим образом: приставка, корень, несколько суффиксов и окончание. Мы пойдем другим путем. Во-первых, мы не будем отделять приставку от корня. Это связано с тем, что часто приставка вместе с корнем образуют совершенно новый смысл. Возьмем для примера слова «приставка» и «ставка». «При-» – это всего лишь приставка у слова «ставка», но «приставка» и «ставка» – это совершенно разные вещи.

Кроме того, приставка не выражает никакой грамматической категории. Когда мы добавляем приставку «пере-» («переделать» или «перекурдячить»), мы меняем лексический смысл слова, а не грамматический. Поэтому приставку и корень мы рассматриваем как единое целое и называем это основой слова.

Еще одно отличие от школы: мы практически никогда не отделяем суффиксы от окончаний, так как в большинстве случаев они выражают грамматическую категорию. В нашем примере «-л-» и «-а-» выражают прошедшее время, женский род, и мы объединяем их в флексию. При таком делении мы получаем изменяемую часть слова – флексию и неизменяемую – основу.

 
Удобные допущения


Вы уже, наверное, привыкли к тому, что в компьютерной лингвистике ничего просто так не бывает. Вот и здесь: в разных формах слова может меняться даже часть корня. Можно, конечно, нудно рассуждать о том, что это всего лишь изменяемая согласная в корне «дру-г» и «дру-зь», но для решения практических задач нам удобнее принять, что у этих слов одинаковая основа «дру-», а флексии разные.

Итак, мы определили, что лексемы (основы слов) и флексии будут храниться отдельно. Наконец мы подходим вплотную к тому, как решает задачи лемматизации наш морфологический модуль в ABBYY.

 
Окончательная лемматизация


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

Если внимательно посмотреть на окончания, можно заметить, что их можно приписать как к «коту», так и к «киту». Если мы добавим «а», получим родительный падеж, если «у», – дательный и так далее. Всё это – разные формы двух слов, которые принадлежат одной и той же парадигме, и поэтому одинаково меняются.

 
Разбор китов

Когда мы получаем форму слова, мы начинаем её анализировать не сначала, а с конца. Сначала мы предполагаем, что есть нулевое окончание, которое помечено розовым цветом и показывает, что перед нами именительный падеж, единственное число. Затем мы идём по данному нам слову «китами» и встречаем букву «и». Мы находим букву «и» в префиксном дереве. Следующая буква — «м». Эти буквы не являются последними, поэтому мы продолжаем двигаться вглубь дерева. Следующая буква «а», которая помечена розовым, конечный узел в дереве. Значит, «-ами» слева направо, или «-има» справа налево — это один из вариантов окончания, которым может заканчиваться слово.

Итак, мы нашли два окончания: пустое и «-ами». Теперь нам нужно искать основу в префиксном дереве словаря. Этот поиск осуществляется уже слева направо. Мы последовательно находим буквы «к», «и» и «т», и на букве «т» дерево заканчивается. То есть «кит» — это одно из слов, хранящихся в этом дереве. Если мы попробуем искать дальше, то увидим, что следующая буква «а», но указателей на следующую вершину нет, значит, мы прекращаем поиск.

Мы видим, что три буквы слева и три буквы справа срослись в слово, а вот пустое окончание нам не пригодилось. Мы принимаем решение, что «китами» — это форма множественного числа, творительного падежа, лексемы с начальной формой «кит».

Об ещё не освещённых задачах, решаемых подсистемой морфологии, читайте в следующем посте.

 

 

Псевдолемматизация, композиты и прочие странные словечки

 
Содержание цикла статей про морфологию

Не все задачи успели мы с вами обозреть в предыдущем посте, поэтому продолжать будем в этом.

Часто случается, что в интернете появляется какой-нибудь неологизм. Например, «затроллить». Слово «тролль» в словаре есть, но «затролля» уже нет, а, как мы выяснили ранее, приставка при разборе не отделяется от корня, так что мы понятия не имеем, что это за «затроллить» и как его изменять. Чтобы проанализировать это слово, нам придётся воспользоваться псевдолемматизацией. Для этого мы снова пользуемся так называемым обратным деревом окончаний (записанных справа налево).

Мы сразу находим пустое окончание. Можно предположить, что «затроллить» – это существительное, которое оканчивается пустым окончанием. Далее мы видим мягкий знак, а на мягкий знак просто так ничего не оканчивается. А вот «-ть» – это типичное окончание для глаголов. Таким образом, мы можем предположить, что у слова «затроллить» основа «затролли-», окончание «-ть». Теперь мы можем получить другие формы. Если отбросить «-ть» и подставить флексию прошедшего времени мужского рода «л», мы получим слово «затроллил».

Кроме того, мы понимаем, что «затроллить» » это инфинитив глагола, а значит, когда мы будем преобразовывать предложение, в котором нам встретилось слово «затроллить», на другой язык, мы поймём, что это слово выражало какое-то действие в инфинитиве. Таким образом, мы можем его перевести транслитерацией, например, «затроллинг» или «to troll», а за- ещё как-нибудь выразить. Именно в этом и состоит задача псевдолемматизации: разбирать неизвестные слова, пусть и не понимая семантику.

 
Верхушка айсберга

Мы рассмотрели базовые задачи, с которыми сталкивается морфология в компьютерной лингвистике. Важно понимать, что это малая толика того, чем она занимается. Вот еще неполный список проблем, над которыми мы работаем.

 
Композиты

Возьмем слово «паротепловозостроительный». Что такое пар-, тепло-, воз-, строительный – по отдельности понятно. Проблемы начинаются тогда, когда мы начинаем эти корни сочетать, а сочетать их можно практически бесконечно.

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

Кроме того, не все слова можно склеить друг с другом. Например, рассмотрим слово тепло-возо-строительный можно сперва восстановить первую связь тепловозо-строительный (смысл слова «строить тепловозы»). А можно вторую: тепло-возостроительный (смысл «обогревать возостроение»). Но слова возостроительный не существует. В то же время слово «тепловоз» – вполне словарное. Получается, что важен порядок склеивания кусков композита. Носитель языка быстро понимает, как правильно восстановить смысл слова. Но алгоритм анализа композитов обязан перебрать факториал разных вариантов последовательностей.

 
Словосочетания

Допустим, мы пытаемся проанализировать авиабилет, на котором написано: «Los Angeles-San Francisco».
Если поделить по пробелам, то мы получим слово «Angeles-San» и два отдельные слова «Los» и «Francisco». Что такое “Angeles-San”? Уважительное обращение к японцу по имени Анджелес? Наша система должна понимать, что “Los Angeles” – это одно словосочетание, “San Francisco”– другое, и что нет таких словосочетаний как “Los Francisco” и “San Angeles”.

 
Алгоритмы исправления ошибок ввода

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

 
Статистическая обработка

В русском языке, как, впрочем, и в любом другом, разные слова употребляются с разной частотой. Во многих задачах знание этой частоты оказывает неоценимую помощь. Например, в той же псевдолемматизации. Система находит два варианта, и необходимо принять решение, какой из них выбрать. Если есть контекст, то из него можно получить некоторые сведения, которые помогут определить правильный вариант. Если контекста нет, надо выводить все найденные варианты. И в этом случае лучше выдавать проранжированные варианты: те, которые статистически встречаются чаще, выдавать первыми, а те, что реже – последними.

 
Discuss!

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

 

Как Яндекс использует лингвистику в поиске

Яндексу ежедневно приходится отвечать на десятки миллионов запросов. Поисковая система должна уметь быстро и точно их обрабатывать. Без применения лингвистики поисковая система сможет найти только точные совпадения в проиндексированных документах. Чтобы найти релевантные документы, системе необходимо правильно определить язык запроса, исправить опечатки, произвести морфологический разбор каждого слова, расширить запрос синонимами или вообще его переформулировать. В этой лекции Алексей Зобнин постарался дать студентам Малого ШАДа ответы на следующие вопросы:

 

 

Страница лекции

Изначально лекция рассчитана на старшеклассников, но и взрослые смогут почерпнуть из нее много полезного.
Презентацию можно скачать здесь.

Лекции Малого ШАДа посвящены информатике, математике, лингвистике и смежным областям знаний.

Докладчики — ведущие ученые, специалисты наукоёмких компаний и преподаватели известных вузов. После каждой лекции проходит дискуссия со слушателями и ответы на вопросы.

Мы стараемся сохранить в нашей аудитории неформальную атмосферу выездных школ и конференций. Лекции полностью независимы друг от друга, и слушатели могут свободно выбирать интересные темы. Занятия бесплатные.

 

 

 

Определение части речи слов в русском тексте (POS-tagging) на Python 3

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

[('съешьте', 'глаг.'), ('еще', 'нареч.'), ('этих', 'местоим. прил.'), ('мягких', 'прил.'), ('французских', 'прил.'), ('булок', 'сущ.'), ('да', 'союз'), ('выпейте', 'глаг.'), ('чаю', 'сущ.')]

Зачем это нужно? Например, для автоматического определения тегов для блог-поста (для отбора существительных). Морфологическая разметка является одним из первых этапов компьютерного анализа текста. 

 

Существующие решения


Конечно, все уже придумано до нас. Существует mystem от Яндекса, TreeTagger с поддержкой русского языка, на питоне есть nltk, а также pymorphy от kmike. Все эти утилиты отлично работают, правда, у pymorphy нет поддержки питона 3, а у nltk поддержка третей версии питона только в бете (и там вечно что-то отваливается). Но реальная цель для создания модуля — академическая, понять как работает морфологический анализатор.

 

Алгоритм


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

 

Данные


Для обучения нашего скрипта я использовал национальный корпус русского языка. Часть корпуса, СинТагРус, представляет собой коллекцию текстов с размеченной информацией для каждого слова, такой как, часть речи, число, падеж, время глагола и т.д. Так выглядит часть корпуса в XML формате:

 
<se>
<w><ana lex="между" gr="PR"></ana>М`ежду</w>
<w><ana lex="то" gr="S-PRO,n,sg=ins"></ana>тем</w> 
<w><ana lex="конкурент" gr="S,m,anim=pl,nom"></ana>конкур`енты</w>
<w><ana lex="наступать" gr="V,ipf,intr,act=pl,praes,3p,indic"></ana>наступ`ают</w> 
<w><ana lex="на" gr="PR"></ana>на</w> 
<w><ana lex="пятка" gr="S,f,inan=pl,acc"></ana>п`ятки</w> .
</se>
<se>
<w><ana lex="вот" gr="PART"></ana>Вот</w> 
<w><ana lex="так" gr="ADV-PRO"></ana>так</w>,
<w><ana lex="за" gr="PR"></ana>з`а</w> 
<w><ana lex="пять" gr="NUM=acc"></ana>пять</w> 
<w><ana lex="минута" gr="S,f,inan=pl,gen"></ana>мин`ут</w> 
<w><ana lex="до" gr="PR"></ana>до</w> 
<w><ana lex="съемка" gr="S,f,inan=pl,gen"></ana>съёмок</w> ,
<w><ana lex="родиться" gr="V,pf,intr,med=m,sg,praet,indic"></ana>род`илс`я</w> 
<w><ana lex="новый" gr="A=m,sg,nom,plen"></ana>н`овый</w>
<w><ana lex="персонаж" gr="S,m,anim=sg,nom"></ana>персон`аж</w> .
</se>


Предложения заключены в теги <se>, внутри которых расположены слова в теге <w>. Информация о каждом слове содержится в теге <ana>, аттрибут lex соответствует лексеме, gr — грамматические категории. Первая категория — это часть речи:

'S': 'сущ.', 
'A': 'прил.', 
'NUM': 'числ.', 
'A-NUM': 'числ.-прил.', 
'V': 'глаг.', 
'ADV': 'нареч.', 
'PRAEDIC': 'предикатив', 
'PARENTH': 'вводное', 
'S-PRO': 'местоим. сущ.', 
'A-PRO': 'местоим. прил.', 
'ADV-PRO': 'местоим. нареч.', 
'PRAEDIC-PRO': 'местоим. предик.', 
'PR': 'предлог', 
'CONJ': 'союз', 
'PART': 'частица', 
'INTJ': 'межд.'


 

SVM


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


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

Даже в академических целях реализовать SVM лень, поэтому воспользуемся готовой библиотекой LIBLINEAR на С++, которая имеет обертку для питона. Для обучения модели используем функцию train(prob, param), которая принимает в качестве первого аргумента задачу: problem(y, x), где y — это массив частей речи для каждого примера из массива x. Каждый пример представлен в свою очередь вектором характеристик. Чтобы добиться такой постановки задачи, нам нужно сначала соотнести каждую часть речи и каждую характеристику с неким числовым номером. Например:

 
'''
съешьте - глагол
выпейте - глагол 
чаю - сущ.
'''

x = [{1001: 1, 2001: 1, 3001: 1}, # 1001 - съешьте, 2001 - ьте, 3001 - те
{1002: 1, 2002: 1, 3001: 1}, # 1002 - выпейте, 2002 - йте, 3001 - те
{1003: 1, 2003: 1, 3002: 1}] # 1003 - чаю, 2003 - чаю, 3002 - аю
y = [1, 1, 2] # 1 - глагол, 2 - сущ.

import liblinearutil as svm

problem = svm.problem(y, x) # создаем задачу
param = svm.parameter('-c 1 -s 4') # параметры обучения
model = svm.train(prob, param) # обучаем модель

# используем модель для распознания слова 'съешьте'
label, acc, vals = svm.predict([0], {1001: 1, 2001: 1, 3001: 1}, model, '') # [0] - обозначает, что часть речи нам неизвестна


В итоге наш алгоритм такой:
 
  1. Читаем файл корпуса и для каждого слова определяем его характеристики: само слово, окончание (2 и 3 последних буквы), приставка (2 и 3 первые буквы), а также части речи предыдущих слов
  2. Каждой части речи и характеристике присваиваем порядковый номер и создаем задачу для обучения SVM
  3. Обучаем модель SVM
  4. Используем обученную модель для определения части речи слов в предложении: для этого каждое слово нужно опять представить в виде характеристик и подать на вход SVM модели, которая подберет наиболее подходящий класс, т.е. часть речи.


 

Реализация


С исходными кодами можете ознакомиться здесь: github.com/irokez/Pyrus/tree/master/src

 
Корпус

Для начала нужно получить размеченный корпус. Национальный корпус русского языка распространяется очень загадочным образом. На самом сайте корпуса можно только производить поиск по текстам, но при этом скачать целиком корпус нельзя:
“Оффлайновая версия корпуса недоступна, однако для свободного пользования предоставляется случайная выборка предложений (с нарушенным порядком) из корпуса со снятой омонимией объёмом 180 тыс. словоупотреблений (90 тыс. – пресса, по 30 тыс. из художественных текстов, законодательства и научных текстов)”.
При этом в википедии написано
“The corpus will be made available off-line and distributed for non-commercial purposes, but currently due to some technical and/or copyright problems it is accessible only on-line.”

Хотя для наших целей пойдет и небольшая выборка из корпуса, доступная тут: www.ruscorpora.ru/download/shuffled_rnc.zip

Файлы в полученном архиве нужно пропустить через утилиту convert-rnc.py, которая переводит текст в UTF-8 и исправляет XML разметку. После этого, возможно, еще нужно пофиксить XML вручную (xmllint вам в помощь). Файл rnc.py содержит простой класс Reader для чтения нормализованных XML файлов нац. корпуса.
 
import xml.parsers.expat

class Reader:
	def __init__(self):
		self._parser = xml.parsers.expat.ParserCreate()
		self._parser.StartElementHandler = self.start_element
		self._parser.EndElementHandler = self.end_element
		self._parser.CharacterDataHandler = self.char_data		

	def start_element(self, name, attr):
		if name == 'ana':
			self._info = attr

	def end_element(self, name):
		if name == 'se':
			self._sentences.append(self._sentence)
			self._sentence = []
		elif name == 'w':
			self._sentence.append((self._cdata, self._info))
		elif name == 'ana':
			self._cdata = ''

	def char_data(self, content):
		self._cdata += content

	def read(self, filename):
		f = open(filename)
		content = f.read()
		f.close()

		self._sentences = []
		self._sentence = []
		self._cdata = ''
		self._info = ''

		self._parser.Parse(content)		

		return self._sentences


Метод Reader.read(self, filename) читает файл и выдает список предложений:
 
[[('Вод`итель', {'lex': 'водитель', 'gr': 'S,m,anim=sg,nom'}), ('дес`ятки', {'lex': 'десятка', 'gr': 'S,f,inan=sg,gen'}), ('кот`орую', {'lex': 'который', 'gr': 'A-PRO=f,sg,acc'}), ('прест`упники', {'lex': 'преступник', 'gr': 'S,m,anim=pl,nom'}), ('пойм`али', {'lex': 'поймать', 'gr': 'V,pf,tran=pl,act,praet,indic'}), ('у', {'lex': 'у', 'gr': 'PR'}), ('ВВЦ', {'lex': 'ВВЦ', 'gr': 'S,m,inan,0=sg,gen'}), ('оказ`ал', {'lex': 'оказать', 'gr': 'V,pf,tran=m,sg,act,praet,indic'}), ('им', {'lex': 'они', 'gr': 'S-PRO,pl,3p=dat'}), ('`яростное', {'lex': 'яростный', 'gr': 'A=n,sg,acc,inan,plen'}), ('сопротивл`ение', {'lex': 'сопротивление', 'gr': 'S,n,inan=sg,acc'}), ('за', {'lex': 'за', 'gr': 'PR'}), ('что', {'lex': 'что', 'gr': 'S-PRO,n,sg=acc'}), ('поплат`ился', {'lex': 'поплатиться', 'gr': 'V,pf,intr,med=m,sg,praet,indic'}), ('ж`изнью', {'lex': 'жизнь', 'gr': 'S,f,inan=sg,ins'})]]


 
Обучение и разметка текста

Библиотеку SVM можно скачать тут: http://www.csie.ntu.edu.tw/~cjlin/liblinear/. Чтобы обертка под питон заработала под 3-й версией я написал небольшой патч.

Файл pos.py содержит два основных класса: Tagger и TaggerFeatures. Tagger — это, собственно, класс, который осуществляет разметку текста, т.е. определяет для каждого слова его часть речи. Метод Tagger.train(self, sentences, labels) принимает в качестве аргументов список предложений (в том же формате, что и выдает rnc.Reader.read), а также список частей речи для каждого слова, после чего обучает SVM модель, используя библиотеку LIBLINEAR. Обученная модель впоследствии сохраняется (через метод Tagger.save), чтобы не обучать модель каждый раз. Метод Tagger.label(self, sentence)производит разметку предложения. 

Класс TaggerFeatures предназначен для генерации характеристик для обучения и разметки. TaggerFeatures.from_body() возвращает характеристику по форме слова, т.е. возвращает ID слова в корпусе. TaggerFeatures.from_suffix() и TaggerFeatures.from_prefix() генерируют характеристики по окончанию и приставке слов.

Чтобы запустить обучение модели, был написан скрипт train.py, который читает файлы корпуса при помощи rnc.Reader, а затем вызывает метод Tagger.train:
 
import sys
import re

import rnc
import pos

sentences = []
sentences.extend(rnc.Reader().read('tmp/media1.xml'))
sentences.extend(rnc.Reader().read('tmp/media2.xml'))
sentences.extend(rnc.Reader().read('tmp/media3.xml'))

re_pos = re.compile('([\w-]+)(?:[^\w-]|$)'.format('|'.join(pos.tagset)))

tagger = pos.Tagger()

sentence_labels = []
sentence_words = []
for sentence in sentences:
	labels = []
	words = []
	for word in sentence:
		gr = word[1]['gr']
		m = re_pos.match(gr)
		if not m:
			print(gr, file = sys.stderr)

		pos = m.group(1)
		if pos == 'ANUM':
			pos = 'A-NUM'

		label = tagger.get_label_id(pos)
		if not label:
			print(gr, file = sys.stderr)

		labels.append(label)

		body = word[0].replace('`', '')
		words.append(body)

	sentence_labels.append(labels)
	sentence_words.append(words)

tagger.train(sentence_words, sentence_labels, True)
tagger.train(sentence_words, sentence_labels)
tagger.save('tmp/svm.model', 'tmp/ids.pickle')


После того, как модель обучена и сохранена, мы, наконец, получили скрипт для разметки текста. Пример использования показан в test.py:
 
import sys
import pos

sentence = sys.argv[1].split(' ')

tagger = pos.Tagger()
tagger.load('tmp/svm.model', 'tmp/ids.pickle')

rus = {
	'S': 'сущ.', 
	'A': 'прил.', 
	'NUM': 'числ.', 
	'A-NUM': 'числ.-прил.', 
	'V': 'глаг.', 
	'ADV': 'нареч.', 
	'PRAEDIC': 'предикатив', 
	'PARENTH': 'вводное', 
	'S-PRO': 'местоим. сущ.', 
	'A-PRO': 'местоим. прил.', 
	'ADV-PRO': 'местоим. нареч.', 
	'PRAEDIC-PRO': 'местоим. предик.', 
	'PR': 'предлог', 
	'CONJ': 'союз', 
	'PART': 'частица', 
	'INTJ': 'межд.', 
	'INIT': 'инит', 
	'NONLEX': 'нонлекс'
}

tagged = []
for word, label in tagger.label(sentence):
	tagged.append((word, rus[tagger.get_label(label)]))

print(tagged)


Работает так:
$ src/test.py "Съешьте еще этих мягких французских булок, да выпейте же чаю"
[('Съешьте', 'глаг.'), ('еще', 'нареч.'), ('этих', 'местоим. прил.'), ('мягких', 'прил.'), ('французских', 'прил.'), ('булок,', 'сущ.'), ('да', 'союз'), ('выпейте', 'глаг.'), ('же', 'частица'), ('чаю', 'сущ.')]


 

Тестирование


Для оценки точности классификации работы алгоритма, метод обучения Tagger.train() имеет необязательного параметр cross_validation, который, если установлен как True, выполнит перекрестную проверку, т.е. данные обучения разбиваются на K частей, после чего каждая часть по очереди используется для оценки работы метода, в то время как остальная часть используется для обучения. Мне удалось добиться средней точности в 92%, что вполне неплохо, учитывая, что была использована лишь доступная часть нац. корпуса. Обычно точность разметки части речи колеблется в пределах 96-98%

 

Заключение и планы на будущее


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

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

Буду рад ответить на вопросы и предложения.
Исходный код доступен здесь: github.com/irokez/Pyrus
Демо: http://vps11096.ovh.net:8080
15 июня 2016 в 19:51Разработка

Az.js: JavaScript-библиотека для обработки текстов на русском языке

Как чуден и глубок русский курлык
— Генератор постов

Обработка естественного языка (natural language processing, NLP) — тема, на мой взгляд, очень интересная. Во-первых, задачи тут чисто алгоритмические: на вход принимаем совершенно примитивный объект, строчку, а извлечь пытаемся вложенный в него смысл (ну или хотя бы частичку смысла). Во-вторых, необязательно быть профессиональным лингвистом, чтобы решать эти задачи: достаточно знать родной язык на более-менее приличном уровне и любить его.

А ещё с небольшими затратами можно сделать какого-нибудь бестолкового чат-бота — или, как вот я, генератор постов на основе того, что вы писали на своей страничке в соцсети. Возможно, кто-то из вас уже видел это приложение — оно довольно глупое, чаще всего выдает бессмысленный и бессвязный текст, но изредка всё же дает повод улыбнуться.

Бессвязность текстов в нынешней версии «Генератора» вызвана тем, что на самом деле никакого анализа он производить не умеет. Просто в одних случаях «предсказывает» продолжение предложения по собранным биграммам, а в других — заменяет в готовом предложении некоторые слова на другие, которые заканчиваются похоже. Вот и вся начинка.

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

Az


Свою библиотеку я назвал Az. С одной стороны — это первая буква кириллицы, «азъ», и в то же время первая и последняя буквы латиницы. Сразу дам все ссылки:
— GitHub;
— Документация: либо на гитхабе же, либо на Doclets.io;
— Демо.
Установить библиотеку вы можете как из npm, так и из bower (в обоих местах она имеет название az). На свой страх и риск — она ещё не покрыта полностью тестами и до выхода первой версии (который произойдет скоро, я надеюсь) у неё могут измениться публичные API. Лицензия: MIT.

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

 

Az.Tokens


Суть токенизации очень проста: как я упоминал выше, на входе мы принимаем строку — а на выходе получаем «токены», группы символов, которые (вероятно) являются отдельными сущностями в этой строке.

Обычно для этой цели используется что-нибудь типа одного вызова split по простой регулярке, но мне этого показалось мало. Например, если разделить строку по пробелам, мы потеряем сами пробелы — иногда это удобно, но не всегда. Ещё хуже, если мы захотим предварительно разбить строку по точкам, вопросительным и восклицательным знакам (надеясь выделить так предложения): мало того, что теряются конкретные знаки препинания, так ещё и точки на самом деле не всегда завершают предложения. А потом мы понимаем, что в реальных текстах могут встретиться, скажем, ссылки (и точки в них точно никакого отношения к пунктуации не имеют) и регулярка становится совсем страшной.

Az предлагает свое, не-деструктивное, решение этой задачи. Гарантируется, что после токенизации каждый символ будет принадлежать некоторому токену и только одному. Пробелы, переводы строк и прочие невидимые символы объединяются в один тип токенов, слова — в другой, пунктуация — в третий. Если все токены склеить вместе — получится в точности исходная строка.

При этом токенизатор достаточно умен, чтобы понимать, что дефис, обрамленный пробелами — это знак препинания (вероятно, на его месте подразумевалось тире), а прижатый хотя бы с одной стороны к слову — часть самого слова. Или что «habrahabr.ru» — это ссылка, а «mail@example.com» — это, вероятно, емэйл (да, полная поддержка соответствующего RFC не гарантируется). #hashtag — это хэштег, user — упоминание.

И наконец — раз уж RegExp'ы для этой цели использовать не стоит — Az.Tokens умеет парсить HTML (а заодно — вики и Markdown). Точнее говоря, никакой древовидной структуры на выходе не будет, но все теги будут выделены в свои токены. Для тегов <script> и <style> сделано дополнительное исключение: их содержимое превратится в один большой токен (вы же не собирались разбивать на слова свои скрипты?).

А вот и пример обработки Markdown-разметки:



Обратите внимание, скобки в одних случаях превращаются в пунктуацию (светло-синие прямоугольники), а в других — в Markdown-разметку (показана зеленым цветом).

Естественно, отфильтровать ненужные токены (например, выбросить теги и пробелы) элементарно. И конечно же, все эти странные фичи отключаются отдельными флажками в опциях токенизатора.

 

Az.Morph


После того, как текст разбит на слова (с помощью Az.Tokens или любым другим образом), из них можно попытаться извлечь морфологические атрибуты — граммемы. Граммемой может быть часть речирод или падеж — у таких граммем есть значения, которые сами по себе являются граммемами, только «булевыми» (например, мужской родтворительный падеж). «Булевые» граммемы могут и не относиться к какой-то родительской граммеме, а присутствовать сами по себе, как флаги — скажем, помета устаревшее или возвратный (глагол).

Полный список граммем можно найти на странице проекта OpenCorpora. Почти все используемые библиотекой значения соответствуют обозначениям этого проекта. Кроме того, для анализа используется словарь OpenCorpora, упакованный в специальном формате, но об этом ниже. Вообще создатели проекта OpenCorpora большие молодцы и я вам рекомендую не только ознакомиться с ним, но и принять участие в коллаборативной разметке корпуса — это также поможет и другим опенсорсным проектам.

Каждый конкретный набор граммем у слова называется тегом. Всевозможных тегов значительно меньше, чем слов — поэтому они пронумерованы и хранятся в отдельном файле. Чтобы уметь склонять слова (а Az.Morph это тоже умеет), нужно как-то уметь менять их теги. Для этого существуют парадигмы слов: они ставят в соответствие тегам определенные префиксы и суффиксы (то есть один и тот же тег в разных парадигмах имеет разные пары префикс+суффикс). Зная парадигму слова, достаточно «откусить» от него префикс и суффикс, соответствующие текущему тегу, и добавить префикс/суффикс того тега, в который мы его хотим перевести. Как и тегов, парадигм в русском языке относительно немного — это позволяет в словаре хранить для каждого слова только пару индексов: номер парадигмы и номер тега в ней.

Вот пример: слово «крепкая» имеет тег, по-русски кратко обозначаемый как «ПРИЛ, кач жр, ед, им» — то есть это качественное прилагательноеженского рода единственного числа в именительном падеже. Этому тегу (в той парадигме, которой принадлежит слово «крепкая»), соответствует пустой префикс и суффикс «-кая». Предположим, мы хотим получить из этого слова его сравнительную степень, да ещё и не обычную, а особую, с префиксом «по-»: «КОМП, кач сравн2». У неё в этой парадигме, как нетрудно догадаться, префикс «по-» и суффикс «-че». Отрезаем «-кая», добавляем «по-» и «-че» — получаем искомую форму «покрепче».

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

Такое вот относительно сумбурное изложение внутреннего механизма склонений в Az.Morph. По сути эта часть библиотеки — порт замечательного морфологического анализатора pymorphy2 за авторством kmike (на Хабре была пара статей об этой библиотеке). Кроме самого анализатора, рекомендую ознакомиться с его документацией — там много полезной информации, которая полностью применима и к Az тоже. Кроме того, Az использует формат словарей, аналогичный словарям pymorphy2, за исключением небольших деталей (которые позволили сделать словарь на 25% компактнее). По этой причине, увы, самостоятельно собрать их не получится — но в будущем такая возможность, конечно, появится.

Как я уже упомянул, основные словари хранятся в хитром формате DAWG (в вики есть статья о directed acyclic word graph, как об абстрактной структуре данных, но о конкретной реализации информации мало). Реализуя его поддержку в JS, я оценил фичу pymorphy2, позволяющую при поиске слова сразу проверять вариант с «ё» вместо «е» — это не дает особых потерь в производительности из-за того, что при спуске по префиксному дереву легко обойти ветви, соответствующие обеим буквам. Но мне этого показалось мало и я аналогичным образом добавил возможность нечеткого поиска слов с опечатками (то есть можно задать максимальное расстояние Дамерау-Левенштейна, на котором должно находиться искомое слово от заданного). Кроме того, можно находить «растянутые» слова — по запросам «гоооол» или «го-о-о-ол» найдется словарное «гол». Разумеется, эти особенности тоже опциональны: если вы работаете в «тепличных условиях», с грамотными, вычитанными текстами — поиск опечаток стоит запретить. А вот для написанных пользователями записей это может быть весьма актуально. В планах — заодно ловить и наиболее распространенные ошибки, не являющиеся опечатками.

Как видите, по заданному слову библиотека может вернуть разные варианты разбора из словаря. И это связано не только с опечатками: классический пример грамматической омонимии — слово «стали», которое может оказаться формой как существительного «сталь», так и глагола «стать». Чтобы решить однозначно (снять омонимию) — нужно смотреть на контекст, на соседние слова. Az.Morph этого пока не умеет (да и задача эта уже не для морфологического модуля), поэтому вернет оба варианта.

Более того: даже если в словаре не нашлось ничего подходящего, библиотека применит эвристики (называемые предсказателями или парсерами), которые смогут предположить, как слово склоняется — например, по его окончанию. Тут бы вставить весёлую историю с башорга про анализатор, считающий слово «кровать» глаголом, да одна беда — оно, конечно же, есть в словаре, и только как существительное :)

Впрочем, у меня нашлись свои курьёзы. Например, у слова «философски» среди прочих вариантов нашлись некие «филососки» (с исправленной опечаткой). Но удивительнее всего оказалось, что слово «мемас» стабильно разбирается как глагол (!), инфинитив у которого — «мемасти» (!!!). Не сразу получается понять, как такое вообще возможно — но такую же парадигму имеет, например, слово «пасти». Ну и формы типы «мемасём», «мемасёмте», «мемасённый», «мемасено», «мемасши», по-моему, прекрасны.

Несмотря на эти странности, обычно результаты оказываются довольно адекватными. Этому способствует то, что (как и в pymorphy2) каждому варианту присваивается оценка «правдоподобия» и они сортируются по убыванию этой оценки. Так что если делаете алгоритм побыстрее — можно брать первый вариант разбора, а если хочется точности — стоит перебрать все.

 

Производительность


Что касается скорости, то в целом тут всё не очень радужно. Либа писалась без упора на этот фактор. Предполагается, что приложениям на JS (особенно браузерным) реже приходится сталкиваться с особенно большими объемами данных. Если хочется быстро анализировать массивные коллекции документов — стоит уделить внимание pymorphy2 (особенно его оптимизированной версии, использующей реализацию на C для работы со словарем).

По моим грубым замерам, конкретные цифры (в браузере Chrome) примерно таковы:
 
Впрочем, серьёзных бенчмарков пока не проводилось. Кроме того, библиотеке недостает тестов — поэтому приглашаю погонять её на упомянутой выше демке. Надеюсь, ваша помощь приблизит релиз первой версии :)

 

Дальнейшие планы


Главный пункт в roadmap библиотеки — эксперименты с синтаксическим анализом. Имея варианты разбора каждого слова в предложении, можно строить более сложные гипотезы об их взаимосвязях. Насколько мне известно, в опенсорсе таких инструментов совсем немного. Тем сложнее и интереснее будет думать над этой задачей.

Кроме того, не снимается вопрос оптимизации — JS вряд ли сможет угнаться за кодом на C, но что-то улучшить наверняка можно.

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

И, разумеется, я открыт вашим идеям, предложениям, багрепортам, форкам и пулреквестам.

 

 

 

24 мая 2014 в 00:28Разное

Дореформенный словарь: рецепт

Как, наверное, многим хабрапользователям известно, сегодня, 24 мая, отмечают день славянской письменности — праздник тех, для кого слово ОРЕХ означает всё же не «operational expense». Сегодня я расскажу, как можно из современного русского морфологического словаря изготовить словарь русского языка с дореформенной орфографией. Обо всём по порядку.



Как многие из нас знают, революция 1917 года отменила в России не только обязательства по долгам, но и некоторые буквы из русского алфавита. Но дореформенные правила забытыми не остались, текстов, опубликованных до реформы, тоже сохранилось изрядно (даже в моей скромной домашней библиотеке найдётся пара томов), да и вообще тема создания морфологического словаря для той, винтажной, грамматики сама по себе интересна. Реформа состояла в том, что из обращения убрали некоторые буквы (і,ѣ, ѵ и ѳ), а также изменили некоторые правила, прямого отношения к употреблению этих букв не имеющих. Подробнее в википедии

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


Что же такое морфологический словарь или словарь с поддержкой морфологии? Я этим термином называю не тот словарь, который просто содержит все возможные словоформы каждого слова, а тот, который знает как по каждому слову эти словоформы порождать. Что, разумеется, не только приводит к экономии места, но и даёт надежду, что мы не забыли вместе со словом "ищущий" добавить и слово "ищущей" (род.п. ср. род). За способ порождения словоформ слова отвечает грамматическая категория слова, каждое слово ссылается на некую грамматическую категорию.
Кроме того, чтобы не было комбинаторных взрывов от таких слов как серо-буро-малиновый, в словарь добавляются так называемые композитные правила. Они нужны для того, чтобы порождать подобные конструкции. Каждое композитное правило ответственно за порождение слов по каким-то законам. У композита может быть явная точка членения (как дефис в приведённом примере), так и неявная, когда части композита просто пристыковывается друг к другу. К примеру, частным случаем композитного правила может быть способ образования глаголов с приставкой "пере": переписатьпеределатьпередвинутьперепеть… Для русского языка композиты без явной точки членения могут показаться и ненужными, но те, кто знают немецкий язык, вероятно, согласятся, что они необходимы.

Итак, мы из русского словаря с морфологией делаем словарь для дореформенного русского. Будем смотреть на отличия и вносить их постепенно в новый словарь. Итак, для начала рассмотрим наиболее простые моменты:

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

Буквы ѵ и ѳ к моменту реформы доживали последние дни, список слов, их содержащих, весьма невелик. Довольно легко восстанавливается.

Буква і употреблялась в слове мір (тот, что вселенная, а не антоним к войне), а также в обычных словах перед гласными и й, кроме тех, что были образованы по композитным правилам (химія, но семиязычный). В словаре основ и грамматических категорий не составляет большого труда это исправить: поиск с заменой в виде регулярного выражения — несложная манипуляция.

Правила с с/з в конце приставок из-, воз-, раз-, роз-, низ (изслѣдованіеразсказъ) также вносится несложно, как и отмена модификации приставок без-, через-, чрез (безполезныйчерезстрочный). 

Заметим, что в случае если наш современный русский словарь всё-таки обходился без композитных правил, то эти изменения, как и сохранения -и- в композитах на конце первой части придётся обеспечить вручную.

Далее, поработаем с окончаниями. Прилагательные во множественном числа получают в дополнение к -ые окончание -ыя, а в единственном числе мужского рода винительного падежа заменим -ого и -его на -аго и -яго. Это несложно, как и добавить не очень мудрёные изменении в окончаниях существительных. Добавляем слова еяонѣоднѣоднѣхъоднѣмъоднѣми (можно хоть как неизменяемые, если неохота возиться с грамматическими категориями на этот счёт). 

И после этих несложных манипуляций мы дошли до самого интересного. Как восстанавливать ѣ?
Тема непростая, в википедии есть отдельная статья на этот счёт. Сначала разберёмся с простыми частями. За творительный, дательный и предложный падежи, сравнительные и превосходные формы прилагательных и глаголы на -ѣть отвечает грамматическая категория. Числительные на двѣ — поменяем вручную, как и возвратные местоимения. Наречий и предлогов несколько больше, но их замена — тоже вполне подъёмная задача. А вот что делать с толпой словарных слов?
Здесь нам на выручку придёт… украинский язык! Неожиданно, не находите ли?

Дѣло въ томъ, что… ой, простите, увлёкся. Украинский и русский языки весьма похожи (ну неужели?), в частности, похожи многие слова. Правило такое — во многих случаях, когда в русском языке использовалась ѣ, в украинском есть очень похожее слово с буквой і на этом месте. Не знаем, какая вторая буква была в слове репа? ОК, проверяем в украинском словаре ріпа и репа. Аналогично, скажем, слово ремонт. Конечно, бывает, что меняется значение слова (к примеру, что означает украинское слово неділя?), но для наших целей это не очень важно. Хуже для нас когда аналога в украинском нет — как для слова “отец”, к примеру. Что ж, полностью от ручной работы избавиться не удастся, порадуемся, что её объём можно сильно сократить. Обладая таким нехитрым знанием и украинским морфологическим словарём автоматизировать разметку станет легче. 
 
Небольшое отступление: лингвистика
Причина этого явления видимо в том, что когда-то, до разделения общего праязыка на русский и украинский ѣ и е произносились по-разному, но русский и украинский язык далее разошлись и в русском ѣ стали произноситься так же, как е, а в украинском как і. 
Есть, кстати, ещё одно проверочное правило — если в корне под ударением используется буква ё, то без ударения — буква е, но и здесь не обошлось без ислючений сѣло — сёла.


А если украинского словаря под рукой не оказалось? Дуже шкода :) Придётся положиться на собственную аккуратность и порадоваться хотя бы, что корней с ѣ всё же менее 9000.

После всех манипуляций следует разобраться с дореформенными, более строгими, чем современные, правилами переноса — если вы планируете поддержать их для своего словаря. 
Въ результатѣ мы получимъ морфологическій словарь русскаго языка использующій дореформенную орѳографію.
Спасибо за внимание, и
З днем ​​слов'янської писемності!

UPD: По просьбе paulousky (а также редактора блога) добавил примеров.

КОМПЬЮТЕРНАЯ ЛИНГВИСТИКА: МОРФОЛОГИЧЕСКИЙ АНАЛИЗ

Видео - Чердак  4 минуты

https://youtu.be/oj0Kjt9gMBc

Зачем было придумано понятие части речи? Что такое морфологический анализ? И почему школьник лучше компьютера определяет, к какой части речи относится слово? Об этом во второй части курса «Компьютерная лингвистика» рассказывает Светлана Тимошенко из Лаборатории компьютерной лингвистики ИППИ РАН.

https://chrdk.ru/other/komputernaya_lingvistika_2

 

 

 

Как научить свою нейросеть анализировать морфологию

 

https://habrahabr.ru/post/339954/

 

 

Морфологическая библиотека PHPMorphy

 Веб-программирование  5 февраля 2010 г., 7:58

 

Не так давно по работе столкнулся с задачей разбора морфологии слов, учитывая что русская морфология довольно сложная, то писать свой скрипт я не видел смысла и считаю это было бы не рационально. Недолгие поиски привели меня к библиотеки PhPMorhy (http://sourceforge.net/projects/phpmorphy/), в данной статье я хотел бы немного описать эту библиотеку.

Итак, библиотека состоит из php классов, и отдельно подключаемыми словарями. На сайте библиотеки можно скачать словари для русского(Windows-1251 и UTF-8), английского, немецкого, украинского, эстонского языков. Словари основываются на aot и ispell словарях.

Что же умеет эта библиотека? Для начала хотел обратить внимание что можно получить корень слова, морфологические формы слова, определение частей речи, грамматические формы. Я думаю многие разработчики понимают пользу таких преобразований, например, есть возможность улучшить поиск в своей системе, если получать корень слова и искать уже по нему. В данном случае моя задача состояла сбор анкоров в СЕО системе, учитывая морфологию слов.

Данная библиотека имеет разные режимы работы, которые могут влиять на скорость загрузки и обработки данных. Так как словари весят от 6 до 15мб, то есть возможность частичной подгрузки словарей. Минус библиотеки состоит в том что нельзя подгрузить сразу 2 и более словарей, к примеру что бы проверять словосочетания: игры online. Так же с кодировками, нужно запрашивать слова в той кодировке что и словарь. Все слова должны быть написаны в ВЕРХНЕМ регистре. Для получения информации о словосочетании вам придется разделать его на массив. Все слова так же будут выданы в массиве, даже если было все одно слово, на выходе вы получите многотомный массив.

 

Вот небольшой пример использования кода:

$keywords = mb_convert_case($row['keywords'], MB_CASE_UPPER, "UTF-8");
$list_keywords = explode(" ", str_replace("-"," ", $keywords));
foreach($list_keywords as $word){
   if(!empty($word) && $word!=''){
      if(eregi("[a-zA-z]", $word)){
           $eng[] = $word;
      }else{$rus[] = $word;}
   }
}unset($keywords);

$keywords = array();
if(isset($rus) && !empty($rus)){
     $morphyRUS = new phpMorphy($dir, 'ru_ru', array('storage' => PHPMORPHY_STORAGE_FILE));
     array_add($keywords, $morphyRUS->getPseudoRoot($rus));
}

if(isset($eng) && !empty($eng)){
    $morphyENG = new phpMorphy($dir, 'en_en', array('storage' => PHPMORPHY_STORAGE_FILE));
    array_add($keywords, $morphyENG->getPseudoRoot($eng));
}

Функция array_add в данном случае делает однотомный массив и склеивает русские и английские словоформы.

Если слова нет в словаре, то он возвращает пустой массив.