Как найти самый короткий путь зависимости между двумя словами в Python?
Я пытаюсь найти путь зависимости между двумя словами в дереве зависимостей Python.
Для приговора
Роботы в популярной культуре, чтобы напомнить нам об удивительности несвязанного человеческого агентства.
Я использовал practnlptools (https://github.com/biplab-iitb/practNLPTools), чтобы получить результат анализа зависимости, например:
nsubj(are-5, Robots-1)
xsubj(remind-8, Robots-1)
amod(culture-4, popular-3)
prep_in(Robots-1, culture-4)
root(ROOT-0, are-5)
advmod(are-5, there-6)
aux(remind-8, to-7)
xcomp(are-5, remind-8)
dobj(remind-8, us-9)
det(awesomeness-12, the-11)
prep_of(remind-8, awesomeness-12)
amod(agency-16, unbound-14)
amod(agency-16, human-15)
prep_of(awesomeness-12, agency-16)
который также можно визуализировать как (снимок сделан из https://demos.explosion.ai/displacy/)
Длина пути между "роботами" и "есть" равна 1, длина пути между "роботами" и "awesomeness" будет равна 4.
Мой вопрос приведен выше результата анализа зависимости, как я могу получить путь зависимости или длину пути зависимости между двумя словами?
Из моего текущего результата поиска будет помогать nltk ParentedTree?
Благодарю!
Ответы
Ответ 1
Ваша проблема может быть легко понята как проблема графа, где мы должны найти кратчайший путь между двумя узлами.
Чтобы преобразовать ваш синтаксический анализ в графе, мы сначала должны иметь дело с тем, что он приходит как строка. Вы хотите получить следующее:
'nsubj(are-5, Robots-1)\nxsubj(remind-8, Robots-1)\namod(culture-4, popular-3)\nprep_in(Robots-1, culture-4)\nroot(ROOT-0, are-5)\nadvmod(are-5, there-6)\naux(remind-8, to-7)\nxcomp(are-5, remind-8)\ndobj(remind-8, us-9)\ndet(awesomeness-12, the-11)\nprep_of(remind-8, awesomeness-12)\namod(agency-16, unbound-14)\namod(agency-16, human-15)\nprep_of(awesomeness-12, agency-16)'
чтобы выглядеть так:
[('are-5', 'Robots-1'), ('remind-8', 'Robots-1'), ('culture-4', 'popular-3'), ('Robots-1', 'culture-4'), ('ROOT-0', 'are-5'), ('are-5', 'there-6'), ('remind-8', 'to-7'), ('are-5', 'remind-8'), ('remind-8', 'us-9'), ('awesomeness-12', 'the-11'), ('remind-8', 'awesomeness-12'), ('agency-16', 'unbound-14'), ('agency-16', 'human-15'), ('awesomeness-12', 'agency-16')]
Таким образом, вы можете кормить список кортежей конструктору графа из модуля networkx, который будет анализировать список и строить график для вас, а также дать вам аккуратный метод, который даст вам длину кратчайшего пути между двумя заданными узлами.
Необходимый импорт
import re
import networkx as nx
from practnlptools.tools import Annotator
Как получить строку в формате списка нужных файлов.
annotator = Annotator()
text = """Robots in popular culture are there to remind us of the awesomeness of unbound human agency."""
dep_parse = annotator.getAnnotations(text, dep_parse=True)['dep_parse']
dp_list = dep_parse.split('\n')
pattern = re.compile(r'.+?\((.+?), (.+?)\)')
edges = []
for dep in dp_list:
m = pattern.search(dep)
edges.append((m.group(1), m.group(2)))
Как построить график
graph = nx.Graph(edges) # Well that was easy
Как вычислить кратчайшую длину пути
print(nx.shortest_path_length(graph, source='Robots-1', target='awesomeness-12'))
Этот скрипт покажет, что кратчайший путь, заданный для анализа зависимости, на самом деле имеет длину 2, так как вы можете получить от Robots-1
до awesomeness-12
, пройдя через remind-8
1. xsubj(remind-8, Robots-1)
2. prep_of(remind-8, awesomeness-12)
Если вам не нравится этот результат, вы можете подумать о фильтрации некоторых зависимостей, в этом случае не допускайте xsubj
зависимостей в график.
Ответ 2
Ответ HugoMailhot велик. Я буду писать что - то подобное для Spacy пользователей, которые хотят, чтобы найти кратчайший путь к зависимости между двумя словами ( в то время как HugoMailhot ответ полагается на practNLPTools).
Предложение:
Роботы в популярной культуре, чтобы напомнить нам об удивительности несвязанного человеческого агентства.
имеет следующее дерево зависимостей:
Вот код, чтобы найти самый короткий путь зависимости между двумя словами:
import networkx as nx
import spacy
nlp = spacy.load('en')
# https://spacy.io/docs/usage/processing-text
document = nlp(u'Robots in popular culture are there to remind us of the awesomeness of unbound human agency.', parse=True)
print('document: {0}'.format(document))
# Load spacy dependency tree into a networkx graph
edges = []
for token in document:
# FYI https://spacy.io/docs/api/token
for child in token.children:
edges.append(('{0}-{1}'.format(token.lower_,token.i),
'{0}-{1}'.format(child.lower_,child.i)))
graph = nx.Graph(edges)
# https://networkx.github.io/documentation/networkx-1.10/reference/algorithms.shortest_paths.html
print(nx.shortest_path_length(graph, source='robots-0', target='awesomeness-11'))
print(nx.shortest_path(graph, source='robots-0', target='awesomeness-11'))
print(nx.shortest_path(graph, source='robots-0', target='agency-15'))
Вывод:
4
['robots-0', 'are-4', 'remind-7', 'of-9', 'awesomeness-11']
['robots-0', 'are-4', 'remind-7', 'of-9', 'awesomeness-11', 'of-12', 'agency-15']
Чтобы установить spacy и networkx:
sudo pip install networkx
sudo pip install spacy
sudo python -m spacy.en.download parser # will take 0.5 GB
Некоторые тесты, касающиеся анализа синтаксического зависимостей: https://spacy.io/docs/api/
Ответ 3
Этот ответ основывается на Stanford CoreNLP для получения дерева зависимостей предложения. Он занимает совсем немного кода из ответа HugoMailhot при использовании networkx.
Перед запуском кода необходимо:
-
sudo pip install pycorenlp
(интерфейс python для Stanford CoreNLP) - Загрузить Stanford CoreNLP
-
Запустите сервер Stanford CoreNLP следующим образом:
java -mx4g -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLPServer -port 9000 -timeout 50000
Затем можно запустить следующий код, чтобы найти самый короткий путь зависимости между двумя словами:
import networkx as nx
from pycorenlp import StanfordCoreNLP
from pprint import pprint
nlp = StanfordCoreNLP('http://localhost:{0}'.format(9000))
def get_stanford_annotations(text, port=9000,
annotators='tokenize,ssplit,pos,lemma,depparse,parse'):
output = nlp.annotate(text, properties={
"timeout": "10000",
"ssplit.newlineIsSentenceBreak": "two",
'annotators': annotators,
'outputFormat': 'json'
})
return output
# The code expects the document to contains exactly one sentence.
document = 'Robots in popular culture are there to remind us of the awesomeness of'\
'unbound human agency.'
print('document: {0}'.format(document))
# Parse the text
annotations = get_stanford_annotations(document, port=9000,
annotators='tokenize,ssplit,pos,lemma,depparse')
tokens = annotations['sentences'][0]['tokens']
# Load Stanford CoreNLP dependency tree into a networkx graph
edges = []
dependencies = {}
for edge in annotations['sentences'][0]['basic-dependencies']:
edges.append((edge['governor'], edge['dependent']))
dependencies[(min(edge['governor'], edge['dependent']),
max(edge['governor'], edge['dependent']))] = edge
graph = nx.Graph(edges)
#pprint(dependencies)
#print('edges: {0}'.format(edges))
# Find the shortest path
token1 = 'Robots'
token2 = 'awesomeness'
for token in tokens:
if token1 == token['originalText']:
token1_index = token['index']
if token2 == token['originalText']:
token2_index = token['index']
path = nx.shortest_path(graph, source=token1_index, target=token2_index)
print('path: {0}'.format(path))
for token_id in path:
token = tokens[token_id-1]
token_text = token['originalText']
print('Node {0}\ttoken_text: {1}'.format(token_id,token_text))
Выход:
document: Robots in popular culture are there to remind us of the awesomeness of unbound human agency.
path: [1, 5, 8, 12]
Node 1 token_text: Robots
Node 5 token_text: are
Node 8 token_text: remind
Node 12 token_text: awesomeness
Обратите внимание, что Stanford CoreNLP может быть протестирован онлайн: http://nlp.stanford.edu:8080/parser/index.jsp
Этот ответ был протестирован с помощью Stanford CoreNLP 3.6.0., Pycorenlp 0.3.0 и python 3.5 x64 в Windows 7 SP1 x64 Ultimate.