From 053fad1ec61e2ca4c61fd2a01407c0b56ea72ec3 Mon Sep 17 00:00:00 2001 From: Lilian Gasser <gasserli@ethz.ch> Date: Mon, 14 Jan 2019 17:31:15 +0100 Subject: [PATCH] train NER from txt file --- notebooks/NER_train_first-attempt.ipynb | 84 +----------- src/python/example_train-ner.py | 166 ++++-------------------- src/python/utils_ner.py | 36 +++++ 3 files changed, 64 insertions(+), 222 deletions(-) diff --git a/notebooks/NER_train_first-attempt.ipynb b/notebooks/NER_train_first-attempt.ipynb index 46250c1e..83c231a6 100644 --- a/notebooks/NER_train_first-attempt.ipynb +++ b/notebooks/NER_train_first-attempt.ipynb @@ -18746,78 +18746,8 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 35, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "list_ents = alldicts['0_Bührer Gerald (R, SH), Berichterstatter_0']['ents']\n", - "list_ents" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "def get_entitities_in_training_format(list_ents):\n", - "\n", - " list_ents_train = []\n", - " for dict_ent in list_ents:\n", - " #print(dict_ent)\n", - " start = dict_ent['start']\n", - " end = dict_ent['end']\n", - " label = dict_ent['label']\n", - " if label == 'PERSON':\n", - " label = 'PER'\n", - " if label == 'ORGANIZATION':\n", - " label = 'ORG'\n", - " if label == 'LOCATION':\n", - " label = 'LOC'\n", - " tpl_ent = (start, end, label)\n", - "\n", - " list_ents_train.append(tpl_ent)\n", - " \n", - " return list_ents_train" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_entitities_in_training_format(list_ents)" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "scrolled": false - }, "outputs": [ { "data": { @@ -20116,21 +20046,13 @@ " {'entities': []})]" ] }, - "execution_count": 28, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "train_data = []\n", - "for speaker, somedict in alldicts.items():\n", - " text = somedict['text']\n", - " ents_as_list = somedict['ents']\n", - " ents_in_dict = {}\n", - " ents_in_dict['entities'] = get_entitities_in_training_format(ents_as_list)\n", - " tpl = (text, ents_in_dict)\n", - " train_data.append(tpl)\n", - "train_data" + "utils_ner.transform_to_training_format(alldicts)" ] }, { diff --git a/src/python/example_train-ner.py b/src/python/example_train-ner.py index 8135d1d4..4cad6a69 100644 --- a/src/python/example_train-ner.py +++ b/src/python/example_train-ner.py @@ -16,142 +16,31 @@ import random from pathlib import Path import spacy from spacy.util import minibatch, compounding +import sys +sys.path.append("./src/python") +from utils_ner import read_from_txt, transform_to_training_format -# training data -TRAIN_DATA = [ - ("Who is Shaka Khan?", {"entities": [(7, 17, "PERSON")]}), - ("I like London and Berlin.", {"entities": [(7, 13, "LOC"), (18, 24, "LOC")]}), -] - -TRAIN_DATA = [(' Gestatten Sie mir zunächst zwei Vorbemerkungen.', {'entities': []}), - (' Die Finanzkommission hat an ihrer letzten Sitzung, als sie vom Bericht der Arbeitsgruppe über die Eidgenössische Versicherungskasse Kenntnis genommen und die dringlichen Vorstösse beschlossen hat, die beiden Kommissionssprecher bestimmt.', - {'entities': [(110, 124, 'ORG'), (126, 144, 'ORG')]}), - (' Aufgrund der Tatsache, dass die Antwort des Bundesrates ja erst zu Beginn dieser Woche eingetroffen ist und die Finanzkommission erst nächste Woche wieder Sitzung hat, werden Herr Narbel und ich bezüglich den Antworten des Bundesrates nicht als Kommissionssprecher sprechen, sondern als Fraktionssprecher.', - {'entities': [(48, 59, 'ORG'), (195, 201, 'PER')]}), - ('Eine zweite Vorbemerkung: Es ist heute morgen anlässlich des Verschiebungsantrages zur Traktandenliste der Vorwurf erhoben worden, die Finanzkommission würde dieses Geschäft benutzen, um mit dem Finanzminister alte Abrechnungen zu begleichen.', - {'entities': []}), - (' Es ist auch im weiteren durch den Präsidenten der Finanzkommission notabene ausgeführt worden, dass es sich eigentlich nicht um ein derart bedeutendes Geschäft handle, dass dieses heute vorgezogen auf die Traktandenliste gehöre.', - {'entities': []}), - ('Ich möchte hier in aller Form in Abrede stellen, dass die Finanzkommission bei der Behandlung dieses Geschäftes sich von irgendwelchen parteipolitischen Hickhack-Überlegungen leiten liess, im Gegenteil.', - {'entities': []}), - ("Die Finanzkommission musste sich Caisse fédérale d'assurance (CFA) 17 mars 1994 fragen, ob es denn nicht Aufgabe des Parlamentes im Sinne der Kontrolle sei, Fragen zur Diskussion zu stellen, Anträge und Vorschläge zu einer Angelegenheit zu machen, die nicht erst seit kurzem, sondern wie Sie wissen eigentlich seit 1988 zur Diskussion steht Ja, wir die klare Mehrheit der Finanzkommission glauben, dass es eben keine Bagatelle ist, wenn die Rechnung der Eidgenössischen Versicherungskasse seit 1988 nie mehr ordnungsgemäss testiert werden konnte, wenn seit 1988 mehrere Anläufe im Bereiche der Informatik gemacht worden und teilweise fehlgeschlagen sind, wenn bei den Verbuchungen zeitweilig Arbeitsrückstände von bis zu 18 Monaten festzustellen waren und wenn schliesslich Saldodifferenzen, kumuliert in dreistelliger Millionenzahl, festgestellt werden mussten.", - {'entities': []}), - ('Wir glauben daher, dass es Aufgabe der Finanzkommissionen und des Parlamentes ist, sich darum zu kümmern, nachdem bereits in früheren Jahren durch die Finanzdelegation beider Räte und durch die Geschäftsprüfungskommissionen scheinbar ohne grossen Erfolg auf diese Missstände hingewiesen worden ist.', - {'entities': []}), - ('Es hat ebenfalls nichts mit parteipolitischem Hickhack zu tun, wenn angesichts der grossen Dauer, während der diese Unvollkommenheiten und Missstände angedauert haben, die Frage fachlicher Unzulänglichkeiten und der politischen Verantwortung gestellt werden muss.', - {'entities': []}), - ('Den Vorsorgeeinrichtungen der ersten und der zweiten Säule kommt in verschiedener Hinsicht eine Schlüsselrolle zu.', - {'entities': []}), - ('Das Vertrauen der Versicherten in die entsprechenden Organisationen hat für die soziale Stabilität in diesem Lande, das wissen wir alle, eine hohe Bedeutung.', - {'entities': []}), - ('Wenn wir die Mängel, aber auch die grundsätzlichen Probleme der Eidgenössischen Versicherungskasse ansehen, so gibt es eigentlich zwei Problemkreise.', - {'entities': []}), - ('Zum einen geht es um Probleme in der Buchführung, der Informatik, um Probleme, die sicher im Zusammenhang auch mit Führungsfragen stehen, und daraus wiederum haben enorme Arbeitsrückstände resultiert, die die Versicherten wegen der Verzögerung bei der Bearbeitung von Dossiers am eigenen Leibe erfahren haben.', - {'entities': []}), - ('Zum anderen das hat nichts mit Fehlern der EVK zu tun geht es aber um einige grundsätzliche Fragestellungen.', - {'entities': [(44, 47, 'ORG')]}), - (' Es geht um folgendes: 1.', {'entities': []}), - ('den Problemkreis der Beziehungen zu den Regiebetrieben; 2.', - {'entities': []}), - ('die rechnungsmässige Stellung der EVK gegenüber der Rechnung des Bundes; 3.', - {'entities': [(38, 41, 'ORG')]}), - ('die Versicherungsform, sprich Leistungs oder Beitragsprimat; 4.', - {'entities': []}), - ('die Frage des Deckungsgrades, d. h., wieviel vorhandenes Kapital in Prozenten des technisch notwendigen Kapitals vorhanden oder nicht vorhanden sein muss.', - {'entities': []}), - ('Zum Zeitpunkt, als die Arbeitsgruppe der Finanzkommissionen beider Räte eingesetzt worden ist dies war im Frühling 1993 -, mussten wir feststellen, dass man immer noch mit deutlich über 10000 Versicherungsdossiers im Rückstand war und dass nach wie vor fehlende Saldonachweise sowie erhebliche Saldodifferenzen zwischen dem Regiebetrieb PTT, der Bundeskasse und der EVK vorhanden waren.', - {'entities': [(386, 389, 'ORG')]}), - ('Wir mussten auch aufgrund der Berichte der Eidgenössischen Finanzkontrolle noch im Sommer 1993 zur Kenntnis nehmen, dass mit einergemäss der Finanzkontrolle raschen Behebung dieser Mängel nicht gerechnet werden könne.', - {'entities': [(43, 58, 'ORG'), (60, 75, 'ORG')]}), - ('Nachdem über Jahre hinweg immer wieder Versprechungen gemacht worden sind, dass die Mängel behoben werden könnten, ist die Finanzkommission klar der Auffassung, dass es nicht mehr angehen kann, das auf der Zeitachse nun beliebig weiter hinauszuschieben.', - {'entities': []}), - ('Was die schriftliche Stellungnahme des Bundesrates zu den dringlichen Interpellationen der Finanzkommission angeht, gestatte ich mir nun einige Bemerkungen, und zwar im Namen der Fraktion, wie ich gesagt habe, und nicht im Namen der Finanzkommission.', - {'entities': [(41, 52, 'ORG')]}), - ('Wir begrüssen es und nehmen mit Genugtuung zur Kenntnis, dass in den letzten Quartalen weitere Fortschritte, vor allem im Bereich der Informatik, gemacht worden sind Fortschritte auch in bezug auf die Schnittstellen bei den Lohndaten zwischen dem Arbeitgeber und der EVK Wir sind aber von der Antwort des Bundesrates in folgenden fünf Punkten nicht befriedigt: 1.', - {'entities': [(273, 276, 'ORG'), (311, 322, 'ORG')]}), - ('Es wird keine feste Zusage gemacht, wann der Bundesrat damit rechnet, dass die Rechnung der EVK ordnungsgemäss abgenommen werden kann.', - {'entities': [(46, 55, 'ORG')]}), - ('2.', {'entities': []}), - ('Die in der Antwort dargelegten Personalmassnahmen vermögen in dieser Form aufgrund unserer Beurteilung nicht zu genügen.', - {'entities': []}), - ('3.', {'entities': []}), - ('Bezüglich der Überprüfung der rechtlichen Form der EVKStatuten sind wir von der Antwort der Landesregierung nicht überzeugt.', - {'entities': []}), - (' Es ist ja so, dass die Statuten der EVK dem Parlament zur Genehmigung lediglich vorgelegt werden können.... (Zwischenrufe Marti Werner und Leuenberger Ernst) Wir haben mit dem Büro klar abgemacht und den Präsidenten der Finanzkommission gestern informiert das habe ich gesagt -, dass wir beide, Herr Narbel und ich, nicht befugt sind, zu den Antworten des Bundesrates als Kommissionssprecher Stellung zu nehmen, weil wir diese Antworten in der Kommission nicht behandelt haben, dass wir dazu aber als Fraktionsvertreter sprechen.', - {'entities': [(37, 40, 'ORG'), - (124, 129, 'PER'), - (130, 136, 'PER'), - (141, 152, 'PER'), - (307, 313, 'PER')]}), - (' Das ist klar, sauber und eindeutig.', {'entities': []}), - (' Diese Rednerliste ist gemacht worden, als man noch davon ausgegangen ist, dass die Kommissionssprecher zu den Berichten sprechen würden.', - {'entities': []}), - (' Ich bitte einfach darum, dass wir strikt zwischen Kommissionsreferaten und Fraktionserklärungen trennen.', - {'entities': []}), - (' Das haben wir immer so gemacht, wollen es auch heute so machen und wollen es auch in Zukunft so halten.', - {'entities': []}), - ('Wir alle haben ja gelegentlich die eine und dann wieder die andere Rolle inné.', - {'entities': []}), - ('Das wollen wir respektieren.', {'entities': []}), - ('Herr Bührer Gerald hat seinen Auftrag als Kommissionssprecher vortrefflich erfüllt bis zu dem Punkt, als er für die Fraktion zu sprechen begann.', - {'entities': [(5, 11, 'PER'), (12, 18, 'PER')]}), - (' Das muss er unter anderem Titel machen.', {'entities': []}), - ('Wenn wie das jetzt geschieht die Kommissionssprecher referieren, haben sie die Kommission zu repräsentieren und nicht die Fraktion.', - {'entities': []}), - ('Es ist so, bleibt so, war immer so.', {'entities': []}), - (' Herr Bührer Gerald und Herr Narbel haben sich von in der Rednerliste eingeschriebenen Fraktionskollegen je vier Minuten abtreten lassen.', - {'entities': [(6, 12, 'PER'), (13, 19, 'PER'), (29, 35, 'PER')]}), - ('Sie haben offensichtlich vereinbart, je vier Minuten nicht als Kommissionssprecher zu reden.', - {'entities': []}), - ('Ich sehe, dass Unwille entstanden ist, weil die beiden Teile ihrer Referate aneinandergehängt werden.', - {'entities': []}), - ('Ich möchte deshalb einen Vorschlag zur gütlichen Einigung machen: Den Teil, den die beiden Herren im Namen der Kommission sprechen, werden sie jetzt vorwegnehmen.', - {'entities': []}), - ('Dann folgen die Redner und Rednerinnen gemäss Rednerliste; die beiden von Fraktionskollegen an Herrn Bührer und Herrn Narbel abgetretenen Redezeitanteilewerden nachträglich in die Rednerliste eingefügt Erscheint das als korrekt?', - {'entities': [(101, 107, 'PER'), (118, 124, 'PER')]}), - (' Es ist ja schon eigenartig, dass ein Kommissionsreferent vom Präsidenten der zuständigen Kommission während seines Referates unterbrochen wird.', - {'entities': []}), - ('Ich bin nun, Frau Präsidentin, schon einige Zeit in diesem 17.', - {'entities': []}), - ('März 1994 Eidgenössische Versicherungskasse (EVK) Parlament, aber in 23 Jahren habe ich das noch nie erlebt!', - {'entities': []}), - ('Und damit zum Ordnungsantrag von Herrn Leuenberger Ernst von heute morgen.', - {'entities': [(39, 50, 'PER'), (51, 56, 'PER')]}), - ('Ich bin der Auffassung, dass die Fragen der EVK von diesem Rat zu einer Stunde zu behandeln sind, die die Präsidentin beziehungsweise das Büro bestimmt, und nicht zu einer Stunde, die Herr Leuenberger Ernsthier wohl nur als Mitglied des Rates, aber nebenbei noch Präsident der Finanzkommission bestimmen will: dann nämlich, wenn offenbar die Medienvertreter weg sind, dann nämlich, wenn die Sonne untergegangen ist, damit wir, wie er dargelegt hat, diese Angelegenheit unter uns beraten können.', - {'entities': [(44, 47, 'ORG'), (189, 200, 'PER'), (201, 210, 'PER')]}), - ('Unser Kommissionsreferent hat dargelegt, dass wir hier keine Parteipolitik und keine parteipolitischen Rankünen machen wollen.', - {'entities': []}), - ('Das ist auch die Auffassung der CVP-Fraktion.', {'entities': []}), - ('Aber offenbar hat Herr Leuenberger Angst, dass hier ein anderes Spiel gespielt werden soll, und deswegen will er diese Angelegenheit verschieben.', - {'entities': [(26, 37, 'PER')]}), - (' Damit komme ich nun aber zur Sache.', {'entities': []}), - ('Verschiedene Tatsachen und Entwicklungen haben dazu geführt, dass die EVK in den vergangenen Jahren ins Gerede kam.', - {'entities': [(72, 75, 'ORG')]}), - ('Im Vordergrund stand für unsere Fraktion namentlich die Tatsache, dass die Finanzkontrolle die Rechnung nicht mehr abnahm, und der Umstand, dass die Versicherungsnehmer keine Versicherungsausweise erhielten und so letztlich keine Uebersicht über ihre eigene Vorsorge mehr hatten.', - {'entities': []}), - ('Gerüchte über Zahlungsprobleme, über Unzulänglichkeiten in den Statuten, über unlösbare Probleme in der Informatik und mit den angeschlossenen Organisationen, über das Verhältnis des Bundes zur EVK und vieles andere mehr brachten auch unsere Fraktion dazu, unmissverständlich die Einsetzung einer Arbeitsgruppe zu verlangen.', - {'entities': [(201, 204, 'ORG')]}), - (' Fast alle diese Gerüchte haben sich nachträglich leider als Halb oder teilweise sogar als Wahrheiten entpuppt Im Vordergrund stand dabei unsere Sorge über die soziale Abfederung der Versicherten, wobei wir sowohl die Bediensteten des Bundes als auch die Versicherten der angeschlossenen Organisationen im Auge haben.', - {'entities': []}), - ('Die Arbeitsgruppe Gemperli brachte in der Folge einiges an den Tag.', - {'entities': [(18, 26, 'PER')]}), - ('Neben der Auflistung der organisatorischen und versicherungstechnischen Ungereimtheiten ist für uns höchstens noch die Aussage beruhigend, dass die Versicherten wegen der bekannten Unzulänglichkeiten persönlich nicht zu Schaden kamen.', - {'entities': []}), - ('Auch wenn diese Aussage an sich beruhigend wirkt, darf sie nicht darüber hinwegtäuschen, dass die Zustände in der EVK in mancherlei Beziehung unbefriedigend, in einigen Bereichen gar chaotisch sind.', - {'entities': [(123, 126, 'ORG')]}), - ('Was letztlich die Aufarbeitung der bestehenden Probleme, die Anpassung der EVK an die gesetzlichen Erfordernisse und das Ausräumen und der Abtransport des politischen Schuttes kosten werden, wird uns die Geschichte mitteilen.', - {'entities': [(75, 78, 'ORG')]}) -] - @plac.annotations( model=("Model name. Defaults to blank 'en' model.", "option", "m", str), output_dir=("Optional output directory", "option", "o", Path), n_iter=("Number of training iterations", "option", "n", int), + train_data=("Training data. So far document-wise.", "option", "t", Path), + print_output=("Print output. Boolean.", "option", "p", bool) ) -def main(model=None, output_dir=None, n_iter=100): - """Load the model, set up the pipeline and train the entity recognizer.""" +def main(model=None, output_dir=None, n_iter=100, train_data=None, print_output=False): + """Load training data and the model, set up the pipeline and train the entity recognizer.""" + if train_data is not None: + dict_onedoc = read_from_txt(train_data) + TRAIN_DATA = transform_to_training_format(dict_onedoc) + print(TRAIN_DATA[:10]) + # TODO: format checks + else: + sys.exit("no training data") + + + if model is not None: nlp = spacy.load(model) # load existing spaCy model print("Loaded model '%s'" % model) @@ -198,8 +87,10 @@ def main(model=None, output_dir=None, n_iter=100): # test the trained model for text, _ in TRAIN_DATA: doc = nlp(text) - print("Entities", [(ent.text, ent.label_) for ent in doc.ents]) - print("Tokens", [(t.text, t.ent_type_, t.ent_iob) for t in doc]) + if print_output: + print(text) + print("Entities", [(ent.text, ent.label_) for ent in doc.ents]) + #print("Tokens", [(t.text, t.ent_type_, t.ent_iob) for t in doc]) # save model to output directory if output_dir is not None: @@ -214,18 +105,11 @@ def main(model=None, output_dir=None, n_iter=100): nlp2 = spacy.load(output_dir) for text, _ in TRAIN_DATA: doc = nlp2(text) - print("Entities", [(ent.text, ent.label_) for ent in doc.ents]) - print("Tokens", [(t.text, t.ent_type_, t.ent_iob) for t in doc]) + if print_output: + print(text) + print("Entities", [(ent.text, ent.label_) for ent in doc.ents]) + #print("Tokens", [(t.text, t.ent_type_, t.ent_iob) for t in doc]) if __name__ == "__main__": plac.call(main) - - # Expected output: - # Entities [('Shaka Khan', 'PERSON')] - # Tokens [('Who', '', 2), ('is', '', 2), ('Shaka', 'PERSON', 3), - # ('Khan', 'PERSON', 1), ('?', '', 2)] - # Entities [('London', 'LOC'), ('Berlin', 'LOC')] - # Tokens [('I', '', 2), ('like', '', 2), ('London', 'LOC', 3), - # ('and', '', 2), ('Berlin', 'LOC', 3), ('.', '', 2)] - diff --git a/src/python/utils_ner.py b/src/python/utils_ner.py index dfaa8d07..b7c5ee0d 100644 --- a/src/python/utils_ner.py +++ b/src/python/utils_ner.py @@ -78,3 +78,39 @@ def read_from_txt(filename): def render_dict(alldicts): for sent_key, sent_dict in alldicts.items(): displacy.render(sent_dict, style='ent', jupyter=True, manual=True, options=options_sner) + + +def transform_to_training_format(alldicts): + + def get_entitities_in_training_format(list_ents): + + list_ents_train = [] + for dict_ent in list_ents: + start = dict_ent['start'] + end = dict_ent['end'] + label = dict_ent['label'] + if label == 'PERSON': + label = 'PER' + if label == 'ORGANIZATION': + label = 'ORG' + if label == 'LOCATION': + label = 'LOC' + tpl_ent = (start, end, label) + + list_ents_train.append(tpl_ent) + + return list_ents_train + + train_data = [] + + for speaker, somedict in alldicts.items(): + text = somedict['text'] + ents_as_list = somedict['ents'] + ents_in_dict = {} + ents_in_dict['entities'] = get_entitities_in_training_format(ents_as_list) + tpl = (text, ents_in_dict) + train_data.append(tpl) + + return train_data + + -- GitLab