Übungsblatt 10

Präsenzaufgaben

Aufgabe 1     Herunterladen von Ressourcen

Laden Sie sich zunächst die Ressource corpora/treebank über den NLTK Download-Manager herunter.


In [1]:
import nltk
nltk.download()


showing info https://raw.githubusercontent.com/nltk/nltk_data/gh-pages/index.xml
Out[1]:
True

Aufgabe 2     Von Daten zu Regelwahrscheinlichkeiten

Gegeben sei folgende kontextfreie Grammatik:


In [2]:
cfg = """
S -> NP VP
VP -> V NP PP
VP -> V NP
NP -> DET N
NP -> DET N PP
PP -> P NP

DET -> "the" | "a"
N -> "boy" | "woman" | "telescope"
V -> "saw"
P -> "with"
"""

Sie modelliert sehr einfache Sätze der Form SBJ saw OBJ mit optionaler Präpositionalphrase am Ende. Diese Präpositionalphrase kann entweder der näheren Bestimmung des Objekts oder der näheren Bestimmung der in der Verbalphrase ausgedrückten Handlung dienen.

Im folgenden sollen aus der Penn Treebank Wahrscheinlichkeiten für die einzelnen Regeln extrahiert werden, um dieser Ambiguität Herr zu werden.

(a) Relevante Konstruktionen erkennen

Nutzen das im NLTK enthaltene Sample der Penn Treebank (nach Installation unter nltk.corpus.treebank zu finden) zunächst zur Identifikation der für eine Disambiguierung nützlichen (Teil-)bäume der Penn Treebank.

Gegebenfalls müssen Sie die obige Grammatik den Strukturen, die Sie finden, anpassen.

Hinweis: Sie können sich bei der Analyse auf die jeweils 30 häufigsten Konstruktionen der Baumbank beschränken.


In [3]:
from collections import defaultdict
from nltk.corpus import treebank

def find_relevant_constructions(lhs, only_with=None):
    lhs_nt = nltk.grammar.Nonterminal(lhs)
    should_filter = only_with is not None
    if should_filter:
        filter_by = list(map(nltk.grammar.Nonterminal, only_with))
        def passes_filter(tup):
            for f in filter_by:
                if f not in tup:
                    return False
            return True

    # iterate over all trees and all their productions
    # and count those which pass the filter
    counter = defaultdict(int)
    for tree in treebank.parsed_sents():
        for prod in tree.productions():
            if prod.lhs() == lhs_nt:
                # either we do not have to filter or the rhs has to pass the filter
                if not should_filter or passes_filter(prod.rhs()):
                    counter[prod.rhs()] += 1

    # sort right sides of productions according to frequency
    return [ (k, counter[k]) for k in sorted(counter.keys(), key=counter.__getitem__) ]

In [4]:
constructions = find_relevant_constructions('VP', only_with=["NP"])
constructions[-30:]


Out[4]:
[((VB, NP, PP-DIR), 18),
 ((VBN, NP, NP-TMP), 18),
 ((VB, NP, SBAR-ADV), 20),
 ((VBG, NP, PP-LOC), 20),
 ((VB, NP, ADVP), 21),
 ((VBZ, NP, PP-CLR), 23),
 ((VBN, NP, ADVP-TMP), 24),
 ((VBD, NP, PP), 24),
 ((VB, NP, PP-LOC), 24),
 ((VBD, NP, PP-TMP), 24),
 ((VB, NP, ADVP-MNR), 25),
 ((VB, NP, PP-MNR), 26),
 ((VB, PRT, NP), 28),
 ((VBN, NP, NP), 28),
 ((VBN, NP, PP-LOC-CLR), 29),
 ((VB, NP, PP), 34),
 ((VB, NP, PP-TMP), 38),
 ((VBG, NP, PP-CLR), 48),
 ((VBN, NP, PP-TMP), 52),
 ((VBN, NP, PP-LOC), 59),
 ((VBD, NP, PP-CLR), 66),
 ((VB, NP, PP-CLR), 88),
 ((VBN, NP, PP), 170),
 ((VBN, NP, PP-CLR), 178),
 ((VBP, NP), 185),
 ((VBN, NP), 250),
 ((VBZ, NP), 261),
 ((VBG, NP), 375),
 ((VBD, NP), 378),
 ((VB, NP), 805)]

