# Vektordatenbanken ## Einleitung Mit der steigenden Komplexität der Daten verlieren konventionelle Datenbanken immer mehr an Effektivität. Um dieses Problem zu lösen wurden Vektordatenbanken entwickelt. Aber was sind eigentlich Vektordatenbanken? Vektordatenbanken unterscheiden sich von ihren relationalen Geschwistern durch ihre Fähigkeit hochdimensionale Eigenschaften zu speichern. Die Daten werden nicht in Tabellen sondern als Vektoren in einem mehrdimensionalen Gebiet gespeichert. So kann bei einer Suche nach naheliegenden Punkten gesucht werden um ähnliche Ergebnisse zu erreichen. Die Vektoren werden durch sogenannte Embeddings berechnet. Diese sind ML Algorithmen die nach verschiedenen Faktoren den Vektor auf unzählig vielen Dimensionen berechnen. ### Was ist ein Vektor? Nach Definition von Wikipedia ist ein Vektor: ``` "Im engeren Sinne versteht man in der analytischen Geometrie unter einem Vektor ein mathematisches Objekt, das eine Parallelverschiebung in der Ebene oder im Raum beschreibt." ``` Quelle: https://de.wikipedia.org/wiki/Vektor In anderen Worten: Ein Vektor ist eine Wegbeschreibung für den direkten Weg von einem Punkt zu einem anderen. Man kann es sich vorstellen wie der Luftweg auf einer Karte, mit dem unterschied, dass ein Vektor auch in mehrdimensionalen Bereichen existiert. ![[EigenesBeispiel_Vektordatenbanken.png]] Dieses Bild zeigt wie unser Beispiel anschliessend funktionieren wird. Das Embedding nimmt die Bilder und erstellt einen Vektor anhand von verschiedenen Eigenschaften. In unserem Fall sind das Bilder von Früchten. Wenn wir nun also ein Bild eines Apfels Aufrufen um die ähnlichsten Bilder ausgegeben zu bekommen, gibt es die Bilder mit dem nächsten Vektor aus. In diesem Fall sind es zwei andere Bilder von Äpfel. ## Implementation with Weaviate Die Datenbank und das Embedding laufen beide in einem Docker Container. Ein Docker Compose kann über den [wizard](https://weaviate.io/developers/weaviate/installation/docker-compose) generiert werden. Das Docker Compose genutzt in meinem Beispielprojekt ist im Repository aufzufinden. Anschliessend werden wir ein Python Skript schreiben um ein Schema zu erstellen, unsere Beispieldaten in die Datenbank einzufügen und anschliessend mit einem Test schauen ob sie funktioniert. Die Datenbank läuft auf einem Server. Als erstes muss also eine Verbindung mit diesem geschehen. In Python können wir dafür einen Weaviate Client erstellen: ```python client = weaviate.Client("http://localhost:8080/") ``` Der Port könnte unterschiedlich sein je nach Konfiguration im Docker Compose und muss gegebenenfalls angepasst werden. ### Schema Ähnlich wie in einer herkömmlichen SQL-Datenbank, braucht auch unsere Vektordatenbank ein Schema. Dieses Schema enthält alle Daten die pro Datensatz erwartet werden. In unserem Beispiel, Früchte, nehmen wir der einfachheitshalber nur den Namen der Datei und das Bild. Das Schema wird als Python Dictionary der *schema.create()* Methode übergeben. ```python schema = { "classes": [ { "class": "Fruit", "moduleConfig": { "img2vec-neural": { "imageFields": [ "image" ] } }, "vectorIndexType": "hnsw", "vectorizer": "img2vec-neural", "properties": [ { "name": "file_name", "dataType": ["text"] }, { "name": "image", "dataType": ["blob"] } ] } ] } client.schema.create(schema) ``` Für das Bild muss ausserdem noch das Modul des Embeddings und der Vector Index Algorithmus angegeben werden. Das Embedding ist für uns *img2vec-neural*, kann jedoch je nach Auswahl des Docker Composes unterschiedlich sein. In den Properties geben wir die Felder an. Jedes Feld muss auch ein Datentyp haben. ### Daten hochladen Um die Bilder in die Datenbank zu speichern müssen sie zuerst Base64 kodiert werden. Ich werde hier nicht erklären wie aber ein Beispiel kann im Repository gefunden werden. In Weaviate werden Daten als sogenannte batches Hochgeladen. Dieser Batch Prozess besteht aus drei Schritten: 1. Einen Batcher initialisieren 2. Die Elemente zum Batcher hinzufügen 3. Sicherstellen, dass der letzte Batch bereits gesendet wurde Den Batch können wir folgendermassen initialisieren: ```python client.batch.configure(batch_size=100, dynamic=True, timeout_retries=3, callback=None) ``` Die *batch_size* bestimmt die Grösse des Batches, mit *dynamic* auf *True* wird die Grösse jedoch auf die vorhandenen Daten angepasst. Eine *batch_size* wird jedoch trotzdem empfohlen. Anschliessend können wir die Datensätze zu unserem Batch hinzufügen: ```python with client.batch as batch: for encoded_file_path in os.listdir(b64_dir): with open(b64_dir + encoded_file_path) as file: file_lines = file.readlines() base64_encoding = " ".join(file_lines) base64_encoding = base64_encoding.replace("\n", "").replace(" ", "") file_name = encoded_file_path.replace(".b64", "") data_properties = { "file_name": file_name, "Image": base64_encoding } batch.add_data_object(data_properties, "Fruit") ``` Hier wird durch alle Base64 Encodings der Daten iteriert, ein Data Object erstellt und dem Batch hinzugefügt. Der temporäre Datensatz innerhalb der Schleife wird auch wieder als Dictionary gespeichert. Am ende wird der Batch automatisch ausgeführt. Jetzt ist unsere Vektordatenbank schon eingerichtet und funktionstüchtig. ### Queries Um jedoch zu überprüfen ob unser Batch funktioniert hat und um zu schauen ob die Vektordatenbank tatsächlich funktioniert, müssen wir einen Query machen. Es ist möglich bestimmte Elemente auszulösen, ähnlich wie bei SQL, ich werde hierbei jedoch nur auf Queries nach ähnlichen Bildern eingehen. Mein Query: ```python query_result = client.query\ .get("Fruit", ["file_name"])\ .with_near_image(testImage, encode=False)\ .with_limit(1)\ .do() ``` Diese Suche gibt das Bild mit dem naheliegendsten Vektor. In der *get()* Methode gibt man an, welche Felder man auslesen will. Wir wollen den Dateipfad einer Frucht. Dieser Teil ist vergleichbar mit dem *SELECT* in einem SQL Query. Mit *with_near_image()* können wir das Bild nehmen welches am ähnlichsten ist (zumindest im Bezug auf den Vektor). Durch *with_limit(1)* nehmen wir nur das erste Resultat. Will man mehrere Ergebnisse, kann dieses erhöht werden. Und zu guter Letzt mit *do()* führen wir den Query aus. Machen wir das mit unserem Test Bild (ein Apfel) kommt heraus, dass es am ähnlichsten wie *apfel-2.jpg* ist. Der Gesamte Code: [Repository](https://github.com/luis-kueng/Weaviate-PictureSearch_Example)