Der nltk3 Glattbutt Trainer api (ich es geschrieben habe) tut Training auf Sequenzen mit mehrdimensionalen beschriebenen Merkmale Token handhaben, wie Sie Ihre Daten ein Beispiel ist. Die praktischen Grenzen können jedoch schwerwiegend sein. Die Anzahl der möglichen Vorlagen beim multidimensionalen Lernen steigt drastisch an, und die aktuelle nltk-Implementierung des Brill-Trainers tauscht Speicher mit für Geschwindigkeit, ähnlich wie Ramshaw und Marcus 1994, "Erforschen der statistischen Ableitung von Transformationsregelsequenzen ...". Speicherverbrauch kann RIESIG sein und es ist sehr einfach, das System mehr Daten und/oder Vorlagen als kann es behandeln. Eine sinnvolle Strategie besteht darin, die Vorlagen nach der Häufigkeit zu ordnen, mit der sie gute Regeln erzeugen (siehe Beispiel print_template_statistics()). In der Regel können Sie die niedrigste Bewertung (z. B. 50-90%) mit wenig oder ohne Verlust der Leistung und einer erheblichen Verringerung der Trainingszeit verwerfen.
Eine andere oder zusätzliche Möglichkeit besteht darin, die Implementierung des ursprünglichen Algorithmus von Brill zu verwenden, der sehr unterschiedliche Speicher-Geschwindigkeits-Kompromisse hat; Es wird nicht indiziert und benötigt daher weniger Speicher. Es nutzt einige Optimierungen und ist eigentlich ziemlich schnell darin, die allerbesten Regeln zu finden, aber es ist im Allgemeinen sehr langsam gegen Ende des Trainings, wenn es viele konkurrierende Kandidaten mit niedriger Punktzahl gibt. Manchmal brauchst du diese sowieso nicht. Aus irgendeinem Grund scheint diese Implementierung in neueren nltks weggelassen worden zu sein, aber hier ist die Quelle (ich habe sie gerade getestet) .
Es gibt auch andere Algorithmen mit anderen Abwägungen und insbesondere die schnellen speichereffizienten Indizierung Algorithmen von Florian und Ngai 2000 (http://www.aclweb.org/anthology/N/N01/N01-1006.pdf) und probabilistischen Regel Probenahme von Samuel 1998 (https://www.aaai.org/Papers/FLAIRS/1998/FLAIRS98-045.pdf) wäre eine sinnvolle Ergänzung sein.Wie Sie auch bemerkt haben, ist die Dokumentation nicht vollständig und zu sehr auf die Wort-zu-Wort-Kennzeichnung ausgerichtet, und es ist nicht klar, wie man daraus generalisieren kann. Fixieren der Dokumente ist (auch) auf der Todo-Liste.
Allerdings war das Interesse für verallgemeinerte (nicht-POS-Tagging) Tbl in Nltk eher begrenzt (die völlig ungeeignete API von Nltk2 war seit 10 Jahren unberührt), also halten Sie nicht die Luft an. Wenn Sie ungeduldig werden, möchten Sie vielleicht mehr dedizierte Alternativen, insbesondere mutbl und fntbl (google ihnen, ich habe nur Reputation für zwei Links).
Wie auch immer, hier ist eine schnelle Skizze für nltk:
Zuerst wird eine hartkodierte Konvention in nltk ist, dass markierten Sequenzen (‚Tags‘ ein beliebiges Label bedeutet Sie Ihre Daten zuordnen möchten, nicht unbedingt kofinanzieren of-Speech) werden als Sequenzen von Paaren dargestellt, [(token1, tag1), (token2, tag2), ...]. Die Tags sind Strings; in viele grundlegende Anwendungen, so sind die Token. Zum Beispiel kann die Token Worte und die Saiten ihrer POS, wie in
[('And', 'CC'), ('now', 'RB'), ('for', 'IN'), ('something', 'NN'), ('completely', 'RB'), ('different', 'JJ')]
(Als beiseite, diese Sequenz-of-Token-Tag-Paare Konvention ist allgegenwärtig in nltk und seine Dokumentation, aber es sollte wohl besser sein als benannte Tupel ausgedrückt eher als Paare, so dass anstelle von
[token for (token, _tag) in tagged_sequence]
sagen Sie zum Beispiel sagen konnte
[x.token for x in tagged_sequence]
Der erste Fall nicht auf nicht-Paare, aber die zweite Taten duck typing so die tagged_sequence jede Sequenz von benutzerdefinierten Instanzen sein könnte, solange sie haben ein Attribut „Token“.)
Nun Sie könnten eine reichere Darstellung dessen haben, was ein Token bei Ihrer Entsorgung ist. Eine vorhandene Tagger-Schnittstelle (nltk.tag.api.FeaturesetTaggerI) erwartet jedes Token als Feature-Set und nicht als String. Dies ist ein Wörterbuch, das Feature-Namen zu Feature-Werten für jedes Element in der Sequenz zuordnet.
Eine markierte Sequenz kann dann folgendermaßen aussehen
[({'word': 'Pierre', 'tag': 'NNP', 'iob': 'B-NP'}, 'NNP'),
({'word': 'Vinken', 'tag': 'NNP', 'iob': 'I-NP'}, 'NNP'),
({'word': ',', 'tag': ',', 'iob': 'O' }, ','),
...
]
Es andere Möglichkeiten sind (wenn auch mit weniger Unterstützung in der übrigen nltk). Zum Beispiel könnten Sie ein benanntes Tupel für jedes Token oder eine benutzerdefinierte Klasse haben, mit der Sie einen beliebigen Betrag der dynamischen Berechnung zu Attributzugriff hinzufügen können (vielleicht mit @property, um eine konsistente Schnittstelle anzubieten).
Der Brill-Tagger muss nicht wissen, welche Ansicht Sie derzeit auf Ihren Tokens bereitstellen. Es ist jedoch erforderlich, dass Sie einen initialen Tagger bereitstellen, der Sequenzen von Tokens in Ihrer Repräsentation in Sequenzen von Tags aufnehmen kann. Sie können die vorhandenen Tagger in nltk.tag.sequential nicht direkt verwenden, da sie [(word, tag), ...] erwarten. Aber Sie können möglicherweise noch ausnutzen. Im folgenden Beispiel wird diese Strategie (in MyInitialTagger) und die Token-als-FeatureSet-Dictionary-Ansicht verwendet.
from __future__ import division, print_function, unicode_literals
import sys
from nltk import tbl, untag
from nltk.tag.brill_trainer import BrillTaggerTrainer
# or:
# from nltk.tag.brill_trainer_orig import BrillTaggerTrainer
# 100 templates and a tiny 500 sentences (11700
# tokens) produce 420000 rules and uses a
# whopping 1.3GB of memory on my system;
# brill_trainer_orig is much slower, but uses 0.43GB
from nltk.corpus import treebank_chunk
from nltk.chunk.util import tree2conlltags
from nltk.tag import DefaultTagger
def get_templates():
wds10 = [[Word([0])],
[Word([-1])],
[Word([1])],
[Word([-1]), Word([0])],
[Word([0]), Word([1])],
[Word([-1]), Word([1])],
[Word([-2]), Word([-1])],
[Word([1]), Word([2])],
[Word([-1,-2,-3])],
[Word([1,2,3])]]
pos10 = [[POS([0])],
[POS([-1])],
[POS([1])],
[POS([-1]), POS([0])],
[POS([0]), POS([1])],
[POS([-1]), POS([1])],
[POS([-2]), POS([-1])],
[POS([1]), POS([2])],
[POS([-1, -2, -3])],
[POS([1, 2, 3])]]
iobs5 = [[IOB([0])],
[IOB([-1]), IOB([0])],
[IOB([0]), IOB([1])],
[IOB([-2]), IOB([-1])],
[IOB([1]), IOB([2])]]
# the 5 * (10+10) = 100 3-feature templates
# of Ramshaw and Marcus
templates = [tbl.Template(*wdspos+iob)
for wdspos in wds10+pos10 for iob in iobs5]
# Footnote:
# any template-generating functions in new code
# (as opposed to recreating templates from earlier
# experiments like Ramshaw and Marcus) might
# also consider the mass generating Feature.expand()
# and Template.expand(). See the docs, or for
# some examples the original pull request at
# https://github.com/nltk/nltk/pull/549
# ("Feature- and Template-generating factory functions")
return templates
def build_multifeature_corpus():
# The true value of the target fields is unknown in testing,
# and, of course, templates must not refer to it in training.
# But we may wish to keep it for reference (here, truepos).
def tuple2dict_featureset(sent, tagnames=("word", "truepos", "iob")):
return (dict(zip(tagnames, t)) for t in sent)
def tag_tokens(tokens):
return [(t, t["truepos"]) for t in tokens]
# connlltagged_sents :: [[(word,tag,iob)]]
connlltagged_sents = (tree2conlltags(sent)
for sent in treebank_chunk.chunked_sents())
conlltagged_tokenses = (tuple2dict_featureset(sent)
for sent in connlltagged_sents)
conlltagged_sequences = (tag_tokens(sent)
for sent in conlltagged_tokenses)
return conlltagged_sequences
class Word(tbl.Feature):
@staticmethod
def extract_property(tokens, index):
return tokens[index][0]["word"]
class IOB(tbl.Feature):
@staticmethod
def extract_property(tokens, index):
return tokens[index][0]["iob"]
class POS(tbl.Feature):
@staticmethod
def extract_property(tokens, index):
return tokens[index][1]
class MyInitialTagger(DefaultTagger):
def choose_tag(self, tokens, index, history):
tokens_ = [t["word"] for t in tokens]
return super().choose_tag(tokens_, index, history)
def main(argv):
templates = get_templates()
trainon = 100
corpus = list(build_multifeature_corpus())
train, test = corpus[:trainon], corpus[trainon:]
print(train[0], "\n")
initial_tagger = MyInitialTagger('NN')
print(initial_tagger.tag(untag(train[0])), "\n")
trainer = BrillTaggerTrainer(initial_tagger, templates, trace=3)
tagger = trainer.train(train)
taggedtest = tagger.tag_sents([untag(t) for t in test])
print(test[0])
print(initial_tagger.tag(untag(test[0])))
print(taggedtest[0])
print()
tagger.print_template_statistics()
if __name__ == '__main__':
sys.exit(main(sys.argv))
Das obige Setup erstellt einen POS-Tagger.Wenn Sie stattdessen ein anderes Attribut anvisieren möchten, z. B. einen IOB-Tagger erstellen, benötigen Sie einige kleine Änderungen , damit auf das Zielattribut (das Sie sich als Lesen-Schreiben vorstellen können) von der 'Tag'-Position aus zugegriffen wird Ihr Korpus [(Token, Tag), ...] und alle anderen Attribute (die Sie sich als schreibgeschützt vorstellen können) werden von der Position 'token' aus aufgerufen. Zum Beispiel:
1) konstruiert Ihren Korpus [(Token-Tag), (Token-Tag), ...] für IOB Tagging
def build_multifeature_corpus():
...
def tuple2dict_featureset(sent, tagnames=("word", "pos", "trueiob")):
return (dict(zip(tagnames, t)) for t in sent)
def tag_tokens(tokens):
return [(t, t["trueiob"]) for t in tokens]
...
2) entsprechend den Anfang Tagger ändern
...
initial_tagger = MyInitialTagger('O')
...
3) ändern sich die Merkmalsextraktionsklassendefinitionen
class POS(tbl.Feature):
@staticmethod
def extract_property(tokens, index):
return tokens[index][0]["pos"]
class IOB(tbl.Feature):
@staticmethod
def extract_property(tokens, index):
return tokens[index][1]