2016-09-29 18 views
6

Próbuję pracować nad ekstrakcją tematu w zdaniu, tak aby uzyskać sentymenty zgodne z tematem. Używam nltk w python2.7 do tego celu. Podjąć następujące zdanie jako przykład:Jak wyodrębnić tematy w zdaniu i ich odpowiednich frazach zależnych?

Donald Trump is the worst president of USA, but Hillary is better than him

On widzimy, że Donald Trump i Hillary są dwa tematy i nastroje związane z Donald Trump jest ujemny, ale związane z Hillary są pozytywne. Do tej pory jestem w stanie przełamać to zdanie na kawałki rzeczownika zwrotów, i jestem w stanie uzyskać następujące:

(S 
    (NP Donald/NNP Trump/NNP) 
    is/VBZ 
    (NP the/DT worst/JJS president/NN) 
    in/IN 
    (NP USA,/NNP) 
    but/CC 
    (NP Hillary/NNP) 
    is/VBZ 
    better/JJR 
    than/IN 
    (NP him/PRP)) 

Teraz, jak mam podejść w znajdowaniu przedmiotów z tych rzeczowników zwrotów? Jak pogrupować frazy przeznaczone dla obu podmiotów? Gdy będę już miał zdania przeznaczone dla obu podmiotów oddzielnie:, mogę przeprowadzić analizę sentymentu na obu osobno.

EDIT

zajrzałem do biblioteki wspomnianym przez @Krzysiek (spacy), a to dało mi zależnościami drzew, a także w zdaniach.

Oto kod:

from spacy.en import English 
parser = English() 

example = u"Donald Trump is the worst president of USA, but Hillary is better than him" 
parsedEx = parser(example) 
# shown as: original token, dependency tag, head word, left dependents, right dependents 
for token in parsedEx: 
    print(token.orth_, token.dep_, token.head.orth_, [t.orth_ for t in token.lefts], [t.orth_ for t in token.rights]) 

Oto drzewa zależnościami:

(u'Donald', u'compound', u'Trump', [], []) 
(u'Trump', u'nsubj', u'is', [u'Donald'], []) 
(u'is', u'ROOT', u'is', [u'Trump'], [u'president', u',', u'but', u'is']) 
(u'the', u'det', u'president', [], []) 
(u'worst', u'amod', u'president', [], []) 
(u'president', u'attr', u'is', [u'the', u'worst'], [u'of']) 
(u'of', u'prep', u'president', [], [u'USA']) 
(u'USA', u'pobj', u'of', [], []) 
(u',', u'punct', u'is', [], []) 
(u'but', u'cc', u'is', [], []) 
(u'Hillary', u'nsubj', u'is', [], []) 
(u'is', u'conj', u'is', [u'Hillary'], [u'better']) 
(u'better', u'acomp', u'is', [], [u'than']) 
(u'than', u'prep', u'better', [], [u'him']) 
(u'him', u'pobj', u'than', [], []) 

Daje wgląd w głębokości w zależności od różnych wyrazy zdań. Oto link do artykułu opisującego zależności między różnymi parami. Jak mogę użyć tego drzewa do dołączenia do nich słów kontekstowych dla różnych tematów?

Odpowiedz

5

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 ... :)

+0

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. –

+0

ok, więc odpowiedź na twój komentarz jest dodana po EDYCJI w oryginalnej odpowiedzi, ponieważ w komentarzu jest za mało miejsca. – Krzysiek

+0

@Krzysiek błąd: Obiekt AttributeError: 'spacy.tokens.doc.Doc' nie ma atrybutu "next_sent'' po wpisaniu' engine2.process() ' –

4

Przechodziłem przez bibliotekę Spacy więcej, i W końcu wymyśliłem rozwiązanie poprzez zarządzanie zależnościami. Dzięki this repo, wymyśliłem, jak włączyć przymiotniki również w moim subiektywnym czasowniku obiektu (czyniąc go SVAO), jak również biorąc złożone tematy w zapytaniu. Tu idzie moje rozwiązanie:

from nltk.stem.wordnet import WordNetLemmatizer 
from spacy.en import English 

SUBJECTS = ["nsubj", "nsubjpass", "csubj", "csubjpass", "agent", "expl"] 
OBJECTS = ["dobj", "dative", "attr", "oprd"] 
ADJECTIVES = ["acomp", "advcl", "advmod", "amod", "appos", "nn", "nmod", "ccomp", "complm", 
       "hmod", "infmod", "xcomp", "rcmod", "poss"," possessive"] 
COMPOUNDS = ["compound"] 
PREPOSITIONS = ["prep"] 

