Information Retrieval

Beim Information Retrieval geht es darum, aus einer potentiell riesigen Menge von Dokumenten (en. corpus) diejenigen herauszufinden, die einer Suchanfrage (en. query) am besten entsprechen. Die am besten bekannte Anwendung ist die Internet-Suche, die uns für eine Suchanfrage meist die richtige Seite aus Billionen von Webseiten herausfindet.

Es gibt dabei zwei Probleme zu lösen:

  • Die Dokumente identifizieren, die der Suche entsprechen (en. selection).
  • Die Resultat-Liste der Relevanz entsprechend sortieren (ranking).

Um diese Aufgaben schnell zu lösen, ist es im allgemeinen nicht möglich, den ganzen Korpus für jede Anfrage zu durchsuchen. Stattdessen bauen wir einen Index, der es erlaubt, effizient die geforderten Dokumente zu finden.

Wir verwenden Jupyter Notebooks, um Code zu schreiben und dokumentieren. Erstelle ein neues Github-Repo mit einen Jupyter Notebook, um darin die Aufgaben zu lösen.

Haben unsere Elemente einen eindeutigen Schlüssel (key), der uns für die Suchanfrage genügt, so können wir beispielsweise ein Dictionary verwenden. Der Lookup dauert mit Binärsuche $𝒪(log(n))$, mit einer Hashtable sogar nur konstante (von der Korpusgrösse unabhängige) Zeit, also $𝒪(1)$.

In der Datenbank-Sprache nennen wir einen eindeutigen Schlüssel auch primary key.

Für die Entwicklung benötigen wir ein kleines Toy-Dataset. Zum Beispiel dieses hier:

thurgau = {
    0: {'name': 'Romanshorn', 'population': 11556, 'latitude': 47.56586, 'longitude': 9.37869},
    1: {'name': 'Amriswil', 'population': 14313, 'latitude': 47.54814, 'longitude': 9.30327},
    2: {'name': 'Arbon', 'population': 15459, 'latitude': 47.51360, 'longitude': 9.42999},
    3: {'name': 'Weinfelden', 'population': 11893, 'latitude': 47.56638, 'longitude': 9.10588},
    4: {'name': 'Frauenfeld', 'population': 26093, 'latitude': 47.55856, 'longitude': 8.89685},
    5: {'name': 'Kreuzlingen', 'population': 22788, 'latitude': 47.645837,'longitude': 9.178608},
    6: {'name': 'Egnach', 'population': 4897, 'latitude': 47.54565, 'longitude': 9.37864},
}

Der Zugriff auf einen Eintrag mit bekanntem key ist trivial: thurgau[6] liefert Egnach blitzschnell zurück - und das täte es auch, wenn das Dataset 6 Millionen Einträge hätte. Nun möchten wir den Korpus aber nach den Ortsnamen durchsuchen können.

Schreibe eine Funktion def linear_search(dataset, attribute, query), die einen beliebigen Korpus (=Dataset) durchsucht nach allen Einträgen, die ein Attribut attribute mit dem exakten Wert query haben.

linear_search(thurgau, 'name', 'Egnach') soll also alle Einträge für Egnach liefern:

def linear_search(dataset, attribute, query):
    """Returns an iterable (list or generator) of all elements
       in dataset for which the given attribute matches query."""
    pass
 
for result in linear_search(thurgau, 'name', 'Egnach'):
    print(result)

Python Generators

Die einfachste Möglichkeit sammelt alle Resultate in einer Liste und gibt diese zurück. Das kann aber teuer werden, wenn das Resultset gross ist.

Eine schönere Variante sind Python Generators: statt die Resultate zu sammeln, verwenden wir das yield Keyword, um jedes Suchresultat zurückzugeben. Unsere Funktion wird damit zu einem Generator, über den wir mit for iterieren können. Statt auf einmal alle Resultate zu berechnen, wird immer nur das nächste berechnet.

TQDM

Mit dem tqdm-Modul lässt sich der Fortschritt bequem darstellen. Das Modul muss einmalig z.B. mit %pip install tqdm installiert werden.

Lösung

Die lineare Suche für das Toy-Dataset dauert nicht lange - aber was, wenn unser Dataset grösser ist? In dieser Aufgabe erstellst du ein (oder zwei) grössere Datasets. Nach dem einlesen sollen sie ein ähnliches Format haben wie das Toy-Dataset: ein Dictionary mit int Keys und für jeden Eintrag einem weiteren Dictionary als Value.

Lade die Datenbank aller Ortschaften der Welt von http://download.geonames.org/export/dump/ herunter und lies sie als Dictionary places in Python ein. Du kannst entweder cities500.zip (ca. 140k Ortschaften) oder allCountries.zip (12M Features) verwenden - je nachdem, wie mutig du bist… Achtung, das zweite Dataset enthält nicht nur Ortschaften, sondern auch Quartiere, Parks, Länder und Kontinente.

Das Dictionary sollte als Schlüssel die geonameid verwenden und als Wert wiederum ein Dictionary von den Spaltennamen (Attributen) zu den Werten jedes Ortes. Obige URL beschreibt auch das Datenformat und die Spalten. Verzichte dabei auf Pandas - wir wollen in diesem Block herausfinden, wie Datenbanken funktionieren und nicht eine benutzen!

Du hast alles richtig gemacht, wenn dein Code ungefähr das folgende ausgibt:

print(places[2658985])
{'geonameid': '2658985', 'name': 'Romanshorn', 'asciiname': 'Romanshorn', 'alternatenames': 'Romanshorn,Romanskhorn,luo man si huo en,Романсхорн,羅曼斯霍恩', 'latitude': '47.56586', 'longitude': '9.37869', 'feature class': 'P', 'feature code': 'PPLA3', 'country code': 'CH', 'cc2': '', 'admin1 code': 'TG', 'admin2 code': '2011', 'admin3 code': '4436', 'admin4 code': '', 'population': '8956', 'elevation': '', 'dem': '401', 'timezone': 'Europe/Zurich', 'modification date': '2013-04-02'}

Hinweise:

Lösung

Wie lange dauert die lineare Suche nach Ortschaften mit dem Namen London? Wieviele Londons gibt es?

In Jupyter Notebooks lässt sich die Zeit einer Operation sehr einfach mit dem %%time Magic messen:

%%time
for result in linear_search(places, 'name', 'London'): print(result)

Lineare Suche dauert uns zulange - wie können wir die Suche beschleunigen? Diskutiert und implementiert!

Lösungsidee

Das Erstellen des Index dauert ziemlich lange und lohnt sich natürlich nur, wenn wir mehr als einmal suchen. Wie oft müssen wir suchen, damit wir die Indexerstellung kompensiert haben?

from tqdm.auto import tqdm
 
def build_attribute_index(dataset, attribute):
    index = dict()
    for id, entity in tqdm(dataset.items()):
        pass  # Your code here: add the entity to the index
    return index

Probiert zuerst mit dem Toy-Dataset, bevor ihr euch an die grossen Datasets wagt!

  • talit/retrieval.1723384064.txt.gz
  • Zuletzt geändert: 2024-08-11 13:47
  • von hof