Zählen Sie nun, wie oft die jeweiligen Konstruktionen in der Penn Treebank vorkommen und berechnen Sie die relativen Häufigkeiten als Approximation der Regelwahrscheinlichkeiten. Das Vorgehen wird in folgender Formel veranschaulicht:

$$P(V, N\!P, P\!P \mid V\!P) = \dfrac{count(V\!P \rightarrow V\:N\!P\:P\!P)}{count(V\!P \rightarrow \setminus*)}$$

In [5]:
# the following are numbers counted from iteratively executing the previous code cell
# with different parameters for find_relevant_constructions
vp_with_pp_frq = 170 + 34 + 24 + 11 + 4 + 4
vp_without_frq = 805 + 378 + 375 + 261 + 250 + 185
vp_with_pp = vp_with_pp_frq / (vp_with_pp_frq + vp_without_frq)
vp_without = vp_without_frq / (vp_with_pp_frq + vp_without_frq)

np_with_pp_frq = 2188
np_without_frq = 2020
np_with_pp = np_with_pp_frq / (np_with_pp_frq + np_without_frq)
np_without = np_without_frq / (np_with_pp_frq + np_without_frq)

det_the_frq = 4038
det_a_frq = 1874
det_the = det_the_frq / (det_the_frq + det_a_frq)
det_a = det_a_frq / (det_the_frq + det_a_frq)

(vp_with_pp, vp_without, np_with_pp, np_without, det_the, det_a)


Out[5]:
(0.09876049580167932,
 0.9012395041983207,
 0.5199619771863118,
 0.48003802281368824,
 0.6830175913396481,
 0.3169824086603518)

(b) Erstellen einer PCFG

Die aus den Daten extrahierten relativen Häufigkeiten sollen nun zur Erstellung einer probabilistischen kontextfreien Grammatik (PCFG) genutzt werden.


In [6]:
# {} are placeholders that are filled by the .format method
pcfg = """
S -> NP VP     [1.0]
VP -> V NP PP  [{}]
VP -> V NP     [{}]
NP -> DET N    [{}]
NP -> NP PP [{}]
PP -> P NP     [1.0]

DET -> "the"     [{}]
DET -> "a"       [{}]
N -> "boy"       [0.4]
N -> "woman"     [0.4]
N -> "telescope" [0.2]
V -> "saw"       [1.0]
P -> "with"      [1.0]
""".format(
    vp_with_pp, vp_without, np_without,
    np_with_pp, det_the, det_a
)
grammar = nltk.PCFG.fromstring(pcfg)

In [7]:
from IPython.display import display

parser = nltk.ViterbiParser(grammar)
for tree in parser.parse("the boy saw a woman with a telescope".split()):
    display(tree)


Wenn Sie sich die extrahierten Wahrscheinlichkeiten und das disambiguierte Ergebnis ansehen, überrascht Sie dann das Ergebnis der Syntaxanalyse?

Aufgabe 3     Weiterverarbeitung syntaktischer Analysen

In dieser Aufgabe sollen Sie die Ausgaben eines state-of-the-art-Parsers, nämlich des Stanford Parsers, weiterverarbeiten.

Mit dem Ziel, Sie erst einmal mit den typischen Strukturen einer solchen Aufgabe vertraut zu machen, sollen Sie in dieser Aufgabe lediglich entscheiden, ob die Eingabe einen Infinitivsatz mit Objekt enthält.

Zur Klarheit betrachten Sie die folgenden positiven und negativen Beispiele:


In [8]:
pos_examples = [
    "Er beabsichtigt , den Kuchen ganz alleine zu essen .",
    "Er behauptet , ihn gesehen zu haben ."
]
neg_examples = [
    "Er glaubt , nach Hause zu fliegen .",
    "Zu fliegen ist schön .",
    "Er will gehen ."
]

Zur Erinnerung die wichtigsten Schritte zur Nutzung des Stanford Parsers im NLTK:

Initialisierung:


