СУБД Neo4j — это NoSQL база данных, ориентированная на хранение графов. Изюминкой продукта является декларативный язык запросов Cypher.

Cypher позаимствовал ключевые слова типа WHERE, ORDER BY из SQL; синтаксис из таких разных языков как Python, Haskell, SPARQL; и в результате появился язык, позволяющий делать запросы к графам в визуальной форме наподобие ASCII art. Например, заголовок данной статьи я бы представил в виде графа (Neo4j) — [изучаем] -> (Wordnet). И это почти готовый запрос к базе данных!



Для изучения граф-ориентированной базы данных нужен какой-нибудь граф. Это может быть социальная сеть, дамп википедии или схема железных дорог. Мы пойдём простым путём и воспользуемся огромным общедоступным графом лексической базы Wordnet. Лингвисты из Принстона проделали гигантскую работу по систематизации словарного запаса английского языка, а энтузиасты перевели базу данных на многие языки, включая русский. Например, в этой базе свыше 80 тысяч существительных, связанных между собой лексическими отношениями, такими как «синоним», «часть большего», «материал для» и т. п. Эта база является естественным графом, и мы её импортируем в Neo4j.

Установка Neo4j


Процесс установки для разных ОС описан на сайте. Всё описываемое здесь ПО платформенно-независимое, но для определенности все инструкции будут для Debian/Ubuntu.

1. Добавить репозиторий


wget -O - https://debian.neo4j.org/neotechnology.gpg.key | sudo apt-key add -
echo 'deb http://debian.neo4j.org/repo stable/' >/tmp/neo4j.list
sudo mv /tmp/neo4j.list /etc/apt/sources.list.d
sudo apt-get update

2. Установить Neo4j (community edition)


sudo apt-get install neo4j

Эта команда установит ПО в вашу домашнюю директорию и запустит сервис, который будет работать от имени пользователя neo4j.

3. Разрешить удалённый доступ


Если вы установили Neo4j на свой компьютер, этот шаг пропустите. Если же требуется доступ к серверу с других компьютеров в локальной сети, отредактируйте файл /var/lib/neo4j/conf/neo4j-server.properties

Для доступа с любого компьютера локальной сети установите параметры:

org.neo4j.server.webserver.address=0.0.0.0
dbms.security.auth_enabled=false

По умолчанию используется порт 7474, изменить порт можно, добавив строку в тот же файл:

org.neo4j.server.webserver.port=7474

Обратите внимание, что мы не настроили безопасность СУБД! Более подробно читайте инструкцию.

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

Установка клиента (Python)


Для того, чтобы импортировать базу Wordnet в Neo4j, воспользуемся скриптом на Питоне.

1. Сначала нужно установить библиотеку py2neo


pip install py2neo

2. Скачайте с гитхаба мой скрипт


mkdir habrawordnet2neo4j
cd habrawordnet2neo4j
git clone https://github.com/sergey-zarealye-com/wordnet2neo4j.git

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

Получение лексической базы данных Wordnet


На странице Download проекта Wordnet предлагается скачать базу вместе с программным обеспечением для её просмотра. Но мы-то хотим использовать для просмотра Neo4j! Поэтому достаточно скачать только файлы с данными:


Разархивируйте файлы в доступное место.

Импорт данных в Neo4j


Лексические данные в Wordnet лежат в файлах по частям речи. Например, существительные находятся в файле data.noun; глаголы — в data.verb; а с другими частями речи я и не пробовал.

1. Импорт существительных


Для импорта существительных перейдите в директорию, куда поместили мои скрипты (мы ее назвали просто habrawordnet2neo4j) и выполните команду в консоли:

python wordnet2neo4j.py -i rwn3/data.noun --neo4j http://127.0.0.1:7474 --nodelabel Ruswordnet --reltype Pointer --encoding cp1251 --limit 1000

Давайте разберём параметры поподробнее.

