Ricerca di similarità, Parte 6 Proiezioni casuali con LSH Forest

LSH Forest Random Projections for Similarity Search, Part 6

Comprendere come hashare i dati e rifletterne la similarità attraverso la costruzione di iperpiani casuali

La ricerca di similarità è un problema in cui, dato un query, l’obiettivo è trovare i documenti più simili ad esso tra tutti i documenti del database.

Introduzione

Nella scienza dei dati, la ricerca di similarità appare spesso nel dominio del NLP, nei motori di ricerca o nei sistemi di raccomandazione dove è necessario recuperare i documenti o gli elementi più rilevanti per una query. Esistono molteplici modi per migliorare le prestazioni di ricerca in volumi massivi di dati.

Nella parte precedente, abbiamo esaminato il principale paradigma di LSH che consiste nel trasformare i vettori di input in valori hash di dimensioni inferiori, preservando al contempo le informazioni sulla loro similarità. Per ottenere i valori hash (firme) sono state utilizzate le funzioni minhash. In questo articolo, proietteremo casualmente i dati di input per ottenere vettori binari analoghi.

Ricerca di similarità, Parte 5: Locality Sensitive Hashing (LSH)

Esplora come le informazioni sulla similarità possono essere incorporate nella funzione di hash

towardsdatascience.com

Idea

Considera un gruppo di punti in uno spazio ad alta dimensione. È possibile costruire un iperpiano casuale che funge da parete e separa ciascun punto in due sottogruppi: positivo e negativo. Ad ogni punto sul lato positivo viene assegnato il valore “1” e ad ogni punto sul lato negativo viene assegnato il valore “0”.

Esempio di un iperpiano che separa due punti in uno spazio tridimensionale

Come si può determinare il lato di un iperpiano per un certo vettore? Utilizzando il prodotto interno! Tornando all’essenza dell’algebra lineare, il segno del prodotto scalare tra un vettore dato e il vettore normale all’iperpiano determina su quale lato si trova il vettore. In questo modo, ogni vettore del dataset può essere separato in uno dei due lati.

Calcolare il prodotto interno di un vettore con il vettore normale di un iperpiano e confrontarlo con 0 può indicare su quale lato si trova il vettore rispetto all'iperpiano

Ovviamente, codificare ogni vettore del dataset con un singolo valore binario non è sufficiente. Dovrebbero essere costruiti diversi iperpiani casuali, in modo che ogni vettore possa essere codificato con tanti valori di 0 e 1 in base alla sua posizione relativa a un iperpiano specifico. Se due vettori hanno lo stesso codice binario, ciò indica che nessuno degli iperpiani costruiti potrebbe averli separati in regioni diverse. Pertanto, è probabile che siano molto vicini l’uno all’altro nella realtà.

Per trovare il vicino più vicino per una determinata query, è sufficiente codificare la query con 0 e 1 nello stesso modo, controllando la sua posizione relativa a tutti gli iperpiani. Il vettore binario trovato per la query può essere confrontato con tutti gli altri vettori binari ottenuti per i vettori del dataset. Questo può essere fatto linearmente utilizzando la distanza di Hamming.

La distanza di Hamming tra due vettori è il numero di posizioni in cui i loro valori sono diversi.

Esempio di calcolo della distanza di Hamming. Una coppia di vettori a sinistra è più simile tra loro poiché la loro distanza di Hamming è più piccola.

I vettori binari con le distanze di Hamming più basse rispetto alla query vengono presi come candidati e quindi confrontati completamente con la query iniziale.

Perché gli iperpiani vengono costruiti casualmente?

Nella fase attuale, sembra logico chiedersi perché gli iperpiani vengono costruiti in modo casuale e non in modo deterministico, cioè potrebbero essere definite regole personalizzate per separare i punti del dataset. Ci sono due motivi principali dietro a questo:

  • Innanzitutto, l’approccio deterministico non è in grado di generalizzare l’algoritmo e può portare all’overfitting.
  • In secondo luogo, la casualità consente di formulare affermazioni probabilistiche sulle prestazioni dell’algoritmo che non dipendono dai dati di input. Per un approccio deterministico, ciò non funzionerebbe perché potrebbe funzionare bene su alcuni dati e avere una scarsa prestazione su altri. Un buon esempio di ciò è l’algoritmo di quick-sort deterministico che funziona in media in tempo O(n * log n). Tuttavia, funziona in tempo O(n²) su un array ordinato come scenario peggiore. Se qualcuno ha conoscenza del flusso di lavoro dell’algoritmo, queste informazioni possono essere utilizzate per danneggiare in modo significativo l’efficienza del sistema fornendo sempre i dati peggiori. Ecco perché è molto più preferibile un quick-sort casuale. La stessa situazione si verifica con gli iperpiani casuali.

