Text Indexing

Nachdem wir in Information Retrieval ein einzelnes Attribut indexiert haben, möchten wir dasselbe für einen ganzen Text erreichen. Statt der Geodatenbank verwenden wir dafür Artikel von Wikipedia.

Natürlich benötigen wir auch wieder ein Toy-Dataset:

documents = {
    1: "Ein Film über Sterne, mit Harrison Ford.",
    2: "Ein Film über Sterne von Antoine de Saint-Exupéry.",
    3: "In diesem Film gewinnt James Bond gegen Dr. No.",
}

Anders als bei der Geodatenbank müssen wir hier noch den Text in einzelne Wörter (en. Tokens) zerlegen.

Schreibe eine Funktion def tokenize(text) die einen Text in einzelne Wörter zerlegt und diese Tokens mit yield zurückgibt.

Die Funktion soll im folgenden Text-Indexing-Code aufgerufen werden, der ganz ähnlich funktioniert wie der Attribut-Indexing-Code:

from tqdm.auto import tqdm
 
def build_text_index(dataset, index=None):
    """Builds and returns full-text index for all documents in a dataset. Adds to an existing index if one is given."""
    if index is None:
        index = dict()
    for id, document in tqdm(dataset):
        for token in tokenize(document):
            index.setdefault(token, set()).add(id)
    return index
 
toy_index = build_text_index(documents.items())

Anschliessend sollten wir unseren Index durchsuchen können:

def query_index(index, query):
    result_set = set()
    return index.get(query, set())
 
query_index(toy_index, "James")

Fragen

  1. Findet dein System einen Film über 'bond (Kleinbuchstaben)?
  2. Findet dein System einen Film über 'Ford' (ohne Satzzeichen)?
  3. Wieviele Filme über 'Sterne' findest du?

Passe deinen Tokenizer so an, dass er sinnvoll mit Satzzeichen umgeht. Zudem möchten wir alle Texte in Kleinbuchstaben umwandeln. Der Index sollte anschliessend etwa so aussehen:

{'ein': {1, 2},
 'film': {1, 2, 3},
 'über': {1, 2},
 'sterne': {1, 2},
 'mit': {1},
 'harrison': {1},
 'ford': {1},
 'von': {2},
 'antoine': {2},
 'de': {2},
 'saint': {2},
 'exupéry': {2},
 'in': {3},
 'diesem': {3},
 'gewinnt': {3},
 'james': {3},
 'bond': {3},
 'gegen': {3},
 'dr': {3},
 'no': {3}}

Um unser System zu testen, müssen wir es natürlich auf eine grösseres Dataset loslassen. Dazu indexieren wir Film-Artikel aus wikipedia.de.

Als Start nehmen wir alle Wikipedia-Artikel über Filme von 1985 (387 Filme, 0.7MB). Das CSV hat nur zwei Spalten:

  1. Wikipedia-Url (was auch gut als Identifier taugt)
  2. Artikel-Text

Grössere Datasets gibt es auf https://if.ksr.ch/talit/indexing (nur KSR-intern).

Erstelle einen Index aller WP-Artikel über Filme von 1985. Schreibe dazu eine Generator-Funktion, um die WP-Artikel-Tupel aus dem .csv.zip zu yielden.

Lösung

Wieviele Filme mit 'harrison' gibt es? Und wieviele mit einem 'delorean'?

Wenn du den erstellten Index untersuchst, wirst du Wörter wie der oder ein finden, die in praktisch allen Artikeln vorkommen. Entsprechend benötigen die sehr viel Platz im Index, und liefern auch kaum interessante Resultate.

Normalerweise werden solche Stop Words bereits bei der Indexierung ignoriert. Natürlich könnten wir eine Stop-Word-Liste aus dem Internet laden - aber eigentlich haben wir ja alle Informationen selber bereits zur Hand, um eine solche zu erstellen: Wir kennen die Frequenz jedes Wortes, also in wie vielen Artikeln es vorkommt. Wörter, die in mehr als der Hälfte der Artikel vorkommen, dürften kaum interessant sein. Mehr als an der absoluten Frequenz sind wir also interessiert an der relativen Dokumentenhäufigkeit (en. document frequency): $\frac{frequency}{n}$.

Erstelle eine Liste aller Wörter in unserem Index, absteigend sortiert nach relativer Dokumentenhäufigkeit.

Erstelle daraus eine Stop-Word-Liste (mit einem Cut-off bei 50%) und modifiziere tokenize so, dass die Funktion zusätzlich alle stop-words unterdrückt.

Lösung

Beim Information Retrieval geht es nicht nur darum, welche Dokumente zur Query passen, sondern welche am besten dazu passen. Ranking ist eine komplexe und manchmal undurchsichtige Wissenschaft, die von vielen gegenläufigen Interessen getrieben ist: Beispielsweise möchten ganz viele Webseitenbetreiber zuoberst in den Suchresultaten von Google landen - andererseits möchten die Benutzer die wirklich relevante Webseite zuoberst haben. Und Google möchte irgendwie Geld verdienen, indem es die obersten Plätze verkauft.

Nehmen wir folgendes Beispiel: Wir indexieren alle Film-Artikel der 1980er Jahre und wollen möglichst gute Suchresultate für folgende Queries:

  • Luke Skywalker

Unsere Query-Funktion muss erstens mit mehreren Wörtern umgehen können - es bietet sich an, die tokenize Funktion auch hier anzuwenden und eine Suchanfrage für jedes Token in der Query durchzuführen. Die Frage ist allerdings, wie wir die einzelnen Resultatlisten kombinieren…

Schreibe eine neue Query-Funktion, die mehrere Wörter kombinieren kann. Welche Kombinationsmethode wählst du?

Die Schnittmenge scheint für die Query Luke Skywalker gut zu funktionieren, weil skywalker ein ziemlich spezifisches Wort ist, das nur in wenigen Filmen vorkommt - aber was, wenn wir eine Query ohne spezifische Wörter haben? Teste die Suche mit der Query luke zeitung!

Aufgabe 5: Ranking mit log IDF

Statt eine strikte AND-Verknüpfung aller Suchterme möchten wir ein Ranking erstellen, das alle Dokumente einschliesst, die mindestens einen Suchterm enthält. Jeder Such-Begriff soll entsprechend seiner Inverse-Document-Frequency (IDF) zum Rang beitragen. Das bedeutet, dass das Auftreten des seltenen Begriffs skywalker mehr zum Rang beiträgt als ein häufiges Wort wie zeitung. Trotzdem sollte ein einzelnes seltenes Wort nicht alles andere ausblenden dürfen.

Damit ein einzelner Wert nicht komplett dominiert, transformieren wir die IDF-Werte mit dem Logarithmus. Die Gewichte sind damit näher beieinander:

  • talit/indexing.1723384425.txt.gz
  • Zuletzt geändert: 2024-08-11 13:53
  • von hof