Terminologieerkennung

Voraussetzung für diese Lektion ist die Lektion Vorverarbeitung von Texten.

Viele Fachgebiete haben ihre eigene Terminologie. Wir können diese Terminologie einfach finden durch die Frequenzen der Wörter in einer Sammlung mit Texten aus einem Fachgebiet mit den Wortfrequenzen in einem allgemeinen Corpus zu vergleichen. Außerdem sehen wir, wie wir Texte aus Wikipedia herunterladen und nutzen können.

Herunterladen von Wikipedia-Texten eines Themengebietes

Um das Verfahren zu zeigen, brauchen wir eine Sammlung mit Texten aus einem bestimmten Fachgebiet. Hierfür bietet sich die Wikipedia an. Wir können leicht eine Menge Texte aus einer Wikipediakategorie herunterladen. ALs Beispiel nehmen wir Wikipedia-Artikel aus der Kategorie Bibliothek.

Wikipedia hat eine RESTfull API, die wir zum herunterladen der Text benutzen können.

In [1]:
import requests

def getWikiText(title):
    response = requests.get(
        'https://de.wikipedia.org/w/api.php',
        params={
            'action': 'query',
            'format': 'json',
            'titles': title,
            'prop': 'extracts',
            'explaintext': True
        }
    ).json()
    page = next(iter(response['query']['pages'].values()))
    if page != None and 'extract' in page:
        return page['extract']
    else:
        return ''

Wir testen die oben stehende Funktion zum herunterladen des Textes einer Wikipedia-Seite:

In [4]:
testtext = getWikiText('Technische Informationsbibliothek')
print(testtext[:400]+'...')
Die Technische Informationsbibliothek (TIB) ist die Deutsche Zentrale Fachbibliothek für Technik sowie Architektur, Chemie, Informatik, Mathematik und Physik mit Sitz in Hannover. Die TIB ist die weltweit größte Spezialbibliothek für Technik und Naturwissenschaften. Sie hat den Auftrag, in ihren Spezialgebieten die nationale wie internationale Forschung und Industrie sowie als Universitätsbiblioth...

Wir können auch die Liste aller Titel in einer Kategorie bekommen. Wie üblich bei Abfragen an Webservern mit möglicherweise langen Ergebnislisten, bekommen wir nur einen Teil der Ergebnisse und ein sogenanntes Resumption Token, mit der wir den nächsten Teil der Ergebnisse bekommen können.

In [13]:
def getWikiPages(category):
    titles = []
    response = requests.get(
        'https://de.wikipedia.org/w/api.php',
        params={
            'action': 'query',
            'list': 'categorymembers',
            'cmtitle': 'Category:'+category,
            'cmtype':'page',
            'format': 'json'
        }
    ).json()

    #pprint.pprint(response)

    while(response):
        titles.extend( [t['title'] for t in response['query']['categorymembers']] )
        if 'continue' in response:
            cont = response['continue']['cmcontinue']
            response = requests.get(
                'https://de.wikipedia.org/w/api.php',
                params={
                    'action': 'query',
                    'list': 'categorymembers',
                    'cmtitle': 'Kategorie:' + category,
                    'cmtype': 'page',
                    'cmcontinue': cont,
                    'format': 'json'
                }
            ).json()
        else:
            break

        if not cont:
            response = None

    return titles

Wir testen die Funktion:

In [14]:
getWikiPages('Militärbibliothek')
Out[14]:
['Bayerische Armeebibliothek',
 'Eidgenössische Militärbibliothek',
 'Großherzoglich Oldenburgische Militär-Bibliothek',
 'K.u.k. Marinebibliothek',
 'Österreichische Militärbibliothek',
 'Pritzker Military Museum & Library',
 'Weimarer Militärbibliothek']

In ähnlicher Weise bekommen wir alle Unterkategorien einer Kategorie:

In [21]:
def unterkat(category):
    titles = []
    response = requests.get(
        'https://de.wikipedia.org/w/api.php',
        params={
            'action': 'query',
            'list': 'categorymembers',
            'cmtitle': 'Kategorie:' + category,
            'cmtype':'subcat',
            'format': 'json'
        }
    ).json()

    while(response):
        titles.extend( [t['title'][10:] for t in response['query']['categorymembers']] )
        if 'continue' in response:
            cont = response['continue']['cmcontinue']
            response = requests.get(
                'https://de.wikipedia.org/w/api.php',
                params={
                    'action': 'query',
                    'list': 'categorymembers',
                    'cmtitle': 'Kategorie:' + category,
                    'cmtype': 'subcat',
                    'cmcontinue': cont,
                    'format': 'json'
                }
            ).json()
        else:
            break

        if not cont:
            response = None

    return titles

