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