Namen aus einem Text extrahieren o. Named Entity Recogntion

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

/Edit

Inzwischen hat sich viel geklärt. Viele Fragen sind nicht mehr relevant, dafür andere:
(Ich möchte grundsätzlich alle Namen in einem Text finden)

Ich möchte meinen Code etwas verfeinern. Momentan sieht es so aus, dass ich meinen Text tekenize und die Satzzeichen entferne. Dann nehme ich meine Namensliste und arbeite mit folgendem Code:

Code: Alles auswählen

set(NAMENSLISTE).intersection(TEXT)
Grundsätzlich habe ich so meine Namen im Text gefunden und habe mir diese ausgeben lassen.

Jetzt habe ich noch zwei Fragen:

1.
Wie bekomme ich die Textstellen? Z.B. den Kontext, also das Wort vor und nach dem gefundenen Namen?

2.
Was ist jetzt aber mit Namen, die nicht in der Liste vorhanden sind? Ich suche nach einer Lösung, dass er mir zum Beispiel alle Wörter ausgibt, die nach einem "Dr." kommen.

---

Ursprünglicher Beitrag:


Hallo alle!

Ich glaube ich habe etwas nicht ganz so simples vor. Ich möchte aus einem beliebigen deutschen Text die Namen (Hans, Peter usw...) rausfiltern, und wollte hier Fragen, wie ich da am besten vorgehen sollte.

Ich dachte mir, dass ich folgendermaßen vorgehe:
1. den Text den ich habe vorbereiten:
- split()
- Stoppwörter entfernen
- Satzzeichen entfernen
- tokenize?
- codieren des Textes? Ich nutze Python 2.7, könnte aber auch 3.4 benutze, was bietet sich hier eher an?

2. Eine Liste mit Namen erstellen, die gefunden werden sollen:
- ich habe eine Liste mit einigen tausend deutschen Namen
- tokenize?
- muss die Liste weiter vorbereitet werden?

3. Ich den Text mit der Liste abgleiche und mir alle Namen in einer neuen Liste ausgebe (evtl. mit Position im Text?)
- Hier weiß ich leider nicht wie ich vorzugehen habe? :K

Ich weiß, dass diese Methode alles andere als Perfekt ist, aber es wäre ein Anfang für mich.

Vielen Dank für die hoffentlich hilfreichen Antworten. :D
Zuletzt geändert von Uhrenmacher am Freitag 3. Oktober 2014, 14:50, insgesamt 4-mal geändert.
BlackJack

@Uhrenmacher: Was meinst Du mit „codieren des Textes”? Wozu soll Stoppwörter entfernen gut sein? Da sind doch sowieso keine Namen beziehungsweise falls ein Stoppwort wiedererwarten auch ein Name sein kann, dann sollte man es vielleicht *nicht* entfernen.

Ich würde die Namen in ein `set()` einlesen und dann mit einem regulären Ausdruck (`re`-Modul) der einen poteniellen Namen erkennt alle Treffer auf enthaltensein in der Namensmenge testen. Je nach dem was Du mit Position der Fundstelle meinst, kann man diese Information direkt am „match”-Objekt von der Suche mit dem regulären Ausdruck ablesen, oder man müsste den Text zeilenweise verarbeiten und die Zeilennummern mitzählen (`enumerate()`). Entweder reicht dann die Zeilennummer oder man greift wieder auf das „match”-Objekt zurück um zusätzlich den Index in der Zeile zu ermitteln.
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

Vielen Dank für die schnelle Antwort.

Die stopwords wollte ich entfernen, da meine Textdatei dadurch um einiges kleiner wird. Ich dachte mir, dass ich dadurch die Performance verbessern könnte? Es handelt sich um Texte, die mehrere Tausend Seiten haben können. Wenn das egal ist und es dadurch nicht schneller wird, bleiben sie natürlich drin.

Das mit dem set() ist eine gute Idee. Das habe ich jetzt so gelöst:
set(NAMENSLISTE).intersection(DERTEXT)

Das mit dem "match"-Objekt bzw. enumerate() habe ich noch nie gemacht, kannst Du mir hierbei helfen? Wie und wann und wo kommt das rein?
BlackJack

@Uhrenmacher: Das geht ja nur wenn `DERTEXT` schon nur die Worte aus dem eigentlichen Text enthält.

