Das Bild-für-Bild-Suchsystem der zweiten Generation
Dieser Artikel ist der zweite Teil von The Journey to Optimizing Billion-scale Image Search von UPYUN. Wenn Sie den ersten Teil verpasst haben, klicken Sie hier.
Das Bild-für-Bild-Suchsystem der zweiten Generation
Das Search-by-Image-System der zweiten Generation setzt technisch auf die CNN + Milvus-Lösung. Das System basiert auf Feature-Vektoren und bietet eine bessere technische Unterstützung.
Merkmalsextraktion
Im Bereich der Computer Vision hat sich der Einsatz von künstlicher Intelligenz zum Mainstream entwickelt. Auch die Merkmalsextraktion des Bildsuchsystems der zweiten Generation verwendet ein Faltungsneuronales Netz (CNN) als zugrunde liegende Technologie
Der Begriff CNN ist schwer zu verstehen. Hier konzentrieren wir uns auf die Beantwortung von zwei Fragen:
- Was kann CNN leisten?
- Warum kann ich CNN für eine Bildsuche verwenden?
1-meme.jpg
Im Bereich der künstlichen Intelligenz gibt es viele Wettbewerbe, und die Bildklassifizierung ist einer der wichtigsten. Die Aufgabe der Bildklassifizierung besteht darin, festzustellen, ob es sich bei dem Inhalt des Bildes um eine Katze, einen Hund, einen Apfel, eine Birne oder um andere Arten von Objekten handelt.
Was kann CNN tun? Es kann Merkmale extrahieren und Objekte erkennen. Es extrahiert Merkmale aus mehreren Dimensionen und misst, wie nahe die Merkmale eines Bildes den Merkmalen von Katzen oder Hunden sind. Wir können die Merkmale, die den Merkmalen am nächsten kommen, als Identifizierungsergebnis auswählen, das uns anzeigt, ob es sich bei dem Inhalt eines bestimmten Bildes um eine Katze, einen Hund oder etwas anderes handelt.
Welcher Zusammenhang besteht zwischen der Objektidentifizierungsfunktion von CNN und der Suche nach Bildern? Was wir wollen, ist nicht das endgültige Identifikationsergebnis, sondern der aus mehreren Dimensionen extrahierte Merkmalsvektor. Die Merkmalsvektoren von zwei Bildern mit ähnlichem Inhalt müssen nahe beieinander liegen.
Welches CNN-Modell soll ich verwenden?
Die Antwort lautet VGG16. Warum sollte man es wählen? Erstens hat VGG16 eine gute Generalisierungsfähigkeit, das heißt, es ist sehr vielseitig. Zweitens haben die von VGG16 extrahierten Merkmalsvektoren 512 Dimensionen. Wenn es nur wenige Dimensionen gibt, kann die Genauigkeit beeinträchtigt werden. Wenn es zu viele Dimensionen gibt, sind die Kosten für die Speicherung und Berechnung dieser Merkmalsvektoren relativ hoch.
Die Verwendung von CNN zur Extraktion von Bildmerkmalen ist eine gängige Lösung. Wir können VGG16 als Modell und Keras + TensorFlow für die technische Implementierung verwenden. Hier ist das offizielle Beispiel von Keras:
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
model = VGG16(weights=’imagenet’, include_top=False)
img_path = ‘elephant.jpg’
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
features = model.predict(x)
Die hier extrahierten Merkmale sind Merkmalsvektoren.
1. Normalisierung
Um nachfolgende Operationen zu erleichtern, normalisieren wir oft die Features:
Was anschließend verwendet wird, ist auch das normalisierte norm_feat
.
2. Beschreibung des Bildes
Das Bild wird mit der Methode image.load_img
von keras.preprocessing
geladen:
from keras.preprocessing import image
img_path = 'elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224))
Es handelt sich dabei um die TensorFlow-Methode, die von Keras aufgerufen wird. Für Details siehe die TensorFlow-Dokumentation. Das endgültige Bildobjekt ist eigentlich eine PIL Image Instanz (die von TensorFlow verwendete PIL).
3. Konvertierung von Bytes
In der Praxis wird der Bildinhalt oft über das Netzwerk übertragen. Daher ziehen wir es vor, statt Bilder aus dem Pfad zu laden, Bytes-Daten direkt in Bildobjekte, d.h. PIL Images, zu konvertieren:
import io
from PIL import Image
# img_bytes: 图片内容 bytes
img = Image.open(io.BytesIO(img_bytes))
img = img.convert('RGB')
img = img.resize((224, 224), Image.NEAREST)
Das obige img ist dasselbe wie das Ergebnis der Methode image.load_img. Es gibt zwei Dinge zu beachten:
- Sie müssen eine RGB-Konvertierung durchführen.
- Sie müssen die Größe ändern (resize ist der zweite Parameter von
load_img method
).
4. Verarbeitung des schwarzen Randes
Bilder, wie z. B. Screenshots, können gelegentlich ziemlich viele schwarze Ränder haben. Diese schwarzen Ränder haben keinen praktischen Wert und sind sehr störend. Aus diesem Grund ist das Entfernen von schwarzen Rändern ebenfalls eine gängige Praxis.
Ein schwarzer Rand ist im Wesentlichen eine Reihe oder Spalte von Pixeln, in der alle Pixel (0, 0, 0) sind (RGB-Bild). Um den schwarzen Rand zu entfernen, muss man diese Zeilen oder Spalten finden und löschen. Dies ist eigentlich eine 3-D-Matrix-Multiplikation in NumPy.
Ein Beispiel für das Entfernen horizontaler schwarzer Ränder:
# -*- coding: utf-8 -*-
import numpy as np
from keras.preprocessing import image
def RemoveBlackEdge(img):
Args:
img: PIL image instance
Returns:
PIL image instance
"""
width = img.width
img = image.img_to_array(img)
img_without_black = img[~np.all(img == np.zeros((1, width, 3), np.uint8), axis=(1, 2))]
img = image.array_to_img(img_without_black)
return img
Das ist ziemlich genau das, was ich über die Verwendung von CNN zur Extraktion von Bildmerkmalen und zur Implementierung anderer Bildverarbeitungsfunktionen sagen möchte. Werfen wir nun einen Blick auf Vektorsuchmaschinen.
Vektorielle Suchmaschine
Das Problem der Extraktion von Merkmalsvektoren aus Bildern ist gelöst. Die verbleibenden Probleme sind:
- Wie speichert man Merkmalsvektoren?
- Wie berechnet man die Ähnlichkeit von Merkmalsvektoren, d. h. wie sucht man? Die Open-Source-Vektorsuchmaschine Milvus kann diese beiden Probleme lösen. Bislang hat sie sich in unserer Produktionsumgebung gut bewährt.
3-milvus-logo.png
Milvus, die Vektorsuchmaschine
Die Extraktion von Merkmalsvektoren aus einem Bild reicht bei weitem nicht aus. Wir müssen diese Merkmalsvektoren auch dynamisch verwalten (Hinzufügen, Löschen und Aktualisieren), die Ähnlichkeit der Vektoren berechnen und die Vektordaten im Bereich der nächsten Nachbarn zurückgeben. Die Open-Source-Vektorsuchmaschine Milvus erfüllt diese Aufgaben recht gut.
Im weiteren Verlauf dieses Artikels werden spezifische Praktiken und zu beachtende Punkte beschrieben.
1. Anforderungen an die CPU
Um Milvus zu verwenden, muss Ihre CPU den Befehlssatz avx2 unterstützen. Bei Linux-Systemen können Sie mit dem folgenden Befehl überprüfen, welche Befehlssätze Ihre CPU unterstützt:
cat /proc/cpuinfo | grep flags
Sie erhalten dann eine Meldung wie diese:
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm abm cpuid_fault epb invpcid_single pti intel_ppin tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc dtherm ida arat pln pts
Was auf die Flags folgt, sind die Befehlssätze, die Ihre CPU unterstützt. Natürlich sind das viel mehr, als ich brauche. Ich möchte nur sehen, ob ein bestimmter Befehlssatz, wie z.B. avx2, unterstützt wird. Fügen Sie einfach ein grep hinzu, um es zu filtern:
cat /proc/cpuinfo | grep flags | grep avx2
Wenn kein Ergebnis zurückgegeben wird, bedeutet das, dass dieser spezifische Befehlssatz nicht unterstützt wird. Sie müssen dann Ihren Rechner wechseln.
2. Kapazitätsplanung
Die Kapazitätsplanung ist unsere erste Überlegung beim Entwurf eines Systems. Wie viele Daten müssen wir speichern? Wie viel Speicherplatz und Festplattenplatz benötigen die Daten?
Machen wir ein paar kurze Berechnungen. Jede Dimension eines Vektors ist float32. Ein Float32-Typ benötigt 4 Bytes. Ein Vektor mit 512 Dimensionen benötigt also 2 KB Speicherplatz. Umgekehrt gilt das Gleiche:
- Tausend 512-dimensionale Vektoren benötigen 2 MB Speicherplatz.
- Eine Million 512-dimensionale Vektoren erfordern 2 GB Speicherplatz.
- 10 Millionen 512-dimensionale Vektoren erfordern 20 GB Speicherplatz.
- 100 Millionen 512-dimensionale Vektoren erfordern 200 GB Speicherplatz.
- Eine Milliarde 512-dimensionaler Vektoren erfordern 2 TB Speicherplatz.
Wenn wir alle Daten im Speicher ablegen wollen, benötigt das System mindestens die entsprechende Speicherkapazität.
Es wird empfohlen, das offizielle Tool zur Größenberechnung zu verwenden: Milvus-Größenberechnungstool.
In Wirklichkeit ist unser Speicher vielleicht gar nicht so groß. (Es macht nichts, wenn Sie nicht genug Speicher haben. Milvus spült die Daten automatisch auf die Festplatte.) Zusätzlich zu den ursprünglichen Vektordaten müssen wir auch die Speicherung anderer Daten wie z. B. Protokolle berücksichtigen.
3. Systemkonfiguration
Weitere Informationen über die Systemkonfiguration finden Sie in der Milvus-Dokumentation:
- Milvus-Server-Konfiguration: https://milvus.io/docs/v0.10.1/milvus_config.md
4. Datenbank-Design
Sammlung und Partition
- Sammlung ist auch als Tabelle bekannt.
- Partition bezieht sich auf die Unterteilungen innerhalb einer Sammlung.
Die zugrundeliegende Implementierung von Partitionen ist eigentlich die gleiche wie die von Sammlungen, nur dass eine Partition unter einer Sammlung liegt. Aber mit Partitionen wird die Organisation der Daten flexibler. Wir können auch eine bestimmte Partition in einer Sammlung abfragen, um bessere Abfrageergebnisse zu erzielen.
Wie viele Sammlungen und Partitionen können wir haben? Die grundlegenden Informationen über Sammlungen und Partitionen befinden sich in den Metadaten. Milvus verwendet entweder SQLite (Milvus-interne Integration) oder MySQL (externe Verbindung erforderlich) für die interne Metadatenverwaltung. Wenn Sie für die Verwaltung der Metadaten standardmäßig SQLite verwenden, werden Sie bei einer zu großen Anzahl von Sammlungen und Partitionen erhebliche Leistungseinbußen erleiden. Daher sollte die Gesamtzahl der Sammlungen und Partitionen 50.000 nicht überschreiten (Milvus 0.8.0 begrenzt diese Zahl auf 4.096). Wenn Sie eine größere Anzahl einstellen müssen, empfiehlt es sich, MySQL über eine externe Verbindung zu verwenden.
Die von Milvus' Sammlung und Partition unterstützte Datenstruktur ist sehr einfach, nämlich ID + vector
. Mit anderen Worten, es gibt nur zwei Spalten in der Tabelle: ID und Vektordaten.
Anmerkung:
- Die ID sollte eine ganze Zahl sein.
- Wir müssen sicherstellen, dass die ID innerhalb einer Sammlung und nicht innerhalb einer Partition eindeutig ist.
Bedingte Filterung
Bei der Verwendung herkömmlicher Datenbanken können wir Feldwerte als Filterbedingungen angeben. Milvus filtert zwar nicht genau auf die gleiche Weise, aber wir können einfache bedingte Filterung mit Hilfe von Sammlungen und Partitionen implementieren. Ein Beispiel: Wir haben eine große Menge an Bilddaten und die Daten gehören zu bestimmten Benutzern. Dann können wir die Daten in Partitionen nach Benutzer unterteilen. Die Verwendung des Benutzers als Filterbedingung bedeutet also eigentlich die Angabe der Partition.
Strukturierte Daten und Vektor-Mapping
Milvus unterstützt nur die Datenstruktur ID + Vektor. In Geschäftsszenarien benötigen wir jedoch strukturierte Daten, die eine geschäftliche Bedeutung haben. Mit anderen Worten: Wir müssen strukturierte Daten über Vektoren finden. Dementsprechend müssen wir die Mapping-Beziehungen zwischen strukturierten Daten und Vektoren über die ID pflegen.
structured data ID <--> mapping table <--> Milvus ID
Auswahl des Index
Sie können sich auf die folgenden Artikel beziehen:
- Arten von Indizes: https://www.milvus.io/docs/v0.10.1/index.md
- Wie man einen Index auswählt: https://medium.com/@milvusio/how-to-choose-an-index-in-milvus-4f3d15259212
5. Verarbeitung der Suchergebnisse
Die Suchergebnisse von Milvus sind eine Sammlung von ID + Entfernung:
- ID: die ID in einer Sammlung.
- Abstand: ein Abstandswert von 0 ~ 1 gibt den Grad der Ähnlichkeit an; je kleiner der Wert, desto ähnlicher sind die beiden Vektoren.
Filtern von Daten, deren ID -1 ist
Wenn die Anzahl der Sammlungen zu klein ist, können die Suchergebnisse Daten enthalten, deren ID -1 ist. Diese müssen wir selbst herausfiltern.
Paginierung
Die Suche nach Vektoren ist ganz anders. Die Abfrageergebnisse werden in absteigender Reihenfolge der Ähnlichkeit sortiert, und die ähnlichsten (topK) Ergebnisse werden ausgewählt (topK wird vom Benutzer zum Zeitpunkt der Abfrage angegeben).
Milvus unterstützt keine Paginierung. Wir müssen die Paginierungsfunktion selbst implementieren, wenn wir sie für unsere Arbeit benötigen. Wenn wir zum Beispiel zehn Ergebnisse auf jeder Seite haben und nur die dritte Seite anzeigen wollen, müssen wir angeben, dass topK = 30 ist und nur die letzten zehn Ergebnisse zurückgegeben werden.
Ähnlichkeitsschwelle für Unternehmen
Der Abstand zwischen den Vektoren zweier Bilder liegt zwischen 0 und 1. Wenn wir entscheiden wollen, ob zwei Bilder in einem bestimmten Geschäftsszenario ähnlich sind, müssen wir einen Schwellenwert innerhalb dieses Bereichs angeben. Die beiden Bilder sind ähnlich, wenn der Abstand kleiner als der Schwellenwert ist, oder sie sind sehr unterschiedlich, wenn der Abstand größer als der Schwellenwert ist. Sie müssen den Schwellenwert an Ihre eigenen geschäftlichen Anforderungen anpassen.
Dieser Artikel wurde von rifewang, Milvus-Benutzer und Softwareentwickler bei UPYUN, geschrieben. Wenn Ihnen dieser Artikel gefällt, können Sie ihn gerne auf https://github.com/rifewang lesen.
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word