def unterkat_rekursiv(category):
    result = []
    todo = [category]
    while(len(todo) > 0):
        cat = todo.pop()
        for sub in unterkat(cat):
            if not sub in result:
                result.append(sub)
                todo.append(sub)
    return result

Test:

In [22]:
unterkat_rekursiv('Bibliothek nach Typ')
Out[22]:
['Bibliothekstyp',
 'Dokumentenserver',
 'Hochschulbibliothek',
 'Klosterbibliothek',
 'Kommunale Bibliothek',
 'Nationalbibliothek',
 'Parlamentsbibliothek',
 'Regionalbibliothek',
 'Spezialbibliothek',
 'Virtuelle Bibliothek',
 'Digitaler Auskunftsdienst',
 'Virtuelle Fachbibliothek',
 'Blindenbibliothek',
 'Bibliothek (Christentum)',
 'Esperanto-Sammlung',
 'Landwirtschaftliche Bibliothek',
 'Medizinische Bibliothek',
 'Militärbibliothek',
 'Pharmazeutische Bibliothek',
 'Dombibliothek',
 'Russische Nationalbibliothek',
 'Handschrift der Russischen Nationalbibliothek (Sankt Petersburg)']

Wir kombinieren alles und speichern alle heruntergeladenen Texte:

In [24]:
import codecs
import os

def save_text(dir,page_name, page_text):
    file_name = page_name.replace(' ','_').replace('/','_')
    f_out = codecs.open(dir + '/' + file_name+".txt", "w", "utf-8")
    f_out.write(page_text)
    f_out.close()
    

def save_all_texts(category,directory):
    if not os.path.exists(directory):
        os.makedirs(directory)
    for cat in unterkat_rekursiv(category):
        for title in getWikiPages(cat):
            text = getWikiText(title)
            try: #Speicern klappt nicht immer bei Sonderzeichen im Titel. Darum kümmern wir uns jetzt mal nicht.
                save_text(directory,title,text)
            except:
                continue 
    
save_all_texts('Bibliothek nach Typ','Corpora/Bibliothek')

Es sollten jetzt über 800 Texte gespeichert worden sein.

Zählen der Wörter

Wir nutzen die standard Vorverarbeitung der Texte und zählen die Lemmata. Um das zählen zu vereinfachen nutzen wir die Klasse Counter aus Collections

In [3]:
import glob
import nltk
import codecs
import treetaggerwrapper
from collections import Counter

tagger = treetaggerwrapper.TreeTagger(TAGLANG='de')
fdist = Counter() 
filelist = glob.glob("Corpora/Bibliothek/*.txt")

for datei in filelist:
    try:
        textfile = codecs.open(datei, "r", "utf-8")
    except:
        continue
    text = textfile.read()
    textfile.close()
    
    sentences = nltk.sent_tokenize(text,language='german')
    sentences_tok = [nltk.word_tokenize(sent,language='german') for sent in sentences]
    
    for sent in sentences_tok:
        tags = tagger.tag_text(sent,tagonly=True) 
        tags = treetaggerwrapper.make_tags(tags);
        for tag in tags:
            fdist.update([tag.lemma])

Wir schauen uns die 10 häufigsten Wörter an:

In [4]:
print(fdist.most_common(10))
[('die', 61835), ('.', 30159), (',', 26147), ('und', 17991), ('in', 16149), ('@card@', 14053), ('eine', 9270), ('==', 8216), ('werden', 7830), ('sein', 7315)]

Diese Liste ist vokommen uninteressant. Wir sehen die folgende Probleme:

  1. Satzzeichen werden mitgezählt.
  2. Der Sentence Splitter hat Probleme mit Wiki-Text ("==")
  3. Die häufigsten Wörter kommen in allen deutsche Texten oft vor.

Wir lösen zunächst die ersten beide Problemen mit einer Verbesserung vom oben stehenden Code. Wir teilen den Text erst in Absätze auf und dann erst in Sätzen. Satzzeichen können leicht erkannt werden, da diese vom Treetagger ein Tag, das mit einem Dollarzeichen anfängt bekommen.

In [5]:
import re

fdist = Counter()
filelist = glob.glob("Corpora/Bibliothek/*.txt")

h_mark = re.compile(r'==+')
leerzeilen = re.compile(r'\n+')

nrOfWords = 0

