Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
| Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung Nächste Überarbeitung | Vorherige Überarbeitung | ||
| talit:spatial [2024-08-08 07:42] – [Python Generators] hof | talit:spatial [2024-09-23 11:49] (aktuell) – hof | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | # Information Retrieval | ||
| - | ## Build Your Own Google | ||
| - | |||
| - | <nodisp 1> | ||
| - | ++++Repo| | ||
| - | https:// | ||
| - | ++++ | ||
| - | </ | ||
| - | |||
| - | Beim _Information Retrieval_ geht es darum, aus einer potentiell riesigen Menge von Dokumenten (en. _corpus_) diejenigen herauszufinden, | ||
| - | |||
| - | Es gibt dabei zwei Probleme zu lösen: | ||
| - | * Die Dokumente identifizieren, | ||
| - | * 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. | ||
| - | |||
| - | ### Jupyter | ||
| - | 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. | ||
| - | |||
| - | ## Direkter Schlüssel | ||
| - | |||
| - | 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))$, | ||
| - | |||
| - | In der Datenbank-Sprache nennen wir einen eindeutigen Schlüssel auch _primary key_. | ||
| - | |||
| - | ### Toy-Dataset | ||
| - | |||
| - | Für die Entwicklung benötigen wir ein kleines Toy-Dataset. Zum Beispiel dieses hier: | ||
| - | <code python> | ||
| - | thurgau = { | ||
| - | 0: {' | ||
| - | 1: {' | ||
| - | 2: {' | ||
| - | 3: {' | ||
| - | 4: {' | ||
| - | 5: {' | ||
| - | 6: {' | ||
| - | } | ||
| - | </ | ||
| - | ### Aufgabe 1 - Lineare Suche | ||
| - | |||
| - | 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, | ||
| - | |||
| - | `linear_search(thurgau, | ||
| - | |||
| - | <code python> | ||
| - | def linear_search(dataset, | ||
| - | """ | ||
| - | in dataset for which the given attribute matches query.""" | ||
| - | pass | ||
| - | |||
| - | for result in linear_search(thurgau, | ||
| - | 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 [[generators|Python Generators]]: | ||
| - | . | ||
| - | #### TQDM | ||
| - | Mit dem `tqdm` Modul lassen sich lange Schleifen mit einem Progress-Monitor versehen. | ||
| - | |||
| - | <nodisp 1> | ||
| - | ++++Lösung| | ||
| - | <code python> | ||
| - | %pip install tqdm | ||
| - | %pip install ipywidgets | ||
| - | |||
| - | from tqdm.auto import tqdm | ||
| - | |||
| - | def linear_search(dataset, | ||
| - | """ | ||
| - | for town in tqdm(dataset.values()): | ||
| - | if town[attribute] == query: | ||
| - | yield town | ||
| - | </ | ||
| - | ++++ | ||
| - | </ | ||
| - | |||
| - | ### Aufgabe 2 - Ernsthafte Datasets | ||
| - | |||
| - | 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: | ||
| - | |||
| - | Lade die Datenbank aller Ortschaften der Welt von http:// | ||
| - | |||
| - | 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, | ||
| - | |||
| - | Du hast alles richtig gemacht, wenn dein Code ungefähr das folgende ausgibt: | ||
| - | |||
| - | <code python> | ||
| - | print(places[2658985]) | ||
| - | </ | ||
| - | <code bash> | ||
| - | {' | ||
| - | </ | ||
| - | |||
| - | ++++Hinweise: | ||
| - | * https:// | ||
| - | * https:// | ||
| - | ++++ | ||
| - | |||
| - | <nodisp 1> | ||
| - | ++++Lösung| | ||
| - | Zuerst muss die Datei in `data/ | ||
| - | |||
| - | <code python> | ||
| - | def read_geonames(filename): | ||
| - | from io import TextIOWrapper | ||
| - | import zipfile | ||
| - | import csv | ||
| - | | ||
| - | result = {} | ||
| - | with zipfile.ZipFile(f' | ||
| - | with myzip.open(f' | ||
| - | reader = csv.DictReader(TextIOWrapper(csv_file, | ||
| - | for data in tqdm(reader, | ||
| - | result[int(data[' | ||
| - | |||
| - | return result | ||
| - | |||
| - | cities = read_geonames(' | ||
| - | places = read_geonames(' | ||
| - | </ | ||
| - | ++++ | ||
| - | </ | ||
| - | |||
| - | ## Separater Index | ||
| - | |||
| - | ### Aufgabe 3a | ||
| - | 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: | ||
| - | |||
| - | <code python> | ||
| - | %%time | ||
| - | for result in linear_search(places, | ||
| - | </ | ||
| - | ### Aufgabe 3b | ||
| - | Lineare Suche dauert uns zulange - wie können wir die Suche beschleunigen? | ||
| - | |||
| - | ++++Lösungsidee| | ||
| - | Wir speichern einen zusätzlichen _Index_, zum Beispiel als Dictionary vom gewünschten Such-Kriterium zum einem [[https:// | ||
| - | |||
| - | <code python> | ||
| - | index = { | ||
| - | ' | ||
| - | # ... | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | Der Lookup passiert dann zweistufig: zuerst werden die Ids der Resultate im Index gesucht, anschliessend werden die Dokumente aus dem Dataset geladen. | ||
| - | ++++ | ||
| - | |||
| - | 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? | ||
| - | |||
| - | **Tipp:** | ||
| - | Mit dem `tqdm`-Modul lässt sich der Fortschritt bequem darstellen. Das Modul muss einmalig z.B. mit `%pip install tqdm` installiert werden. | ||
| - | |||
| - | <code python> | ||
| - | from tqdm.auto import tqdm | ||
| - | |||
| - | def build_attribute_index(dataset, | ||
| - | 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, | ||
| - | |||
| - | <nodisp 1> | ||
| - | ++++Lösung| | ||
| - | |||
| - | `name_idx` ist ein Dictionary vom `name` Attribut auf die Menge der passenden `geonameid`s. Erweiterungsidee: | ||
| - | |||
| - | <code python> | ||
| - | from tqdm.auto import tqdm | ||
| - | |||
| - | def build_attribute_index(dataset, | ||
| - | index = dict() | ||
| - | for id, entity in tqdm(dataset.items()): | ||
| - | # setdefault retrieves the value for key or adds | ||
| - | # the given default value if the key does not exist. | ||
| - | index.setdefault(entity[attribute], | ||
| - | return index | ||
| - | |||
| - | def query_index(dataset, | ||
| - | """ | ||
| - | for id in idx[query]: | ||
| - | yield dataset[id] | ||
| - | |||
| - | %time name_idx = build_attribute_index(places, | ||
| - | </ | ||
| - | |||
| - | <code python> | ||
| - | %%time | ||
| - | for result in query_index(places, | ||
| - | print(result) | ||
| - | </ | ||
| - | ++++ | ||
| - | </ | ||
| - | |||
| ## Range Queries | ## Range Queries | ||
| Zeile 267: | Zeile 62: | ||
| ++++ | ++++ | ||
| </ | </ | ||
| + | |||
| ### Aufgabe 4c | ### Aufgabe 4c | ||
| Zeile 288: | Zeile 84: | ||
| ++++Hinweise| | ++++Hinweise| | ||
| - | * Das Wurzel-Element | + | * Das Wurzel-Element |
| * Rekursion ist praktisch: Schaffe einen neuen Knoten `node = Node(key, value)`, anschliessend rufe die Funktion für den linken und rechten Subtree auf: | * Rekursion ist praktisch: Schaffe einen neuen Knoten `node = Node(key, value)`, anschliessend rufe die Funktion für den linken und rechten Subtree auf: | ||
| <code python> | <code python> | ||
| Zeile 297: | Zeile 93: | ||
| ++++Lösung| | ++++Lösung| | ||
| - | < | + | < |
| <code python> | <code python> | ||
| def build_tree(sorted_tuples, | def build_tree(sorted_tuples, | ||
| Zeile 361: | Zeile 157: | ||
| ++++ | ++++ | ||
| </ | </ | ||
| + | |||
| + | |||
| ### Graph Visualization ### | ### Graph Visualization ### | ||