-i		путь к файлу данных Wordnet
--neo4j		URL сервера базы данных Neo4j
--nodelabel	Метка узлов, соответствующих словам Wordnet 
		в создаваемом графе (в Neo4j узлы графа снабжают 
		текстовыми метками; это просто идентификатор)
--reltype	Тип ребер графа, соответствующих указателям Wordnet 
		(в Neo4j ребра графа могут иметь тип; это просто 
		идентификатор)
--encoding	Кодировка файла данных; русскоязычная база записана 
		в кодировке cp1251; для англоязычных файлов этот 
		параметр не нужно указывать
--limit		Максимальное количество обрабатываемых строк файла; 
		дело в том, что мой скрипт работает довольно медленно, 
		и чтобы попробовать можно ограничить объем импортируемых 
		данных, например первыми 1000 строками файла; для импорта 
		полного файла этот параметр не нужно указывать, 
		и приготовьтесь подождать час-полтора.


2. Импорт глаголов


Для импорта глаголов выполните команду в консоли:

python wordnet2neo4j.py -i rwn3/data.verb --neo4j http://127.0.0.1:7474 --nodelabel Ruswordnet --reltype Pointer --encoding cp1251 --limit 1000

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

3. Убедитесь, что данные импортированы


Для этого откройте в браузере консоль Neo4j (введите адрес и порт сервера СУБД) и введите следующий запрос:

MATCH (node)-[relation]-() RETURN node, relation LIMIT 100

Если получили в экране изображение графа, то все прошло успешно.

Выполняем простые запросы


Все дальнейшие действия будем выполнять в браузере, в консоли Neo4j. Я буду считать, что в качестве меток узлов вы использовали Ruswordnet, а в качестве типа рёбер Pointer (как указано в предыдущем разделе). И что вы импортировали именно русскую базу Wordnet целиком.

1. Hello World


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

MATCH (n:Ruswordnet {name: "выкапывание_трупа"}) RETURN n

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

Ключевое слово MATCH означает примерно то же самое, что SELECT в SQL. Грубо говоря, «найти подходящие к шаблону элементы графа».

Круглыми скобками обозначаются узлы графа. Шаблон (n:Ruswordnet) обозначал бы, что мы хотим найти все узлы с меткой «Ruswordnet». Здесь n — идентификатор, можно сказать «переменная». ?

Узлы графа (и рёбра тоже) можно снабжать произвольными атрибутами. Чтобы найти конкретный узел, мы задали в запросе условие на атрибуты в формате, похожем на JSON: {name: «выкапывание_трупа»}. Таким образом, фраза

MATCH (n:Ruswordnet {name: "выкапывание_трупа"})

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

Ключевое слово RETURN говорит нам, какие переменные нас интересуют. В данном случае мы хотели просто увидеть узел (узлы), соответствующие заданным условиям, поэтому пишем RETURN n. Важно понимать, что n — это коллекция узлов, удовлетворяющих запросу. Чтобы убедиться в этом, просто замените понятие в запросе:

MATCH (n:Ruswordnet {name: "лев"}) RETURN n

Если вы импортировали базу Wordnet целиком, вы увидите шесть узлов понятий «лев». Давайте разберёмся, почему.

2. Переменные = коллекции


Выполним такой запрос:

match (n:Ruswordnet {name: "лев"})--(m) return n,m

Здесь мы задали уже более сложный шаблон для поиска. Мы хотим найти все узлы (n), соответствующие понятию «лев», а также все узлы (m), связанные с львами. Связь, т. е. ребро графа обозначается двумя дефисами. Можно в явном виде указывать интересующее нас направление символом --> (это я и называл ASCII art).



Если у вас не отображаются имена смысловых единиц, нажмите на кнопку Ruswordnet(23) в левом верхнем углу графа, и в строке состояния внизу консоли выберите «name» в поле Caption. Так будет нагляднее.

