# Bildersuche

# Einführung

Die Bildersuche ist eine beliebte und leistungsstarke Anwendung, die es Benutzern ermöglicht, ähnliche Bilder anhand von Merkmalen oder visuellem Inhalt zu finden. Mit der schnellen Entwicklung der Computer Vision und des Deep Learning wurde diese Fähigkeit erheblich verbessert.

Dieser Leitfaden soll Ihnen helfen, die neuesten Techniken und Tools für die Bildersuche zu nutzen. In diesem Leitfaden lernen Sie, wie Sie:

  • Einen Datensatz mit Vektor-Embeddings mithilfe eines öffentlichen Datensatzes und Modells erstellen
  • Die Bildähnlichkeitssuche mit MyScale durchführen, einer leistungsstarken Plattform, die den Suchprozess optimiert und schnelle und genaue Ergebnisse liefert

Wenn Sie mehr daran interessiert sind, die Möglichkeiten von MyScale zu erkunden, können Sie den Abschnitt Datensatz erstellen überspringen und direkt zum Abschnitt Daten in MyScale importieren gehen.

Sie können diesen Datensatz in der MyScale-Konsole importieren, indem Sie den Anweisungen im Abschnitt Daten importieren für den Datensatz Bildersuche folgen. Nach dem Import können Sie direkt zum Abschnitt Abfragen von MyScale gehen, um diese Beispielanwendung zu nutzen.

# Voraussetzungen

Bevor wir beginnen, müssen wir den ClickHouse Python-Client (opens new window) und die HuggingFace datasets-Bibliothek installieren, um Beispieldaten herunterzuladen.

pip install datasets clickhouse-connect

Um den Schritten im Abschnitt Datensatz erstellen folgen zu können, müssen wir Transformers und andere erforderliche Abhängigkeiten installieren.

pip install requests transformers torch tqdm

# Datensatz erstellen

# Daten herunterladen und verarbeiten

Wir laden Daten aus dem Unsplash-Datensatz (opens new window) herunter und verwenden den Lite-Datensatz.

wget https://unsplash-datasets.s3.amazonaws.com/lite/latest/unsplash-research-dataset-lite-latest.zip
# Entpacken Sie die heruntergeladenen Dateien in ein temporäres Verzeichnis
unzip unsplash-research-dataset-lite-latest.zip -d tmp

Wir lesen die heruntergeladenen Daten ein und wandeln sie in Pandas-Datenframes um.

import numpy as np
import pandas as pd
import glob
documents = ['photos', 'conversions']
datasets = {}
for doc in documents:
    files = glob.glob("tmp/" + doc + ".tsv*")
    subsets = []
    for filename in files:
        df = pd.read_csv(filename, sep='\t', header=0)
        subsets.append(df)
    datasets[doc] = pd.concat(subsets, axis=0, ignore_index=True)
df_photos = datasets['photos']
df_conversions = datasets['conversions']

# Generieren von Bild-Embeddings

Um Embeddings aus Bildern zu extrahieren, definieren wir eine Funktion extract_image_features, die das Modell clip-vit-base-patch32 (opens new window) von HuggingFace verwendet. Die resultierenden Embeddings sind 512-dimensionale Vektoren.

import torch
from transformers import CLIPProcessor, CLIPModel
model = CLIPModel.from_pretrained('openai/clip-vit-base-patch32')
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
def extract_image_features(image):
    inputs = processor(images=image, return_tensors="pt")
    with torch.no_grad():
        outputs = model.get_image_features(**inputs)
        outputs = outputs / outputs.norm(dim=-1, keepdim=True)
    return outputs.squeeze(0).tolist()

Danach wählen wir die ersten 1000 Foto-IDs aus dem Datenrahmen df_photos aus, laden die entsprechenden Bilder herunter und extrahieren ihre Bild-Embeddings mithilfe der Funktion extract_image_features.

