In [1]:
import nltk
nltk.download()
Out[1]:
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.
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]:
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]:
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?
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)
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))