for datei in filelist:
    try:
        textfile = codecs.open(datei, "r", "utf-8")
    except:
        continue
    text = textfile.read()
    textfile.close()
    
    text = h_mark.sub('\n\n',text)
    leerzeilen = re.compile(r'\n+')
    paragraphs = leerzeilen.split(text)
    for par in paragraphs:
        sentences = nltk.sent_tokenize(par,language='german')
        sentences_tok = [nltk.word_tokenize(sent,language='german') for sent in sentences]
    
        for sent in sentences_tok:
            tags = tagger.tag_text(sent,tagonly=True) 
            nrOfWords += len(tags)
            words = [tag.lemma for tag in treetaggerwrapper.make_tags(tags) if not tag.pos[0] == "$"]
            fdist.update(words)
In [6]:
print(fdist.most_common(10))
[('die', 61836), ('und', 17991), ('in', 16151), ('@card@', 14058), ('eine', 9275), ('werden', 7831), ('sein', 7315), ('von', 7193), ('Bibliothek', 6938), ('zu', 6289)]

Relative Frequenzen

Es ist nicht so interessant zu wissen, welche Wörter häufig in unserere Textsammlung vorkommen. ielmehr interessiert es uns, welche Wörter häufiger vorkommen als man es im allgemeinen erwarten würden. Hierzu müssen wir die Frequenzen in unserem Korpus mit Frequenezen in einem allgemeinen Korpus vergleichen. Hierfür nutzen wir eine Liste mit Wortfrequenzen der 100 000 häufigsten Wörter aus einer Sammlung verschiedener Korpora mit insgesamt 7 Milliarden Wörtern des Instituts für Deutsche Sprache in Mannheim. Die Liste und Informationen dazu finden Sie auf der Webseite des IDS: http://www1.ids-mannheim.de/kl/projekte/methoden/derewo.html

In [7]:
refdist = nltk.FreqDist()
nrOfWordsRef = 0

freqdata = codecs.open("Corpora/DeReKo-2014-II-MainArchive-STT.100000.freq", "r", "utf-8")
for line in freqdata:
    (word,lemma,pos,freq) = line.split('\t')
    nrOfWordsRef+= float(freq)
    refdist[lemma] = float(freq)
    
freqdata.close()

Um die Frequenzen einigermaßen vergleichbar zu machen, multiplizieren wir die Frequenzen aus unseren Daten mit einer Konstante, die wir wie folgt ermitteln. Genau genommen brauch wir diese Konstante nicht, da die Wörter, die wir finden, hierdurch nicht anders werden.

In [39]:
c = refdist['es']/fdist['es']
print(c)
9398.320585437445

Schließlich berechnen wir die relative Frequenzen. Damit die Zahlen besser lesbar sind, nehmen wir den Logarithmus der relativen Frequenz. Wir sortieren die Liste und geben die 100 häufigsten Wörter aus:

In [37]:
import math
import pprint

