Niedawno właśnie rozwiązałem bardzo podobny problem - potrzebowałem wydobyć temat (y), działanie, przedmiot (y). I otwieram źródło moich prac, więc możesz sprawdzić tę bibliotekę: https://github.com/krzysiekfonal/textpipeliner
Oparte na spacy (przeciwnik do nltk), ale również oparte na drzewie zdań.
Tak na przykład przejdźmy ten dokument osadzony w przestronne jako przykład:
import spacy
nlp = spacy.load("en")
doc = nlp(u"The Empire of Japan aimed to dominate Asia and the " \
"Pacific and was already at war with the Republic of China " \
"in 1937, but the world war is generally said to have begun on " \
"1 September 1939 with the invasion of Poland by Germany and " \
"subsequent declarations of war on Germany by France and the United Kingdom. " \
"From late 1939 to early 1941, in a series of campaigns and treaties, Germany conquered " \
"or controlled much of continental Europe, and formed the Axis alliance with Italy and Japan. " \
"Under the Molotov-Ribbentrop Pact of August 1939, Germany and the Soviet Union partitioned and " \
"annexed territories of their European neighbours, Poland, Finland, Romania and the Baltic states. " \
"The war continued primarily between the European Axis powers and the coalition of the United Kingdom " \
"and the British Commonwealth, with campaigns including the North Africa and East Africa campaigns, " \
"the aerial Battle of Britain, the Blitz bombing campaign, the Balkan Campaign as well as the " \
"long-running Battle of the Atlantic. In June 1941, the European Axis powers launched an invasion " \
"of the Soviet Union, opening the largest land theatre of war in history, which trapped the major part " \
"of the Axis' military forces into a war of attrition. In December 1941, Japan attacked " \
"the United States and European territories in the Pacific Ocean, and quickly conquered much of " \
"the Western Pacific.")
Można teraz utworzyć prostą konstrukcję rur (więcej o rur w readme tego projektu):
pipes_structure = [SequencePipe([FindTokensPipe("VERB/nsubj/*"),
NamedEntityFilterPipe(),
NamedEntityExtractorPipe()]),
FindTokensPipe("VERB"),
AnyPipe([SequencePipe([FindTokensPipe("VBD/dobj/NNP"),
AggregatePipe([NamedEntityFilterPipe("GPE"),
NamedEntityFilterPipe("PERSON")]),
NamedEntityExtractorPipe()]),
SequencePipe([FindTokensPipe("VBD/**/*/pobj/NNP"),
AggregatePipe([NamedEntityFilterPipe("LOC"),
NamedEntityFilterPipe("PERSON")]),
NamedEntityExtractorPipe()])])]
engine = PipelineEngine(pipes_structure, Context(doc), [0,1,2])
engine.process()
A w rezultacie otrzymasz:
>>>[([Germany], [conquered], [Europe]),
([Japan], [attacked], [the, United, States])]
Właściwie to na silnie (Fajka wyszukiwania s) w innej bibliotece - grammaregex.Można przeczytać o tym z postu: https://medium.com/@krzysiek89dev/grammaregex-library-regex-like-for-text-mining-49e5706c9c6d#.zgx7odhsc
EDITED
Właściwie przykład przedstawiłem w readme odrzuca przym, ale wszystko, co potrzebne jest, aby dostosować strukturę rury przekazany do silnika w zależności od potrzeb. Na przykład dla swoich zdań przykładowych mogę zaproponować taką strukturę/rozwiązanie, które daje krotki z 3 elementów (subj, czasownik, ADJ) na każdym zdaniu:
import spacy
from textpipeliner import PipelineEngine, Context
from textpipeliner.pipes import *
pipes_structure = [SequencePipe([FindTokensPipe("VERB/nsubj/NNP"),
NamedEntityFilterPipe(),
NamedEntityExtractorPipe()]),
AggregatePipe([FindTokensPipe("VERB"),
FindTokensPipe("VERB/xcomp/VERB/aux/*"),
FindTokensPipe("VERB/xcomp/VERB")]),
AnyPipe([FindTokensPipe("VERB/[acomp,amod]/ADJ"),
AggregatePipe([FindTokensPipe("VERB/[dobj,attr]/NOUN/det/DET"),
FindTokensPipe("VERB/[dobj,attr]/NOUN/[acomp,amod]/ADJ")])])
]
engine = PipelineEngine(pipes_structure, Context(doc), [0,1,2])
engine.process()
Będzie daje wyniki:
[([Donald, Trump], [is], [the, worst])]
Trochę złożoność polega na tym, że masz zdanie złożone, a lib tworzy jedną krotkę na zdanie - wkrótce dodam możliwość (potrzebuję tego również dla mojego projektu), aby przekazać listę struktur rur do silnika, aby umożliwić produkcję więcej krotek w jednym zdaniu. Ale na razie możesz go rozwiązać, tworząc drugi silnik dla złożonych wysłanników, którego struktura różni się tylko VERB/conj/VERB zamiast VERB (te regex zaczynają się zawsze od ROOT, więc VERB/conj/VERB prowadzą cię do drugiego czasownika w związek zdanie):
pipes_structure_comp = [SequencePipe([FindTokensPipe("VERB/conj/VERB/nsubj/NNP"),
NamedEntityFilterPipe(),
NamedEntityExtractorPipe()]),
AggregatePipe([FindTokensPipe("VERB/conj/VERB"),
FindTokensPipe("VERB/conj/VERB/xcomp/VERB/aux/*"),
FindTokensPipe("VERB/conj/VERB/xcomp/VERB")]),
AnyPipe([FindTokensPipe("VERB/conj/VERB/[acomp,amod]/ADJ"),
AggregatePipe([FindTokensPipe("VERB/conj/VERB/[dobj,attr]/NOUN/det/DET"),
FindTokensPipe("VERB/conj/VERB/[dobj,attr]/NOUN/[acomp,amod]/ADJ")])])
]
engine2 = PipelineEngine(pipes_structure_comp, Context(doc), [0,1,2])
A teraz po uruchomieniu oba silniki otrzymasz oczekiwany wynik :)
engine.process()
engine2.process()
[([Donald, Trump], [is], [the, worst])]
[([Hillary], [is], [better])]
to jest to, czego potrzebujesz myślę. Oczywiście po prostu szybko stworzyłem strukturę rur dla danego przykładu zdania i nie będzie działać dla każdego przypadku, ale widziałem wiele struktur zdań i będzie to już całkiem niezły procent, ale wtedy możesz po prostu dodać więcej FindTokensPipe itp. Sprawy, które nie będą działać obecnie i jestem pewien, że po kilku zmianach zajmiesz się naprawdę dużą liczbą możliwych zdań (angielski nie jest zbyt skomplikowany, więc ... :)
Próbowałem Twojego rozwiązania, ale odrzuca przymiotniki/przysłówki używane dla różnych przedmiotów. Ponieważ muszę przeprowadzić analizę uczuć, sprawi to, że wszystkie instrukcje/relacje będą neutralne. –
ok, więc odpowiedź na twój komentarz jest dodana po EDYCJI w oryginalnej odpowiedzi, ponieważ w komentarzu jest za mało miejsca. – Krzysiek
@Krzysiek błąd: Obiekt AttributeError: 'spacy.tokens.doc.Doc' nie ma atrybutu "next_sent'' po wpisaniu' engine2.process() ' –