Perché le proiezioni casuali LSH vengono chiamate anche “alberi”?

Il metodo delle proiezioni casuali viene talvolta chiamato LSH Tree. Questo perché il processo di assegnazione del codice hash può essere rappresentato sotto forma di un albero decisionale in cui ogni nodo contiene una condizione che indica se un vettore si trova sul lato negativo o positivo di un iperpiano corrente.

Il primo nodo controlla su quale lato si trova un vettore rispetto all'iperpiano rosso. I nodi al secondo livello controllano la stessa condizione ma rispetto all'iperpiano verde. Infine, il terzo livello controlla la posizione relativa dell'iperpiano blu. Sulla base di queste 3 condizioni, al vettore viene assegnato un hash a 3 bit.

Foresta di iperpiani

Gli iperpiani vengono costruiti in modo casuale. Questo può comportare una situazione in cui separano male i punti del dataset, come mostrato nella figura sottostante.

Vengono costruiti 4 iperpiani per rappresentare i punti del dataset come vettori binari di lunghezza 4. Anche se i punti D ed E hanno lo stesso codice hash, sono relativamente lontani l'uno dall'altro (FP). La situazione inversa si verifica con una coppia di punti E e F che si trovano in regioni diverse ma sono molto vicini l'uno all'altro (FN). Tenendo conto della distanza di Hamming, l'algoritmo prevede normalmente che il punto D sia più vicino a E rispetto al punto F.

Tecnicamente, non è un grosso problema quando due punti hanno lo stesso codice hash ma sono lontani l’uno dall’altro. Nel passaggio successivo dell’algoritmo, questi punti vengono presi come candidati e confrontati completamente, in questo modo l’algoritmo può eliminare i casi di falsi positivi. La situazione è più complicata con i falsi negativi: quando due punti hanno diversi codici hash ma in realtà sono vicini l’uno all’altro.

Perché non utilizzare lo stesso approccio con gli alberi decisionali dell’apprendimento automatico classico che vengono combinati in foreste casuali per migliorare la qualità complessiva delle previsioni? Se un stimatore commette un errore, gli altri stimatori possono produrre previsioni migliori e ridurre l’errore di previsione finale. Utilizzando questa idea, il processo di costruzione di iperpiani casuali può essere ripetuto in modo indipendente. I valori hash risultanti possono essere aggregati per una coppia di vettori in modo simile a quanto fatto con i valori minhash nel capitolo precedente:

Se una query ha lo stesso codice hash almeno una volta con un altro vettore, allora vengono considerati candidati.

Utilizzando questo meccanismo è possibile ridurre il numero di falsi negativi.

Trade-off tra qualità e velocità

È importante scegliere un numero appropriato di iperpiani da eseguire sul dataset. Più iperpiani vengono scelti per suddividere i punti del dataset, meno collisioni ci sono e più tempo ci vuole per calcolare i codici hash e più memoria per memorizzarli. In parole precise, se un dataset è composto da n vettori e lo dividiamo in k iperpiani, allora in media ogni possibile codice hash verrà assegnato a n / 2ᵏ vettori.

k = 3 results in ²³ = 8 buckets

Complessità

Allenamento

La fase di allenamento di LSH Forest consiste in due parti:

  1. Generazione di k iperpiani. Questa è una procedura relativamente veloce poiché un singolo iperpiano in uno spazio d-dimensionale può essere generato in O(d) di tempo.
  2. Assegnazione dei codici hash a tutti i vettori del dataset. Questo passaggio potrebbe richiedere tempo, soprattutto per dataset di grandi dimensioni. Ottenere un singolo codice hash richiede O(dk) di tempo. Se un dataset è composto da n vettori, la complessità totale diventa O(ndk).

Il processo sopra descritto viene ripetuto diverse volte per ogni albero nella foresta.

Training complexity

Inferenza

Uno dei vantaggi di LSH Forest è la sua inferenza veloce che include due passaggi:

  1. Ottenere il codice hash di una query. Questo è equivalente al calcolo di k prodotti scalari che richiedono O(dk) di tempo (d – dimensionalità).
  2. Trovare i vicini più vicini alla query nello stesso bucket (vettori con lo stesso codice hash) calcolando distanze precise ai candidati. Il calcolo della distanza procede linearmente per O(d). Ogni bucket contiene in media n / 2ᵏ vettori. Pertanto, il calcolo della distanza per tutti i potenziali candidati richiede O(dn / 2ᵏ) di tempo.

La complessità totale è O(dk + dn / 2ᵏ).

Come al solito, il processo sopra descritto viene ripetuto diverse volte per ogni albero nella foresta.

Inference complexity

