Глава 1. Исследование естественно

advertisement
ПРАВИТЕЛЬСТВО РОССИЙСКОЙ ФЕДЕРАЦИИ
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ
ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ
ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ
«Национальный исследовательский университет
«Высшая школа экономики»»
Факультет бизнес-информатики
Кафедра инноваций и бизнеса в сфере ИТ
ВЫПУСКНАЯ КВАЛИФИКАЦИОННАЯ РАБОТА
На тему
«Формализация и алгоритмизация диалога на естественном
языке с прикладной интеллектуальной системой»
Студент группы № 473
Орлов Егор Константинович
Научный руководитель
профессор,
д.т.н.
В.А.
Рецензент
к.т.н. Куликов С.А.
Москва, 2012 г.
Фомичёв
Оглавление
Введение. ...................................................................................................... 4
Глава 1. Исследование естественно-языковых интерфейсов и
способов обработки запросов пользователей в естественно-языковых
интерфейсах ................................................................................................. 6
1.1. Эволюция и типы естественно-языковых интерфейсов. ............ 6
1.1.1. Естественно-языковые интерфейсы к
неструктурированным данным............................................................ 6
1.1.2. Естественно-языковые интерфейсы к структурированным
данным (базам данных). ....................................................................... 7
1.1.3. Естественно-языковые интерфейсы к структурированным
данным (онтологиям, базам знаний). .................................................. 8
1.2. Русскоязычные ЕЯ-интерфейсы ................................................. 13
1.3. Возможность применения аппарата СК-языков для
представления содержания запросов пользователей в ЕЯИ ............. 14
Глава 2. Разработка подхода к созданию русскоязычного
естественно-языкового интерфейса к реляционной базе данных ........ 17
2.1. Требования к естественно-языковому интерфейсу ..................... 17
2.2. Форматы входных запросов к естественно-языковому
интерфейсу.............................................................................................. 17
2.3. Описание основных блоков реализации ....................................... 18
2.3.1. Построение семантического представления запроса ............ 19
2.3.2. Интерпретация семантического представления для создания
SQL запроса ......................................................................................... 25
Глава 3. Программная реализация разработанного подхода к созданию
естественно-языкового интерфейса ........................................................ 29
3.1. Выбор языка программирования и системы управления базами
данных ..................................................................................................... 30
3.2. Модуль построения семантического представления запроса
пользователя ........................................................................................... 36
3.2.1. Токенизация............................................................................... 37
3.2.2. Определение морфологических характеристик .................... 39
3.2.3. Построение синтаксического дерева ...................................... 44
3.2.4. Выявление ИС ........................................................................... 46
3.2.5. Построение отношений между ИС ......................................... 48
3.3. Реализация модуля интерпретации семантического
представления ......................................................................................... 50
Заключение ................................................................................................ 53
Список использованных источников ...................................................... 54
Приложение 1. Исходный код программы ............................................. 57
Файл PyMorphyInterface.py ................................................................... 57
Файл SemanticProcessor.py .................................................................... 67
Файл EntitiesExtractor.py ....................................................................... 82
Файл RelationshipsExtractor.py .............................................................. 89
Файл Tokenizer.py .................................................................................. 97
Файл LinguisticDatabase.py.................................................................... 98
Файл consts.py....................................................................................... 108
Файл Chunker.py ................................................................................... 115
Введение.
В последние 5-7 лет резко возрос интерес к развитию
компьютерной лингвистики и обработки естественного языка (ЕЯ), что
обусловлено необходимостью автоматического извлечения
информации из растущего количества документов, баз данных и баз
знаний. Например, уже появился такой термин, как BioNLP – это набор
инструментов обработки естественного языка для
автоматизированного извлечения информации из множества
биомедицинских текстов[20].
Несмотря на то, что обработка ЕЯ активно развивается более
полувека, тем не менее, делать вывод о том, что компьютер «умеет
понимать» ЕЯ сложно, потому что на данный момент существует
большое количество нерешённых проблем, которые сдерживают
качественный рывок данного направления науки. Одной из них
является автоматическое преобразование ЕЯ-текста в выражение на
некотором формальном языке, обладающим широкими
выразительными возможностями для представления смысла
различных текстов и других источников информации. Важность этой
задачи обусловлена тем, что формальный язык уже относительно
просто интерпретировать компьютером, например, преобразовать его
в SQL-запрос к базе данных.
На данный момент эта задача в достаточно узких
формулировках решается, подтверждением чего является появление
множества естественно-языковых интерфейсов (ЕЯИ), позволяющих
осуществлять взаимодействие пользователя с компьютером на языке,
который люди используют в общении между собой. ЕЯИ существуют
как к структурированным (реляционные базы данных, базы знаний и
т.д.), так и к неструктурированным источниками информации
(веб-сайты, электронные книги и т.д.). Однако функциональность
данных интерфейсов крайне ограничена, поэтому они не получают
широкого распространения, хотя потребность в этом крайне высока.
Подтверждением этому может служить тот факт, что компания
Anboto[19], занимающаяся созданием ЕЯИ, получила премию «Startup
of the year worldwide 2010».
Сложность распространения ЕЯИ заключается в том, что языки,
на которых говорят люди, отличаются не только лексикой, но и
грамматикой, синтаксисом и пунктуацией, что зачастую требует
серьёзной переработки ЕЯИ. Например, морфологический и
синтаксический анализ текстов на английском языке осуществлять
проще, чем на русском. Этот фактор отчасти объясняет небольшое
количество ЕЯИ, принимающих на входе русский язык.
Принимая во внимание всё вышесказанное, цель данной работы
заключается в том, чтобы разработать подход к построению
русскоязычный ЕЯИ к реляционной базе данных, и сделать
программную реализацию этого подхода на примере туристической
базы данных.
Задачи для достижения цели данной работы состоят в том,
чтобы:
1. Проанализировать существующие естественно-языковые
интерфейсы, их особенности и основные компоненты;
2. Определить требования и ограничения для
разрабатываемого ЕЯИ;
3. Описать последовательность шагов преобразования
ЕЯ-текста, необходимых для реализации ЕЯИ с определённой
функциональностью;
4. Определить метод реализации каждого шага;
5. Выполнить программную реализацию разработанного
подхода.
Глава 1. Исследование естественно-языковых интерфейсов
и
способов
обработки
запросов
пользователей
в
естественно-языковых интерфейсах
1.1. Эволюция
и
типы
естественно-языковых
интерфейсов.
Реальность устроена так, что информацию необходимо
получать из больших объёмов данных, которые могут храниться либо
структурированно, либо неструктурированно. В структурированном
представлении, различные атрибуты данных хранятся раздельно, и
обрабатываются раздельно, в то время как в неструктурированном
представлении данные хранятся просто как последовательность
символов. Например, xml-документ или реляционная база данных
являются структурированными источниками, текст веб-сайта – нет.
Интерфейсы как к структурированным, так и не к структурированным
данным были важной областью исследований, начиная с 1960 года.
1.1.1.
Естественно-языковые
интерфейсы
к
неструктурированным данным.
Процесс установления взаимодействия между человеком и
компьютером был успешно реализован в 1966 году в системе ELIZA,
которая была разработана Джозефом Вейзенбаумом. Основным
принципом работы ELIZA был процесс замены ключевых слов на
выражения, хранящиеся в базе знаний. Хотя ELIZA не воспринимала
многие феномены ЕЯ, она до сих пор остаётся важным этапом на пути
развития
ЕЯИ, поскольку впервые программисты попытались
реализовать
взаимодействие
человек-компьютер
в
виде
человек-человек.
Поскольку большое количество информации доступно в
неструктурированном
виде,
содержащих
требуемую
интерфейсов
данной
поиск
релевантных
информацию,
категории.
Точный
был
документов,
главной
поиск
целью
информации,
называемый извлечением информации – это следующий шаг и
развитие
диалоговых
систем
является
дальнейшим
развитием
человеко-машинного взаимодействия. Извлечение информации – это
процесс, который выборочно структурирует и совмещает найденные
данные из нескольких текстов. Диалоговые системы осуществляют
неформальную коммуникацию с пользователем и отвечают на
задаваемые вопросы.
Идея совмещения обработки ЕЯ с широкими возможностями
извлечения и поиска информации не является новой и ограниченно
реализована. Такие исследователи, как Зай (1996) и Арампатзис (1998)
экспериментировали с различными подходами к осуществлению
синтаксического анализа. Однако никакие из этих экспериментов не
увенчались
значительным
увеличением
точности,
и
часто
заканчивались снижением производительности. Но Катз и Лин (2003)
из Массачусетского Технологического Университета показали, что
технология обработки ЕЯ, в целом не безнадёжна. Уменьшение или
увеличение производительности главным образом зависело от способа
реализации технологии обработки ЕЯ. Катз и Лин (2003) обнаружили
два широких лингвистических феномена, которые были сложны для
обработки в парадигме извлечения информации. Они предложили
использовать
тернарные
лингвистические
отношения,
феномены
чтобы
семантической
обрабатывать
симметрии
и
неоднозначности.
Борис Катз разработал ЕЯИ START (SynTactic Analysis using
Reversible Transformations) – программную систему для ответа на
вопросы, которые задаются на ЕЯ.
1.1.2.
Естественно-языковые
интерфейсы
к
структурированным данным (базам данных).
Система
LUNAR
была
первым
работоспособным
ЕЯ-интерфейсом к базе данных, появившись в конце 1960-х. Система
LUNAR содержала результаты химического анализа лунных камней и
значительно повлияла на дальнейшее развитие подходов к обработке
ЕЯ.
Затем появилось несколько БД с ЕЯИ, имеющих различные
подходы к обработке ЕЯ. К середине 1980-х, ЕЯИ к БД стали очень
популярной областью исследований, и исследователями было
реализовано множество прототипов. Такие системы как ASK и
CHAT-80
включали
оригинальные
способы
обработки,
и
их
реализация была нацелена на обработку запросов. ЕЯИ к БД
рассматривались как многообещающий путь к тому, чтобы сделать
базы
данных
доступными
для
пользователей
без
навыков
программирования, и возлагались большие надежды на коммерческие
перспективы таких систем. Система English Wizard, разработанная
компанией Linguistic technology, была среди систем, от которых
ожидалась большая коммерческая выгода.
Использование ЕЯИ, однако, распространилось в значительно
меньшей степени, чем это прогнозировалось, в основном из-за
развития альтернативных графических и основанных на формах
интерфейсах, например, метода «запрос по примеру». Но эти
альтернативные варианты интерфейсов были менее естественными для
работы с ними и запросы, требующие квантификации или обращения к
нескольким таблицам, были очень сложны для формирования с
помощью графических или основанных на формах интерфейсах, в то
время как они могли легко записываться на ЕЯ. ЕЯИ, которые
воспринимают различные феномены ЕЯ, являются областью активных
исследований на сегодняшний день.
1.1.3.
Естественно-языковые
интерфейсы
к
структурированным данным (онтологиям, базам знаний).
В связи с увеличением интереса к семантическим технологиям
в последнее время, а также под влиянием проекта семантическая
паутина, создано большое количество онтологий и баз знаний.
Необходимость сделать содержимое Семантического Веба
доступным для конечных пользователей становится растущей
проблемой, вытекающей из увеличения количества информации,
хранящейся в базах знаний (БЗ), использующих онтологии. ЕЯИ
предоставляют средства доступа с помощью запросов для конечных
пользователей без необходимости изучать RDF, OWL, SPARQL или
любые другие языки. ЕЯИ скрывают формальность онтологий и
языков запроса от конечных пользователей, предоставляя им знакомый
и интуитивно понятный способ формулирования запросов.
Большинство БЗ предоставляют возможности для обращения к
данным с помощью таких формальных языков, как SPARQL или
SeRQL. Однако они имеют достаточно сложный синтаксис, требуют
хорошего понимания схемы данных и способствуют появлению
ошибок, благодаря необходимости печатать длинные и сложные
URL-адреса. Эти языки являются аналогами использования SQL для
запросов к ставшим традиционными реляционных базах данных и не
должны рассматриваться, как инструмент конечного пользователя.
Различные методы для удобного пользователю доступа к
знаниям были разработаны ранее. Некоторые из них предоставляют
графический интерфейс, где пользователи могут «путешествовать» по
онтологии, другие предлагали интерфейс, основанный на формах для
осуществления
семантического
поиска
в
онтологии,
скрывая
сложности формальных языков. Наиболее сложные интерфейсы
предоставляют обычное текстовое поле ввода для запроса на ЕЯ.
Оценка четырёх типов ЕЯИ 48 конечными пользователями
технологиями Семантического Веба на удобство была проведена в
[3]. Выводом данной работы является то, что ЕЯИ оказались наиболее
привлекательными, и пользователи отдавали им намного большее
предпочтение, оставив позади интерфейсы с использованием меню,
форм и графических средств создания запросов. Несмотря на
предпочтение пользователей, системы с ЕЯИ встречаются редко
благодаря высоким затратам, связанным с их разработкой и
настройкой под новые предметные области, требующие вовлечения
как экспертов с рассматриваемой предметной области так и
компьютерных лингвистов.
Один шаг навстречу удобству для пользователя – это создание
графического интерфейса с формами, в которых запрос к информации
может быть выражен с помощью наложенных ограничений. Обычно
такие ограничения создаются в виде готовых блоков, которые
пользователь может добавить, чтобы создать сложный запрос. Такие
системы могут быть подстроены под определённую прикладную
область или иметь общую направленность. Хороший пример
интерфейса с формами – это платформа управления знаниями KIM[5].
Интерфейсы этого типа хорошо подходят для узкой предметной
области потому, что в этом случае достигается наибольшая
производительность. Преимуществом такой системы является то, что
пользователи уже привыкли к использованию форм, а недостатком –
то, что при реализации для общей предметной области система может
быть слишком ограничена, либо слишком сложна из-за большого
количества полей ввода или выбора из альтернативных опций.
Вероятно, благодаря необычайной популярности поисковых
систем
наподобие
поисковые
Google,
интерфейсы,
пользователи
которые
стали
предпочитать
предоставляют
единственное
текстовое поле ввода, в котором они описывают необходимую
информацию, а работа по нахождению релевантных результатов
возлагается на систему. В то время, как такой способ является
привлекательным для полнотекстового поиска, использование его для
концептуального
поиска
требует
дополнительных
шагов
для
преобразования запроса пользователя в семантические ограничения.
SemSearch[6] – система семантического поиска с таким
интерфейсом, подобным интерфейсу поисковой системы Google. Он
требует наличия набора понятий (классов или экземпляров) в качестве
строки запроса (например, запрос «новости: аспиранты» требует найти
все экземпляры класса новостей, которые находятся в отношении с
аспирантами).
Такой
подход
позволяет
использовать
простые
текстовые поля в качестве полей ввода, но требует хорошего знания
онтологии предметной области и не предоставляет средств для
спецификации искомого отношения между поисковыми словами, что
может снизить точность в случае, когда в онтологии находится
несколько применимых отношений. Другой примечательный пример
одной из наиболее зрелых среди этого семейства систем – это
AquaLog[7]. В ней используется ограниченный язык для запросов к
онтологиям, а также обучающий механизм, который улучшает
производительность системы с течением времени, в результате
обработки словаря пользователя для формирования запросов. Работа
этой системы основана на принципе конвертирования ЕЯ-запроса во
множества, состоящие из трёх элементов, совместимых с онтологией,
которые затем используются для извлечения информации из базы
знаний. В этой системе используется поверхностный анализ
предложения и WordNet, что требует синтаксически правильно ввода.
Наверное, эта система больше всего подходит для предложений,
содержащих до двух «троек», выраженных в форме вопроса
(например, начинающихся с вопросительных слов что?, кто?).
Orakel[8, 18] – это ЕЯИ к базам знаний. Главное его
преимущество
–
это
поддержка
составных
семантических
конструкций, которые помогают поддерживать вопросы, включающие
числовые выражения, союзы и отрицание. Эти преимущество
достигается за счёт обязательной настройки под определённую
предметную
область.
Этот
процесс
оказывается
достаточно
дорогостоящим, потому что требует привлечения экспертов из
предметной области.
Система Orakel принимает вопросы-фактоиды, начинающиеся с
так называемых wh-наречий, таких как who, what, where, which и т.д. и
также выражения «how many» для подсчёта и «how» за которым
следует прилагательное для запроса специальных значений атрибута,
например, в вопросе «How long is the Rhein?». Фактоид в этом
контексте означает то, что Orakel в качестве ответов предоставляет
только факты, которые могут быть найдены в базах знаний или базах
данных, но не даёт ответа на вопросы «why» или «how», в которых
спрашивается об объяснениях, способах совершения чего-либо или
причин какого-либо события.
ONLI
(Ontology
Natural
Language
Interaction)[9]
–
ЕЯ
вопросно-ответная система, используемая в качестве интерфейса к
системе RACER. Предпосылкой для ONLI является то, что
пользователь знаком с предметной областью для онтологии. Данная
система переводит ЕЯ-запросы пользователя на язык nRQL. Не
предоставлено никаких данных о стоимости настройки системы под
новую предметную область.
Querix[10] – это другая ЕЯ вопросно-ответная система, которая
преобразует
ЕЯ-запросы
на
язык
SPARQL.
В
случае
неоднозначностей, Querix полагается на уточняющие вопросы к
пользователю.
В
итоге
можно
заключить,
что
существующие
ЕЯИ
поддерживают либо поиск, похожий на поиск по ключевым словам,
либо требуют корректно сформированных ЕЯ-вопросов. Интеграция
этих двух подходов является перспективным направлением развития
для ЕЯИ.
1.2. Русскоязычные
естестенно-языковые
интерфейсы
Количество русскоязычных интерфейсов крайне мало и на
данный момент только начинают появляться системы анализа текстов,
которые позволяют извлекать из текста сущности и отношения между
ними. Экспериментальные ЕЯИ были разработаны в рамках проекта
InBASE[21] РосНИИ Искусственного Интеллекта под руководством
А.С.Нариньяни.
Технология InBASE является первой и пока единственной
отечественной промышленной технологией, позволяющей строить
ЕЯИ к реляционным базам данных. В настоящее время InBASE
проверена на нескольких пилотных внедрениях, накоплен опыт в ходе
создания пилотных ЕЯИ (как демонстрационных, так и
коммерческих)[22].
Разработанная система построения ЕЯИ к РСУБД является
средством для создания, отладки и тестирования ЕЯИ.
Для создания простого ЕЯИ к базе данных средней сложности
требуется несколько человеко-часов, а для доведения его качества до
пригодного к использованию неподготовленным пользователем –
порядка нескольких человеко-дней.
Система анализа, заложенная в продукционную программу
SNOOP, имеют высокую степень независимости от языка. Например,
при построении англоязычного и русскоязычного ЕЯИ нет никаких
отличий в качестве анализа при одной и той же системе анализа. Более
того, один и тот же ЕЯ-интерфейс может понимать запросы на обоих
языках, если в словаре есть соответствующая лексика.
При разработке проекта были построены ЕЯИ к базам данных
различной сложности, степени нормализации и из разных предметных
областей. Работа с системой показала, что система анализа
обеспечивает достаточно хорошее качество понимания ЕЯ в этих
разных предметных областях.
Следует отметить, что в системе InBase используется дерево
разбора в семантической грамматике, являющееся
усовершенствованием синтаксического дерева для более удобной
семантической обработки, и язык RDF для преставления данных о
предметной области и схемы БД.
1.3. Возможность применения аппарата СК-языков
для представления содержания запросов пользователей в
естестенно-языковых интерфейсах
Огромное влияние на проектирование анализаторов связных текстов
(или
дискурсов)
оказывают
используемые
методы
формального
отображения смысла текстов, а также методы формального представления
промежуточных результатов смыслового анализа текстов.
В последнее десятилетие наиболее интенсивно исследования по
проектированию лингвистических процессоров развивались за рубежом,
поскольку их конструирование является трудоемкой задачей и требует
устойчивого финансирования в течение ряда лет. При этом для
формального представления смысла текстов использовались главным
образом средства, предоставляемые теорией концептуальных графов,
эпизодической логикой, теорией представления дискурсов. В большинстве
проектов, реализованных в нашей стране в 1980-е и 1990-е годы,
применялись
различные
варианты
языков,
предлагаемых
теорией
семантических сетей и теорией фреймоподобных языков представления
знаний.
Все подходы, за исключением теории К-представлений, наделены
рядом недостатков, которые сужают область применения этих подходов,
так как не позволяют передавать смысловую структуру произвольных
ЕЯ-текстов, встречающихся в реальных предметных областях[1].
Анализ возможностей этих подходов показывает, что любые
формальные выражения, рассматриваемые этими подходами, имеют аналог
в теории К-представлений. Например, выражению [книга: {*} @ 50],
обозначающему в теории концептуальных графов ЕЯ-конструкцию «50
книг»,
может
быть
сопоставлено
формальное
выражение
теории
К-представлений нек множ * (Колич-элем, 50)(Качеств-состав, книга), где
нек
—
информационная
соответствующая
словам
с
единица
(квантор
референтности),
лексемой
некоторый,
Колич-элем
и
Качеств-состав — бинарные реляционные символы, обозначающие
отношения «Количество элементов множества» и «Качественный состав
множества».
Помимо
этого,
теория
К-представлений
обладает
рядом
преимуществ по отношению ко всем перечисленным выше, а также к
другим известным подходам к формализации содержания ЕЯ-текстов.
В теории К-представлений сформулирована гипотеза о возможности
построения концептуальных структур, выражающих смысл произвольных
предложений и дискурсов на ЕЯ, относящихся к любым областям
деятельности человека. Другие же известные подходы к формальному
описанию
содержания
ЕЯ-текстов
лишь
отмечают
расширение
выразительных возможностей (как правило, языка логики предикатов
первого порядка).
Общими преимуществами аппарата теории К-представлений по
сравнению с теорией концептуальных графов являются:
1) более четкое структурирование предметных областей на основе
определения множества типов;
2) рассмотрение отношения совместимости на множестве сортов
предметной области, позволяющее связывать со многими сущностями не
одну, а несколько «координат» по разным «семантическим осям»;
3) наличие средств формального описания смысловой структуры
таких дискурсов, в которых есть ссылки на смысл предыдущих фраз или
более крупных частей текста;
4) возможность моделирования смысловой структуры фраз с прямой
и косвенной речью;
5)
возможность
рассмотрения
информационной
единицы,
соответствующей слову понятие, что расширяет арсенал средств
формального представления энциклопедической информации;
6) возможности использования логических связок и и или для
соединения обозначений объектов или понятий или целей.
Кроме этого, по сравнению с теорией концептуальных графов
теория К-представлений позволяет строить составные обозначения целей и
команд, а также составные обозначения множеств объектов и множеств
понятий — это является преимуществом данной теории и по сравнению с
эпизодической логикой.
Наконец, только теория К-представлений предлагает формальную
модель лингвистической базы данных, причем эта модель является широко
применимой. Учитывая сказанное выше, сделан вывод целесообразности
использования теории К-представлений в качестве методологической
основы данной работы.
Глава 2. Разработка подхода к созданию русскоязычного
естественно-языкового интерфейса к реляционной базе данных
2.1.
Требования
к
естественно-языковому
интерфейсу
Естественно-языковой интерфейс (ЕЯИ) создаётся для
выполнения запросов к реляционной базе данных. В нашем случае –
это туристическая база данных.
Полагаясь на это, определим, что пользователь в своём запросе
может задавать следующие параметры запроса:
1. Куда?
2. Когда?
3. На сколько?
4. За сколько?
В ответ ему задаётся или уточняющий вопрос или выдаются
какие-либо данные из туристической базы. Например, на запрос “тур в
Швейцарию” будет выведено несколько туров в Швейцарию из базы
данных (если они в ней есть).
2.2.
Форматы
входных
запросов
к
естественно-языковому интерфейсу
Для расширения входного языка системы определим, что
запрос может быть введён как одним предложением, так и связным
текстом, например, “недорогой тур в Австрию на 17 февраля" или
«хочу поехать в австралию. С25 по 31 июня.»
Параметры запроса вводятся в простом виде (без использования
логических связок) с использованием модификаторов (“не”, “дороже”,
“дешевле” и т.д.). Например, запрос “ тур не в Африку не дороже 40000
рублей” будет обработан, запрос “тур в Африку или Австралию” будет
обработан некорректно. При этом в первом запросе параметрами
являются сущности: Африка, 40000 рублей. У сущности Африка есть
модификатор “не”, а у сущности “40000 рублей” модификатор - “не
дороже”.
Примечание. Модификатор – это конструкция, накладывающая
какое-либо ограничение на сущность. Например, в выражении
«зелёный мяч», «мяч» – это сущность, а «зелёный» - модификатор,
ограничивающий сущность мяч цветом.
После выполнения одного запроса к системе можно задавать
второй эллиптический запрос. При обработке этого запроса будут
использоваться параметры из предыдущего с возможной
заменой/дополнением.
Пример.
Запрос 1: “тур в Сербию на 7 дней”
Система выдала, что нет таких туров.
Запрос 2: а в Черногорию?
Система выдала список, автоматически добавив условие “7
дней” так как запрос эллиптический.
Вот ещё некоторые возможные варианты запросов:
«Есть поездка в Турцию, но чтобы на 12 дней?»
«Куда-нибудь в Европу на майские праздники»
«Есть ли Швейцария за 40000 рублей?»
Важно отметить, что текст запроса может содержать в себе
информацию, не относящуюся к характеристике тура, при этом он
будет обработан так, что из него будет взята только информация о
туре. Например, такой запрос будет корректным: «хочу поехать в
Индию, но чтобы моя мама не знала. Говорят, там можно увидеть
настоящих йогов».
2.3. Описание основных блоков реализации
подхода
Подход включает в себя следующие крупные блоки,
осуществляющие ключевую функциональность системы:
1. Построение СП запроса
2. Интерпретация СП для создания SQL запроса
3. Выполнение запроса к БД и возврат результата.
Рассмотрим блоки 1 и 2 подробнее, потому что третий блок
относительно прост и состоит только из интерфейса к БД.
2.3.1. Построение семантического представления запроса
Построение семантического представления запроса является
самой сложной и объёмной частью алгоритма и осуществляется
следующим образом:
1. Токенизация
2. Определение морфологических характеристик
3. Выявление ИС
4. Построение отношений между ИС
Две первые задачи могут быть решены с использованием
существующих средств пакета NLTK (natural language toolkit)[12],
третья задача с помощью морфологического анализатора
Pymorphy[11], для решения оставшихся задач будет необходимо
разработать оригинальный инструмент для создания К-представлений.
В следующих разделах будет рассмотрено решение этих задач более
детально.
2.3.1.1. Токенизация запроса
Зачача токенизации предложений состоит в том, чтобы
преобразовать строку с текстом в массив строк, каждым элементом
которого является слово или пунктуационный символ.
Формально, смысл данного шага заключается в том, чтобы
преобразовать входную строку s во множество элементов {t1,…,tn},
где ti соответствует одной неделимой синтаксической единице текста
(синтаксеме). Например, для предложения «Поступила книга,
называющаяся «Инструментарий веб-аналитика»» результатом
токенизации будет множество с элементами:
 Поступила
 книга
 ,
 называющаяся
 «Инструментарий веб-аналитика»