from PIL import Image
import requests
from tqdm.auto import tqdm
# Wählen Sie die ersten 1000 Foto-IDs aus
photo_ids = df_photos['photo_id'][:1000].tolist()
# Erstellen Sie einen neuen Datenrahmen nur mit den ausgewählten Foto-IDs
df_photos = df_photos[df_photos['photo_id'].isin(photo_ids)].reset_index(drop=True)
# Behalten Sie nur die Spalten 'photo_id' und 'photo_image_url' im Datenrahmen
df_photos = df_photos[['photo_id', 'photo_image_url']]
# Fügen Sie dem Datenrahmen eine neue Spalte 'photo_embed' hinzu
df_photos['photo_embed'] = None
# Laden Sie die Bilder herunter und extrahieren Sie ihre Embeddings mithilfe der Funktion 'extract_image_features'
for i, row in tqdm(df_photos.iterrows(), total=len(df_photos)):
    # Konstruieren Sie eine URL, um ein Bild mit kleinerer Größe herunterzuladen, indem Sie die Bild-URL ändern
    url = row['photo_image_url'] + "?q=75&fm=jpg&w=200&fit=max"
    try:
        res = requests.get(url, stream=True).raw
        image = Image.open(res)
    except:
        # Entfernen Sie das Foto, wenn der Bild-Download fehlschlägt
        photo_ids.remove(row['photo_id'])
        continue
    # Extrahieren Sie das Feature-Embedding
    df_photos.at[i, 'photo_embed'] = extract_image_features(image)

# Erstellen des Datensatzes

Wir haben jetzt zwei Datenrahmen: einen für Fotoinformationen mit Embeddings und einen für Konvertierungsinformationen.

df_photos = df_photos[df_photos['photo_id'].isin(photo_ids)].reset_index().rename(columns={'index': 'id'})
df_conversions = df_conversions[df_conversions['photo_id'].isin(photo_ids)].reset_index(drop=True)
df_conversions = df_conversions[['photo_id', 'keyword']].reset_index().rename(columns={'index': 'id'})

Schließlich konvertieren wir die Datenrahmen in Parquet-Dateien und laden sie dann in das Hugging Face-Repository myscale/unsplash-examples (opens new window) hoch, indem wir den Schritten (opens new window) folgen. Dadurch wird der Zugriff auf die Daten erleichtert und das Teilen der Daten ermöglicht.

import pyarrow as pa
import pyarrow.parquet as pq
import numpy as np
# Erstellen Sie ein Table-Objekt aus den Daten und dem Schema
photos_table = pa.Table.from_pandas(df_photos)
conversion_table = pa.Table.from_pandas(df_conversions)
# Schreiben Sie die Tabelle in eine Parquet-Datei
pq.write_table(photos_table, 'photos.parquet')
pq.write_table(conversion_table, 'conversions.parquet')

# Daten in MyScale importieren

# Daten laden

Um Daten in MyScale zu importieren, laden wir zunächst Daten aus dem HuggingFace-Datensatz myscale/unsplash-examples (opens new window), der im vorherigen Abschnitt erstellt wurde. Der folgende Codeausschnitt zeigt, wie Daten geladen und in Panda-Datenrahmen umgewandelt werden.

Hinweis: photo_embed ist ein 512-dimensionaler Gleitkommavektor, der die aus einem Bild extrahierten Bildmerkmale mithilfe des CLIP (opens new window)-Modells darstellt.

from datasets import load_dataset
photos = load_dataset("myscale/unsplash-examples", data_files="photos-all.parquet", split="train")
conversions = load_dataset("myscale/unsplash-examples", data_files="conversions-all.parquet", split="train")
# Transformieren Sie die Datensätze in einen Panda-Datenrahmen
photo_df = photos.to_pandas()
conversion_df = conversions.to_pandas()
# Konvertieren Sie photo_embed von np array in Liste
photo_df['photo_embed'] = photo_df['photo_embed'].apply(lambda x: x.tolist())

# Tabelle erstellen

Als nächstes erstellen wir Tabellen in MyScale. Bevor Sie beginnen, müssen Sie Ihre Cluster-Host-, Benutzername- und Passwortinformationen aus der MyScale-Konsole abrufen. Der folgende Codeausschnitt erstellt zwei Tabellen, eine für Fotoinformationen und eine für Konvertierungsinformationen.