Теперь мы поняли, что лев это, оказывается не только болгарская валюта (bulgarian_money), копейкой для которой является стотинка, но и большая кошка, и созвездие, астрологический знак, и что-то, связанное с гордостью.

3. Подключаем рёбра


В базе Wordnet ребра называются указателями (Pointer), и используется большое количество лингвистических типов указателей. Они обозначаются символами, некоторые из которых я привожу в таблице:
Символ Английское наименование лингвистического отношения Лингвистическое отношение
! Antonym Антоним
@ Hypernym Обобщение
@i Instance Hypernym Экземпляр обобщения
~ Hyponym Уточнение
~i Instance Hyponym Экземпляр уточнения
#m Member holonym Понятие, включающее в себя данное понятие
#s Substance holonym Вещество, из которого состоит предмет
#p Part holonym Предмет, включающий в себя как часть данный предмет
%m Member meronym Часть более общего понятия
%s Substance meronym Из какого вещества состоит предмет
%p Part meronym Часть предмета
= Attribute Атрибут
+ Derivationally related form Производная форма

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

MATCH (n:Ruswordnet {name: "лев"})-[p:Pointer {pointer_symbol: "@"}]->(m) 
RETURN n,m

Квадратными скобками обозначаются спецификации рёбер. В этом запросе мы хотим найти рёбра типа Pointer, атрибут которых pointer_symbol равен «@» т. е. символу обобщения. Кстати, противоположный обобщению символ уточнения «~».



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

Давайте разберемся, что такое part holonym:

MATCH (n:Ruswordnet {name: "лев"})-[p:Pointer {pointer_symbol: "#p"}]->(m) 
RETURN n,m

А, теперь понятно: лев входит в зодиак в качестве составной части, значит зодиак является part holonym для льва.

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

MATCH (n)-[p:Pointer {pointer_symbol: "#s"}]->(m) 
RETURN n,m LIMIT 10

В этом запросе мы не накладываем никаких условий на узлы (n) и (m). Мы только хотим, чтобы их связывали рёбра с атрибутом «#s». Обратите внимание, появилось ключевое слово LIMIT, знакомое нам из SQL. Если бы его здесь не было, сервер вернул бы нам очень много результатов, и плохо было бы нашему браузеру.

В результате запроса мы узнали, что сигареты состоят из марихуаны, а суп из воловьих хвостов — из воловьих хвостов.

4. Цепочки произвольной длины


В детстве все играли в такую игру: превратить муху в слона. Для этого нужно было менять по одной букве в слове, пока слово МУХА не превратилось в слово СЛОН. Давайте узнаем в лексическом графе, связаны ли между собой ЛЕВ и ОВЦА.
MATCH (n:Ruswordnet {name: "лев"})-[p:Pointer*1..3]-(m:Ruswordnet {name: "овца"}) 
RETURN n,m,p

Конструкция [p:Pointer*1..3] говорит, что требуется найти цепочку рёбер типа Pointer длиной от одного до трех, связывающую узел «лев» с узлом «овца».



Это отличается от классической детской игры, но тоже интересно: ОВЦА — ПРОСТАК — ЧЕЛОВЕК — ЛЕВ… это звучит гордо. Кстати, можно попытаться найти связь и между мухой и слоном, только немного увеличить предельную длину цепочки. Я использовал значение 6. Кстати, не пытайтесь сразу поставить 100 — процесс поиска скорее всего сорвется т. к. число вариантов для перебора путей в графе будет слишком велико. Итак, вот как связаны слон и муха лексически:



Думаю, на этом этапе вы многое поняли о базе данных Neo4j, и способны самостоятельно открыть много интересного в базе данных Wordnet, а может применить Neo4j в своих проектах. Мы применяем связку Neo4j c Wordnet в системе поиска по киноархивам. Если вы хотите заниматься исследованиями в области машинного обучения, приглашаю на стажировку или на постоянную работу в НИКФИ — научно-исследовательский кинофотоинститут.

Комментарии (0)