def getSubsFromConjunctions(subs): 
    moreSubs = [] 
    for sub in subs: 
     # rights is a generator 
     rights = list(sub.rights) 
     rightDeps = {tok.lower_ for tok in rights} 
     if "and" in rightDeps: 
      moreSubs.extend([tok for tok in rights if tok.dep_ in SUBJECTS or tok.pos_ == "NOUN"]) 
      if len(moreSubs) > 0: 
       moreSubs.extend(getSubsFromConjunctions(moreSubs)) 
    return moreSubs 

def getObjsFromConjunctions(objs): 
    moreObjs = [] 
    for obj in objs: 
     # rights is a generator 
     rights = list(obj.rights) 
     rightDeps = {tok.lower_ for tok in rights} 
     if "and" in rightDeps: 
      moreObjs.extend([tok for tok in rights if tok.dep_ in OBJECTS or tok.pos_ == "NOUN"]) 
      if len(moreObjs) > 0: 
       moreObjs.extend(getObjsFromConjunctions(moreObjs)) 
    return moreObjs 

def getVerbsFromConjunctions(verbs): 
    moreVerbs = [] 
    for verb in verbs: 
     rightDeps = {tok.lower_ for tok in verb.rights} 
     if "and" in rightDeps: 
      moreVerbs.extend([tok for tok in verb.rights if tok.pos_ == "VERB"]) 
      if len(moreVerbs) > 0: 
       moreVerbs.extend(getVerbsFromConjunctions(moreVerbs)) 
    return moreVerbs 

def findSubs(tok): 
    head = tok.head 
    while head.pos_ != "VERB" and head.pos_ != "NOUN" and head.head != head: 
     head = head.head 
    if head.pos_ == "VERB": 
     subs = [tok for tok in head.lefts if tok.dep_ == "SUB"] 
     if len(subs) > 0: 
      verbNegated = isNegated(head) 
      subs.extend(getSubsFromConjunctions(subs)) 
      return subs, verbNegated 
     elif head.head != head: 
      return findSubs(head) 
    elif head.pos_ == "NOUN": 
     return [head], isNegated(tok) 
    return [], False 

def isNegated(tok): 
    negations = {"no", "not", "n't", "never", "none"} 
    for dep in list(tok.lefts) + list(tok.rights): 
     if dep.lower_ in negations: 
      return True 
    return False 

def findSVs(tokens): 
    svs = [] 
    verbs = [tok for tok in tokens if tok.pos_ == "VERB"] 
    for v in verbs: 
     subs, verbNegated = getAllSubs(v) 
     if len(subs) > 0: 
      for sub in subs: 
       svs.append((sub.orth_, "!" + v.orth_ if verbNegated else v.orth_)) 
    return svs 

def getObjsFromPrepositions(deps): 
    objs = [] 
    for dep in deps: 
     if dep.pos_ == "ADP" and dep.dep_ == "prep": 
      objs.extend([tok for tok in dep.rights if tok.dep_ in OBJECTS or (tok.pos_ == "PRON" and tok.lower_ == "me")]) 
    return objs 

def getAdjectives(toks): 
    toks_with_adjectives = [] 
    for tok in toks: 
     adjs = [left for left in tok.lefts if left.dep_ in ADJECTIVES] 
     adjs.append(tok) 
     adjs.extend([right for right in tok.rights if tok.dep_ in ADJECTIVES]) 
     tok_with_adj = " ".join([adj.lower_ for adj in adjs]) 
     toks_with_adjectives.extend(adjs) 

    return toks_with_adjectives 

def getObjsFromAttrs(deps): 
    for dep in deps: 
     if dep.pos_ == "NOUN" and dep.dep_ == "attr": 
      verbs = [tok for tok in dep.rights if tok.pos_ == "VERB"] 
      if len(verbs) > 0: 
       for v in verbs: 
        rights = list(v.rights) 
        objs = [tok for tok in rights if tok.dep_ in OBJECTS] 
        objs.extend(getObjsFromPrepositions(rights)) 
        if len(objs) > 0: 
         return v, objs 
    return None, None 

def getObjFromXComp(deps): 
    for dep in deps: 
     if dep.pos_ == "VERB" and dep.dep_ == "xcomp": 
      v = dep 
      rights = list(v.rights) 
      objs = [tok for tok in rights if tok.dep_ in OBJECTS] 
      objs.extend(getObjsFromPrepositions(rights)) 
      if len(objs) > 0: 
       return v, objs 
    return None, None 

def getAllSubs(v): 
    verbNegated = isNegated(v) 
    subs = [tok for tok in v.lefts if tok.dep_ in SUBJECTS and tok.pos_ != "DET"] 
    if len(subs) > 0: 
     subs.extend(getSubsFromConjunctions(subs)) 
    else: 
     foundSubs, verbNegated = findSubs(v) 
     subs.extend(foundSubs) 
    return subs, verbNegated 

