Werkzeuge & Bibliotheken
Lucene und Natural Language Toolkit
Martin Finke, OS Computerlinguistik, SS 2014
Gliederung
Lucene
• Überblick
• Tokenisierung
• Filterung
• Zusammengesetzte Wörter
• Stemming
Natural Language Toolkit (NLTK)
• Suche, Kontext
• Statistiken
• Tokenisierung
• Part-of-speech Tagging
• Parser
• Merkmalstrukturen
Apache Lucene
• Textindizierung und -suche
• keine Satzerkennung
• akzeptiert nur reinen Text, PDF/Word-Formate müssen außerhalb geparst werden
• enthält viele Klassen für Sprachverarbeitung
“‘ Zahl isthyphenation valuean dieser Position (nur bei Zeilenumbruch verwen- det)
Muster erstellt aus Wortliste (~ 43.000 Einträge) # Beispiel: HyphenationCom- poundWordTokenFilter benutzt Worttrennungsgrammatik
Attribute wie erwähnt:
// CompoundWordTokenFilterBase.java:70
CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
PositionIncrementAttribute posIncAtt = addAttribute(PositionIncrementAttribute.class);
Aus einem Eingabetoken werden in der Ausgabe ein oder mehrere Token:
// CompoundWordTokenFilterBase.java:105 boolean incrementToken() {
if (!tokens.isEmpty()) {
// verarbeite "tokens" hier...
return true;
}
if (input.incrementToken()) {
// Zerlege das (neue) Eingabetoken:
decompose();
return true;
} }
Token-Zerlegung
// HyphenationCompoundWordTokenFilter.java:169 void decompose() {
// hyphenator ist ein HyphenationTree
Hyphenation hyphens = hyphenator.hyphenate(termAtt.buffer(),
0, termAtt.length(), 1, 1);
if (hyphens == null) { return;
}
int[] hyp = hyphens.getHyphenationPoints();
for (int i = 0; i < hyp.length; ++i) { // ...
Siehe auch Zeile 195, 214 # Stemming >Snowball is a smallstring processing language designed for creating stemming algorithms for use in Information Retrieval.
verarbeitet nur Kleinbuchstaben (d.h. vorher LowerCaseFilter) Snowball → Java Compiler
// GermanMinimalStemFilter.java:47 boolean incrementToken() {
if (input.incrementToken()) { if (!keywordAttr.isKeyword()) {
* int newlen = stemmer.stem(termAtt.buffer(), termAtt.length());
termAtt.setLength(newlen);
} } }
Keyword: Flag auf Token, um z.B. Stemming zu vermeiden
Stemming
Beispiel: Entfernung desnen-Suffix
// GermanMinimalStemmer.java:64 int stem(char s[], int len) {
// ...
return len - 3;
// ...
Entfernt außerdem Umlaute und Suffixe: en,se,es,er,n,e,sundr.
Besser: A Fast and Simple Stemming Algorithm for German Words(Jörg Cau- manns)
implementiert inGermanStemmer.java:644 (label break, eigentlicher Algorithmus: r_prelude)
Synonyme
SynonymFilter, bekommt eineSynonymMap(beide experimental) SynonymMapenthält zwei Hashtabellen:
fst : word → list<ord>
words : ord → word words = {
0 = Eisschrank, 1 = Gefrierschrank, 2 = Gefriertruhe, 3 = Kühltruhe, 4 = Tiefkühlschrank, 5 = Tiefkühltruhe }
fst = {
Eisschrank = [ 1,2,3,4,5], Gefrierschrank = [0, 2,3,4,5], Gefriertruhe = [0,1, 3,4,5], Kühltruhe = [0,1,2, 4,5], Tiefkühlschrank = [0,1,2,3, 5], Tiefkühltruhe = [0,1,2,3,4 ] }
https://sites.google.com/site/kevinbouge/synonyms-lists
Natural Language Toolkit (NLTK)
Python-Bibliothek mit Modulen zur Verarbeitung natürlicher Sprache:
$ python
from __future__ import division import nltk, re, pprint
from nltk.book import * Suche:
text4.concordance("terror") text5.concordance("lol") Wörter im gleichen Kontext:
text1.similar("monstrous") text2.similar("monstrous")
# Kontexte, in denen manchmal "monstrous"
# und manchmal "very" vorkommt:
text2.common_contexts(["monstrous", "very"]) Wortfilter:
[w for w in set(text1) if len(w) > 15]
Verteilungsgraphen
$ pip install numpy matplotlib
text4.dispersion_plot(["citizens", "democracy", "freedom", "duties", "America"])
Vokabelmenge und -vielfalt
sorted(set(text4))
len(text4) / (len(set(text4)))
# len() ist Token-Anzahl, NICHT String-Länge
Kollokation
das gehäufte benachbarte Auftreten von Wörtern
text4.collocations()
Tokenisierung
faust1 = open("Goethe/Faust 1.txt", 'rU').read() tokens = nltk.word_tokenize(faust1)
tokens[:100]
Part-of-speech Tagging
text = nltk.word_tokenize("They refuse to permit us to obtain the refuse permit") nltk.pos_tag(text)
# Bedeutung der Tags:
nltk.help.upenn_tagset()
Parser: Syntax-Kategorien Top-Down-Parser
testgrammar = nltk.data.load('file:testgrammar.cfg') sent = "Mary saw Bob"
rd_parser = nltk.RecursiveDescentParser(testgrammar) rd_parser.trace() # Zeige Einzelschritte
for tree in rd_parser.parse(sent.split()):
print (tree)
(S (NP Mary) (VP (V saw) (NP Bob)))
Demo:
nltk.app.rdparser_app.app()
Bottom-Up-Parser
sr_parse = nltk.ShiftReduceParser(testgrammar) sr_parse.trace()
for tree in sr_parse.parse(sent.split()):
print (tree)
Demo:
nltk.app.srparser_app.app()
• kein Backtracking (evtl. kein Ergebnis)
• findet nur höchstens ein Ergebnis
Merkmalstrukturen
fs1 = nltk.FeatStruct(PER=3, NUM='pl', GND='fem') fs2 = nltk.FeatStruct(POS='N', AGR=fs1)
print (fs2)
Unifikation
fs1 = nltk.FeatStruct(NUMBER=74, STREET='rue Pascal') fs2 = nltk.FeatStruct(CITY='Paris')
print (fs1.unify(fs2)) # symmetrisch funktioniert auch verschachtelt
Fall und Genus
nltk.data.show_cfg('grammars/book_grammars/german.fcfg') OBJCASEgibt an, welchen Fall das Objekt bekommt.
sent = 'ich folge den Katzen'
cp = nltk.load_parser('grammars/book_grammars/german.fcfg') for tree in cp.parse(sent.split()):
print (tree)
Kategorie vonKatze: N[AGR=[GND=fem, NUM=sg, PER=3]]
Uneindeutigkeit
*sent = 'ich folge den Katze' cp._trace = 2
# for-Schleife wie oben
Das Wortdenist hier nicht eindeutig:
Det[AGR=[GND='masc', NUM='sg', PER=3], CASE='acc'] -> 'den' * Det[AGR=[NUM='pl', PER=3], CASE='dat'] -> 'den' *
AGRvonKatzelässt sich nicht damit unifizieren.
Klassifikation
from nltk.corpus import names import random
names = ([(name, 'male') for name in names.words('male.txt')] + [(name, 'female') for name in names.words('female.txt')]) random.shuffle(names)
just_names = [name for name, gender in names]
'Paul' in just_names 'Brooklyn' in just_names def gender_features(word):
return {'first_letter': word[0].lower(), 'last_letter': word[-1].lower(), 'length': len(word)}
featuresets = [(gender_features(n), g) for (n,g) in names]
train_set, test_set = featuresets[500:], featuresets[:500]
*classifier = nltk.NaiveBayesClassifier.train(train_set)
nltk.classify.accuracy(classifier, test_set) classifier.show_most_informative_features(40) classifier.classify(gender_features('Brooklyn'))
Frage → SQL
question = 'What cities are located in China'
sqlparser = nltk.load_parser('grammars/book_grammars/sql0.fcfg') trees = sqlparser.nbest_parse(question.split())
answer = trees[0].node['SEM']
query = ' '.join(answer) print query
nltk.data.show_cfg('grammars/book_grammars/sql0.fcfg')
Quellen
Lucene API: http://lucene.apache.org/core/4_8_0/core/
Worttrennungsbibliotheken: http://sourceforge.net/projects/offo/
Bird, Klein, Loper: Natural Language Processing with Python(O’Reilly 2009) Uni Stanford,Natural Language Processing: http://www.coursera.org/course/nlp