Как найти кратчайший путь зависимости между двумя словами в Python?

Я пытаюсь найти путь зависимости между двумя словами в Python с учетом дерева зависимостей.

Для приговора

Роботы в популярной культуре призваны напомнить нам об удивительности свободной человеческой деятельности.

Для получения результат синтаксического анализа зависимости, например:

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, длина пути между «роботами» и «крутизной» будет равна 4.

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

Из моего текущего результата поиска поможет ли ParentedTree nltk?

Спасибо!


person Sean    schedule 29.09.2015    source источник
comment
Привет @Sean, как я могу получить аналогичную древовидную структуру для предложения, используя nltk? В настоящее время у меня нет Grammer, и использование грамматики «treebank» дает мне ошибку некоторых слов, отсутствующих в производственном списке. Спасибо.   -  person skadoosh    schedule 20.07.2016
comment
@skadoosh На самом деле, с помощью NLTK получить такую ​​красивую презентацию сложно. Я предлагаю использовать Spacy.   -  person Sean    schedule 21.07.2016


Ответы (3)


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

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

'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 на график.

person HugoMailhot    schedule 01.10.2015

ответ от 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's 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

Некоторые контрольные показатели анализа зависимостей spacy: https://spacy.io/docs/api/

введите описание изображения здесь

person Franck Dernoncourt    schedule 23.01.2017

Этот ответ основан на Stanford CoreNLP для получения дерева зависимостей предложения. Он заимствует довольно много кода из ответа HugoMailhot при использовании networkx.

Перед запуском кода необходимо:

  1. sudo pip install pycorenlp (интерфейс Python для Stanford CoreNLP)
  2. Загрузите Stanford CoreNLP
  3. Запустите сервер 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's 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.

person Franck Dernoncourt    schedule 25.01.2017