2.3.1.2. Определение морфологических характеристик
синтаксем
Задача данного шага состоит в том, чтобы нормализовать
каждый токен (привести его к базовой форме) и определить его
морфологические характеристики. Выходом данного шага является
множество пар {(b1,m1),…,(bn,mn)}, где bi - базовая форма токена, а mi
- морфологические характеристики.
Морфологические характеристики обязательно должны
включать в себя часть речи, также для существительных необходимо
знать падеж и число.
Примечание. В дальнейшем, возможно будут использованы и
другие морфологические характеристики.
2.3.1.3. Извлечение семантических сущностей
Задача данного шага заключается в том, чтобы из множества
пар {(b1,m1),…,(bn,mn)}, где bi - базовая форма токена, а mi морфологические характеристики объединить некоторые пары в более
крупные блоки соответствующие одной СС.
Например, паре (мяч, м1) соответствует СС нек мяч, а пары
(зелёный, м1) и (мяч, м2) соответствуют одной СС: нек мяч*(Цвет,
Зелёный).
Важно отметить, что в предложении СС соответствуют
существительным (c возможным числом впереди), местоимениям и
глаголам.
К каждой СС может применятся модификатор, в результате
чего появляется уже новая СС. Для существительный модификаторами
являются прилагательные, а для глаголов - наречия. В предыдущем
примере СС нек мяч в дальнейшем изменяется модификатором (Цвет,
Зелёный). Ещё пример: глаголу “передвигаться” соответствует СС
передвижение1, а выражению “быстро передвигаться” соответствует
модифицированная СС передвижениние1*(Скорость, быстро).
Для выделения СС будем строить семантико-синтаксическое
дерево (ССД), листьями которого являются пары вида (базовая форма
токена, морфологические характеристики), а узлами - пары вида (тип
узла, семантическая сущность). Семантическая сущность берётся из
лексико-семантического словаря[1] (в нём каждому слову ставится в
соответствие самантическая единица и набор сортов) по базовой форме
слова.
Примечание. По мнению автора, в дальнейшем для
установления отношений между существительным и прилагательными
необходимо использовать словарь существительно-прилагательных
фреймов, который по сортам существительного и прилагательного
будет содержать семантическое отношение между ними.
Будем использовать в системе следующие выделенные типы
узлов:
1.
NP - элементарная единица для представления
именованных сущностей (ИС), представляет собой именную группу
(существительное с набором модификаторов):
{<NOT>?<ADJ|NB>*<N|CNJ>+}
Замечание. Для существительного модификатором является
число, стоящее перед ним, отрицательная частица и прилагательные.
2.
VP - единица для ИС, представленной в предложении
глаголом с модификаторами:
{<ADV>*<V><ADV>*}
Замечание. Модификаторами для глагола являются наречия и
отрицательная частица.
3.
PP - это NP c стоящим перед ним предлогом и
отрицательной частицей (второе необязательно), объединённые для
удобства построения СП:
{<NOT>?<P><NP>}
4.
NPP - это единица, представляющая собой ИС, которая
образована при использовании семантического отношения из СПФ:
{<NP><PP|NP>+}
5.
VPP - это единица, представляющая собой ИС,
образованную при использовании семантического отношения из
СГПФ:
{<VP><NP|PP|CLAUSE>*}
6.
CLAUSE - новая ИС, образованная с использованием
семантического отношения из СГПФ в сочетанием NP и VPP:
{<NP><VPP>}
Используемые обозначения:
NOT - частица “не"
ADJ - прилагательное
NB - число
N - существительное
CNJ - союз
P - предлог
V - глагол
ADV - наречие
* - 0 или больше элементов
| - или
+ - один или больше элементов
? - любой символ
Построение ССД осуществляется снизу-вверх, то есть сначала
строятся узлы более низкого уровня (NP, VP), затем объединяясь в
узлы более высокого уровня (NPP, VPP и другие). Пример дерева
представлен на рис. 1.
Рис. 1. Пример синтаксического дерева для текста: «поездка в
Африку за 20000 рублей»
2.3.1.4. Построение отношений между семантическими
сущностями
Задачей этого этапа является завершение построения ССД с
помощью объединения нескольких узлов в один с помощью
семантических отношений.
Так узлы (NP, нек тур) и PP(нек континент*(Название,
“Африка”)) могут быть объединены в узел
(NPP, нек тур*(Конечный пункт, нек континет*(Название,
“Африка”))).
А узлы (VP, ехать) и PP(нек континент*(Название, “Африка”))
могут иметь родительский узел
(VPP, передвижение1*(нек континент*(Название, “Африка”)))
Построение таких отношений осуществляется между
вершинами с метками NP, PP, VP причём отношение между NP и PP
берётся из словаря предложных фреймов[1], а между VP и NP или PP
берётся из словаря глагольно-предложных фреймов[1].
Таким образом на выходе этого блока получаем одно
семантическое представление текста, если дерево обработано
полностью, иначе несколько семантических представлений в первых
заполненных узлах (если смотреть сверху дерева).
Важно отметить, что при построении отношений между
сущностями из эллиптической конструкции, системе может не хватать
определённых сущностей, так как они не указываются прямым
текстом, а подразумеваются. В этом случае будет использовать
сущности, полученные в результате обработки предыдущего запроса и
дополнять ими текущий запрос. Например, если первый запрос “тур в
Нидерланды 16 мая”, а второй запрос “а в Киргизию?", то последний
запрос будет дополнен сущностью “нек поездка*(Дата_отъезда, нек
дата*(День, 17)(Месяц, “Май”) ", чтобы к ней присоединить
выражение “(Конечный пункт, нек страна*(Название, “Киргизия"))” и
в результате семантическое представление будет иметь вид “нек
поездка*(Дата_отъезда, нек дата*(День, 17)(Месяц, “Май”)(Конечный
пункт, нек страна*(Название, “Киргизия")) "
2.3.2. Интерпретация семантического представления для
создания SQL запроса
На данном шаге набор семантических представлений,
полученный на выходе предыдущего шага будет обрабатываться с
целью формирования SQL-запроса к реляцеонной базе данных с
турами.
Для этой цели нас интересуют следующие элементы запроса:
1. Название таблицы, к которой осуществляется запрос
2. Названия полей, которые необходимо показывать в
результате
3.1. Название поля фильтрации (соответствует колонке в
таблице)
3.2. Оператор который используется для фильтрации
3.3. Значение поля для фильтрации
4. Количество выводимых строк (аналог “select top N” в
SQL-запросе)
Примечание. В данной работе обработка пунктов 1, 2 и 4
упрощена, потому что предполагается, что пользователю выводятся
все записи соответствующие критериям поиска из заранее
определённой таблицы. То есть берём готовый SQL-префикс: “select *
from tours “.
Пример. Для СП:
нек поездка*(Конечный пункт,страна*(Название,
“Швейцария”))(Цена,сумма*(число,20000)*(Валюта,рубль))
будет построен SQL-запрос:
select * from tours where price=20000 and country='Швейцария'
Извлечение информации из СП начинается с того, что от
сущности в семантическом представлении отделяются модификаторы,
причём каждый модификатор тоже разделяется на пары (Ключ,
Значение). Например, СП из предыдущего примера будет разбито на
элементы:
1. поездка
2. (Конечный пункт, нек страна*(Название, “Швейцария”))
3. (Цена,сумма*(число,20000)*(Валюта,рубль))
Примечание. Элементу “поездка” может быть поставлено в
соответствие название таблицы.
2.3.2.1. Извлечение названия поля фильтрации
После того, как СП разбито на части, производится обработка
левой части каждого модификатора, которая и связана с названием
поля таблицы. Эта связь осуществляется с помощью простого словаря,
ключом которого является функциональный символ или бинарный
реляционный символ, а значением - название поля таблицы.
Пример. Словарь:
{“Конечный пункт”:”country”, “Цена”:“price”}
2.3.2.2. Извлечение оператора для фильтрации
Процесс извлечения оператора для фильтрации состоит из двух
шагов. На первом шаге определяется тип оператора “по умолчанию"
для каждого поля. Это осуществляется с помощью словара, ставящего
в соответствие левой части модификатора значение оператора.
Пример. Словарь:{“Конечный пункт”: “in”, “Цена”: “=”}
На втором шаге можно попытаться найти значение оператора в
правой части разделённого модификатора. При этом оператор по
умолчанию будет заменён на найденный.
Пример. Есть модификатор (Конечный пункт, ~(нек
страна*(Название, “Швейцария”))), где “~” - символ, означающий
логическое отрицание. Оператор по умолчанию в этом случае - “in"
(см. предыдущий пример), но в правой части содержится символ “~",
который требует замены оператора на “not in".
Поиск оператора на втором шаге осуществляется с помощью
поиска отрицательной частицы перед семантической сущностью. При
этом выбор оператора производится из соответствующего словаря,
аналогичного упомянутому выше.
Пример. Словарь:{“Конечный пункт”: “not in”, “Цена”: “<>”}
2.3.2.3. Извлечение значения поля для фильтрации (с
использованием базы знаний)
Значение поля для фильтрации извлекается только из правой
части разбитого модификатора. Например, при левой части “Конечный
пункт” поиск будет осуществляться по правой части: “нек
страна*(Название, “Швейцария”)”.
Здесь уже подход к поиску будет несколько иным.
Предположим, что в правой части разбитого модификатора есть
сущность с возможными модификаторами. Исходя из этого
предположения, можно искать необходимые значения для фильтрации,
используя поиск по типам, соответствующим функциональному или
бинарному реляционному символу в модификаторе. Таким образом,
если этот символ возвращает нам сущность типа “зндаты” (значение
даты), то можно его обрабатывать одним образом (извлекать день,
месяц, год), а если сущность типа “геогроб” (географический объект),
то - другим.
2.3.2.4. Использование базы знаний
Важным моментом данной работы является то, что при
интерпретации семантического представления на данном шаге система
использует географическую базу знаний. Такая необходимость
возникает из-за того, что пользователь системы может создать запрос:
“тур в Африку”, а таблица с данными в системе содержит только поле
Страна. Соответственно, необходимо знать, что Африка - это материк,
а материк может содержать внутри себя страны. В данном случае
Африка содежит: Египет, Ливию, Алжир, Тунис, Камерун, Зимбабве и
т.д.
База знаний о внешнем мире устроена просто и состоит из
множества семантических представлений: {s1, s2, s3,..., sN}, где si - это
конкретное семантическое представление.
Пример. База знаний в нашем случае содержит семантические
представления:

нек материк*(Название, “Африка”):х1

нек страна*(Название, “Египет”):х2

нек страна*(Название, “Алжир”):х3

нек страна*(Название, “Зимбабве”):x4

Содержит1(x1, x2)

Содержит1(x1, x3)

Содержит1(x1, x4)
при этом типом бинарного реляционного символа
“Содержит1” является выражение "{геогроб, геогроб}”, что
означает, что это бинарное отношение на множестве
географических объектов.
Теперь для того, чтобы осуществлять поиск по базе
знаний необходимо специфицировать отношения, второй
агрумент которых будет подходящим элементом для построения
критерия поиска в реляционной таблице. Продолжая пример,
таким отношением будет одно отношение “Содержит1“.
Таким образом, при запросе “тур в Африку”, системой
будет создана сущность “нек материк*(Название, “Африка”)”,
затем эта сущность будет искаться внутри базы знаний и для неё
будет найдена переменная х1. Потом будет искаться отношение
“Содержит1“, и при первом аргументе х1, второй аргумент будет
обработан с целью извлечения значения фильтрации. В этом
случае значением фильтрации будет часть SQL-выражения
“(‘Египет’,’Алжир‘,’Зимбабве‘)”
Глава 3. Программная реализация разработанного подхода
к созданию естественно-языкового интерфейса
Предложенный подход реализован в виде программы на языке
Python с использованием СУБД SQLite.
Цель данного раздела - подробно рассмотреть программную
реализацию подхода, поэтому раздел будет состоять из описания
причин именно такого выбора языка программирования и СУБД. Затем
будут описаны модули построения СП, интерпретатора СП в
SQL-запрос, представляющие собой ядро системы. Также будет дано
устройство БЗ, интерфейса к БД и интерфейса пользователя.
В целом, архитектура приложения представлена на рис. 2:
Рис. 2. Архитектура разрабатываемого ЕЯИ
3.1. Выбор языка программирования и системы
управления базами данных
Для того чтобы обосновать выбор Python для данного проекта,
необходимо ответить на вопрос: “Почему люди вообще используют
Python?” Принимая во внимание то, что сейчас существует около 1
миллиона пользователей Пайтона, полностью точный ответ на
поставленный вопрос невозможен; выбор средства разработки часто
зависит от уникальных ограничений и индивидуальных предпочтений.
Python – это современный язык программирования,
разработанный Guido Van Rossum в 90х (и названный в честь
комедийного сериала Монти Пайтон). Хотя Пайтон не является
лучшим выбором для любого приложения, его сильные стороны
делают его хорошо применимым для многих приложений.
Из личного опыта автора и анализа специальной литературы
[12, 13, 14] можно выделить следующие сильные стороны языка
Пайтон:
Качество приложений. Для многих удобочитаемость,
логичность и общее качество создаваемых приложений отделяют
Пайтон от других инструментов. Код на этом языке программирования
разработан удобочитаемым и, следовательно, просто используемым
повторно и просто поддерживаемым. Однородность кода делает его
легко понятным, даже если его писали не Вы.
Пайтон прост в использовании. Программисты, знакомые с
традиционными языками, обнаружат лёгкость освоения Пайтона. В
нём содержаться все из знакомых конструкций, таких как циклы,
условные выражения, массивы и т.д., но все они намного проще в
использовании по следующим причинам:
Типы ассоциируются с объектами, а не с переменными.
Переменной может быть присвоено значение любого типа, и массив
может содержать объекты различных типов. Это означает, что нет
необходимости в объявлении типа для переменной, и программный
код не остаётся в границах объявленных типов.
Пайтон работает на более высоком уровне абстракции. С одной
стороны, это результат того, как устроен Пайтон, а с другой – результат
включения обширной стандартной библиотеки. Программа скачивания
Веб-страницы может быть написана в три строки.
Синтаксические правила очень просты. Хотя для того чтобы
стать экспертом в Пайтоне, необходимо время и усилия, даже
начинающие могут освоить в достаточной степени синтаксис Пайтона,
чтобы быстро написать требуемый код.
Поддержка функционального и объектно-ориентированного
программирования, без которых сложно себе представить любой
современный язык.
Пайтон хорошо подходит для быстрой разработки приложений.
Те факты, что время, затраченное на разработку кода на Пайтоне
обычно составляет 20-40% от времени разработки на С или Java, и
количество строк кода в приложении на Пайтоне составлет 20-40% от
количества строк в эквивалентной программе на С[14]. Меньшее число
строк кода означает меньшее количество времени на отладку и на
последующую поддержку. Конечно, это зависит от конкретного
приложения, но в среднем, прирост продуктивности является
значительным. Программы на Пайтоне запускаются без компиляции,
что экономит время.
Большинство программ написанных на Пайтоне, могут
запускаться без изменений на большинстве компьютерных платформ.
Интеграция компонентов. Скрипты на Пайтоне могут легко
взаимодействовать с другими частями приложения, используя
широкий набор интеграционных механизмов. Сегодня, код на Пайтоне
может использовать библиотеки на языках C и C++, Java и .NET
компоненты.
Пайтон – это Open Source проект, то есть каждый может
изменять, улучшать и расширять исходный код. Философия Open
Source наиболее всего соответствует научной философии, поскольку
исследователи издавна полагались на открытость исследований и
результатов друг друга.
Хотя Пайтон имеет много преимуществ, он не является
совершенным решением для всех задач. Для того, чтобы решить о
применимости Пайтона в конкретной ситуации, необходимо
рассмотреть и его недостатки:
Пайтон – это не самый быстрый язык. Возможным недостатком
Пайтона является его скорость выполнения. Существуют
определённые задачи, например, использование регулярных
выражений, с которыми Пайтон справляется не менее эффективно, чем
любая программа на С. Тем не менее, в большинстве случаев
программы на Пайтоне выполняются медленнее, чем на С. Но
современные компьютеры обладают достаточной вычислительной
мощностью, что для большинства приложении скорость работы
является менее важным приоритетом, чем скорость разработки. Кроме
того, использование библиотек на C и С++ решает и эту проблему.
Для Пайтона ещё не создано такое большое количество
библиотек, как для C и Java. Однако Пайтон легко расширяем либо
собственными средствами, либо использованием библиотек на С или
других языках.
Строки в пайтоне хранятся не в юникоде, что осложняет
присваивание переменным русскоязычных символов, однако этот
недостаток будет устранён в следующей версии Python по сведениям
портала python.org.
Пайтон не проверяет корректность типов переменных во время
начала отладки, то есть интерпретатор не помогает разработчику найти
несовпадения типов переменных. Но эти ошибки не так сложно
обнаружить и исправить, и они всплывают на поверхность в ходе
отладки. Большинство разработчиков Пайтон предпочитают
гибкость динамического определения типов соответствующим
издержкам[14].
Согласно русской народной пословице «Лучше один раз
увидеть, чем сто раз услышать», продемонстрируем пример кода
(модифицированный из [12]) программы, которая считывает файл
file.txt и выводит на экран все слова, оканчивающиеся на 'ся':
>>> for line in open("file.txt"):
...
...
for word in line.split():
if word.endswith('ся'):
...
print word
Эта программа иллюстрирует основные возможности Пайтона.
Во-первых, пробелы используются для вложенных блоков кода; таким
образом, строка, начинающаяся с if, попадает в блок кода для
предыдущей строки, начинающейся с for; это гарантирует то, что тест
на окончание 'ся' будет выполнен для каждого слова. Во-вторых,
демонстрируется применимость ООП, поскольку каждая переменная
является сущностью, имеющей конкретные атрибуты и методы.
Например, значение переменной line – это нечто большее, чем
последовательность символов, это объект класса string, который имеет
метод split(), который используется для разбивки предложения на
слова. Наконец, показывается удобочитаемость Пайтона, позволяющая
в данном случае даже человеку без знания программирования угадать,
что делает программа.
Пайтон широко используется при разработке приложений,
научных исследованиях, и образовании по всему миру. Множество
историй успеха Пайтона можно прочитать по адресу
http://www.python.org/about/success/.
Кто использует Пайтон сегодня?
Кроме использования отдельными пользователями, Пайтон
также применяется в реальных программах реальных компаний. Ниже
приведены примеры:
Google широко использует Python в своей поисковой системе, и
там работает создатель Пайтона.
Большая часть Youtube создавалась на Пайтоне.
Популярная программа BitTorrent.
Популярный фреймворк для веб-разработки App Engine от
Google написан на Python.
В популярной онлайн-игре EVE Online большая чать кода
написана на Python.
Intel, Cisco, Hewlett-Packard, Seagate, Qualcomm, и IBM
используют Python для тестирования своей продукции.
Industrial Light & Magic, Pixar, и другие используют Python для
создания анимированных мультфильмов.
JPMorgan Chase, UBS, Getco, и Citadel используют Python
прогнозирования на финансовых рынках.
NASA, Los Alamos, Fermilab, JPL, и другие используют Python
для научных целей.
iRobot использует Python для разработки коммерческих
роботов
На Пайтоне написаны большие модули для обработки
естественного языка, например, используемые в данном проекте NLTK
и Pymorphy.
И другие проекты. Как видно из примеров, пайтон применяется
в различных предметных областях. Наверное, в большинстве западных
организации python используется как для краткосрочных тактических
задач, так и для долгосрочной стратегической разработки ПО. Пайтон
доказал свою применимость в обоих случаях.
Для данного проекта Пайтон выбран, потому что его синтаксис
и семантика являются прозрачными, и он обладает отличной
функциональностью для работы с текстовыми строками и базами
данных. Как интерпретируемый язык, Пайтон способствует
интерактивной разработке. Как объектно-ориентированный, он
позволяет инкапсулировать данные и методы для лёгкого повторного
использования. Как динамический язык, Пайтон позволяет добавлять
атрибуты объектам после создания и поддерживает динамическое
определение типов.
В качестве СУБД, используемой в проекте, выбран SQLite,
потому что он хорошо интегрируется с Python’ом.
3.2.
Модуль
построения
семантического
представления запроса пользователя
Данный модуль принимает на вход семантическое
представление, а на выходе выдаёт массив семантических
представлений текста.
Структурная схема данного модуля и его окружения
представлена на рис. 3:
Рис. 3. Структурная схема модуля «Построение СП»
Модуль осуществляет свою работу в следующем порядке:
1.
Токенизация
2.
Определение морфологических характеристик
3.
Построение синтаксического дерева
4.
Выявление ИС
5.
Построение отношений между ИС
Выход каждого шага является входом для другого и
реализуется отдельным классом.
Рассмотрим реализацию каждого шага подробнее.
3.2.1. Токенизация
Вход: строка введённого текста
Выход: массив с частями текста
Токенизация осуществляется классом Tokenizer, который по
своей сути является интерфейсом к соответствующему разделу
библиотеки NLTK “nltk.tokenize”, в которой есть класс
WordPunctTokenizer, который используется для токенизации текста.
Структурная схема данного класса и его окружения
представлена на рис. 4:
Рис. 4. Структурная схема класса Tokenizer и его окружения
Для токенизации в библиотеке NLTK есть несколько
токенайзеров:
1. RegexpTokenizer - разбивает строку на подстроки с помощью
регулярных выражений
2. WhitespaceTokenizer - разбивает строку на подстроки,
используя один или несколько последовательных пробелов как
разделитель.
3. BlanklineTokenizer - в качестве разделителя используется
любая последовательность пустых строк. Пустая строка в данном
случае - это строка, не содержащая символов, или содержащая только
пробелы из знаки табуляции.
4. WordpunctTokenizer - разбивает строку на подстроки из
алфавитных и неалфавитных символов. Для решения нашей задаче
будет использован именно этот вариант, так он в данном случае
оказывается удобен.
Например, для предложения:
Предприниматель едет на автомобиле, арендованном в
компании "Трастед интернейшнлз"
Результат токенизации будет выглядеть как массив:
1.
Предприниматель
2.
едет
3.
на
4.
автомобиле
5.
,
6.
арендованном
7.
в
8.
компании
9.
"
10.
Трастед
11.
интернейшнлз
12.
"
Очевидно, что такой результат удобен для дальнейшего
определения морфологических характеристик.
3.2.2. Определение морфологических характеристик
Вход: массив с частями текста
Выход: массив пар (базовая форма токена, морфологические
характеристики).
Определение морфологических характеристик производится
классом PyMorphyInterface, являющимся интерфейсом к библиотеке
PyMorphy.
Система запрашивает у PyMorphy базовую форму токена и
набор морфологических характеристик. В случае нескольких базовых
форм или нескольких наборов морфологических характеристик
пользователю задаётся уточняющий вопрос.
После этого PyMorphyInterface осуществляет конвертацию
грамматической информации от PyMorphy в формат, используемый в
приложении и создаёт готовую для добавления в массив результата
пару вида (базовая форма токена, морфологические характеристики).
Структурная схема класса PyMorphyInterface и его окружения
представлена на рис. 5:
Рис. 5. Структурная схема класса PyMorphyInterface и его
окружения
3.2.2.1. Описание библиотеки PyMorphy
За основу морфологического анализатора взята библиотека
PyMorphy[11], в основу которой взяты наработки с сайта aot.ru.
Словари с сайта aot.ru содержат следующую информацию:
парадигмы слов и конкретные правила образования;
ударения;
пользовательские сессии;
набор префиксов (продуктивных приставок);
леммы (неизменяемые части слова, основы);
грамматическая информация - в отдельном файле.
Из этих словарей в PyMorphy используются правила
образования слов, префиксы, леммы и грамматическая информация.
Все слова образуются по одному принципу:
[префикс]+[приставка]+[основа]+[окончание]
Префиксы - это всякие “мега”, “супер” и т.д. Набор префиксов
хранится просто списком.
Имеются в виду приставки, присущие грамматической форме,
но не присущие неизменяемой части слова (“по”, “наи”). Например,
“наи” в слове “наикрасивейший”, т.к. без превосходной степени будет
“красивый”.
Правила образования слов - это то, что надо приписать спереди
и сзади основы, чтобы получить какую-то форму. В словаре хранятся
пары “приставка - окончание”, + “номер” записи о грамматической
информации (которая хранится отдельно).
Правила образования слов объединены в парадигмы. Например,
для какого-нибудь класса существительных может быть описано, как
слово выглядит во всех падежах и родах. Зная, что существительное
принадлежит к этому классу, мы сможем правильно получить любую
его форму. Такой класс - это и есть парадигма.
Леммы - это неизменяемые части слов. В словаре хранится
информация о том, какой лемме соответствуют какие парадигмы
(какой набор правил для образования грамматических форм слова).
Одной лемме может соответствовать несколько парадигм.
Грамматическая информация - просто пары (уникальный
индекс записи, грам. информация).
Файл со словарем - обычный текстовый, для каждого раздела
сначала указано число строк в нем, а потом идут строки.
По сути, есть слово, и его надо найти среди всех разумных
комбинаций вида:
<префикс>+<приставка>+<лемма>+<окончание>
и:
<приставка>+<лемма>+<окончание>
Если слово начинается с одного из возможных префиксов, то
мы его (префикс) отбрасываем и пытаемся морфологически
анализировать остаток (рекурсивно), а потом просто припишем
отброшенный префикс к полученным формам.
В итоге получается, что задача сводится к поиску среди
комбинаций:
<лемма>+<окончание>
Теперь ищутся подходящие леммы, которые потом
анализируются на предмет того, есть ли для них подходящие
окончания.
Для поиска задействован стандартный питоновский
ассоциативный массив (dict, или любой объект, поддерживающий
__getitem__, __setitem__ и __contains__), в который помещены все
леммы. Получился словарь вида:
lemmas: {base -> [rule_id]}
т.е. ключ - это лемма, а значение - список номеров допустимых
парадигм. А дальше процесс происходит следующим образом: сначала
считаем, что лемма - это первая буква слова, потом, что это 2 первых
буквы и т.д. По лемме пытаемся получить список парадигм. Если
получили, то в каждой допустимой парадигме пробегаем по всем
правилам и смотрим, получится ли наше слово, если правило
применить. Получается - добавляем его в список найденных форм.
В русском языке есть слова, у которых неизменяемая часть
отсутствует. Выяснилось, что есть в словаре такая хитрая магическая
лемма “#”, которая и соответствует всем пустым леммам. Для всех
слов нужно искать еще и там.
Реализован “предсказатель”, который может работать со
словами, которых нет в словаре. Это не только неизвестные науке
редкие слова, но и просто описки.
Для предсказателя реализованы 2 подхода, которые работают
совместно.
Первый подход: угадывание префикса.
Если слова отличаются только тем, что к одному из них
приписано что-то спереди, то, скорее всего, склоняться они будут
одинаково.
Реализуется очень просто: пробуем считать сначала одну
первую букву слова префиксом, потом 2 первых буквы и т.д. А то, что
осталось, передаем морфологическому анализатору. Ну и делаем это
только для не очень длинных префиксов и не очень коротких остатков.
Второй подход: предсказание по концу слова.
Если 2 слова оканчиваются одинаково, то и склоняться они,
скорее всего, будут одинаково.
Второй подход чуть сложнее в реализации (так-то сильно
сложнее, если нужна хорошая реализация)) и “поумнее” в плане
предсказаний.
Первая сложность связана с тем, что конец слова может
состоять не только из окончания, но и из части леммы. Для простоты
тут задействован опять ассоциативный массив с предварительно
подготовленными всеми возможными окончаниями слов (до 5 букв).
Их получилось несколько сот тысяч. Ключ массива - конец слова,
значение - список возможных правил. Дальше - все как при поиске
подходящей леммы, только у слова берем не начало, а 1, 2, 3, 4,
5-буквенные концы, а вместо лемм у нас теперь новый массив.
Вторая сложность - получается много заведомого мусора.
Мусор этот отсекается, если учесть, что полученные слова могут быть
только существительными, прилагательными, наречиями или
глаголами.
Но даже после этого у нас остается слишком много
не-мусорных правил. Для определенности, для каждой части речи
оставляем только самое распространенное правило.
3.2.3. Построение синтаксического дерева
Вход: массив пар вида (базовая форма токена, морфологические
характеристики)
Выход: синтаксическое дерево
Синтаксическое дерево в программной реализации - это объект
класса ImmutableTree, наглядно представляющий собой дерево,
листьями которого являются пары вида (базовая форма токена,
морфологические характеристики), а узлами - пары вида (тип узла).
Построение синтаксического дерева выполняется классом
Chunker (от англ. to chunk - делить на куски), являющимся
интерфейсом к классу nltk RegexpParser. Этот класс строит
синтаксическое дерево на основе шаблонов регулярных выражений,
являющихся грамматикой.
Пример. В данном приложении грамматикой является
выражение вида:
grammar = r"""
NP: {<NOT>?<ADJ|NB>*<N|CNJ>+}
PP: {<NOT>?<P><NP>}
NPP: {<NP><PP|NP>+}
VP: {<ADV>*<V><ADV>*}
VPP: {<VP><NP|PP|CLAUSE>+}
CLAUSE: {<NP><VPP>}
"""
Результат построения синтаксического дерева представлен на
рис. 6:
Рис. 6. Синтаксическое дерево для текста «Предприниматель с
собакой быстро едет на автомобиле»
Структурная схема класса Chunker и его окружения
представлена на рис. 7:
Рис. 7. Структурная схема класса Chunker и его окружения
3.2.4. Выявление ИС
Вход: синтаксическое дерево (объект ImmutableTree)
Выход: семантико-синтаксическое дерево с начальной
разметкой
Семантико-синтаксическое дерево в системе представлено
парой синтаксического дерева и словаря, где ключом является элемент
дерева, а значением - семантическая сущность. Словарь необходим для
того, чтобы привязать к узлу дерева семантическую сущность, так это
не позволяет объект ImmutableTree.
Таким образом, работа данного шага заключается в обходе
синтаксического дерева и заполнения указанного выше словаря. Всё
это выполняется классом EntitiesExtractor.
На этом шаге строится начальная разметка ССД, то есть узлам
типа NP, VP ставится в соответствие СС сущность с возможными
модификаторами.
СС берётся из лексико-семантического словаря[1] для всех
листьев синтаксического дерева по базовой форме токена (в
лексико-семантическом словаре каждому слову ставится в
соответствие самантическая единица и набор сортов по базовой форме
слова).
Лексико-семантический словарь реализован в виде следующей
реляционной таблицы, интерфейс к которой представляет класс LSD
(сокр. Lexico-semantic dictionary):
Рис. 8. Структурная схема таблицы для хранения
лексико-семантического словаря
Структурная схема класса EntitiesExtractor и его окружения
представлена на рис. 9:
Рис. 9. Структурная схема класса EntitiesExtractor и его
окружения
3.2.5. Построение отношений между ИС
Вход: семантико-синтаксическое дерево с начальной разметкой
Выход: семантико-синтаксическое дерево с конечной
разметкой
На данном шаге выполняется построение семантических
сущностей для узлов VPP, NPP, CLAUSE за счёт объединения
сущностей, соответствующих их детям (NP, VP, PP и т.д.). Для
реализации этой функциональности создан класс
RelationshipsExtractor.
Отношения между NP/PP и VP строятся из словаря
глагольно-предложных фреймов, интерфейсом к которому является
класс DVPF (сокр. Dictionary of Verbal-Preposicional Frames).
Словарь глагольно-предложных фреймов реализован в виде
реляционной таблицы (рис.10):
dvpf_description.
Рис. 10. Структурная схема таблицы для хранения словаря
глагольно-предложных фреймов
Отношения же между NP и PP строятся из словаря предложных
фреймов, интерфейсом к которому является класс DPF (сокр.
Dictionary of Preposicional Frames).
Словарь предложных фреймов реализован в виде реляционной
таблицы (рис. 11):
dpf_description.
Рис. 11. Структурная схема таблицы для хранения словаря
предложных фреймов
Структурная схема класса RelationshipsExtractor и его
окружения представлена на рис. 12:
Рис. 12. Структурная схема класса EntitiesExtractor и его
окружения
3.3.
Реализация
модуля
интерпретации
семантического представления
Задача данного модуля заключается в том, чтобы преобразовать
набор семантических представлений в запрос на языке SQL.
Данный модуль состоит только из одного класса SQLExtractor,
который и осуществляет всё преобразование, также работая с базой
знаний.
Механизм интерпретации основан на использовании
нескольких типов словарей:
1. Словарь соответствий функциональных символов полям
таблицы. Например, если в СП есть функциональный символ “Цена”,
то соответствующем ему полем таблицы будет “price”.
2. Словарь соответствий функциональных символов
SQL-операторам. То есть ключом словаря является функциональный
символ, а значением - набор пар, первым элементом которых является
флаг оператора (“по умолчанию”, “отрицание”, “больше” и т.д.), а
вторым элементом его SQL-выражение, напрмер, “=”, “<>”, “>” и т.д.
3. Словарь, ключом которого является тип сущности, а
значением - функция, извлекающия информацию из сущности.
Например, для типа “зндаты” необходима функция, умеющая
конвертировать текст в SQL-дату, а для типа “знцены” - функция,
умеющая извлекать числовое значение.
Таким образом, К-представление разбивается на части, ищется
тип этой части в первом словаре, затем ищется оператор и всё
завершается поиском ключевого значения.
Важно отметить, что для обработки типа “геогроб” вызывается
особая функция, которая проверяет, является ли значение выражения
материком или страной, и если это значение является материком, то
она обращается к базе знаниий, из которой пытается получить все
страны, расположенные на данном материке. При этом в функции
специфицируется, что расположение осуществляется
функциональным символом “Содержит”, имеющим тип {геогроб,
геогроб}
Структурная схема класса SQLExtractor (модуля интерпретации
СП) и его окружения представлена на рис. 13:
Рис. 13. Структурная схема класса SQLExtractor и его
окружения
3.1. Реализация базы знаний
База знаний представлена классом KnowledgeBase, который
предоставляет метод поиска по заданной сущности, то есть ему
передаётся сущность, а он возвращает функцию или отношение, в
котором эта сущность участвует и другие аргументы этой функции.
Сама база знаний представлена в виде массива пар, первым
элементом которой является СП, вторым - переменная,
соответствующая этому СП. Если первый элемент - это
функциональный символ с аргументами, то на месте второго
аргумента будет пустой символ.
Заключение
В результате выполнения данной работы была разработана
оригинальная система анализа ЕЯ-запросов, включающая в себя
подсистему построения К-представления введённого запроса,
основанную на создании семантико-синтаксического дерева запроса, и
подсистему интерпретации К-представления в запрос на языке SQL за
счёт использования словарей с отображениями частей
К-представления на различные части SQL-запроса. Важными
особенностями этой системы является то, что она использует базу
знаний о мире, например, при запросе тура в Африку система знает,
что Африка – это материк и на этом материке есть такие-то страны,
поэтому поиск будет осуществляться туров именно в эти страны. В
добавление к этому в качестве запроса может быть использован
связный текст, то есть система сумеет интерпретировать следующее
предложение запроса в связи с предыдущим. Последним важным
фактором является способность системы обрабатывать эллиптические
предложения, что представляет значительное удобство для конечного
пользователя.
В ходе выполнения работы этот ЕЯИ был программно
реализован на предметной области «Туризм», что повлияло на
заполнение базы знаний, схему и заполнение реляционной базы
данных и различных словарей для семантико-синтаксического
анализа. Программная реализация была сделана с использованием
языка программирования Python и СУБД SQLite с применением
открытого морфологического анализатора Pymorphy и ряда
инструментов из библиотеки Natural Language Tooklit.
Список использованных источников
1. Фомичёв В.А. Формализация проектирования
лингвистических процессоров, – М .: МАКС Пресс, 2005 г. – 368 с.
2. Fomichov V.A. Semantics-Oriented Natural Language
Processing. Mathematical Models and Algorithms, Springer, 2010. – 354 p.
3. Kaufmann, E., Bernstein, A.: How useful are natural language
interfaces to the semantic web for casual end-users? In: Proceedings of the
Forth European Semantic Web Conference (ESWC 2007), Innsbruck,
Austria (June 2007)
4. A.-M. Popescu, O. Etziony, E. Kautz. Towards a theory of
natural-language interfaces to databases.
5. Popov, B., Kiryakov, A., Ognyano, D., Manov, D., Kirilov, A.,
Goranov, M.: Towards Semantic Web Information Extraction. In: Human
Language Technolo-gies Workshop at the 2nd International Semantic Web
Conference (ISWC2003),Florida, USA (2003)
6. Lei, Y., Uren, V., Motta, E.: Semsearch: a search engine for the
semantic web. In: Managing Knowledge in a World of Networks, Springer
Berlin / Heidelberg (2006) 238-245
7. Lopez, V., Motta, E.: Ontology driven question answering in
AquaLog. In: NLDB 2004 (9th International Conference on Applications of
Natural Language to Infor-mation Systems), Manchester, UK (2004)
8. Cimiano, P., Haase, P., Heizmann, J.: Porting natural language
interfaces between domains: an experimental user study with the orakel
system. In: IUI '07: Proceedings of the 12th international conference on
Intelligent user interfaces, New York, NY, USA, ACM (2007) 180-189
9. Shamima Mithun, Leila Kosseim, V.H.: Resolving quanti_er and
number restriction to question owl ontologies. In: Proceedings of The First
International Workshop on Question Answering (QA2007), Xian, China
(October 2007)
10. Kaufmann, E., Bernstein, A., Zumstein, R.: Querix: A natural
language interface to query ontologies based on clari_cation dialogs. In: 5th
International Semantic Web Conference (ISWC 2006), Springer (November
2006) 980-981
11. Как работает Pymorphy? [Электронный ресурс] – Режим
доступа: http://packages.python.org/pymorphy/algo.html
12. Steven Bird, Ewan Klein, and Edward Loper. Natural Language
Processing with Python. O’Reilly, 2009.
13. Mark Lutz. Learning Python, Fourth Edition. O’Reilly, 2009.
14. Venon L. Ceder. The Quick Python Book. Manning, 2010.
15. About python [Электронный ресурс] – Режим доступа:
http://python.org/about/
16. Beginning Python for Bioinformatics. [Электронный ресурс] –
Режим доступа:
http://onlamp.com/pub/a/python/2002/10/17/biopython.html
17. What is Python and why python? [Электронный ресурс] –
Режим доступа: http://pythoncard.sourceforge.net/what_is_python.html
18. Philipp Cimiano, Peter Haase, J¨org Heizmann, Matthias Mantel.
ORAKEL: A Portable Natural Language Interface to Knowledge Bases.
Karlsrue, 2007
19. Anboto group [Электронный ресурс] – Режим доступа:
http://en.wikipedia.org/wiki/Anboto_Group
20. BioNLP homepage [Электронный ресурс] – Режим доступа:
http://bionlp.sourceforge.net/
21. Жигалов В.А. Технология построения
естественно-языковых интерфейсов к структурированным источникам
данных: дис. … д-ра техн. наук : 07.00.02: защищена 22.01.02: утв.
15.07.02 / Белозеров И.В. – М., 2002. – 215 с.
22. Жигалов В.А., Соколова Е.Г. InBASE: технология
построения ЕЯ интерфейсов к базам данных // Труды Международного
семинара Диалог’2001 по компьютерной лингвистике, Том
2, Аксаково, Июнь 2000, с. 123-135.
Приложение 1. Исходный код программы
Файл PyMorphyInterface.py
#coding: utf-8
from pymorphy import get_morph
import logging
from consts import *
from collections import namedtuple
TaggedToken = namedtuple('TaggedToken',['base','graminfo'])
class PyMorphyInterface:
"""
class to get base form of a word and morphological info
from pymorpy
also formatting pymorphy output to a special format
"""
def __init__(self,
dictionaries_path=PYMORPHY_DICTIONARIES_PATH):
self.pymorphy = get_morph(dictionaries_path)
def tag_token(self, token):
"""
"""
if is_digit(token):
return TaggedToken(token,
PropertiesContainer(POS_TAGS.NUMBER))
base = self.pymorphy.normalize(token.upper())
graminfo = self.pymorphy.get_graminfo(token.upper())
#prints base and graminfo
logging.debug("======Prints base and props======")
logging.debug(list(base)[0])
for gitem in graminfo:
logging.debug("-------graminfo----------")
for k, v in gitem.items():
if k in ['class', 'info']:
logging.debug( k + ":" + unicode(v) )
#NOTE: only first graminfo is taken
#converted_graminfo =
self._convert_properties(graminfo[0], token)
#NOTE: only first baseform is taken [base.pop]
#return TaggedToken( base.pop(), converted_graminfo)
baseindex = 1
graminfoindex = 1
if len(base) > 1:
baseindex =
int(ask_clarification(CLARIFICATION_QUESTIONS.BASEFORM,
token, base))
if len(graminfo) > 1:
graminfoindex =
int(ask_clarification(CLARIFICATION_QUESTIONS.GRAMINFO,
token, map(lambda x: x['class'],graminfo)))
return TaggedToken(list(base)[baseindex -
1],self._convert_properties(graminfo[graminfoindex - 1], token))
def _convert_properties(self, pymorphy_graminfo, token):
"""
Разбивает массив полученных от pymorphy
морфологических признаков
и возвращает словарь с признаками для
русскоязычной МБД:
pymorphy_graminfo - массив, полученный с
помощью Pymorphy
token - слово, морфологические признаки
которого обрабатываются
"""
properties = PropertiesContainer()
cl = pymorphy_graminfo['class']
info = pymorphy_graminfo['info']
#print word, cl, info
#сущ
if cl == u'С':
properties.pos = POS_TAGS.NOUN
if token[0].isupper():
properties.spart = 2
else:
properties.spart = 1
self._fill_grcase(info, properties.grcase)
self._fill_number(info, properties.num)
self._fill_gen(info, properties.gen)
elif cl == u'Г':
properties.pos = POS_TAGS.VERB
if u'пвл' in info:
properties.spart = 7
else:
properties.spart = 6
self._fill_number(info, properties.num)
self._fill_gen(info, properties.gen)
#лицо
if u'1л' in info:
properties.face = 1
elif u'2л' in info:
properties.face = 2
elif u'3л' in info:
properties.face = 3
properties.tense = self._get_tense(info)
if u'св' in info:
properties.perf = 2
elif u'нсв' in info:
properties.perf = 1
properties.vc = self._get_voice(info)
if u'сь' in token[-2:] or u'ся' in token[-2:]:
properties.refl = 1
else:
properties.refl = 2
elif cl in [u'П', u'КР_ПРИЛ']:
if token in ADJECTIVES_TO_PREPOSITION:
properties.pos = POS_TAGS.PREPOSITION
return properties
properties.pos = POS_TAGS.ADJECTIVE
#c.spart = 'something'
self._fill_grcase(info, properties.grcase)
self._fill_number(info, properties.num)
self._fill_gen(info, properties.gen)
elif cl in [u'МС', u'МС-ПРЕДК', u'МС-П']:
properties.pos = POS_TAGS.PRONOUN
for a in self.pymorphy.normalize(token):
if a.lower() in [u'я', u'ты', u'он', u'она', u'оно', u'мы',
u'вы', u'они']:
properties.spart = 3
elif a.lower() in [u'кто', u'что', u'какой',
u'который']:
properties.spart = 4
elif a.lower() in [u'там', u'туда', u'оттуда', u'сюда',
u'здесь', u'так', u'тогда', u'потому',
u'поэтому', u'затем', u'всегда',
u'иногда', u'везде', u'всюду', u'повсюду', u'как',
u'где', u'куда', u'откуда',
u'когда', u'почему', u'зачем', u'отчего', u'как-то',
u'как-нибудь', u'кое-как',
u'где-то', u'где-либо', u'где-нибудь', u'кое-где',
u'когда-то', u'когда-нибудь',
u'когда-либо', u'зачем-то', u'почему-то', u'никак',
u'нигде', u'негде', u'ниоткуда',
u'неоткуда', u'никуда', u'некуда', u'никогда',
u'некогда', u'незачем']:
properties.spart = 5
self._fill_grcase(info, properties.grcase)
self._fill_number(info, properties.num)
self._fill_gen(info, properties.gen)
elif cl in [u'ПРИЧАСТИЕ', u'КР_ПРИЧАСТИЕ']:
properties.pos = POS_TAGS.PRICHASTIE
self._fill_number(info, properties.num)
self._fill_gen(info, properties.gen)
properties.tense = self._get_tense(info)
properties.vc = self._get_voice(info)
if properties.vc == VOICE.ACTIVE:
properties.spart = 9
else:
properties.spart = 10
elif cl == u'ДЕЕПРИЧАСТИЕ':
properties.pos = POS_TAGS.DEEPRICHASTIE
properties.tense = self._get_tense(info)
properties.vc = self._get_voice(info)
if properties.vc == VOICE.ACTIVE:
properties.spart = 9
else:
properties.spart = 10
elif cl == u'ИНФИНИТИВ':
properties.pos = POS_TAGS.VERB
properties.spart = 8
elif cl == u'ЧИСЛ':
if token.lower() == u'сколько':
properties.pos = POS_TAGS.PRONOUN
properties.spart = 4
else:
properties.pos =
POS_TAGS.COUNTABLE_NUMERAL
elif cl == u'ЧИСЛ-П':
properties.pos = POS_TAGS.ORDINAL_NUMERAL
elif cl == u'Н':
properties.pos = POS_TAGS.ADVERB
if u'вопр' in info:
#делаем местоимение из наречия :)
#c.pos = 5
properties.spart = 4
elif cl == u'ПРЕДК':
pass
elif cl == u'ПРЕДЛ':
properties.pos = POS_TAGS.PREPOSITION
elif cl == u'СОЮЗ':
properties.pos = POS_TAGS.CONJUNCTION
elif cl == u'МЕЖД':
properties.pos = POS_TAGS.INTERJECTION
elif cl == u'ЧАСТ':
if token == u'не':
properties.pos = POS_TAGS.NOT
elif cl == u'ВВОДН':
pass
return properties
def _fill_grcase(self, info, grcase):
if u'им' in info:
grcase.add(GRCASES.IMENITELNYI)
if u'рд' in info:
grcase.add(GRCASES.RODITELNYI)
if u'дт' in info:
grcase.add(GRCASES.DATELNYI)
if u'вн' in info:
grcase.add(GRCASES.VINITELNYI)
if u'тв' in info:
grcase.add(GRCASES.TVORITELNY)
if u'пр' in info:
grcase.add(GRCASES.PREDLOZHNYI)
def _fill_gen(self, info, gen):
if u'мр' in info:
gen.add(GEN.MALE)
if u'жр' in info:
gen.add(GEN.FEMALE)
if u'ср' in info:
gen.add(GEN.SREDNIY)
def _fill_number(self, info, num):
if u'ед' in info:
num.add(NUMBER.SINGULAR)
if u'мн' in info:
num.add(NUMBER.PLURAL)
def _get_tense(self, info):
tense = None
if u'нст' in info:
tense = TENSES.PRESENT
elif u'прш' in info:
tense = TENSES.PAST
elif u'буд' in info:
tense = TENSES.FUTURE
return tense
def _get_voice(self, info):
vc = None
if u'дст' in info:
vc = VOICE.ACTIVE
elif u'стр' in info:
vc = VOICE.PASSIVE
return vc
class PropertiesContainer:
def __init__(self, pos=None, spart=None, grcase=set(),
num=set(), gen=set(), face=None, tense=None, perf=None, vc=None,
refl=None):
self.pos=pos
self.spart=spart
self.grcase=grcase
self.num=num
self.gen=gen
self.face=face
self.tense=tense
self.perf=perf
self.vc=vc
self.refl=refl
def __str__(self):
return ''
Файл SemanticProcessor.py
#coding: utf-8
from Tokenizer import Tokenizer
from PyMorphyInterface import *
from Chunker import *
from EntitiesExtractor import *
from RelationshipsExtractor import *
import nltk
from collections import namedtuple
import logging
SimpleLeaf = namedtuple('SimpleLeaf',['token','pos','index'])
TaggedTokenWithSemanticEntity =
namedtuple('TaggedTokenWithSemanticEntity',['token','graminfo','sementi
ty'])
TaggedToken = namedtuple('TaggedToken',['token','graminfo'])
class SemanticProcessor:
def __init__(self, ldb):
"""
Конструктор класса:
"""
self._ldb = ldb
self.last_conditions = []
def process(self, text):
"""
processes given NL text
"""
self.leafindex = -1
# Step 1. Tokenize text
tokenizer = Tokenizer()
tokenized_text = tokenizer.tokenize(text)
#Step 0 Replace not + something for antonym
for token_index in range(len(tokenized_text)):
try:
if tokenized_text[token_index] ==
LOGICAL_OPERATORS.NOT.lower():
if
ANTONYMS_DICTIONARY.has_key(tokenized_text[token_index + 1]):
tokenized_text[token_index + 1] =
ANTONYMS_DICTIONARY[tokenized_text[token_index + 1]]
tokenized_text.pop(token_index)
except:
pass
# Step 2. Tag tokens
tagged_tokens = self.tag_tokens(tokenized_text)
# Step 3. Build syntactic tree
chunked_sentence =
self.build_syntactic_tree(tagged_tokens)
#nltk.download()
#print nltk.ne_chunk(chunked_sentence, binary=True)
#now we bind an entry from LSD to each token
entities_extractor = EntitiesExtractor(self._ldb)
tagged_tokens_with_semantic_entities = [
TaggedTokenWithSemanticEntity(
tagged_token.token,
tagged_token.graminfo,
entities_extractor.load_entities(tagged_token.token))
for tagged_token in tagged_tokens]
#t = self.create_tree(chunked_sentence, tagged_tokens,
tagged_tokens_with_semantic_entities)
#tre.draw()
tree = self.create_immutabletree(chunked_sentence,
tagged_tokens)
tree.draw()
entities_extractor.extract_entities1(tree,
tagged_tokens_with_semantic_entities)
#print entities_extractor.entities
relationshipsextractor = RelationshipsExtractor(self._ldb,
entities_extractor.entities, entities_extractor.prepositions)
default_entity = None
#check for elliptical phrase
non_elliptic_subtrees = [t for t in tree.subtrees(filter=lambda
x: x.node in ['NPP','VPP'])]
if not non_elliptic_subtrees:# and self.last_tree:
default_entity = NamedEntity(u'тур',[u'поездка'])
#for elliptic replacement tests
#last_conditions =
[('country','doo','tar'),('shmantry','foo','bar')]
#print default_entity
relationshipsextractor.extract_relationships(tree,
tagged_tokens_with_semantic_entities, default_entity)
skimmed_representations =
skim_semantic_representations(tree, relationshipsextractor.entities)
conditions =
self.extract_sql_parameters(skimmed_representations, self.last_conditions)
self.last_conditions = conditions
query = self.compose_query(conditions)
print query
results = self._ldb.dbcursor.execute(query).fetchall()
print '№ \t Страна \t Дата \t Длительность \t'
for i in results:
print '%s \t %s \t %s \t %s \t' % (i[0],i[2],i[3],i[4])
#
#function to implement later
#
def extract_information(self, entity, column_flags_values_dict,
operator_flags_values_dict, value_flags_functions_dict,
concept_separator='*', modifier_separator=','):
#
values_dict = {}
#
#go through each modifier
#
for modifier in entity.modifiers:
#
#check whether functional symbol is in column_flags
#
if modifier[0] in column_flags_values_dict.keys():
#
column_name = modifier[0] #column name
#
operator =
operator_flags_values_dict[modifier[0]] #operator
#
#split modifier value
#
for item in modifier[1].split(concept_separator):
#
splitted_modifier =
item.split(modifier_separator)
#
for value_flag in
value_flags_functions_dict.keys():
#
if value_flag in splitted_modifier[0]:
#
values_dict[value_flag] =
value_flags_functions_dict[value_flag](splitted_modifier[1]) # function
performed to extract information from modifier value
#
return (column_name, operator, values_dict)
def extract_sql_parameters(self, skimmed_representations,
last_conditions = []):
"""
Returns list of tuples (column_name, operator,
conditions)
which can be used to compose sql query
"""
#date parameters
datename = 'start_date'
dateflags = [u'Дата']
month = u'Месяц'
monthsdict = {
u'Январь':'01',
u'Февраль':'02'
}
day = u'число'
fetched_day = None
fetched_month = None
date_operator = '='
#price parameters
price_name = 'price'
priceflags = [u'Цена',u'Дешевле']
price = u'число'
currency = u'Валюта'
currencylist = [u'рубль',u'Доллар']
fetched_price = None
fetched_currency = None
price_operator = '='
#destination parameters
destination_names = ['continent','country']
notsign = '~'
destinationflags = [u'Конечный пункт']
name = u'Название'
countrylist = [u'Турция',u'Египет',u'Австралия']
continentlist = [u'Африка',u'Европа']
fetched_country = None
fetched_continent = None
destination_operator = '='
#duration parameters
duration_name = 'duration'
durationflags = [u'Длительность']
duration = u'число'
fetched_duration = ''
duration_operator = '='
#
#
#
test of general method for features' extraction
for entity in skimmed_representations:
info = self.extract_information(entity,
#
{u'Цена':'price', u'Валюта':'currency'},
#
{u'Цена':'=', '~': '<>',u'Валюта':'='},
#
{u'число': str2int ,u'Валюта': lambda x:
get_elements_that_are_matched_in_string([u'рубль',u'Доллар'],x)}
#
)
#
print info
#
for i in info[2]:
#
print i, info[2][i]
#
#
info = self.extract_information(entity,
#
{u'Конечный пункт':'continent'},
#
{u'Конечный пункт':'=', '~': '<>'},
#
{u'Название': lambda x:
get_elements_that_are_matched_in_string([u'Африка',u'Европа'],x)}
#
)
#
print info
#
for i in info[2]:
#
print i, info[2][i]
for entity in skimmed_representations:
for modifier in entity.modifiers:
if modifier[0] in dateflags:
for item in modifier[1].split('*'):
splitted_item = item.split(',')
if day in splitted_item[0]:
fetched_day = str2int(splitted_item[1])
elif month in splitted_item[0]:
for m in monthsdict.keys():
if m in splitted_item[1]:
fetched_month =
monthsdict[m]
elif modifier[0] in priceflags:
if priceflags.index(modifier[0]) == 1:
price_operator = '<='
for item in modifier[1].split('*'):
splitted_item = item.split(',')
if currency in splitted_item[0]:
for c in currencylist:
if c in splitted_item[1]:
fetched_currency =
currencylist.index(c) + 1
elif price in splitted_item[0]:
fetched_price = str2int(splitted_item[1])
elif modifier[0] in destinationflags:
if notsign in modifier[1]:
destination_operator = '<>'
for item in modifier[1].split('*'):
splitted_item = item.split(',')
if name in splitted_item[0]:
for c in countrylist:
#print c, splitted_item[1], c in
splitted_item[1]
if c in splitted_item[1]:
fetched_country = c
for c in continentlist:
if c in splitted_item[1]:
fetched_continent = c
elif modifier[0] in durationflags:
for item in modifier[1].split('*'):
splitted_item = item.split(',')
if duration in splitted_item[0]:
fetched_duration =
int(remove_non_alphanumeric(splitted_item[1]))
print fetched_day, fetched_month
print fetched_price, fetched_currency
print fetched_continent, fetched_country
print fetched_duration
#print last_conditions
conditions = filter(lambda x: not(
(x[0] == price_name and fetched_price)
or (x[0] == datename and (fetched_day or fetched_month))
or (x[0] in destination_names and (fetched_continent or
fetched_country))
or (x[0] == duration_name and fetched_duration))
, last_conditions)
#print conditions
if fetched_price:
conditions.append((price_name, price_operator,
str(fetched_price)))
datevalue = ''
if fetched_day and fetched_month:
datename = "strftime('%d.%m', start_date)"
datevalue = br(str(fetched_day) + '.' + fetched_month)
elif fetched_day:
datename = "strftime('%d', start_date)"
datevalue = br(str(fetched_day))
elif fetched_month:
datename = "strftime('%m', start_date)"
datevalue = br(fetched_month)
if fetched_day or fetched_month:
conditions.append((datename, date_operator,
datevalue))
destination_name = ''
destination_value = ''
if fetched_continent:
destination_name = destination_names[0]
destination_value = br(fetched_continent)
elif fetched_country:
destination_name = destination_names[1]
destination_value = br(fetched_country)
if fetched_continent or fetched_country:
conditions.append((destination_name,
destination_operator, destination_value))
if fetched_duration:
conditions.append((duration_name, duration_operator,
str(fetched_duration)))
print conditions
return conditions
#def _clear_conditions(self, conditions, filter_value):
#
return filter(lambda x: , conditions)
def compose_query(self, conditions):
query = "select * from tours"
if conditions:
query += ' where '
query += ' and '.join(map(lambda x: x[0] + x[1] + x[2],
conditions))
return query
def create_immutabletree(self, tree, tagged_tokens):
"""
creates a tree that can be used as a key in dict
"""
try:
#t.node contains node label ( S, NP , VP, etc...)
tree.node
except AttributeError:
#that exception means 't' is tuple representing a leaf
self.leafindex += 1
return SimpleLeaf(tagged_tokens[tree[0]][0],
tagged_tokens[tree[0]][1].pos,
self.leafindex)
else:
# Now we know that t.node is defined
return nltk.ImmutableTree(tree.node,
[self.create_immutabletree(child, tagged_tokens) for child in tree])
def create_tree(self, tree, tagged_tokens,
tagged_tokens_with_semantic_entities):
try:
#t.node contains node label ( S, NP , VP, etc...)
tree.node
except AttributeError:
#that exception means 't' is tuple representing a leaf
return (tagged_tokens[tree[0]][0],
tagged_tokens[tree[0]][1],
tagged_tokens_with_semantic_entities[tree[0]])
else:
# Now we know that t.node is defined
return nltk.Tree(tree.node, [self.create_tree(child,
tagged_tokens, tagged_tokens_with_semantic_entities) for child in tree])
def tag_tokens(self, tokens):
tagged_tokens = []
pymorphyi = PyMorphyInterface()
for token in tokens:
# если разделитель
if token in '.,:;][}{()<>*&^%$#@!\|/?':
tagged_tokens.append(TaggedToken(token,PropertiesContainer(pos=POS_
TAGS.DELIMETER)))
continue
#
elif token[0] == '@':
#
rm.append(MorphologicalRepresentationEntry(token[1:], [{'part': 12}]))
#
continue
tagged_token = pymorphyi.tag_token(token)
if not len(tagged_token.base):
tagged_tokens.append(TaggedToken(token,None))
else:
tagged_tokens.append(TaggedToken(tagged_token.base,tagged_token.gra
minfo))
#log tagged_tokens
logging.debug("======POS tagging result======:")
for token in tagged_tokens:
logging.debug("%s:%s" % (token.token,
token.graminfo))
#end of log block
return tagged_tokens
def build_syntactic_tree(self, tagged_tokens):
sentence = []
for token_index in range(len(tagged_tokens)):
sentence.append( (token_index,
tagged_tokens[token_index].graminfo.pos))
#grammar = "NP: {<ADJ>*<N>}"
#NP: Chunk sequences of JJ, NN
#PP: Chunk prepositions followed by NP
#VP: Chunk verbs and their arguments
#CLAUSE: Chunk NP, VP
grammar = r"""
NP: {<NOT>?<ADJ|NB>*<N|CNJ>+}
PP: {<NOT>?<P><NP>}
NPP: {<NP><PP|NP>+}
VP: {<ADV>*<V><ADV>*}
VPP: {<VP><NP|PP|CLAUSE>+}
CLAUSE: {<NP|NPP><VPP>}
"""
chunker = Chunker(grammar)
chunked_sentence = chunker.chunk_sentence(sentence)
#
test_sentence1 =
[('tabletka','N'),('NA','P'),('TARELKE','N')]
#
chunker.chunk_sentence(test_sentence1)
#
#
test_sentence2 =
[('tabletka','N'),('LEZHIT','V'),('NA','P'),('TARELKE','N')]
#
chunker.chunk_sentence(test_sentence2)
return chunked_sentence
Файл EntitiesExtractor.py
#coding: utf-8
import logging
from consts import *
class EntitiesExtractor:
def __init__(self, ldb):
self.ldb = ldb
self.entities = {}
self.prepositions = {}
def load_entities(self, token):
logging.debug('fetching entity for token: ' + token)
loaded_entities = []
#artifically create semantic entity for a number of a form
(Число, 30000)
if is_digit(token):
return LoadedSemanticEntity(token,'(%s,%s)' %
(SEMANTIC_ENTITIES.NUMBER, str(token)), 'натчисло',None, None,
None, None)
query_result = self.ldb.query_ldb(TABLE_NAMES.LSD,
{'word': token.lower()})
for entity in query_result:
loaded_entity = LoadedSemanticEntity(
token,
entity[3],
entity[4],
entity[5],
entity[6],
entity[7],
entity[8])
loaded_entities.append(loaded_entity)
#log fetched entity
logging.debug('-------entity fetched!!!--------')
logging.debug(entity)
#NOTE: only first semantic entity is taken
if loaded_entities:
return loaded_entities[0]
else:
return loaded_entities
def attach_modifiers_to_concept(self, tree,
tagged_tokens_with_semantic_entities, concept_pos, modifier_pos):
#code below attaches modifiers to concept
nentity = None
modifiers = []
concept = ''
for child in tree:
#print 'child',child
pos = child.pos
sementity =
tagged_tokens_with_semantic_entities[child.index].sementity
if pos in modifier_pos:
modifiers.append(separate_modifier(sementity.semsit))
elif pos is concept_pos:
parts = separate_concept(sementity.semsit)
concept += parts[0]
if len(parts) > 1:
modifiers.extend(parts[1])
nentity = sementity
#
elif pos == POS_TAGS.CONJUNCTION:
#
conjunction = ''
#
if child.token == LOGICAL_OPERATORS.AND:
#
#
#
#
conjunction = 'AND'
elif child.token == LOGICAL_OPERATORS.OR:
conjunction = 'OR'
concept += conjunction
#print concept_pos, concept
return NamedEntity(concept, nentity.get_sortlist(),
modifiers)
def extract_entities1(self, tree,
tagged_tokens_with_semantic_entities):
"""
Goes through the tree and finds named entities
"""
try:
#t.node contains node label ( S, NP , VP, etc...)
tree.node
except AttributeError:
#that exception means 't' is tuple representing a leaf
pass
else:
#bottom-up approach
for child in tree:
self.extract_entities1(child,
tagged_tokens_with_semantic_entities)
# Now we know that t.node is defined
if tree.node == 'NP':
self.entities[tree] =
self.attach_modifiers_to_concept(tree,
tagged_tokens_with_semantic_entities, POS_TAGS.NOUN,
[POS_TAGS.ADJECTIVE, POS_TAGS.NUMBER])
elif tree.node == 'PP':
self.prepositions[tree] = 'N'
notsign = ''
for child in tree:
if child.pos is POS_TAGS.NOT:
notsign = '~'
elif child.pos is POS_TAGS.PREPOSITION:
self.prepositions[tree] = child.token.lower()
elif child.node == 'NP':
self.entities[tree] = self.entities[child]
self.entities.pop(child)
if notsign:
self.entities[tree].concept = notsign +
self.entities[tree].concept
elif tree.node == 'VP':
self.entities[tree] =
self.attach_modifiers_to_concept(tree,
tagged_tokens_with_semantic_entities, POS_TAGS.VERB,
[POS_TAGS.ADVERB])
class LoadedSemanticEntity:
def __init__(self, token, semsit,st1,st2,st3,st4,comment):
self.token = token
self.semsit = semsit
self.st1 = st1
self.st2 = st2
self.st3 = st3
self.st4 = st4
self.comment = comment
def get_sortlist(self):
"""
returns list containing sorts for an entity
"""
result = []
if self.st1:
result.append(self.st1)
if self.st2:
result.append(self.st2)
if self.st3:
result.append(self.st3)
if self.st4:
result.append(self.st4)
return result
def build_entity(self):
return NamedEntity(self.semsit,self.get_sortlist())
class NamedEntity:
def __init__(self, concept, sorts, modifiers=None):
self.concept = concept
if modifiers:
self.modifiers = modifiers
else:
self.modifiers = []
self.sorts = sorts
print self
def add_modifier(self, modifier):
self.modifiers.append(modifier)
print self
return self
def get_sorts(self):
return self.sorts
def __str__(self):
print self.concept + '*' + '*'.join(map(lambda x:'(%s,%s)' %
(x[0],x[1]), self.modifiers))
return ''
def get_semantic_representation(self):
return self.concept + '*' + '*'.join(map(lambda x:'(%s,%s)' %
(x[0],x[1]), self.modifiers))
class ConjunctedNamedEntity(NamedEntity):
def __init__(self, nentity1, nentity2, conjunction):
self.conjunction = conjunction
self.nentity1 = nentity1
self.nentity2 = nentity2
self.concept = 'conjuncted entity'
self.sorts = nentity1.sorts
def get_semantic_representation(self):
return
NamedEntity.get_semantic_representation(self.nentity1) + self.conjunction
+ NamedEntity.get_semantic_representation(self.nentity2)
Файл RelationshipsExtractor.py
#coding: utf-8
import logging
from consts import *
import itertools
class RelationshipsExtractor:
def __init__(self, ldb, entities, prepositions):
self.ldb = ldb
self.entities = entities
self.prepositions = prepositions
def load_noun_noun_relationship(self, first_sort, second_sort,
preposition):
result = []
logging.debug('fetching relationships for sort1: ' + first_sort
+ ' sort2: ' + second_sort + ' preposition: ' + preposition)
query_result = self.ldb.query_ldb(TABLE_NAMES.DPF,
{DPF_FIELDS_NAMES.FIRST_SORT: first_sort,
DPF_FIELDS_NAMES.SECOND_SORT: second_sort,
DPF_FIELDS_NAMES.PREPOSITION: preposition})
for s in query_result:
logging.debug('-------relationship fetched!!!--------')
logging.debug(s[5])
result.append(PrepositionalFrame(s[1],s[2],s[3],s[4],s[5],s[6]))
return result
def load_verb_noun_relationship(self, semantic_entity,
preposition, sort):
result = []
logging.debug('fetching relationships for semantic entity: ' +
semantic_entity + ' sort: ' + sort + ' preposition: ' + preposition)
query_result = self.ldb.query_ldb(TABLE_NAMES.DVPF,
{DVPF_FIELDS_NAMES.SEMANTIC_ENTITY: semantic_entity,
DVPF_FIELDS_NAMES.SORT: sort,
DVPF_FIELDS_NAMES.PREPOSITION: preposition})
for s in query_result:
logging.debug('-------relationship fetched!!!--------')
logging.debug(s[8])
result.append(VerbalPrepositionalFrame(s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[
8],s[9]))
return result
def extract_relationships(self, tree,
tagged_tokens_with_semantic_entities, default_entity=None, bottomup =
True):
"""
Extracts relationships from given sentence
"""
try:
#t.node contains node label ( S, NP , VP, etc...)
tree.node
except AttributeError:
#that exception means 't' is tuple representing a leaf
pass
else:
#bottom-up approach
if bottomup:
for child in tree:
self.extract_relationships(child,
tagged_tokens_with_semantic_entities, default_entity, bottomup)
# Now we know that t.node is defined
if tree.node == 'NPP':
#code below attaches adjectives to noun
primary_nentity = None
for child in tree:
try:
child.node
except AttributeError:
pass
else:
if child.node == 'NP' and not
primary_nentity:
primary_nentity = self.entities[child]
elif child.node == 'PP' or primary_nentity:
#now take all possible pairs of sorts
relationships = []
second_nentity = self.entities[child]
preposition = self.prepositions[child]
for pair in
itertools.product(primary_nentity.sorts, second_nentity.sorts):
relationship =
self.load_noun_noun_relationship(pair[0], pair[1], preposition)
if relationship:
relationships.extend(relationship)
relationshipindex = 1
if len(relationships) > 1:
relationshipindex =
ask_clarification(CLARIFICATION_QUESTIONS.NOUN_NOUN_REL
ATIONSHIP,
primary_nentity.concept + ' ' +
preposition + ' ' +second_nentity.concept,
map(lambda x: x.relation + ' ' +
x.ex, relationships))
#print "%s(%s,%s)" % (
#first_nentity.concept,
relationships[relationshipindex - 1].relation, second_nentity.concept)
self.entities[tree] =
primary_nentity.add_modifier([relationships[relationshipindex - 1].relation,
second_nentity.get_semantic_representation()])
if tree.node == 'VPP':
for child in tree:
try:
child.node
except AttributeError:
pass
else:
action_entity = None
if child.node == 'VP':
action_entity = self.entities[child]
elif child.node in ['PP', 'NP']:
relationships = []
nentity = self.entities[child]
preposition = self.prepositions[child]
#print nentity.concept, preposition
for sort in nentity.sorts:
relationship_list =
self.load_verb_noun_relationship( action_entity.concept.split('*')[0],
preposition, sort)
if relationship_list:
relationships.extend(relationship_list)
relationshipindex = 1
if len(relationships) > 1:
relationshipindex =
(CLARIFICATION_QUESTIONS.VERB_NOUN_RELATIONSHIP,
action_entity.concept + ' ' + preposition + ' ' + nentity.concept,
map(lambda x:
x.trole + ' ' + x.expl, relationships))
self.entities[tree] =
action_entity.add_modifier([relationships[relationshipindex - 1].trole,
nentity.get_semantic_representation()])
if tree.node == 'CLAUSE':
preposition = 'N'
relationships = []
nentity = self.entities[tree[0]]
action_entity = self.entities[tree[1]]
if tree[0] in self.prepositions:
preposition = self.prepositions[tree[0]]
for sort in nentity.sorts:
relationship =
self.load_verb_noun_relationship( action_entity.concept.split('*')[0],
preposition, sort)
if relationship:
relationships.extend(relationship)
self.entities[tree] =
action_entity.add_modifier([relationships[0].trole,
nentity.get_semantic_representation()])
#print action_entity.concept + '(' +
relationships[0].trole + ', ' + nentity.concept + ')'
if default_entity and tree.node == 'PP':
#code below attaches adjectives to noun
primary_nentity = default_entity
relationships = []
second_nentity = self.entities[tree]
preposition = self.prepositions[tree]
for pair in itertools.product(primary_nentity.sorts,
second_nentity.sorts):
relationship =
self.load_noun_noun_relationship(pair[0], pair[1], preposition)
if relationship:
relationships.extend(relationship)
relationshipindex = 1
if len(relationships) > 1:
relationshipindex =
ask_clarification(CLARIFICATION_QUESTIONS.NOUN_NOUN_REL
ATIONSHIP,
primary_nentity.concept + ' ' + preposition +
' ' +second_nentity.concept,
map(lambda x: x.relation + ' ' + x.ex,
relationships))
#print "%s(%s,%s)" % (
#first_nentity.concept,
relationships[relationshipindex - 1].relation, second_nentity.concept)
self.entities[tree] =
primary_nentity.add_modifier([relationships[relationshipindex - 1].relation,
second_nentity.get_semantic_representation()])
#up-bottom approach
if not bottomup:
for child in tree:
self.extract_relationships(child,
tagged_tokens_with_semantic_entities, default_entity, bottomup)
class VerbalPrepositionalFrame:
def __init__(self, semsit, fm, refl, vc, sprep, grc, str, trole, expl):
self.semsit = semsit
self.fm = fm
self.refl = refl
self.vc = vc
self.sprep = sprep
self.grc = grc
self.str = str
self.trole = trole
self.expl = expl
class PrepositionalFrame:
def __init__(self, prep,sr1,sr2,grc,relation,ex):
self.prep = prep
self.sr1 = sr1
self.sr2 = sr2
self.grc = grc
self.relation = relation
self.ex = ex
Файл Tokenizer.py
#coding: utf-8
from nltk.tokenize import *
import logging
class Tokenizer:
"""
class to tokenize sentence
sentence - string
tokenization result """
def __init__(self):
#self._splitted_text = nltk.word_tokenize(self._text)
#pattern to correctly chunk numbers
#pattern = r'\token+|\$\d+\.\d+|[^\token\s]+'
#tokenizer = RegexpTokenizer("[\w']+")
self.tokenizer = WordPunctTokenizer()
def tokenize(self, sentence):
tokenized_sentence = self.tokenizer.tokenize(sentence)
#print tokenization result
logging.debug("======tokenization result======:")
for t in tokenized_sentence:
logging.debug(t)
return tokenized_sentence
Файл LinguisticDatabase.py
#! /usr/bin/python
#coding: utf-8
import sqlite3
#import apsw
import xml.etree.cElementTree as ET
def execute_query(dbcursor, table_name, conditions = None,
list_conditions = None):
query = "select * from " + table_name
if conditions:
query += ' where '
query += ' and '.join(map(lambda x: x[0] + "='" + x[1] + "'",
conditions.iteritems()))
if list_conditions:
if conditions:
query += ' and '
else:
query += ' where '
query += ' and '.join(
map(lambda x: x[0] + ' in (' + ' ,'.join(
map(lambda y: "'" + y + "'", x[1])) + ')',
list_conditions.iteritems()))
results = dbcursor.execute(query).fetchall()
return results
#a = {'a':'b', 'c':'d'}
#b = {'e': ['f','g'], 'h': ['i']}
#
#execute_query(None,'alpha',a)
#execute_query(None,'alpha',list_conditions = b)
#execute_query(None,'alpha',a,b)
class LinguisticDatabase:
def __init__(self, dbpath):
self.dbconnection = sqlite3.connect(dbpath)
#
###
#
### Backup to memory
#
###
#
#
# We will copy the disk database into a memory database
#
#
memcon=apsw.Connection(":memory:")
#
#
# Copy into memory
#
with memcon.backup("main", self.dbconnection, "main")
as backup:
#
backup.step() # copy whole database in one go
self.dbcursor = self.dbconnection.cursor()
#B = Basis()
#tcolor = TSort(B, 'цвет')
#phys_object = TSort(B, 'физ.об.')
#green = InformationUnit(B, 'зеленый', tcolor)
#fcolor = Function(B,'цвет', phys_object, tcolor)
#KEquals(KFunction(Function,None))
#self.lsd = {
#
'зеленый': KFunction('Цвет',None,'Зеленый')
#
#}
#self.dpf = PrepositionalFramesDictionary(dbpath)
#self.dvpf = VerbPrepositionalFramesDictionary(dbpath)
#self.ss = SortsSystem(dbpath)
#self.rqs = Rqs(dbpath)
def query_ldb(self, table_name, conditions=None):
return execute_query(self.dbcursor, table_name, conditions)
class LexicoSemanticDictionary:
"""Lexico-semantic dictionary"""
def __init__(self, dbpath):
self.con = sqlite3.connect(dbpath)
self.cursor = self.con.cursor()
def insert_entry(self, word, part, sem, sorts, comment):
"""
Добавляет новую запись в лексико-семантический
словарь.
"""
query = "insert into lsd VALUES(NULL, '" + word + "', '" +
part + "', '" + sem + "', '"
for i in sorts:
query += i + "', '"
query += comment + "')"
self.cursor.execute(query)
self.con.commit()
return 'Запись добавлена в словарь!'
def get_entries(self, parameters):
return execute_query(self.cursor, 'lsd', parameters)
def get_sems_by_word(self, word, part=None, semrepr=None,
sorts=None):
"""
Возвращает все записи из
семантико-синтаксического словаря,
соответствующие указанным параметрам поиска.
"""
query = "select * from lsd where word='" + word.lower() +
"'"
self.cursor.execute(query)
results = []
for i in self.cursor:
results.append(i)
return results
class SortsSystem:
def __init__(self, dbpath, xmlpath):
self.con = sqlite3.connect(dbpath)
self.cursor = self.con.cursor()
self.xmltree = ET.parse(xmlpath)
self.doc = self.xmltree.getroot()
def is_generalization(self, sort1, sort2):
print self.doc.find('object')
def is_concretisation(self, sort1, sort2):
"""
Checks whether sort2 is a child of sort1
"""
if sort1 == sort2: return True
sequence = []
cur = sort1
do = True
while do:
query = "select * from sort_system where sort='" + cur +
"'"
self.cursor.execute(query)
for i in self.cursor:
sequence.append(cur)
if i[1] is None:
do = False
break
cur = i[1]
break
else: break
if cur is None: break
if sort2 in sequence:
return True
return False
class PrepositionalFramesDictionary:
"""Dictionary of prepositional semantic-syntactic frames"""
def __init__(self, dbpath):
self.con = sqlite3.connect(dbpath)
self.cursor = self.con.cursor()
def insert_frame(self, prep, fsort, ssort, grcase, rel, expl):
"""Inserts new frame into the dictionary"""
query = "insert into PrepositionalFramesDictionary
VALUES(NULL, '" + prep + "', '" + fsort
query += "', '" + ssort + "', " + grcase + ", '" + rel + "', '"
query += expl + "')"
self.cursor.execute(query)
self.con.commit()
return u'Запись добавлена в словарь!'
class VerbPrepositionalFramesDictionary:
"""Dictionary of verbal-prepositional semantic-syntactic
frames"""
def __init__(self, dbpath):
self.con = sqlite3.connect(dbpath)
self.cursor = self.con.cursor()
def insert_frame(self, semsit, form, reflexivity, voice, prep,
grcase, sort, thematic_role, expl):
"""Inserts new frame into the dictionary"""
query = "insert into VerbarlPrepositionalFramesDictionary
VALUES(NULL, '" + semsit + "', '" + form
query += "', '" + reflexivity + "', '" + voice + "', '" + prep + "',
'"
query += grcase + "', '" + sort + "', '" + thematic_role + "', '" +
expl + "')"
self.cursor.execute(query)
self.con.commit()
return u'Запись добавлена в словарь!'
def get_frames_by_semsit(self, semsit):
"""
Возвращает все записи из словаря предложных
фреймов,
соответствующие указанным параметрам поиска.
"""
query = "select * from dvpf where semsit='" + semsit + "'"
self.cursor.execute(query)
results = []
for i in self.cursor:
results.append(i)
return results
def get_all_frames(self):
"Возвращает все записи словаря глагольно-предложных
семантико-синтаксических фреймов"
query = "select * from dvpf"
self.cursor.execute(query)
results = []
for i in self.cursor:
results.append(i)
return results
class Rqs:
"""Система вопросительных словосочетаний"""
def __init__(self, dbpath):
self.con = sqlite3.connect(dbpath)
self.cursor = self.con.cursor()
def insert_frame(self, prep, qw, relq):
"""Inserts new frame into the dictionary"""
query = "insert into Rqs VALUES(NULL, '" + prep + "', '" +
qw + "', '" + relq + "')"
self.cursor.execute(query)
self.con.commit()
return 'Запись добавлена в словарь!'
def get_frames(self, qw, prep=None):
"""Возвращает все фреймы, содержащие на указанных
параметров"""
if prep is None:
query = "select * from dpf where prep=NULL"
else:
query = "select * from dpf where "
query += "prep ='" + prep + "' AND qw='" + qw + "'"
self.cursor.execute(query)
results = []
for i in self.cursor:
results.append(i)
return results
def get_array(self):
"Возвращает все записи словаря"
query = "select * from Rqs"
self.cursor.execute(query)
results = []
for i in self.cursor:
results.append({'prep': i[1], 'qswd': i[2], 'relq': i[3]})
return results
####
####VERY USEFUL CODE: AN EXPERIMENTATION TO PUT
ALGORITHM ON PYKLIB
####
#from PyKlib.kbasic import *
##initialize basis
#B = Basis()
#ref = InformationUnit(B, 'некоторый', B.int1)
#def _ref(concept):
#
return KIntentionalQuantifier(ref, concept)
##initialize sorts
#sdate = TSort(B, 'зндаты')
#sprice = TSort(B,'знцены')
#physob = TSort(B, 'физоб')
#stimed = TSort(B,'времоб') #объект существующий в
промежутке времени
#sgeoobj = TSort(B,'геогроб') #объект на карте мира
#spricedobj = TSort(B, 'ценоб') #объект, который можно купить
#sdestinobj = TSort(B, 'обсконпун') #объект с конечным пунктом
#snamedobj = TSort(B, 'именоб') #объект с именем
#sname = TSort(B, 'знимени') #значение имени
#
#scurrency = TSort(B, 'знвалюты')
#snumber = TSort(B, 'натчисло')
##initialize inforamtionUnits
#
#africa = InformationUnit(B,'Африка',TConcept(sgeoobj))
#ruble = InformationUnit(B,'Рубль', scurrency)
#summa = InformationUnit(B,'сумма', TConcept(sprice))
#number = InformationUnit(B,'30000', snumber)
#trip = InformationUnit(B, 'поездка',
TConcept(TElementary([stimed,sgeoobj,sprice])))
#continent = InformationUnit(B, 'континент',
TConcept(TElementary([sgeoobj, snamedobj])))
#country = InformationUnit(B, 'страна',
TConcept(TElementary([sgeoobj, snamedobj])))
#
#fcurrency = Function(B,'Валюта',[sprice], [scurrency])
#fdate = Function(B,'Дата', [stimed], [sdate])
#fdestination = Function(B,'Конечный пункт', [sdestinobj],
[sgeoobj])
#fprice = Function(B, 'Цена', [spricedobj], [sprice])
#fnumber = Function(B, 'Число',[sprice],[snumber])
#fname = Function(B, 'Название', [snamedobj],[sname])
#
#ksumma = KConcept(summa,{})
#ktrip = KConcept(trip,{})
#kafrica = _ref(KConcept(continent,{fname:africa}))
#
#print ksumma, ksumma.type, ktrip.type
#
#LSD = {
#
'тур':ktrip,
#
'Африка': kafrica
#}
Файл consts.py
#coding: utf-8
import re
SR2SQLMAPPINGS = {
u'Конечный пункт':'destination',
u'Дата': 'date'
}
ADJECTIVES_TO_PREPOSITION = [
u'дороже',
u'дешевле'
]
ANTONYMS_DICTIONARY={
u'дороже': u'дешевле',
u'дешевле': u'дороже',
u'больше': u'меньше',
u'меньше': u'больше'
}
def remove_non_alphanumeric(text):
"""
Returns string with only alphanumeric characters remained
Keyword arguments:
text -- string which would be processed
"""
pattern = re.compile('[\W_]+')
return pattern.sub('', text)
def skim_semantic_representations(tree, entities):
"""
Returns top-level semantic representations from a tree
"""
fetched_entities = []
try:
fetched_entities.append(entities[tree])
except KeyError:
for child in tree:
fetched_entities.extend(skim_semantic_representations(child, entities))
return fetched_entities
def ask_clarification(question, question_argument=None, options =
None ):
"""
Promts the user to select one of several options
Keyword arguments:
question -- question that is above options to select
question_argument -- argument that can be put in the end of
question
options -- list of options
"""
if question_argument:
question += question_argument + '?'
print question
print
'================================================='
index = 1
for option in options:
print '\t' + str(index) + ': ', option
index += 1
input_text = raw_input('Введите номер соответствующего
элемента: ')
return input_text
def is_digit(string):
try:
int(string)
return True
except ValueError:
return False
def br(string):
return "'" + string + "'"
def get_elements_that_are_matched_in_string(elementslist,
search_body_string):
return [element for element in elementslist if element in
search_body_string]
#above function test
#print get_elements_that_are_matched_in_string(['ab','cd'],'fabcdx')
def str2int(string):
return int(remove_non_alphanumeric(string))
def separate_concept( semantic_representation):
modifiers = []
parts = semantic_representation.split('*')
for p in parts[1:]:
modifiers.append(separate_modifier(p))
return parts[0], modifiers
def separate_modifier( semantic_representation):
parts = map(lambda x:
x.lstrip('(').rstrip(')'),semantic_representation.split(','))
return parts
class SEMANTIC_ENTITIES:
DAY = u'день'
NUMBER = u'число'
class CLARIFICATION_QUESTIONS:
BASEFORM = u'Какая базовая форма соответствует слову '
GRAMINFO = u'К какой части речи относится слово '
NOUN_NOUN_RELATIONSHIP = u'Какое из
семантических отношений реализуется в сочетании: '
VERB_NOUN_RELATIONSHIP = u'Какое из семантических
отношений реализуется в сочетании: '
#SETTINGS PARAMETER
PYMORPHY_DICTIONARIES_PATH =
"""/home/love/Desktop/sema/data/dics/"""
class POS_TAGS:
ADJECTIVE = 'ADJ'
ADVERB = 'ADV'
CONJUNCTION = 'CNJ'
COUNTABLE_NUMERAL = 'NC'
DEEPRICHASTIE = 'DP'
#FOR COMMAS, QUESTION MARKS, ETC
DELIMETER = 'DEL'
INTERJECTION = 'UH'
NOUN = 'N'
ORDINAL_NUMERAL = 'NO'
PREPOSITION ='P'
PRICHASTIE = 'PR'
PRONOUN = 'PRO'
PROPER_NOUN = 'NP'
VERB = 'V'
NUMBER = 'NB'
NOT = 'NOT'
class TENSES:
PRESENT = 'PRS'
PAST = 'PST'
FUTURE = 'FT'
class GRCASES:
IMENITELNYI = 'NOM'
RODITELNYI = 'GEN'
DATELNYI = 'DAT'
VINITELNYI = 'ACC'
TVORITELNY = 'INS'
PREDLOZHNYI = 'LOC'
class NUMBER:
SINGULAR = 'SG'
PLURAL = 'PL'
class GEN:
MALE = 'M'
FEMALE = 'F'
SREDNIY = 'N'
class VOICE:
ACTIVE = 'ACT'
PASSIVE = 'PASS'
class TABLE_NAMES:
LSD = 'lsd'
DPF = 'dpf'
DVPF = 'Dvpf'
class DPF_FIELDS_NAMES:
PREPOSITION = 'prep'
FIRST_SORT = 'sort1'
SECOND_SORT = 'sort2'
GRAMMAR_CASE = 'grcase'
class DVPF_FIELDS_NAMES:
PREPOSITION = 'preposition'
SORT = 'str'
SEMANTIC_ENTITY = 'semsit'
GRAMMAR_CASE = 'grcase'
class LOGICAL_OPERATORS:
AND = u'И'
OR = u'ИЛИ'
NOT = u'НЕ'
Файл Chunker.py
#coding: utf-8
import nltk
import logging
class Chunker:
"""
Used to chunk tagged tokens to obtain named entities
to build semantic relationships
"""
def __init__(self, grammar):
self.parser = nltk.RegexpParser(grammar)
def chunk_sentence(self, sentence):
#log chunker input
logging.debug('------------========CHUNKER
INPUT=======--------------')
for item in sentence:
logging.debug(item)
result = self.parser.parse(sentence)
#result.draw()
#log chunker result
logging.debug('------------========CHUNKER
OUTPUT=======--------------')
logging.debug(traverse(result))
return result
def traverse(t):
try:
t.node
except AttributeError:
#that exception means 't' is tuple representing a leaf
print t,
else:
# Now we know that t.node is defined
print '(', t.node,
for child in t:
traverse(child)
print ')',
Download