word_rel_freq = {lemma:math.log(c * fdist.get(lemma) / refdist.get(lemma,1)) for lemma in fdist }
word_rel_freq = sorted(word_rel_freq.items(), key=lambda x: x[1],reverse=True)
pprint.pprint(word_rel_freq[0:100])
[('sich', 16.797502610993252),
 ('–', 15.988832820461306),
 ('Sie', 15.962829188432577),
 ('Weblinks', 15.799858164762346),
 ('Library', 15.649575961713008),
 ('Fachbibliothek', 15.26196847100485),
 ('Hrsg', 14.994725066230343),
 ('Buchbestand', 14.708967922188148),
 ('Kantonsbibliothek', 14.612118096198229),
 ('Biblioteca', 14.607871805316778),
 ('Inkunabeln', 14.426400950403137),
 ('Codex', 14.330069841464704),
 ('Hochschulbibliothek', 14.330069841464704),
 ('UB', 14.210881324199587),
 ('&', 14.15223259711808),
 ('University', 14.089928713781923),
 ('Stadtteilbibliothek', 14.075539976329823),
 ('Universitäts-', 13.984568198124098),
 ('Bibliotheca', 13.968567856777657),
 ('Bibliothekswesen', 13.960470646545037),
 ('Sondersammlung', 13.918970915638285),
 ('Bibliotheksgebäude', 13.918970915638285),
 ('Open', 13.910460225970375),
 ('Bibliothekssystem', 13.901876482278984),
 ('Online-Katalog', 13.857816492484954),
 ('Altbestand', 13.830417518296839),
 ('u.a.', 13.821115125634526),
 ('E-Books', 13.802246641330143),
 ('Jugendbibliothek', 13.77325910445689),
 ('PDF', 13.77325910445689),
 ('DVDs', 13.75345647716071),
 ('Medieneinheit', 13.74340614130721),
 ('Schulbibliothek', 13.70216318277316),
 ('Hofbibliothek', 13.680885784325875),
 ('m²', 13.659145797689469),
 ('Access', 13.659145797689469),
 ('Musikbibliothek', 13.659145797689469),
 ('Fernleihe', 13.648095961502884),
 ('Büchersammlung', 13.648095961502884),
 ('Hauptbibliothek', 13.625623105650826),
 ('<UNKNOWN>', 13.590937547662936),
 ('OPAC', 13.567126898969217),
 ('Regionalbibliothek', 13.567126898969217),
 ('Mio.', 13.555005538436873),
 ('Studienbibliothek', 13.542735445845059),
 ('Spezialbibliothek', 13.5303129258465),
 ('Bibliotheksbestand', 13.504995117862212),
 ('Nacional', 13.504995117862212),
 ('Gesamtbestand', 13.492091713026303),
 ('digitalisiert', 13.47901963145895),
 ('Arbeit|Arbeiten', 13.47901963145895),
 ('e.V', 13.46577440470893),
 ('Teilbibliothek', 13.424952410188675),
 ('Digitalisat', 13.410966168213935),
 ('Hg.', 13.39678153322198),
 ('Volksbücherei', 13.39678153322198),
 ('bibliographisch', 13.367793996348727),
 ('Bibliothèque', 13.367793996348727),
 ('Katalogisierung', 13.352978910563586),
 ('Public', 13.337941033199044),
 ('Periodika', 13.322673561068257),
 ('audiovisuell', 13.307169374532291),
 ('ISSN', 13.307169374532291),
 ('ZB', 13.291421017564153),
 ('Medienbestand', 13.275420676217712),
 ('Pflichtexemplarrecht', 13.275420676217712),
 ('Hörbücher', 13.275420676217712),
 ('Erdgeschoss', 13.259160155345931),
 ('Http', 13.259160155345931),
 ('Entleihungen', 13.24263085339472),
 ('Privatbibliothek', 13.22582373507834),
 ('Erwerbung', 13.22582373507834),
 ('Bibliotheksverbund', 13.22582373507834),
 ('Forschungsbibliothek', 13.22582373507834),
 ('ZBW', 13.22582373507834),
 ('Informationsversorgung', 13.208729301719039),
 ('CD-ROMs', 13.208729301719039),
 ('Volksbibliothek', 13.19133755900717),
 ('Präsenzbibliothek', 13.173637981907769),
 ('z.B.', 13.173637981907769),
 ('ULB', 13.173637981907769),
 ('Raum|Räumen', 13.155619476405091),
 ('Nutzern', 13.137270337736894),
 ('Privatbibliotheken', 13.137270337736894),
 ('Bereichsbibliothek', 13.137270337736894),
 ('Bücherbus', 13.137270337736894),
 ('Kunstbibliothek', 13.118578204724741),
 ('Science', 13.099530009754046),
 ('Google', 13.080111923896945),
 ('Libraries', 13.080111923896945),
 ('Mediathek', 13.080111923896945),
 ('Nationalbibliotheken', 13.080111923896945),
 ('Spezialsammlung', 13.060309296600765),
 ('Teilbibliotheken', 13.060309296600765),
 ('Dombibliothek', 13.060309296600765),
 ('Leihverkehr', 13.040106589283246),
 ('Druckwerk', 13.040106589283246),
 ('Lesehalle', 13.040106589283246),
 ('Klosterbibliotheken', 13.01948730208051),
 ('Sondersammelgebiete', 12.998433892882678),
 ('Kinderbibliothek', 12.998433892882678),
 ('Spende|Spenden', 12.998433892882678),
 ('kB', 12.976927687661714)]

Abgesehen von einigen Ergebnissen, die auf eine unterschiedliche Lemmatisierung zurückgführt werden können, finden wir tatsächlich nur Wörter, die dem Bibliotheksbereich zugeordnet werden können.

Die relative Frequenz stellt sich so als ein sehr effektive Maß, um Fachvokabular zu erkennen, heraus.

Um tatsächlich alles Fachspezifische Begriffe zu erkennen, müssten wir auch noch Mehrwortausdrücke erkennen und vergleichen. Viele Fachbegriffe werden nicht durch ein Wort, sondern durch eine kurze Nominalphrase ausgedrückt, wie 'Open Access', 'elektronisches Dokument', 'Institut für Weltwirtschaft'. Diese Wortfolgen zu finden ist nicht besonders schwierig. Da diese Wörter in den meisten Freqenzlisten nicht erhalten sind, müsste man die Referenzfrequenzen auch selber ermitteln.