def getAllObjs(v): 
    # rights is a generator 
    rights = list(v.rights) 
    objs = [tok for tok in rights if tok.dep_ in OBJECTS] 
    objs.extend(getObjsFromPrepositions(rights)) 

    potentialNewVerb, potentialNewObjs = getObjFromXComp(rights) 
    if potentialNewVerb is not None and potentialNewObjs is not None and len(potentialNewObjs) > 0: 
     objs.extend(potentialNewObjs) 
     v = potentialNewVerb 
    if len(objs) > 0: 
     objs.extend(getObjsFromConjunctions(objs)) 
    return v, objs 

def getAllObjsWithAdjectives(v): 
    # rights is a generator 
    rights = list(v.rights) 
    objs = [tok for tok in rights if tok.dep_ in OBJECTS] 

    if len(objs)== 0: 
     objs = [tok for tok in rights if tok.dep_ in ADJECTIVES] 

    objs.extend(getObjsFromPrepositions(rights)) 

    potentialNewVerb, potentialNewObjs = getObjFromXComp(rights) 
    if potentialNewVerb is not None and potentialNewObjs is not None and len(potentialNewObjs) > 0: 
     objs.extend(potentialNewObjs) 
     v = potentialNewVerb 
    if len(objs) > 0: 
     objs.extend(getObjsFromConjunctions(objs)) 
    return v, objs 

def findSVOs(tokens): 
    svos = [] 
    verbs = [tok for tok in tokens if tok.pos_ == "VERB" and tok.dep_ != "aux"] 
    for v in verbs: 
     subs, verbNegated = getAllSubs(v) 
     # hopefully there are subs, if not, don't examine this verb any longer 
     if len(subs) > 0: 
      v, objs = getAllObjs(v) 
      for sub in subs: 
       for obj in objs: 
        objNegated = isNegated(obj) 
        svos.append((sub.lower_, "!" + v.lower_ if verbNegated or objNegated else v.lower_, obj.lower_)) 
    return svos 

def findSVAOs(tokens): 
    svos = [] 
    verbs = [tok for tok in tokens if tok.pos_ == "VERB" and tok.dep_ != "aux"] 
    for v in verbs: 
     subs, verbNegated = getAllSubs(v) 
     # hopefully there are subs, if not, don't examine this verb any longer 
     if len(subs) > 0: 
      v, objs = getAllObjsWithAdjectives(v) 
      for sub in subs: 
       for obj in objs: 
        objNegated = isNegated(obj) 
        obj_desc_tokens = generate_left_right_adjectives(obj) 
        sub_compound = generate_sub_compound(sub) 
        svos.append((" ".join(tok.lower_ for tok in sub_compound), "!" + v.lower_ if verbNegated or objNegated else v.lower_, " ".join(tok.lower_ for tok in obj_desc_tokens))) 
    return svos 

def generate_sub_compound(sub): 
    sub_compunds = [] 
    for tok in sub.lefts: 
     if tok.dep_ in COMPOUNDS: 
      sub_compunds.extend(generate_sub_compound(tok)) 
    sub_compunds.append(sub) 
    for tok in sub.rights: 
     if tok.dep_ in COMPOUNDS: 
      sub_compunds.extend(generate_sub_compound(tok)) 
    return sub_compunds 

def generate_left_right_adjectives(obj): 
    obj_desc_tokens = [] 
    for tok in obj.lefts: 
     if tok.dep_ in ADJECTIVES: 
      obj_desc_tokens.extend(generate_left_right_adjectives(tok)) 
    obj_desc_tokens.append(obj) 

    for tok in obj.rights: 
     if tok.dep_ in ADJECTIVES: 
      obj_desc_tokens.extend(generate_left_right_adjectives(tok)) 

    return obj_desc_tokens 

Teraz kiedy przechodzą kwerendy takich jak:

from spacy.en import English 
parser = English() 

sentence = u""" 
Donald Trump is the worst president of USA, but Hillary is better than him 
""" 

parse = parser(sentence) 
print(findSVAOs(parse)) 

otrzymasz następujące:

[(u'donald trump', u'is', u'worst president'), (u'hillary', u'is', u'better')] 

Dziękuję @Krzysiek do rozwiązania zbyt, I w rzeczywistości nie można było wejść do biblioteki, aby ją zmodyfikować. Próbowałem raczej zmodyfikować powyższy link, aby rozwiązać mój problem.

+0

Miły, spacy jest naprawdę potężny;) Przy okazji zaimplementowałem w mojej bibliotece możliwość przekazania listy struktur rur, aby uzyskać więcej niż jedną krotkę na jedno zdanie;) Zastanawiam się, czy twoje rozwiązanie działa na bardzo różne przykłady instrukcji? – Krzysiek

+0

Tak, rzeczywiście. przeanalizuj wiele innych zależności innych niż tylko podmiot-czasownik-obiekty. Postaram się zaimplementować twoją bibliotekę dziś wieczorem i zbadać ją. –

Powiązane problemy