Сегодня на простом примере рассмотрим – как провести краткий обзор неструктурированных данных в виде графа знаний. Для примера возьмем набор текстов из обращений с портала mos.ru. В данном случае, набор состоит из 90 тыс. обращений. Медианная длина обращений составляет 9 слов. В целом, тексты можно разбить на три основные темы: качество окружающей среды; качество городской среды; доля дорожной среды, соответствующей нормативам. Для начала импортируем необходимые библиотеки: Библиотека Stanza позволяет работать над NLP задачами, такими как определение части речи, лемматизация, поиск именованных сущностей, а также определение синтаксической зависимости между словами в предложении. Библиотеку nltk используем для разбивки текстов на отдельные предложения. Stanza сама разбивает текст на предложения, а затем на отдельные слова, но для уменьшения времени обработки лучше предобработать текст. Загрузим данные, для примера выберем отдельную категорию: Разобьём тексты на предложения и удалим короткие предложения: Инициализируем различные препроцессоры stanza с помощью метода Pipeline: В данном случае, мы указали 5 препроцессоров, т.к. для определения синтаксической зависимости («depparse») обязательны 4 («tokenize, pos, lemma, ner») препроцессора. Однако, если необходимо определить только именованные сущности, то можно использовать только 2 препроцессора («tokenize, ner»), что увеличит скорость обработки данных. Стоит учесть, что использование Stanza – вычислительно-затратный процесс, на обработку 90 тыс. обращений может уйти много времени. Однако, Stanza позволяет обрабатывать данные на видеокартах с поддержкой CUDA. В моем случае, обработка 3000 предложений на CPU заняло 26 минут, в то время на видеокарте тот же объем обработан за 3 минуты. Для запуска вычислений на GPU необходимо установить соответствующие инструменты CUDA, при запуске Pipeline должно отобразиться «Use devise: gpu».  В случае проблем с обнаружением видеокарты, посетить данную вебстраницу. Для построения графа необходимо получить список ребер, в данном случае ребром будут два слова или словосочетания с зависимостью между ними. Как раз для поиска этой зависимости будет использоваться Stanza. С помощью «depparse» препроцессора можно определить более 30 различных зависимостей.  Основная сложность заключается в составлении правильной конструкции (Subject – relation - Object) - triplet, которая будет подходить для всех текстов в корпусе. Для примера будут использоваться 6 зависимостей (nsubj, nsubj:pass, obj, obl, nmod, nummod). Выбор зависимостей обусловлен тематикой и окраской текста, который Вы хотите извлечь из всего корпуса. Пример конструкции зависимостей в предложении представлен на рисунке ниже. Как правило, Subject и Object являются существительными, а relation – глаголом. В 3-м примере можно заметить, что Subject – «Андрей», relation – «имел» и Object – «известность». Однако в большинстве случаев связь не так очевидна, как в примере выше и для полного понимания необходимо «навешивать» на Subject и Object дополнительные связи. Далее единый код будет описываться частями для лучшего восприятия: Создаем список, в который будем записывать связи в предложении «Subject – relation – Object» (триплет). Далее для каждого предложения применяем препроцессоры и получаем переменную (doc), которая содержит в себе всю информацию для каждого слова. Далее извлекаем все именованные сущности из предложения в переменную entities. Далее создаем временный словарь temp_d и записываем в него слова, связи для них (head), а также тип этой связи (dep), например: Также создаем словарь res_d, для записи триплетов конкретного предложения. Проводим поиск такого слово в temp_d, которое имеет тип связи «nsubj» или «nsubj:pass», а также проверяем, что это слово относится к именованной сущности. В res_d записываем слово, и слово-связь (head) для него. Также создадим переменные для сохранения дополнительных связей (nmod_1 и nmod_2). Раннее мы определили Subject и relation, осталось найти Object. Для этого мы находим слово в temp_d, которое имеет связь с relation, типа obj или obl. Также должны убедиться, что Object располагается в предложении после relation, т.к. такой тип связи может встречаться несколько раз в предложении. Таким образом получаем следующую запись: {"Андрей": {'head': имел, 'obj': "известность"}}                 Далее найдем окраску нашему отношению, т.е. проверим наличие частицы «не», чтобы лучше понимать контекст: Рассмотрим следующий пример. На вход подается предложение: «Ямы находятся на траектории движения во двор.» Тогда результатом алгоритма будет: {"Ямы": {"head": "находятся", "obj": "траектории"}}.  Сложно определить, какой смысл несет данный триплет и правильно ли он составлен. Именно для этого необходимо «навешивать» дополнительные связи. Попробуем найти дополнительные связи для Object: Снова пробегаемся по нашему словарю и находим слово, которое имеет связь с Object, типа nmod. Далее повторим операцию, только на этот раз проводим поиск слова, которое имеет связь nummod со словом nmod_1. Таким образом, результат должен быть следующим: {"Ямы": {"head": "находятся", "obj": "траектории движения"}}, что приобретает более глубокий смысл. Странно конечно, что Stanza относит «яму» к именованной сущности. В итоге получаем низкопроизводительный код.))) В данной статье хотел донести основную концепцию поиска триплетов. Надеюсь, Ваша реализация получится намного лучше. Далее отфильтруем неполные триплеты, в которых отсутствует Object: В результате получаем список триплетов, а также предложения, из которых они получены.  Осталось отрисовать результат удобным для Вас способом. Для этого можно воспользоваться такими инструментами, как NetworkX, Graphviz, Gephi и другие. Я воспользуюсь инструментом визуализации Vis.js, т.к. на мой взгляд он является самым удобным для формирования интерактивных графов. Для удобства добавил небольшой функционал в Vis.js, который позволяет отображать полное предложение, при нажатии на ребро. Полный код, а также код для отрисовки представлен в notebook. Перед отрисовкой все слова приведены к начальной форме, что усложняет восприятие, однако данный процесс позволяет избавиться от повтора вершин на графе. Таким образом, можем проанализировать текстовые данные и определить основное содержание жалоб и благодарностей, а также найти взаимосвязи между различными обращениями. Профессиональное сообщество