Zu regulären Audrücken gibt es neben der Dokumentation zum `re`-Modul auch ein HOWTO in der Python-Dokumentation. Und `enumerate()` ist auch dokumentiert. Ob Du die Funktion benötigst hängt ja auch davon ab was Du als Ergebnis haben möchtest. Das beschreiben zu können gehört auch zum Programmieren dazu.
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

Ich bin mir da selbst noch unsicher was sinnvoll ist und was nicht bzw. unnötig ist.

Das mit der Dokumentation des re-Moduls und das HOWTO hilft mir nur bedingt. Da mir zwar die Funktionen grundsätzlich erklärt werden, ich aber nicht so richtig weiß, wann ich diese anwenden soll? Sind die Informationen der Textstelle oder der Kontext denn noch vorhanden, nachdem ich set() verwendet habe?

Mein Problem ist, das in der Namensliste auch ambige Wörter sind. Somit wird mir aus dem Text jedes "Aber" rausgeholt, da "Aber" auch in der Namensliste ist. Jetzt wäre es _vielleicht_ sinnvoll, sich von den Namen den Kontext (ein Wort vor und ein Wort danach) anzeigen zu lassen. Dann könnte man eben selbst entscheiden, ob das jetzt ein Name ist oder nicht.

Insgesamt weiß ich nicht, wie ich mit diesen ambigen Wörtern umgehen soll?!

(Sorry, ich kämpfe mich erst seit kurzem durch Python. :| )
BlackJack

@Uhrenmacher: Funktionen und Datentypen zu kennen und dann zu entscheiden wann man was am besten anwendet gehört auch zum Programmieren.

Zeichenketten bestehen auch Zeichen. Das war's. Eine Zeichenkette hat keine Information darüber wie sie mal zustande gekommen ist. Das wäre auch sehr speziell. So etwas könntest Du jetzt gerade bei dieser Aufgabe gebrauchen, aber ganz allgemein interessiert einen das nicht, es macht also keinen Sinn das mit dem Datentyp mitzuschleppen. Wenn Du diese Information benötigst, dann musst Du sie auch selber geeignet speichern.

Das mit dem Edit des Ursprungsbeitrags war ungünstig. Damit zerstörst Du die lineare Kommunikationsaufzeichnung. Jemand der jetzt neu zu diesem Thema kommt, weiss nicht mehr wann welche Information in folgenden Beiträgen schon bekannt war oder erst später dazu kam, beziehungsweise bei den Antworten auch nicht worauf die sich beziehen, auf den ursprünglichen Beitrag oder die Neufassung.
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

BlackJack hat geschrieben:[...]

Das mit dem Edit des Ursprungsbeitrags war ungünstig. Damit zerstörst Du die lineare Kommunikationsaufzeichnung. Jemand der jetzt neu zu diesem Thema kommt, weiss nicht mehr wann welche Information in folgenden Beiträgen schon bekannt war oder erst später dazu kam, beziehungsweise bei den Antworten auch nicht worauf die sich beziehen, auf den ursprünglichen Beitrag oder die Neufassung.
Stimmt, aber leider ist der "Bearbeiten"-Button weg?! Sonst hätte ich die Posting in seinen Ursprung versetzen können.

Also insgesamt habe ich mich jetzt ganz gut durch die ganzen HOWTO durchgewurschtelt und konnte einiges selbst lösen. Jetzt scheitere ich nur noch daran, den Code zu erweitern und zu verbessern. Ich bekomme einfach zu viele Treffer die falsch sind.

Gibt es irgendwo ein HOWTO für ambige Wörter? Wie könnte ich diese raus bekommen?
BlackJack

@Uhrenmacher: Das ist eher eine sehr fachspezifische Frage aus der Linguistik die nicht viel mit Python zu tun hat. Und ich wage mal zu behaupten das man die nicht 100%ig automatisiert erkennen kann. Wenn nicht genug Kontext vorhanden ist, kann man sicher auch Sätze haben bei denen selbst ein Mensch nur zu dem Schluss kommen kann „vielleicht ist das ein Name, vielleicht aber auch nicht”.

Du müsstest letztendlich recherchieren wie man in der Linguistik entscheidet ob ein Wort in einem Text ein Name ist. Und das dann in Python umsetzen. Eventuell unter Zuhilfenahme einer entsprechenden Bibliothek. NLTK zum Beispiel.
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

Okay, vielen Dank!

Bin schon recht weit gekommen, der Rest ist eher Bonus. Wie du schon sagst, kann man das nicht 100% automatisiert ablaufen lassen.
nezzcarth
User
Beiträge: 1634
Registriert: Samstag 16. April 2011, 12:47