Quando il numero di iperpiani k è scelto in modo tale che n ~ 2ᵏ (cosa possibile nella maggior parte dei casi), la complessità totale dell’inferenza diventa O(ldk) (l è il numero di alberi). Fondamentalmente, questo significa che il tempo di calcolo non dipende dalle dimensioni del dataset! Questa sottigliezza porta a una scalabilità efficiente della ricerca di similarità per milioni o addirittura miliardi di vettori.

Tasso di errore

Nella parte precedente dell’articolo su LSH, abbiamo discusso come trovare la probabilità che due vettori vengano scelti come candidati in base alla similarità delle loro firme. Qui useremo quasi la stessa logica per trovare la formula per LSH Forest.

Sia s la probabilità che due vettori abbiano lo stesso bit nella stessa posizione dei loro valori hash (s sarà stimato successivamente)
La probabilità che i codici hash di lunghezza k di due vettori siano uguali
La probabilità che i codici hash di lunghezza k di due vettori siano diversi (o almeno un bit sia diverso)
La probabilità che tutti i l codici hash (per l iperpiani) di due vettori siano diversi
La probabilità che almeno uno dei l codici hash di due vettori sia uguale, quindi i vettori diventeranno candidati

Fino ad ora abbiamo quasi ottenuto la formula per stimare la probabilità che due vettori siano candidati. L’unica cosa che manca è stimare il valore della variabile s nell’equazione. Nell’algoritmo classico LSH, s è uguale all’indice di Jaccard o alla similarità delle firme di due vettori. D’altra parte, per stimare s per LSH forest, verrà utilizzata la teoria dell’algebra lineare.

Francamente parlando, s è la probabilità che due vettori a e b abbiano lo stesso bit. Questa probabilità è equivalente alla probabilità che un iperpiano casuale separi questi vettori dallo stesso lato. Visualizziamolo:

I vettori a e b sono separati dall'iperpiano blu. L'iperpiano verde non li separa.

Dalla figura, è chiaro che un iperpiano separa i vettori a e b in due lati diversi solo nel caso in cui passi tra di loro. Tale probabilità q è proporzionale all’angolo tra i vettori che può essere facilmente calcolato:

La probabilità che un iperpiano casuale separi due vettori (cioè in modo che abbiano bit diversi)
La probabilità che un iperpiano casuale non separi due vettori (cioè in modo che abbiano lo stesso bit)

Inserendo questa equazione in quella ottenuta in precedenza si arriva alla formula finale:

La probabilità che due vettori abbiano almeno un valore hash corrispondente (cioè diventino candidati) in base al numero di iperpiani k e al numero di alberi LSH l

Visualizzazione

Con l’ultima formula ottenuta, visualizziamo la probabilità che due vettori siano candidati in base alla loro similarità del coseno per un diverso numero di iperpiani k e alberi l.

Regolazione del numero di alberi l
Regolazione del numero di iperpiani k

Possiamo fare diverse osservazioni utili, basate sui grafici:

  • Una coppia di vettori con una similarità cosinale di 1 diventa sempre candidata.
  • Una coppia di vettori con una similarità cosinale di 0 non diventa mai candidata.
  • La probabilità P che due vettori siano candidati aumenta (cioè più falsi positivi) quando il numero di iperpiani k diminuisce o il numero di alberi LSH l aumenta. La dichiarazione inversa è vera.

Per riassumere, LSH è un algoritmo molto flessibile: è possibile regolare i diversi valori k e l in base a un problema specifico e ottenere la curva di probabilità che soddisfi i requisiti del problema.

Esempio

Guardiamo l’esempio seguente. Immaginiamo che siano costruiti 5 alberi con l = 5 e k = 10 iperpiani. Oltre a ciò, ci sono due vettori con una similarità cosinale del 0.8. Nella maggior parte dei sistemi, una simile similarità cosinale indica che i vettori sono molto simili tra loro. Sulla base dei risultati delle sezioni precedenti, si scopre che questa probabilità è solo del 2.5%! Ovviamente, è un risultato molto basso per una similarità cosinale così alta. Utilizzando questi parametri di l = 5 e k = 10 si ottiene un elevato numero di falsi negativi! La linea verde qui sotto rappresenta la probabilità in questo caso.

Curva di probabilità basata sulla similarità cosinale di due vettori

Questo problema può essere risolto regolando valori migliori per k e l per spostare la curva verso sinistra.

Ad esempio, se k viene ridotto a 3 (linea rossa), lo stesso valore di similarità cosinale del 0.8 corrisponderà a una probabilità del 68%, che è migliore rispetto prima. A prima vista, sembra che la linea rossa si adatti molto meglio alla situazione rispetto a quella verde, ma è importante tenere presente che l’utilizzo di valori piccoli di k (come nel caso della linea rossa) porta a un elevato numero di collisioni. Per questo motivo, a volte è preferibile regolare il secondo parametro, ovvero il numero di alberi l.

