Я думаю, вам нужен синтаксический анализ. С синтаксической точки зрения ваши предложения выглядят как
is
_______________|_____
| | cat
| | __________|________________
| | | | | | lives
| | | | | | _____|____
| | | | | | | in
| | | | | | | |
Garfield . a large comic strip that Ohio
is
________|____
| | city
| | ____|______
| | | | in
| | | | |
| Town | | Africa
| | | | |
. Cape the oldest South
(я использовал метод из этого вопроса для построения деревьев).
Теперь вместо извлечения подстрок вы должны извлечь поддеревья. Минимальный код для достижения этого сначала находит шаблон «is», а затем выдает левое и правое поддеревья, если они присоединены к «is» с правым видом зависимостей:
def get_head(sentence):
toks = [t for t in sentence]
for i, t in enumerate(toks):
if t.lemma_ == 'be' and i + 1 < len(toks) and toks[i+1].pos_ == 'DET':
yield t
def get_relations(text):
doc = nlp(text)
for sent in doc.sents:
for head in get_head(sent):
children = list(head.children)
if len(children) < 2:
continue
l, r = children[0:2]
# check that the left child is really a subject and the right one is a description
if l.dep_ == 'nsubj' and r.dep_ == 'attr':
yield l, r
for l, r in get_relations(text):
print(list(l.subtree), list(r.subtree))
Будет выведено что-то вроде
[Garfield] [a, large, comic, strip, cat, that, lives, in, Ohio]
[Cape, Town] [the, oldest, city, in, South, Africa]
Таким образом, вы, по крайней мере, правильно отделяете левую часть от правой части. Если вы хотите, вы можете добавить больше фильтров (например, l.pos_ == 'PROPN'
). Другим улучшением будет обработка случаев с более чем двумя детьми «есть» (например, наречия).
Теперь вы можете обрезать поддеревья по своему усмотрению, создавая даже меньшие предикаты (например, «большая кошка», «комический кот», «полосатый кот», «кошка, которая живет в Огайо» и т. Д.). Быстрая и грязная версия такой обрезки может смотреть каждый раз только на одного ребенка:
for l, r in get_relations(text):
print(list(l.subtree), list(r.subtree))
for c in r.children:
words = [r] + list(c.subtree)
print(' '.join([w.text for w in sorted(words, key=lambda x: x.i)]))
Это даст следующий результат
[Garfield], [a, large, comic, strip, cat, that, lives, in, Ohio]
a cat
large cat
comic cat
strip cat
cat that lives in Ohio
[Cape, Town], [the, oldest, city, in, South, Africa]
the city
oldest city
city in South Africa
Вы видите, что некоторые поддеревья неправильны: Кейптаун не является "самым старым городом" в мире. Но, похоже, вам нужны хотя бы некоторые семантические знания, чтобы отфильтровать такие неправильные поддеревья.