BlackJack hat geschrieben:@Uhrenmacher: Das ist eher eine sehr fachspezifische Frage aus der Linguistik die nicht viel mit Python zu tun hat. Und ich wage mal zu behaupten das man die nicht 100%ig automatisiert erkennen kann. Wenn nicht genug Kontext vorhanden ist, kann man sicher auch Sätze haben bei denen selbst ein Mensch nur zu dem Schluss kommen kann „vielleicht ist das ein Name, vielleicht aber auch nicht”.

Du müsstest letztendlich recherchieren wie man in der Linguistik entscheidet ob ein Wort in einem Text ein Name ist. Und das dann in Python umsetzen. Eventuell unter Zuhilfenahme einer entsprechenden Bibliothek. NLTK zum Beispiel.
Ich denke, man könnte da schon einiges machen, aber das wird, wie du ja auch beschreibst, sehr aufwendig, und würde eine Einbettung in ein größeres sprachverarbeitendes System, das z.B. syntaktische oder phonologische Analysen bereitstellt, erfordern. Weiterhin wäre die Frage, wie so oft bei Problemen der Sprachverarbeitung, ob man eine praktisch funktionierende, oder eine theoretisch elegante Lösung haben will; Computerlinguistische Anwendungen sind für mich stellenweise sehr ernüchternd, weil da doch vergleichsweise wenig linguistische Theorie und viel Mathematik/Statistik drin steckt. Damit einher geht auch die Frage, ob's nur für das Deutsche funktionieren soll, oder theoretisch für alle Sprachen. Dann wird's wirklich sehr kniffelig (aber auch sehr spannend :) ).

Grundsätzlich könnte man - ohne dass ich mich mit der spezifischen Fragestellung vorher befasst hätte - schon eine gute Annäherung finden. Beispielsweise können Eigennamen ja nicht an beliebiger Stelle stehen, sondern haben bestimmte Positionen, an denen sie erscheinen können und ihre Verwendung wird durch die Semantik der restlichen Lexeme innerhalb des Satzes bestimmt; in einem Satz wie "Peter trinkt Kaffee" kann man davon ausgehen, dass "Peter" zumindest ein menschliches, belebtes Objekt ist und müsste dann weitere Überlegungen anstellen, um herauszufinden, ob es ein Eigenname, oder nicht ist. Ein weiterer "Trick", der mir gerade einfällt, wäre, dass man beispielsweise schaut, inwiefern die lautliche Beschaffenheit des Wortes zum restlichen Wortschatz der betreffenden Sprache passt, da Eigennamen hier größere Abweichungen, aber auch bestimmte Charakteristika aufweisen.

Das wären so Ansatzpunkte, die ich verfolgen würde, wenn ich die Aufgabe zufriedenstellend lösen wollen würde.
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

Ich spiele gerade ein wenig rum und möchte ein paar Dinge ausprobieren. Zum Thema "Ich möchte die Zeichenkette nach "Dr." ausgegeben haben" habe ich folgenden Code:

Code: Alles auswählen

it = re.finditer(r"Dr\.\s[A-B][a-b+]", 
                 MeinText)
Allerdings bekomme ich folgende Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:/Python27/doctortest.py", line 16, in <module>
    MeinText)
  File "C:\Python27\lib\re.py", line 186, in finditer
    return _compile(pattern, flags).finditer(string)
TypeError: expected string or buffer
Was fehlt bzw. was mache ich falsch? Und wie würde ich die Ergebnisse ausgebkommen?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Ein Ergebnis wuerdest du bekommen, wenn `MeinText` ein String waere. Was ist denn `MeinText` und woher kommt es?
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Uhrenmacher: die Fehlermeldung ist doch eindeutig. "MeinText" ist nicht vom Typ String. Was Du statt dessen hast, weißt nur Du selbst.
Der reguläre Ausdruck ist auch sehr speziell und macht wahrscheinlich nicht das, was Du erwartest.
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

Sirius3 hat geschrieben:@Uhrenmacher: die Fehlermeldung ist doch eindeutig. "MeinText" ist nicht vom Typ String. Was Du statt dessen hast, weißt nur Du selbst.
Der reguläre Ausdruck ist auch sehr speziell und macht wahrscheinlich nicht das, was Du erwartest.
Stimmt, ich hatte den Falschen Text genommen. Mit dem richtigen Text bekomme ich jetzt keine Fehlermeldung.

Nehmen wir zum Beispiel diesen Satz: "Dr. Steffan geht mit Dr. Reinhold zum Dr. kleingeschrieben und so weiter."