A differenza di k, di solito è necessario un numero molto elevato di alberi l per ottenere una forma di linea simile. Nella figura, la linea blu viene ottenuta dalla linea verde cambiando il valore di l da 10 a 500. La linea blu si adatta chiaramente meglio rispetto a quella verde, ma è ancora lontana dall’essere perfetta: a causa di una forte pendenza tra i valori di similarità cosinale di 0.6 e 0.8, la probabilità è quasi uguale a 0 per cosine di 0.3-0.5, il che è sfavorevole. Questa piccola probabilità per una similarità di documenti di 0.3-0.5 dovrebbe essere normalmente più alta nella vita reale.

Sulla base dell’ultimo esempio, è evidente che anche un numero molto elevato di alberi (che richiede molti calcoli) porta comunque a molti falsi negativi! Questo è il principale svantaggio del metodo delle proiezioni casuali:

Anche se potrebbe essere possibile ottenere una curva di probabilità perfetta, ciò richiederebbe molti calcoli o comporterebbe molti collisioni. In caso contrario, si ottiene un alto tasso di falsi negativi.

Implementazione di Faiss

Faiss (Facebook AI Search Similarity) è una libreria Python scritta in C++ utilizzata per la ricerca ottimizzata di similarità. Questa libreria presenta diversi tipi di indici, che sono strutture dati utilizzate per memorizzare ed eseguire query in modo efficiente.

Sulla base delle informazioni presenti nella documentazione di Faiss, scopriremo come costruire un indice LSH.

L’algoritmo delle proiezioni casuali è implementato in Faiss all’interno della classe IndexLSH. Anche se gli autori di Faiss utilizzano una tecnica leggermente diversa chiamata “rotazioni casuali”, essa presenta comunque somiglianze con quanto descritto in questo articolo. La classe implementa solo un singolo albero LSH. Se vogliamo utilizzare una foresta LSH, è sufficiente creare diversi alberi LSH e aggregare i loro risultati.

Il costruttore della classe IndexLSH richiede due argomenti:

  • d: il numero di dimensioni
  • nbits: il numero di bit necessari per codificare un singolo vettore (il numero di bucket possibili è pari a 2ⁿᵇᶦᵗˢ)

Le distanze restituite dal metodo search() sono distanze di Hamming rispetto al vettore di query.

Implementazione di Faiss di IndexLSH

Inoltre, Faiss consente di ispezionare i valori hash codificati per ogni vettore del dataset chiamando il metodo faiss.vector_to_array(index.codes).

Dato che ogni vettore del dataset è codificato da nbits valori binari, il numero di byte necessari per memorizzare un singolo vettore è uguale a:

Lemma di Johnson-Lindenstrauss

Il lemma di Johnson-Lindenstrauss è un favoloso lemma relativo alla riduzione della dimensionalità. Sebbene possa essere difficile comprendere appieno la sua formulazione originale, può essere formulato in parole semplici:

Scegliendo un sottoinsieme casuale e proiettando i dati originali su di esso, si preservano le distanze reciproche corrispondenti tra i punti.

Per essere più precisi, avendo un dataset di n punti, è possibile rappresentarli in uno spazio di O(logn) dimensioni in modo tale che le distanze relative tra i punti saranno quasi preservate. Se un vettore è codificato da ~logn valori binari nel metodo LSH, allora il lemma può essere applicato. Inoltre, LSH crea iperpiani in modo casuale proprio come richiesto dal lemma.

Ciò che è anche incredibile del lemma di Johnson-Lindenstrauss è il fatto che il numero di dimensioni di un nuovo dataset non dipende dalla dimensionalità del dataset originale! In pratica, questo lemma non funziona bene per dimensioni molto piccole.

Conclusioni

Abbiamo esaminato un potente algoritmo per la ricerca di similarità. Basato su un’idea semplice di separare i punti tramite iperpiani casuali, di solito si comporta e scala molto bene su grandi dataset. Inoltre, offre una buona flessibilità consentendo di scegliere un numero appropriato di iperpiani e alberi.

I risultati teorici del lemma di Johnson-Lindenstrauss rafforzano l’uso dell’approccio delle proiezioni casuali.

Risorse

  • LSH Forest: Indici di auto-ottimizzazione per la ricerca di similarità
  • Il lemma di Johnson-Lindenstrauss
  • Documentazione di Faiss
  • Repository di Faiss
  • Riepilogo degli indici di Faiss

Tutte le immagini, se non diversamente specificato, sono dell’autore.