import clickhouse_connect
# Initialisieren Sie den Client
client = clickhouse_connect.get_client(host='YOUR_CLUSTER_HOST', port=443, username='YOUR_USERNAME', password='YOUR_CLUSTER_PASSWORD')
# Tabelle löschen, falls vorhanden
client.command("DROP TABLE IF EXISTS default.myscale_photos")
client.command("DROP TABLE IF EXISTS default.myscale_conversions")
# Tabelle für Fotos erstellen
client.command("""
CREATE TABLE default.myscale_photos
(
    id UInt64,
    photo_id String,
    photo_image_url String,
    photo_embed Array(Float32),
    CONSTRAINT vector_len CHECK length(photo_embed) = 512
)
ORDER BY id
""")
# Tabelle für Konvertierungen erstellen
client.command("""
CREATE TABLE default.myscale_conversions
(
    id UInt64,
    photo_id String,
    keyword String
)
ORDER BY id
""")

# Daten hochladen

Nachdem die Tabellen erstellt wurden, fügen wir die aus den Datensätzen geladenen Daten in die Tabellen ein und erstellen einen Vektorindex, um spätere Vektor-Suchabfragen zu beschleunigen. Der folgende Codeausschnitt zeigt, wie Daten in die Tabellen eingefügt und ein Vektorindex mit Kosinus-Abstandsmetrik erstellt wird.

# Daten aus den Datensätzen hochladen
client.insert("default.myscale_photos", photo_df.to_records(index=False).tolist(),
              column_names=photo_df.columns.tolist())
client.insert("default.myscale_conversions", conversion_df.to_records(index=False).tolist(),
              column_names=conversion_df.columns.tolist())
# Anzahl der eingefügten Daten überprüfen
print(f"Fotos Anzahl: {client.command('SELECT count(*) FROM default.myscale_photos')}")
print(f"Konvertierungen Anzahl: {client.command('SELECT count(*) FROM default.myscale_conversions')}")
# Vektorindex mit Kosinus erstellen
client.command("""
ALTER TABLE default.myscale_photos 
ADD VECTOR INDEX photo_embed_index photo_embed
TYPE MSTG
('metric_type=Cosine')
""")
# Status des Vektorindex überprüfen, stellen Sie sicher, dass der Vektorindex mit dem Status 'Built' bereit ist
get_index_status="SELECT status FROM system.vector_indices WHERE name='photo_embed_index'"
print(f"Index Build-Status: {client.command(get_index_status)}")

# Abfragen von MyScale

# Top K ähnliche Bilder finden

Um die Top K ähnlichen Bilder mithilfe der Vektor-Suche zu finden, befolgen Sie diese Schritte:

Zuerst wählen wir ein Bild zufällig aus und zeigen es mit der Funktion show_image() an.

import requests
import matplotlib.pyplot as plt
from PIL import Image
from io import BytesIO
# Bild mit URL herunterladen
def download(url):
    response = requests.get(url)
    return Image.open(BytesIO(response.content))
# Methode zum Anzeigen eines Online-Bildes mit einer URL definieren
def show_image(url, title=None):
    img = download(url)
    fig = plt.figure(figsize=(4, 4))
    plt.imshow(img)
    plt.show()
# Anzahl der Zeilen in jeder Tabelle anzeigen
print(f"Fotos Anzahl: {client.command('SELECT count(*) FROM default.myscale_photos')}")
print(f"Konvertierungen Anzahl: {client.command('SELECT count(*) FROM default.myscale_conversions')}")
# Wählen Sie ein zufälliges Bild aus der Tabelle als Ziel aus
random_image = client.query("SELECT * FROM default.myscale_photos ORDER BY rand() LIMIT 1")
assert random_image.row_count == 1
target_image_id = random_image.first_item["photo_id"]
target_image_url = random_image.first_item["photo_image_url"]
target_image_embed = random_image.first_item["photo_embed"]
print("Aktuell ausgewähltes Bild id={}, url={}".format(target_image_id, target_image_url))
# Zielbild anzeigen
print("Lade Zielbild...")
show_image(target_image_url)

Ein Beispielbild:

Verwenden Sie dann die Vektor-Suche, um die Top K Kandidaten zu identifizieren, die dem ausgewählten Bild am ähnlichsten sind, und zeigen Sie diese Kandidaten an:

# Datenbank abfragen, um die Top K ähnlichen Bilder zum gegebenen Bild zu finden
top_k = 10
results = client.query(f"""
SELECT photo_id, photo_image_url, distance(photo_embed, {target_image_embed}) as dist
FROM default.myscale_photos
WHERE photo_id != '{target_image_id}'
ORDER BY dist
LIMIT {top_k}
""")
# Bilder herunterladen und der Liste hinzufügen
images_url = []
for r in results.named_results():
    # Konstruieren Sie eine URL, um ein Bild mit kleinerer Größe herunterzuladen, indem Sie die Bild-URL ändern
    url = r['photo_image_url'] + "?q=75&fm=jpg&w=200&fit=max"
    images_url.append(download(url))
# Kandidatenbilder anzeigen
print("Lade Kandidatenbilder...")
for row in range(int(top_k / 5)):
    fig, axs = plt.subplots(1, 5, figsize=(20, 4))
    for i, img in enumerate(images_url[row * 5:row * 5 + 5]):
        axs[i % 5].imshow(img)
    plt.show()

Ähnliche Kandidatenbilder:

# Analyse der Konvertierungsinformationen für jedes Kandidatenbild

Nachdem die Top K ähnlichen Bilder identifiziert wurden, können Sie SQL-Abfragen verwenden, um strukturierte Felder und Vektorfelder zu kombinieren und eine Analyse der Konvertierungsinformationen für jeden Kandidaten durchzuführen.

Um die Gesamtanzahl der Konvertierungen für jedes Kandidatenbild zu berechnen, können Sie die folgende SQL-Abfrage verwenden, um die Bildersuchergebnisse mit der Tabelle conversions zu verbinden:

# Zeigen Sie die Gesamtanzahl der Downloads für jedes Kandidatenbild an
results = client.query(f"""
SELECT photo_id, count(*) as count
FROM default.myscale_conversions
JOIN (
    SELECT photo_id, distance(photo_embed, {target_image_embed}) as dist
    FROM default.myscale_photos
    ORDER BY dist ASC
    LIMIT {top_k}
    ) AS target_photos
ON default.myscale_conversions.photo_id = target_photos.photo_id
GROUP BY photo_id
ORDER BY count DESC
""")
print("Gesamtanzahl der Downloads für jeden Kandidaten")
for r in results.named_results():
    print("- {}: {}".format(r['photo_id'], r['count']))

Beispiel-Ausgabe:

Gesamtanzahl der Downloads für jeden Kandidaten
- Qgb9urMZ8lw: 1729
- f0OL01IHbCM: 1444
- Bgae-sqbe_g: 313
- XYg2zLjxxa0: 207
- BkW8I1n354I: 184
- 5yFOvJZp7Rg: 63
- sKPPBn6OkJg: 48
- joL0nSbZ-lI: 20
- fzDtQWW8dV4: 8
- DCAERnaj31U: 3

Nachdem die Gesamtanzahl der Konvertierungen für jedes Kandidatenbild berechnet wurde, können Sie das Kandidatenbild mit den meisten Downloads identifizieren und die detaillierten Konvertierungsinformationen pro Download-Keyword für dieses Bild untersuchen. Verwenden Sie die folgende SQL-Abfrage:

# Zeigen Sie das beliebteste Kandidatenbild und die Top 5 zugehörigen Download-Keywords an
most_popular_candidate = results.first_item['photo_id']
# Zeigen Sie das beliebteste Bild an
candidate_url = client.command(f"""
SELECT photo_image_url FROM default.myscale_photos WHERE photo_id = '{most_popular_candidate}'
""")
print("Lade das beliebteste Kandidatenbild...")
show_image(candidate_url)
# Finden Sie die Top-5-Download-Keywords
results = client.query(f"""
SELECT keyword, count(*) as count
FROM default.myscale_conversions
WHERE photo_id='{most_popular_candidate}'
GROUP BY keyword
ORDER BY count DESC
LIMIT 5
""")
print("Zugehörige Keywords und Download-Zahlen für das beliebteste Kandidatenbild")
for r in results.named_results():
    print(f"- {r['keyword']}: {r['count']}") 

Das beliebteste Kandidatenbild in den Top 10:

Beispiel-Ausgabe:

Zugehörige Keywords und Download-Zahlen für das beliebteste Kandidatenbild
- bee: 1615
- bees: 21
- bumblebee: 13
- honey: 13
- honey bee: 12