In [9]:
from nltk.parse.stanford import StanfordParser

PATH_TO_CORE = r"/pfad/zu/stanford-corenlp-full-2017-06-09"
PATH_TO_GERMAN_MODEL = r"/pfad/zu/deutschem/modell"
jar = PATH_TO_CORE + '/' + "stanford-corenlp-3.8.0.jar"
model = PATH_TO_GERMAN_MODEL + '/' + "stanford-german-corenlp-2017-06-09-models.jar"

Parser-Erstellung:


In [10]:
parser = StanfordParser(
    jar, model,
    model_path="edu/stanford/nlp/models/lexparser/germanPCFG.ser.gz"
)

Parsen:


In [11]:
for sentence in pos_examples + neg_examples:
    tree_list = list(parser.raw_parse(sentence))
    tree_list[0].pretty_print(unicodelines=True)


                               ROOT                                      
                                │                                         
                                S                                        
 ┌────────┬────────┬────────────┴─────┬────────────────────────────────┐  
 │        │        │                  VP                               │ 
 │        │        │       ┌──────────┴────┬─────────────────┐         │  
 │        │        │       NP             AVP                VZ        │ 
 │        │        │   ┌───┴────┐     ┌────┴─────┐      ┌────┴────┐    │  
PPER    VVFIN      $, ART       NN   ADV        ADV   PTKZU     VVINF  $.
 │        │        │   │        │     │          │      │         │    │  
 Er  beabsichtigt  ,  den     Kuchen ganz     alleine   zu      essen  . 

                   ROOT                                    
                    │                                       
                    S                                      
 ┌───────┬──────┬───┴────────────────┬───────────────────┐  
 │       │      │                    VP                  │ 
 │       │      │        ┌───────────┴─────────┐         │  
 │       │      │        VP                    │         │ 
 │       │      │   ┌────┴─────┐               │         │  
 │       │      │   NP         │               VZ        │ 
 │       │      │   │          │          ┌────┴────┐    │  
PPER   VVFIN    $, PPER       VVPP      PTKZU     VAINF  $.
 │       │      │   │          │          │         │    │  
 Er  behauptet  ,  ihn      gesehen       zu      haben  . 

                ROOT                                    
                 │                                       
                 S                                      
 ┌─────┬─────┬───┴──────────────┬─────────────────────┐  
 │     │     │                  VP                    │ 
 │     │     │        ┌─────────┴─────────┐           │  
 │     │     │        PP                  VZ          │ 
 │     │     │   ┌────┴────┐         ┌────┴─────┐     │  
PPER VVFIN   $, APPR       NN      PTKZU      VVINF   $.
 │     │     │   │         │         │          │     │  
 Er  glaubt  ,  nach     Hause       zu      fliegen  . 

                   ROOT          
                    │             
                    S            
       ┌────────────┼─────┬────┐  
       VZ           │     │    │ 
  ┌────┴─────┐      │     │    │  
PTKZU      VVINF  VAFIN  ADJD  $.
  │          │      │     │    │  
  Zu      fliegen  ist  schön  . 

      ROOT          
       │             
       S            
 ┌─────┼─────┬────┐  
 │     │     VP   │ 
 │     │     │    │  
PPER VMFIN VVINF  $.
 │     │     │    │  
 Er   will gehen  . 


In [12]:
def classify(sentence):
    tree = list(parser.raw_parse(sentence))[0]
    # find all the subtrees that are labeled as 'VP'
    for vp in tree.subtrees(filter=lambda t: t.label() == 'VP'):
        # can we find a subtree in the VP that is an infinitiv ?
        has_infs = list(vp.subtrees(filter=lambda t: t.label().endswith("INF")))
        # can we find a subtree in the VP that is an NP ?
        has_nps = list(vp.subtrees(filter=lambda t: t.label() == 'NP'))
        # if we have found both: we have a hit !
        if has_infs and has_nps:
            return True
    # obviously we did not find anything !
    return False

Die Ausgabe sollte sein:

True True False False False


In [13]:
for p in pos_examples:
    print(classify(p))
for n in neg_examples:
    print(classify(n))


True
True
False
False
False