# Visueller Datensatz Explorer
Moderne Datensätze enthalten immer Millionen von unstrukturierten Daten wie Bilder, Audio-Clips und sogar Videos. Die Abfrage von nächsten Nachbarn in solchen Datensätzen ist eine Herausforderung: 1. Die Messung von Entfernungen zwischen unstrukturierten Daten ist mehrdeutig; 2. Das Sortieren von Daten in Bezug auf Milliarden von Entfernungen erfordert zusätzlichen Aufwand. Glücklicherweise wurde die erste Hürde durch aktuelle Forschungsergebnisse wie CLIP (opens new window) überwunden, und die zweite kann durch fortschrittliche Vektor-Suchalgorithmen verbessert werden. MyScale bietet eine einheitliche Datenbanklösung für DB+KI-Anwendungen, die eine leistungsstarke Suche in großen Datensätzen ermöglicht. In diesem Beispiel zeigen wir, wie eine DB+KI-Anwendung durch das Training eines feinkörnigen Klassifikators mit Hilfe der Hard-Negative-Mining-Technik erstellt werden kann.
In dieser Demo haben wir den unsplash-25k Dataset (opens new window) verwendet, einen Datensatz mit etwa 25.000 Bildern, als unseren Spielplatz. Die Fotos decken komplexe Szenen und Objekte ab.
# Warum arbeiten wir mit einer Datenbank?
Für jemanden, der nach der Rolle der Datenbank gefragt hat, muss ich etwas tiefer in einige KI-Themen eintauchen. Wir alle wissen, dass ein herkömmlicher Klassifikator immer nach einer großen Menge an Daten, Annotationen und vielen Trainingstricks verlangt, um in der Realität eine hohe Genauigkeit zu erzielen. Diese sind ausreichend, aber nicht unbedingt erforderlich, um einen genauen Klassifikator zu erhalten. Eine genauere Schätzung hilft uns, schneller zum Optimum zu gelangen. Dank CLIP können wir jetzt einen guten Ausgangspunkt für den Klassifikator erhalten. Und das Einzige, was uns noch bleibt, ist uns auf Beispiele zu konzentrieren, die ähnlich, aber nicht identisch sind, was in der KI-Terminologie als Hard-Negative-Mining-Technik bezeichnet wird. Jetzt ist es an der Zeit für eine Vektordatenbank, z.B. MyScale, zu glänzen.
MyScale ist eine Vektordatenbank, die leistungsstarke Suchen in Milliarden von Vektoren unterstützt. Kostspielige Operationen wie das Hard-Negative-Mining werden mit MyScale niemals ein Hindernis für KI-Anwendungen und -Forschung sein. Das Finden von Hard-Negativen dauert nur Millisekunden. Der gesamte Feinabstimmungsprozess kann also nur mit wenigen Klicks auf der Webseite erfolgen.
# Wie spielt man mit der Demo?
Warum probieren Sie nicht unsere Online-Demo (opens new window) aus?
# Installation der Voraussetzungen
transformers
: Ausführen des CLIP-Modellstqdm
: Schöne Fortschrittsanzeige für Menschenclickhouse-connect
: MyScale-Datenbankclientstreamlit
: Python-Webserver zum Ausführen der App
python3 -m pip install transformers tqdm clickhouse-connect streamlit pandas lmdb torch
Sie können Metadaten herunterladen, wenn Sie Ihre eigene Datenbank erstellen möchten.
# Unsplash 25K Dataset herunterladen
wget https://unsplash-datasets.s3.amazonaws.com/lite/latest/unsplash-research-dataset-lite-latest.zip
# Entpacken...
unzip unsplash-research-dataset-lite-latest.zip
# Sie haben eine Datei namens `photos.tsv000` in Ihrem aktuellen Arbeitsverzeichnis
# Dann können Sie das CLIP-Feature aus dem Datensatz extrahieren
# Erstellen einer Datenbank mit Vektoren
# Eintauchen in die Daten
Zuerst werfen wir einen Blick auf die Struktur des Unsplash-25k-Datensatzes. Die Datei photos.tsv000
enthält Metadaten und Annotationen für alle Bilder im Datensatz. Eine einzelne Zeile sieht so aus:
photo_id | photo_url | photo_image_url | ... |
---|---|---|---|
xapxF7PcOzU | https://unsplash.com/photos/xapxF7PcOzU | https://images.unsplash.com/photo-1421992617193-7ce245f5cb08 | ... |
Die erste Spalte bezieht sich auf den eindeutigen Bezeichner für dieses Bild. Die nächste Spalte ist die URL zu seiner Beschreibungsseite, auf der der Autor und andere Metainformationen angegeben sind. Die dritte Spalte enthält die Bild-URLs. Bild-URLs können direkt verwendet werden, um das Bild mit der unsplash API (opens new window) abzurufen. Hier ist ein Beispiel für die oben erwähnte photo_image_url
-Spalte:
Wir verwenden den folgenden Code, um die Daten zu laden:
import pandas as pd
from tqdm import tqdm
images = pd.read_csv(args.dataset, delimiter='\t')
# Erstellen einer MyScale-Datenbanktabelle
# Arbeiten mit der Datenbank
Sie benötigen eine Verbindung zu einer Datenbank-Backend, um eine Tabelle in MyScale zu erstellen. Eine detaillierte Anleitung zur Verwendung des Python-Clients finden Sie auf dieser Seite.
Wenn Sie mit SQL (Structured Query Language) vertraut sind, wird es für Sie viel einfacher sein, mit MyScale zu arbeiten. MyScale kombiniert die strukturierte Abfrage mit der Vektorsuche, was bedeutet, dass das Erstellen einer Vektordatenbank genau dasselbe ist wie das Erstellen herkömmlicher Datenbanken. Und so erstellen wir eine Vektordatenbank in SQL:
CREATE TABLE IF NOT EXISTS unsplash_25k(
id String,
url String,
vector Array(Float32),
CONSTRAINT vec_len CHECK length(vector) = 512
) ENGINE = MergeTree ORDER BY id;
Wir definieren die id
des Bildes als Zeichenkette, url
s als Zeichenketten und Feature-Vektoren vector
als ein Array mit fester Länge mit einem Datentyp von 32-Bit-Gleitkommazahlen und einer Dimension von 512. Mit anderen Worten, ein Feature-Vektor eines Bildes enthält 512 32-Bit-Gleitkommazahlen. Wir können dieses SQL mit der gerade erstellten Verbindung ausführen:
client.command(
"CREATE TABLE IF NOT EXISTS unsplash_25k (\
id String,\
url String,\
vector Array(Float32),\
CONSTRAINT vec_len CHECK length(vector) = 512\
) ENGINE = MergeTree ORDER BY id")
# Extrahieren von Features und Befüllen der Datenbank
CLIP (opens new window) ist eine beliebte Methode, die Daten aus verschiedenen Formen (oder wir verwenden den akademischen Begriff "Modalität") in einen einheitlichen Raum überführt und eine leistungsstarke übermodale Suche ermöglicht. Sie können zum Beispiel den Feature-Vektor einer Phrase wie "ein Foto eines Hauses am See" verwenden, um nach ähnlichen Fotos zu suchen und umgekehrt.
Mehrere Schritte des Hard-Negative-Mining können einen genauen Klassifikator mit Hilfe eines Zero-Shot-Klassifikators als Initialisierung trainieren. Wir können einen CLIP-Vektor, der aus dem Text generiert wurde, als unseren anfänglichen Parameter für den Klassifikator verwenden. Dann können wir zum Teil des Hard-Negative-Mining übergehen: das Suchen aller ähnlichen Beispiele und das Ausschließen aller negativen Beispiele. Hier ist ein Codebeispiel, das zeigt, wie man Features aus einem einzelnen Bild extrahiert:
from torch.utils.data import DataLoader
from transformers import CLIPProcessor, CLIPModel
model_name = "openai/clip-vit-base-patch32"
# Es kann einige Minuten dauern, das CLIP-Modell herunterzuladen
model = CLIPModel.from_pretrained(model_name).to(device)
# Der Prozessor wird das Bild vorverarbeiten
processor = CLIPProcessor.from_pretrained(model_name)
# Verwendung der Daten, die wir gerade in dem vorherigen Abschnitt geladen haben
row = images.iloc[0]
# Holen Sie sich die URL und die eindeutige Kennung des Bildes
url = row['photo_image_url']
_id = row['photo_id']
import requests
from io import BytesIO
# Bild herunterladen und laden
response = requests.get(url)
img = Image.open(BytesIO(response.content))
# Das Bild vorverarbeiten und einen PyTorch Tensor zurückgeben
ret = self.processor(text=None, images=img, return_tensor='pt')
# Die Bildwerte erhalten
img = ret['pixel_values']
# Den Feature-Vektor (float32, 512d) erhalten
out = model.get_image_features(pixel_values=img)
# Den Vektor vor dem Einfügen in die DB normalisieren
out = out / torch.norm(out, dim=-1, keepdims=True)
Jetzt haben wir bereits alle Daten gesammelt, die wir zum Erstellen der Tabelle benötigen. Es fehlt nur noch ein Puzzlestück: das Einfügen der Daten in MyScale. Für die Verwendung der INSERT
-Klausel können Sie sich an die SQL-Referenz wenden.
# Eine Demonstration des Einfügens einer einzelnen Zeile in die Tabelle
# Sie müssen den Feature-Vektor in Python-Listen umwandeln
transac = [_id, url, out.cpu().numpy().squeeze().tolist()]
# den Vektor einfach wie ein normales SQL einfügen
client.insert("unsplash_25k", transac)
# Few-shot Learning am Klassifikator
# Initialisieren der Klassifikator-Parameter
Wie oben besprochen, können wir das Text-Feature verwenden, um unseren Klassifikator zu initialisieren.
from transformers import CLIPTokenizerFast, CLIPModel
# Initialisieren des Tokenizers
tokenizer = CLIPTokenizerFast.from_pretrained(model_name)
# Eingabe, wonach auch immer Sie suchen möchten
prompt = 'a house by the lake'
# das tokenisierte Prompt und sein Feature erhalten
inputs = tokenizer(prompt, return_tensors='pt')
out = model.get_text_features(**inputs)
xq = out.squeeze(0).cpu().detach().numpy().tolist()
Mit dem Text-Feature-Vektor können wir einen ungefähren Schwerpunkt der gewünschten Bilder erhalten, der der anfängliche Parameter des Klassifikators sein wird. Daher kann eine Klassifikator-Klasse wie folgt definiert werden:
DIMS = 512
class Classifier:
def __init__(self, xq: list):
# Modell mit DIMS Eingabegröße und 1 Ausgabe initialisieren
# beachten Sie, dass der Bias ignoriert wird, da wir uns nur auf das Ergebnis des inneren Produkts konzentrieren
self.model = torch.nn.Linear(DIMS, 1, bias=False)
# den initialen Abfragevektor `xq` in einen Tensor-Parameter umwandeln, um die Gewichte zu initialisieren
init_weight = torch.Tensor(xq).reshape(1, -1)
self.model.weight = torch.nn.Parameter(init_weight)
# Loss und Optimizer initialisieren
self.loss = torch.nn.BCEWithLogitsLoss()
self.optimizer = torch.optim.SGD(self.model.parameters(), lr=0.1)
Erinnern wir uns an BCEwithLogitsLoss
(Binärer Kreuzentropie-Verlust) dazu da, negative Beispiele abzustoßen und den Entscheidungsvektor zu positiven Beispielen hinzuziehen. Dies gibt Ihnen eine intuitive Vorstellung davon, was im KI-Teil passiert.
# Ähnliche Beispiele mit DB bekommen
Schließlich haben wir die Vektordatenbank mit MyScale und den Klassifikator mit unserem Text-Prompt aufgebaut. Jetzt können wir seine Ähnlichkeitssuchfunktion nutzen, um hartes negatives Mining am Klassifikator durchzuführen. Aber zuerst müssen wir der Datenbank mitteilen, welche Metrik die Merkmale zur Messung der Ähnlichkeit verwenden.
MyScale bietet viele verschiedene Algorithmen, um Ihre Suche auf verschiedenen Metriken zu beschleunigen. Gängige Metriken wie L2
, Kosinus
und IP
werden unterstützt. In diesem Beispiel folgen wir der CLIP-Einrichtung und wählen die Kosinusdistanz als unsere Metrik, um nächstgelegene Nachbarn zu suchen und verwenden einen approximierten Algorithmus für die Suche nach den nächstgelegenen Nachbarn namens MSTG
, um unser Merkmal zu indizieren.
-- Wir erstellen einen Vektorindex vindex auf der Vektorspalte
-- mit den Parametern `metric` und `ncentroids`
ALTER TABLE unsplash_25k ADD VECTOR INDEX vindex vector TYPE MSTG('metric_type=Cosine')
Sobald der Vektorindex erstellt wurde, können wir nun mit dem Operator distance
die nächstgelegenen Nachbarn abfragen.
-- Bitte beachten Sie, dass der Abfragevektor in einen String umgewandelt werden sollte, bevor er ausgeführt wird
SELECT id, url, vector, distance(vector, <query-vector>) AS dist FROM unsplash_25k ORDER BY dist LIMIT 9
Bitte beachten Sie: Für SQL-Verben, die Werte zurückgeben, wie
SELECT
, müssen Sieclient.query()
verwenden, um das Ergebnis abzurufen.
Sie können auch eine gemischte Abfrage durchführen, bei der unerwünschte Zeilen herausgefiltert werden:
SELECT id, url, vector, distance(vector, <query-vector>) AS dist
FROM unsplash_25k WHERE id NOT IN ('U5pTkZL8JI4','UvdzJDxcJg4','22o6p17bCtQ', 'cyPqQXNJsG8')
ORDER BY dist LIMIT 9
Angenommen, wir haben den obigen SQL-Befehl in einen String qstr
benannt, dann kann die Abfrage in Python wie folgt durchgeführt werden:
q = client.query(qstr).named_results()
Das zurückgegebene q
enthält mehrere objektähnliche Wörterbücher. In diesem Fall haben wir 9 zurückgegebene Objekte, da wir die 9 nächsten Nachbarn angefordert haben. Wir können Spaltennamen verwenden, um Werte aus jedem Element von q
abzurufen. Wenn wir zum Beispiel alle IDs und ihre Entfernung zum Abfragevektor erhalten möchten, können wir in Python folgenden Code verwenden:
id_dist = [(_q["id"], _q["dist"]) for _q in q]
# Feinabstimmung des Klassifikators
Mit der Leistung von MyScale können wir nun die nächsten Nachbarn in der Datenbank in einem Augenblick abrufen. Der letzte Schritt dieser App besteht darin, den Klassifikator entsprechend der Überwachung des Benutzers anzupassen.
Ich werde den Schritt des UI-Designs auslassen, weil es zu narrativ ist, um es in diesem Blog zu schreiben 😛 Ich komme direkt zum Punkt, wenn das Modelltraining stattfindet.
# HINWEIS: Bitte fügen Sie dies zum vorherigen Klassifikator hinzu
def fit(self, X: list, y: list, iters: int = 5):
# X und y in Tensoren umwandeln
X = torch.Tensor(X)
y = torch.Tensor(y).reshape(-1, 1)
for i in range(iters):
# Gradienten zurücksetzen
self.optimizer.zero_grad()
# Gewicht vor der Inferenz normalisieren
# Dadurch wird der Gradient begrenzt, da Sie sonst eine Explosion auf dem Abfragevektor haben
self.model.weight.data = self.model.weight.data / torch.norm(self.model.weight.data, p=2, dim=-1)
# Vorwärtsdurchlauf
out = self.model(X)
# Loss berechnen
loss = self.loss(out, y)
# Rückwärtsdurchlauf
loss.backward()
# Gewichte aktualisieren
self.optimizer.step()
Der obige Code gibt Ihnen eine Few-shot-Learning-Pipeline, um den vorhandenen Klassifikator zu trainieren. Mit nur wenigen annotierten Bildern kann der Klassifikator konvergieren und Ihnen eine beeindruckende Genauigkeit für das Konzept in Ihrem Kopf geben.
Der Trainingsprozess ist trivial. Zuerst erinnern wir uns daran, dass der Gewichtsvektor im Allgemeinen ein Indikator ist, der die Ähnlichkeit zwischen der Abfrage und dem Gewünschten misst. Sie können ihn als den Schwerpunkt eines Kegels einer Kugel mit dem Klassifikatorparameter als Richtungsvektor und der Schwellenwert als Radius betrachten. Alles innerhalb des Kegels wird als positiv behandelt, während das Äußere negativ ist. Trainingsschritte werden den Vektor dazu bringen, so viele positive Beispiele wie möglich abzudecken und von den negativen Beispielen wegzubleiben. Wenn wir weiterhin die Theorie des Kegelvektors betrachten, benötigen wir nur einen normierten Vektor, um den Schwerpunkt des Kegels zu beschreiben. Daher müssen wir den gelernten Parameter nach jeder Iteration normalisieren. Wir können auch anders denken: Die positiven Beispiele, die unnormalisierte Vektoren sind, ziehen den Schwerpunkt zu ihren Positionen, und wir könnten am Ende einen Vektor haben, der in der Größenordnung sehr lang ist, aber schlecht in der Beschreibung der Richtung zwischen den positiven Beispielen ist. Dies würde die Leistung der Ähnlichkeitssuche beeinträchtigen. Durch die Normalisierung des Vektors bleibt nur die senkrechte Komponente des Gradienten erhalten. Dadurch wird unser visuelles Ergebnis in unserer Demo stabilisiert.
# Am Ende
In dieser Demo haben wir gezeigt, wie man eine Demo erstellt, die einen Few-shot-gelernten Klassifikator mit MyScale trainiert. Noch wichtiger ist, dass wir auch gezeigt haben, wie man MyScale verwendet, um erweiterte SQL-Abfragen mit seiner fortschrittlichen Vektorsuchmaschine zu speichern, zu indizieren und zu durchsuchen. Ich hoffe, Ihnen hat dieser Blog gefallen!
Referenzen: