Ähnliche Bilder Suche von pHash Entfernung in Elasticsearch
Ähnliches Bild Suche problem
- Millionen von Bildern pHash'ed gespeichert und in Elasticsearch.
- Format ist "11001101...11" (Länge 64), kann aber geändert werden (besser nicht).
Gegeben, unterliegen Bild-hash "100111..10" wir wollen alle zu finden ähnlich wie Bild-hashes in Elasticsearch index innerhalb hamming-Distanz von 8.
Natürlich, Abfrage zurückgeben kann Bilder mit mehr Abstand als 8 und Skript in Elasticsearch oder außerhalb können filter der Ergebnismenge. Aber insgesamt suchen, mal muss innerhalb von 1 Sekunde oder so.
Unsere aktuelle Zuordnung
Jedes Dokument mit verschachtelten images
Feld mit Bild hashes:
{
"images": {
"type": "nested",
"properties": {
"pHashFingerprint": {"index": "not_analysed", "type": "string"}
}
}
}
Unsere schlechte Lösung
Tatsache: Elasticsearch fuzzy-query unterstützt die Levenshtein-Distanz von max 2 nur.
Wir verwendet benutzerdefinierte tokenisierung split 64-bit-Zeichenfolge in 4 Gruppen von 16 bits und do 4-Gruppe suchen, mit vier fuzzy-Abfragen.
Analyzer:
{
"analysis": {
"analyzer": {
"split4_fingerprint_analyzer": {
"type": "custom",
"tokenizer": "split4_fingerprint_tokenizer"
}
},
"tokenizer": {
"split4_fingerprint_tokenizer": {
"type": "pattern",
"group": 0,
"pattern": "([01]{16})"
}
}
}
}
Dann neues Feld-mapping:
"index_analyzer": "split4_fingerprint_analyzer",
Dann Abfrage:
{
"query": {
"filtered": {
"query": {
"nested": {
"path": "images",
"query": {
"bool": {
"minimum_should_match": 2,
"should": [
{
"fuzzy": {
"phashFingerprint.split4": {
"value": "0010100100111001",
"fuzziness": 2
}
}
},
{
"fuzzy": {
"phashFingerprint.split4": {
"value": "1010100100111001",
"fuzziness": 2
}
}
},
{
"fuzzy": {
"phashFingerprint.split4": {
"value": "0110100100111001",
"fuzziness": 2
}
}
},
{
"fuzzy": {
"phashFingerprint.split4": {
"value": "1110100100111001",
"fuzziness": 2
}
}
}
]
}
}
}
},
"filter": {}
}
}
}
Beachten Sie, dass wir Dokumente haben, die passenden Bilder, nicht die Bilder selbst, aber das sollte nicht ändern Sie die Dinge ein Menge.
Das problem ist, dass diese Abfrage gibt Hunderte von tausenden von Ergebnissen auch nach hinzufügen von weiteren Domänen-spezifischen Filter zur Reduzierung der anfänglichen Satz. Script hat zu viel Arbeit zu berechnen, die hamming-Distanz wieder, daher die Abfrage kann einige Minuten in Anspruch nehmen.
Als erwartet, wenn die Erhöhung minimum_should_match
3 und 4, nur die Teilmenge der Bilder, die gefunden werden müssen zurückgegeben werden, aber die Ergebnismenge ist klein und schnell. Unter 95% der benötigten Bilder werden zurückgegeben mit minimum_should_match
== 3 aber wir brauchen 100% (oder 99,9 Prozent), wie mit minimum_should_match
== 2.
Wir haben versucht, ähnliche Ansätze mit n-Gramm, aber noch nicht so viel Erfolg in gleicher Weise zu viele Ergebnisse.
Lösungen, die von anderen Daten-Strukturen und-Abfragen?
Bearbeiten:
Merkten wir, dass es ein Fehler war, in unserem Evaluierungsprozess und minimum_should_match
== 2 liefert 100% der Ergebnisse. Doch die Verarbeitung Zeit danach dauert im Durchschnitt 5 Sekunden. Wir werden sehen, wenn das script lohnt sich die Optimierung.
- Wenn B ist der ganzzahlige Anzahl von bits in jeder Fingerabdruck (0 <= B <= 64). Dann können Sie speichern B mit jedem Dokument, und zunächst filtern Sie alle Datensätze, in denen B < (sourceB - 8) und B - > (sourceB + 8). Sollte reduzieren Sie Ihre Fingerabdrücke unter Berücksichtigung von mindestens 4x gegeben ist die Gleichverteilung.
- Während es wahr ist, dass Elasticsearch fuzzy-Abfrage und die meisten anderen APIs mit einer Unschärfe param unterstützen nur max edit-Distanz von 2, was fuzzy_like_this Abfrage? Ihre docs tun, beachten Sie, dass es eine Ausnahme für Sie, dass hier. Ich denke, dass könnte Ihnen ermöglichen, zu vermeiden, mit dem hacky Lösung, die Sie derzeit haben. Und natürlich, Sie sind nicht immer Ergebnisse innerhalb der hamming-Abstand 8 aber die Levenshtein-Distanz 8, so bin ich nicht sicher, ob Sie neu berechnet, dass.
- das ist eine interessante Idee, obwohl, dass ist eine sehr große Auswahl, um die Suche auf diese Binomialverteilung, also Reduktion ist sehr klein.
- danke, wir haben versucht, fuzzy_like_this, aber es dauert Minuten dauert ES und es ist eine veraltete Funktion. Gute Idee, obwohl.
- Eigentlich ein paar Millionen Elemente ist nicht viel, sogar 100 Millionen 64-bit-Ganzzahlen (also 8 Byte) ist nur 800 MB RAM und passt problemlos auf der GPU. Ich konnte nicht finden eine gute Referenz, aber ich erwarte, dass CUDA-stream über das dataset in 10er Millisekunden und die genaue Liste als Ausgabe. In hoch-dimensionalen Räumen, insbesondere fuzzy-matching kann nicht profitieren viel von Indizierung und Datenstrukturen. Auch bei der Sortierung, die Sie durch gehen könnte 1740 Millionen 32-bit-keys / Sekunde!
- Ich mag die CUDA-Idee. Ohne eine domain-spezifische hashing-Schema, wirklich der einzige Weg, um die Geschwindigkeit Ihrer Berechnung in Elasticsearch ist durch Splitter Ihre Daten über mehrere cluster-Knoten. In der Erwägung, dass auf einer GPU können Sie schreiben eine optimierte Hamming-tester, der parallelisiert werden kann 32-Wege ohne Netzwerk-oder Festplatten-IO.
- Natürlich ^oben^ ist wohl die vorzeitige Optimierung, es sei denn, Sie müssen subsecond Reaktionszeit. (Dein Anwendungsfall ist das recall-konzentriert, so jemand ist eindeutig investiert in die Beurteilung von tausenden von hamming=8 Spiele, die dauern würde, genug user-Zeit/ - Aufwand zum Rendern Suche Leistung weniger kritisch.)
- Re: Sie sind neueste update (
minimum_should_match
gibt 100%): Das ist toll!!! Wenn du einen Weg findest, um deine zu pushen Hamming-Wertung aus jeder Elasticsearch-cluster-Knoten, sparen Sie auf I/O-und post-processing. Look atfunction_score
Abfragen mitscript_score
. Groovy unterstützt den XOR-operator (^
) und Sie können die Verwendung von Java ist Integer.bitCount auf der XOR-Ausgang, um Ihnen Hamming-Gewicht. - Ich bin auf der Suche auf das gleiche problem, diesmal aber beginnend mit einem hex-string. Also mein Skript wird zuerst analysiert den hex-string in einen BigInteger und vergleicht dann mit den ankommenden string mit @PeterDixon-Moses Vorschlag von XOR-und dann bitCount. Es dauert noch viel zu lange, um zu verarbeiten. In der Tat, dies ist die zweite scoring-script, das ich habe versucht, mit ES (ich habe vorher versucht, viele Iterationen des euklidischen Distanz), und ich habe es nicht geschafft, eine bis-Skala noch. Ich möchte Frage Sie, wie Sie erwarten, dass diese Funktionen skalieren elastisch, aber ich kann nicht sehen, um die join-user-group!
- Spotify hat ein open-source-tool namens [zu ärgern], [1] dass sich aus der box. [1]: github.com/spotify/annoy
Du musst angemeldet sein, um einen Kommentar abzugeben.
Habe ich simuliert und implementiert eine mögliche Lösung, die vermeidet teure "fuzzy" - Abfragen. Statt zur index-Zeit, die Sie nehmen
N
StichprobenM
bits, die 64 bits. Ich denke, dies ist ein Beispiel von Locality-sensitive-hashing. So wird für jedes Dokument (und wenn-Abfragen) Beispiel Anzahlx
ist immer von der gleichen bit-Positionen haben eine gleichmäßige Vermischung zwischen Dokumenten.Abfragen verwenden
term
Filterbool query
'sshould
Klausel mit relativ niedrigenminimum_should_match
Schwelle. Untere Schwelle entspricht höheren "Unschärfe". Leider müssen Sie re-index alle Ihre Bilder zum test dieses Ansatzes.Ich denke
{ "term": { "phash.0": true } }
Abfragen nicht gut führen, weil im Durchschnitt jeder filter entspricht50%
von Dokumenten. Mit 16 bits /sample jedes sample entspricht2^-16 = 0.0015%
von Dokumenten.Ich meine tests mit folgenden Einstellungen:
"0"
-"ff"
)short
Typdoc_values = true
)_source
und Proben, nur die original-binary hash)minimum_should_match
= 150 (von 1024)Erhalten Sie schnellere Geschwindigkeit und der unteren Datenträger-Auslastung mit weniger Proben, aber Dokumente zwischen den hamming-Distanzen von 8 und 9 sind nicht so gut getrennt sind (laut meinen Simulationen). 1024 scheint die maximale Anzahl von
should
Klauseln.Prüfungen wurden auf einem single-Core-i5-3570K, 24 GB RAM, 8 GB für LiveCycle ES, version 1.7.1. Ergebnisse von 500 Abfragen (siehe Hinweise unten, die Ergebnisse sind zu optimistisch):
Werde ich testen, wie sich diese Skalen zu 15 Millionen Dokumente, aber es dauert 3 Stunden zu erzeugen und zu speichern 1 Millionen Dokumente zu jedem index.
Sollten Sie testen, oder berechnen, wie tief Sie sollten
minimum_should_match
um die gewünschte trade-off zwischen treer und falschen entspricht, dieser richtet sich auf die Verteilung der hashes.Beispiel Abfrage (3 von 1024 Felder gezeigt):
Edit: Als ich anfing, weitere benchmarks habe ich gemerkt, dass ich erzeugt hatte zu unterschiedlichen hashes für die verschiedenen Indizes, also die Suche von diesen führte zu null Treffern. Die neu generierten Dokumente Ergebnis in etwa 150 - 250 matches /index /query und sollten realistischer sein.
Neuen Ergebnisse sind in der Grafik dargestellt, bevor, ich hatte 4 GB Speicher für ES zu und die restlichen 20 GB für OS. Suche 1 - 3-Indizes hatten gute performance (mittelwertbildungszeit 0.1 - 0.2 Sekunden), aber die Suche mehr als dies führte zu viele Datenträger-E /a und die Anfragen gestartet die Einnahme von 9 - 11 Sekunden! Dies könnte umgangen werden, indem man weniger Proben der hash aber dann recall-und precision-raten wäre nicht so gut, alternativ könnten Sie eine Maschine mit 64 GB RAM und sehen, wie weit Sie erhalten.
Edit 2: ich re-generierte Daten mit
_source: false
und nicht die Speicherung von hash-Beispiele (nur die raw-hash), dies reduziert den Speicherplatz um 60% auf rund 6.7 GB /index (von 1 Millionen Dokumente). Dies hatte keinen Einfluss auf die abfragegeschwindigkeit auf kleinere Datensätze, aber wenn RAM nicht ausreichend und Datenträger verwendet werden musste-Abfragen wurden über 40% schneller.Edit 3: getestet habe ich
fuzzy
Suche mit edit-Distanz von 2 auf einen Satz von 30 Millionen Dokumente, und im Vergleich zu 256 Stichproben der hash zu bekommen Ungefähre Ergebnisse. Unter diesen Bedingungen sind die Methoden in etwa die gleiche Geschwindigkeit, aberfuzzy
gibt genaue Ergebnisse und nicht brauchen, dass extra-Speicherplatz. Ich denke, dieser Ansatz ist nur sinnvoll für "sehr fuzzy" - Abfragen wie hamming-Distanz von größer als 3._source: false
und speichern nur die raw-64-bit-hash (als string), abgetastete Muster sind indiziert, aber nicht gespeichert. Dies reduziert die disk-Auslastung von über 50% und ich bin daran interessiert zu sehen, ob es hilft, Abfrage-performance oder nicht. Auch ich werde ausführen, diese mit 256 samples und vergleichen Sie die Leistung mit integrierten infuzziness
Suche mit edit-Distanz von 2.Ich auch implementiert, die die CUDA-Ansatz mit guten Ergebnissen sogar auf einem laptop mit GeForce 650M Grafikkarte. Die Umsetzung war einfach mit Schub Bibliothek. Ich hoffe, dass der code nicht fehlerhaft sind (ich habe nicht gründlich testen), aber es sollte keine Auswirkungen auf benchmark-Ergebnisse. Zumindest rief ich
thrust::system::cuda::detail::synchronize()
vor dem anhalten der high-precision timer.Lineare Suche war so einfach wie
Suche war 100% korrekt und Weg, schneller als meine ElasticSearch Antwort, in 50 Millisekunden CUDA könnte stream über 35 Millionen hashes! Ich bin mir sicher, dass neuere desktop-Karten sind auch viel schneller als diese. Auch wir bekommen sehr geringe Varianz und konsistentes lineares Wachstum, der Suche nach der Zeit, wie wir gehen durch mehr und mehr Daten. ElasticSearch Treffer schlechtes Gedächtnis-Probleme auf größeren Abfragen wegen zu hoher sampling-Daten.
So, hier bin ich Berichterstattung über die Ergebnisse der "Aus diesen N-hashes, finden diejenigen, die innerhalb von 8 Hamming-Distanz von einem einzigen hash-H". Ich führe diese 500 mal und berichtet Perzentile.
Es gibt einige kernel-Start-overhead, aber nach der Suchraum von mehr als 5 Millionen hashes für die Suche die Geschwindigkeit ist relativ konstant bei 700 Millionen hashes pro Sekunde. Natürlich ist die Obere Schranke für die Anzahl der Hash-Werte gesucht werden, wird durch GPU-RAM).
Update: ich habe wieder laufen meine tests auf GTX 1060 und scannt es etwa 3800 Millionen hashes pro Sekunde 🙂
Habe ich begonnen, an einer Lösung um dieses selbst. Ich habe nur bisher getestet gegen ein Datensatz von rund 3,8 Millionen Dokumente, und ich beabsichtige, zu drängen, dass die nach oben von zehn Millionen.
Meine Lösung bisher ist:
Schreiben einen einheitlichen scoring-Funktion, und registrieren Sie es als plugin. Dann rufen Sie diese beim Abfragen anpassen der
_score
Wert der Dokumente, wie Sie kommen zurück.Als ein groovy-Skript, die Zeit, die zum ausführen des benutzerdefinierten scoring-Funktion wurde extrem unscheinbar, aber schreiben Sie als einen einheitlichen scoring-Funktion (wie gezeigt, in diesem Jahre gekommenen blog-post: http://www.spacevatican.org/2012/5/12/elasticsearch-native-scripts-for-dummies/) war um Größenordnungen schneller.
Meine HammingDistanceScript sah so etwas wie dieses:
Es ist erwähnenswert an dieser Stelle, dass meine Hashwerte sind hex-codiert-Binär-strings. So, das gleiche wie deins, aber hex-codiert reduzieren den storage-Größe.
Auch, ich erwarte einen param_field parameter, der angibt, welches Feld Wert, die ich tun will hamming-Distanz gegen. Sie nicht brauchen, um dies zu tun, aber ich verwende das gleiche script für mehrere Felder, so dass ich tun 🙂
Ich benutze es in Anfragen wie diese:
Ich hoffe das hilft in irgendeiner Weise!
Andere Informationen, die möglicherweise nützlich für Sie, wenn Sie diesen Weg gehen:
1. Denken Sie daran, die es-plugin.Eigenschaften Datei
Diese muss kompiliert werden, die in der Wurzel Ihrer jar-Datei (wenn Sie kleben Sie es in /src/main/resources dann bauen Sie Ihr Glas, es gehe in die richtige Stelle).
Sah meins so aus:
2. Verweisen Sie Ihre benutzerdefinierte NativeScriptFactory impl in elasticsearch.yml
Genau wie auf im Alter blog-post.
Sah meins so aus:
Wenn Sie dies nicht tun, es zeigt immer noch bis auf die plugins-Liste (siehe später), aber Sie erhalten Fehler, wenn Sie versuchen, es zu sagen, dass elasticsearch kann es nicht finden.
3. Nicht die Mühe mit dem elasticsearch-plugin-Skript, um es zu installieren
Es ist nur ein Schmerz, der Esel und alles, was es scheint zu tun ist, packen Sie Ihre Sachen - ein bisschen sinnlos. Statt, nur kleben Sie es in
%ELASTICSEARCH_HOME%/plugins/hamming_distance
und neu starten elasticsearch.
Wenn alles geklappt hat, sehen Sie, dass es geladen wird elasticsearch Start:
UND wenn Sie rufen Sie die Liste der plugins, die es werden da sein:
produziert so etwas wie:
Erwarte ich um testen zu können, gegen die nach oben von zehn Millionen Dokumenten innerhalb der nächsten Woche oder so. Ich werde versuchen, und denken Sie daran, pop zurück und aktualisieren diese mit den Ergebnissen, wenn es hilft.
Hier ist ein unelegant, aber den genauen (brute-force) Lösung, die erfordert, dass die Dekonstruktion Ihre Funktion hash, der in einzelne Boolesche Felder, so dass Sie können führen Sie eine Abfrage wie diese:
Ich bin mir nicht sicher, wie dies durchzuführen vs. fuzzy_like_this, aber der Grund, die FLT Umsetzung ist veraltet ist, dass Sie jeden Begriff im index zur Berechnung edit-Distanz.
(wobei hier/oben, Sie sind die Nutzung Lucene zugrunde liegt, inverted-index-Daten-Struktur und optimierte Vorgänge, die sollte zu Ihrem Vorteil gegeben, Sie haben wahrscheinlich Recht spärlich features)
001 010 020 031 040 ... 631
(position + Status) entspricht Ihr Vorschlag, kann aber sehr unterschiedlich in der Leistung.not query
) zu testen unset bits. Auf diese Weise können Sie reduzieren collection Größe und Weg von der string-Vergleich von numerischen Werten.00 03 ... 63
) und mit den whitespace tokenizer in Verbindung mit minimum_should_match? War es das, was dir 5-20s Reaktionszeiten?phash_00: 1, phash_03:1 ... phash_63: 1
und in Ihre Abfrage suchen nur für bits, die mit einer minimum_should_match? Tut dies erhöht die Geschwindigkeit, wie viele Ergebnisse Sie MÜSSEN noch nicht einmal diejenigen, die bit-Felder?Habe ich verwendet @ndtreviv ist Antwort als Ausgangspunkt. Hier sind meine Notizen für ElasticSearch 2.3.3:
es-plugin.properties
Datei heißt jetztplugin-descriptor.properties
Du keinen Verweis auf die
NativeScriptFactory
imelasticsearch.yml
stattdessen erstellen Sie eine weitere Klasse neben IhremHammingDistanceScript
.plugin-descriptor.properties
Datei:module.registerScript("hamming-distance", HammingDistanceScriptFactory.class);
im 2.Hoffe, dies hilft, die nächste Arme Seele, die zu tun hat mit der beschissenen ES docs.
Hier ist 64-bit-Lösung zu @NikoNyrh ist Antwort. Hamming-Distanz berechnet werden kann, die nur mit XOR-operator mit einer eingebauten __popcll Funktion von CUDA.