Jetzt wollte ich, dass mir Python "Steffan" und "Reinhold" ausgibt. Bin ich hierfür den falschen Weg gegangen?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Dafuer ist deine RE auf jeden Fall falsch, hiermit klappt es:

Code: Alles auswählen

In [4]: import re

In [5]: re.findall('Dr\.\s([A-Z][a-z]+)', "Dr. Steffan geht mit Dr. Reinhold zum Dr. kleingeschrieben und so weiter.")
Out[5]: ['Steffan', 'Reinhold']
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Alternative:

Code: Alles auswählen

def get_doctors(line):
    words = line.split()
    successors = words[1:]
    return [
        successor for word, successor in zip(words, successors)
        if word == 'Dr.' and successor[0].isupper()
    ]
Hier müsste man aber noch mögliche Satzzeichen am Ende rausfiltern.

EDIT:

Code: Alles auswählen

import string

def get_doctors(line):
    words = [word.strip(string.punctuation) for word in line.split()]
    successors = words[1:]
    return [
        successor for word, successor in zip(words, successors)
        if word == 'Dr' and successor.istitle()
    ]
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

Vielen Dank für die vielen Antworten bisher!

Ich hänge nur noch an vielen Kleinigkeiten, und bin teilweise verwirrt.

Code: Alles auswählen

beispielsatz = "Dr. Peter ist verhindert. Auch Dr. med. Alfred ist verhindert."
for m in re.finditer("Dr\.\s([A-Z][a-z]+)", beispielsatz):
    print '%02d-%02d: %s' % (m.start(), m.end(), m.group(0))

!--> Hier bekomme ich "00-08: Dr. Peter" ausgegeben.

it = re.findall('Dr\.\s([A-Z][a-z]+)', beispielsatz)
print it

!--> Hier bekomme ich "Peter" ausgegeben
Ich möchte aber eigentlich "00-08: Peter" bei dem ersten Code raus bekommen, warum kommt da das "Dr." mit?

Dann habe ich versucht den regulären Ausdruck zu erweitern:

Code: Alles auswählen

it = re.findall('(Dr\.|med\.)\s([A-Z][a-z]+)', beispielsatz)
und erhalte die aus Ausgabe [('Dr.', 'Peter'), ('med.', 'Alfred')], wie bekomme ich es hin, dass er mir nur Peter und Alfred ausgibt?

Und bei deinem Code snafu, bekomme ich nur "None" ausgegeben. :K

Eine zusätzliche Frage hätte ich noch, gibt es einen guten deutschen Stemmer oder ähnliches, der Verben auf seine Grundform bringt? (ging -> geht, sprach -> sprechen)
Ich habe den Libstemmer probiert, der mir aber einfach nur die Endungen entfernt, was mir leider so gar nichts bringt. :|
BlackJack

Uhrenmacher: Beim ersten kommt das Dr. mit weil Gruppe 0 immer der gesamte Match ist. Der ist implizit eine Gruppe. Wenn Du eine bestimmte explizite Gruppe haben möchtest musst Du deren Nummer angeben. Bei komplizierteren Ausdrücken empfiehlt es sich benannte Gruppen im Ausdruck zu verwenden, dann muss man nicht zählen und bekommt auch keine Probleme wenn man den Ausdruck verändert und dabei vielleicht die Anzahl und damit auch Positionen von Gruppen ändert.

`findall()` liefert Tupel mit allen Gruppen ausser der impliziten 0ten Gruppe. Wenn da etwas dabei ist was Du nicht haben möchtest, dann musst Du die Gruppe im Ausdruck als „non-capturing” kennzeichnen (``(?:…)``).
Uhrenmacher
User
Beiträge: 16
Registriert: Freitag 3. Oktober 2014, 12:00

Ach, natürlich. Danke! Meine Güte, manchmal steht man echt auf dem Schlauch! 0 -> 2 und schon geht es. :roll:

Dann wäre das nun geklärt :wink:

Verben (deutscher Text) auf ihre Grundform zu bringen, scheint es noch nicht zu geben, oder?
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Uhrenmacher hat geschrieben:Und bei deinem Code snafu, bekomme ich nur "None" ausgegeben. :K
``None`` kommt da ganz bestimmt nicht raus, sondern mindestens eine leere Liste, da die Funktion in beiden Varianten eine List Comprehension als Rückgabewert liefert. Du wirst irgendwas falsch kopiert haben.
Antworten