Esiste un filtro anti-Bloom?


25

Un filtro Bloom consente di tenere traccia in modo efficiente se durante l'elaborazione sono già stati rilevati vari valori. Quando sono presenti molti elementi di dati, un filtro Bloom può comportare un notevole risparmio di memoria su una tabella hash. La caratteristica principale di un filtro Bloom, che condivide con una tabella hash, è che dice sempre "non nuovo" se un oggetto non è nuovo, ma c'è una probabilità diversa da zero che un oggetto sia contrassegnato come "non nuovo "anche quando è nuovo.

Esiste un "filtro anti-Bloom", che ha il comportamento opposto?

In altre parole: esiste una struttura dati efficiente che dice "nuovo" se un articolo è nuovo, ma che potrebbe anche dire "nuovo" per alcuni articoli che non sono nuovi?

Mantenere tutti gli elementi precedentemente visualizzati (ad esempio, in un elenco collegato ordinato) soddisfa il primo requisito ma può utilizzare molta memoria. Spero anche che non sia necessario, dato il secondo requisito rilassato.


Per coloro che preferiscono un trattamento più formale, scrivi b(x)=1 se il filtro Bloom pensa che x sia nuovo, b(x)=0 altrimenti, e scrivi n(x)=1 se x è davvero nuovo e altrimenti.n(x)=0

Quindi ; ; ; P r [ b ( x ) = 1 | n ( x ) = 1 ] = 1 - α , per alcuni 0 < α < 1 .P r [ b ( x ) = 0 | n ( x ) = 1 ] = α P r [ b ( x ) = 1 | n ( x ) = 0 ] = 0Pr[b(x)=0|n(x)=0]=1Pr[b(x)=0|n(x)=1]=αPr[b(x)=1|n(x)=0]=0Pr[b(x)=1|n(x)=1]=1α0<α<1

Sto chiedendo: esiste una struttura dati efficiente, implementando una funzione b con qualche 0<β<1 , tale che ; ; ; ?Pr[b(x)=0|n(x)=0]=βPr[b(x)=0|n(x)=1]=0P r [ b ( x ) = 1 | n ( x ) = 1 ] = 1Pr[b(x)=1|n(x)=0]=1βPr[b(x)=1|n(x)=1]=1


Modifica: sembra che questa domanda sia stata posta in precedenza su StackExchange, come /programming/635728 e /cstheory/6596 con una gamma di risposte da "impossibile essere fatto "attraverso" può essere fatto, ad un certo costo "a" è banale da fare, invertendo i valori di b ". Non mi è ancora chiaro quale sia la risposta "giusta". Ciò che è chiaro è che uno schema di memorizzazione nella cache LRU di qualche tipo (come quello suggerito da Ilmari Karonen) funziona piuttosto bene, è facile da implementare e ha comportato una riduzione del 50% del tempo impiegato per eseguire il mio codice.


Per qualche motivo, sono tentato di dire che questo è molto simile al problema che la cache e gli algoritmi di posizionamento della cache tentano di risolvere. Prendi in considerazione una cache utilizzando la sostituzione LFU (Usata meno frequentemente). Un algoritmo di sostituzione teoricamente ottimale ma impossibile sarebbe quello di sfrattare quello che non vedrai più per il tempo più lungo, come per le cache. Suppongo che la memorizzazione nella cache si basi su alcune ipotesi sulla natura della distribuzione che potrebbero non essere valide in generale, ma vale la pena considerare se ciò si applica.
Patrick87,

Potresti essere interessato al seguente discorso: Set di filtri di appartenenza basati sulla soddisfazione
Kaveh,

@Kaveh: grazie per il puntatore, guarderà.
András Salamon,

Risposte:


12

Seguendo l'idea di hash di Patrick87, ecco una costruzione pratica che soddisfa quasi le tue esigenze: la probabilità di confondere erroneamente un nuovo valore con uno vecchio non è del tutto zero, ma può essere facilmente resa trascurabilmente piccola.

Scegli i parametri e k ; i valori pratici potrebbero essere, diciamo, n = 128 e k = 16 . Sia H una funzione di hash crittografica sicura che produce (almeno) n + k bit di output.nkn=128k=16Hn+k

Facciamo essere un array di 2 k n -bit bitstrings. Questo array memorizza lo stato del filtro, utilizzando un totale di n 2 k bit. (Non importa in che modo questo array è inizializzato; possiamo semplicemente riempirlo con zeri o con bit casuali.)a2k nn2k

  • Per aggiungere un nuovo valore al filtro, calcolare ix , dove i indica i primi k bit e j indica i seguenti n bit di H ( x ) . Lascia a i = j .ij=H(x)ikjnH(x)ai=j

  • Per verificare se un valore è stato aggiunto al filtro, calcolare i x , come sopra, e verificare se a i = j . Se sì, restituisci true; altrimenti restituisce false.ij=H(x)ai=j

Rivendicazione 1: La probabilità di un falso positivo (= nuovo valore falsamente dichiarato di essere stato visto) è . Questo può essere reso arbitrariamente piccolo, a un costo modesto nello spazio di archiviazione, aumentando n ; in particolare, per n 128 , questa probabilità è sostanzialmente trascurabile, essendo, in pratica, molto più piccola della probabilità di un falso positivo a causa di un malfunzionamento dell'hardware.1/2n+knn128

In particolare, dopo che valori distinti sono stati controllati e aggiunti al filtro, la probabilità che si sia verificato almeno un falso positivo è ( N 2 - N ) / 2 n + k + 1 . Ad esempio, con n = 128 e k = 16 , il numero di valori distinti necessari per ottenere un falso positivo con una probabilità del 50% è di circa 2 ( n + k ) / 2 = 2 72 .N(N2N)/2n+k+1n=128k=162(n+k)/2=272

Rivendicazione 2: La probabilità di un falso negativo (= valore aggiunto precedentemente dichiarato erroneamente nuovo) non è maggiore di , dove N è il numero di valori distinti aggiunti al filtro (o, più specificamente, il numero di valori distinti aggiunti dopo che il valore specifico da testare è stato aggiunto più recentemente al filtro).1(12k)N1exp(N/2k)<N/2kN


Ps. Per mettere in prospettiva "trascurabilmente piccoli", la crittografia a 128 bit è generalmente considerata indistruttibile con la tecnologia attualmente nota. Ottenere un falso positivo da questo schema con è probabile quanto qualcuno indovini correttamente la tua chiave di crittografia segreta a 128 bit al primo tentativo . (Con n = 128 e k = 16 , in realtà è circa 65.000 volte meno probabile di così.)n+k=128n=128k=16

Ma se questo ti fa ancora sentire irrazionalmente nervoso, puoi sempre passare a ; raddoppierà i tuoi requisiti di archiviazione, ma posso tranquillamente scommettere qualsiasi somma che ti dispiacerebbe nominare che nessuno vedrà mai un falso positivo con n = 256 - supponendo che la funzione hash non sia interrotta, comunque.n=256n=256


1
Non solo la probabilità può essere paragonata a quella di un malfunzionamento dell'hardware; può anche essere paragonabile alla probabilità che qualcuno indovini la tua chiave RSA per l'accesso SSH al primo tentativo . IMO quest'ultimo trasmette la praticità della tua soluzione più della prima.
R ..

+1 Molto bello - la mia comprensione è che questo risolve il problema di efficienza dello spazio consentendo alcune (molto piccole) possibilità di rispondere erroneamente "non nuovo" quando l'oggetto è, di fatto, nuovo. Analisi molto pratica e buona.
Patrick87,

1
La rivendicazione 1 sta solo affermando che una funzione hash decente ha una bassa probabilità di collisioni. Questo è vero nella pratica già se è almeno 50 o giù di lì. Per la mia applicazione, n = 44 e k = 20 funzionano alla grande con una semplice funzione di hash a 64 bit, non crittograficamente sicura, ma veloce. n+kn=44k=20
András Salamon,

@ AndrásSalamon: Vero, anche se una funzione di crittografia crittografica sicura in realtà fornisce una garanzia leggermente più forte: vale a dire, che non è pratico trovare input in collisione anche se si tenta di cercarli deliberatamente . Con un sufficientemente grande (es. N = 128 come ho suggerito sopra), ciò significa che l'archiviazione dei dati completi non è necessaria anche se il costo di un falso positivo è elevato e anche se potrebbe esserci un avversario attivo che tenta di trovarne uno. Naturalmente, se non hai bisogno di una garanzia così forte, può essere accettabile un rischio di collisione leggermente più elevato. nn=128
Ilmari Karonen,

1
@Newtopian Il motivo per cui ho specificato una funzione di crittografia crittografica è che per quelli, non esiste un modo noto di generare collisioni più efficacemente che con la forza bruta (cioè testando molti input e selezionando quelli che si scontrano), altrimenti sarebbe considerato l'hash rotto (come, diciamo, MD5 al giorno d'oggi lo è). Quindi, per un hash crittografico, possiamo tranquillamente supporre che il tasso di collisione sia lo stesso di una funzione hash casuale ideale. L'uso di una funzione hash universale o di un MAC con chiave (con una chiave segreta casuale) renderebbe questa garanzia ancora più forte.
Ilmari Karonen,

8

No, non è possibile avere una struttura dati efficiente con queste proprietà, se si desidera avere la garanzia che la struttura dati dirà "nuovo" se è veramente nuova (non dirà mai "non nuovo" se è infatti nuovo; non sono ammessi falsi negativi). Una tale struttura di dati dovrà mantenere tutti i dati per rispondere "non nuovo". Vedi la risposta di pents90 su cstheory per una giustificazione precisa.

Al contrario, i filtri Bloom possono ottenere la garanzia che la struttura dei dati dirà "non nuovo" se non è nuova, in modo efficiente. In particolare, i filtri Bloom possono essere più efficienti della memorizzazione di tutti i dati: ogni singolo elemento potrebbe essere piuttosto lungo, ma la dimensione del filtro Bloom si ridimensiona in base al numero di elementi, non alla loro lunghezza totale. Qualsiasi struttura di dati per il tuo problema dovrà ridimensionarsi in base alla lunghezza totale dei dati, non al numero di elementi di dati.


Vedi anche la risposta accettata, poiché la domanda è la stessa
Joe,

-1 Probabilmente dovresti qualificare ciò che intendi quando dici che non è possibile. Chiaramente è possibile farlo in modo efficiente, ed è anche possibile farlo con un basso tasso di errore, quindi trovare un certo equilibrio in una data implementazione dovrebbe essere fattibile ... in particolare, sarebbe utile spiegare esattamente cosa si intende per "tutti i dati di sempre", dal momento che questo non è strettamente necessario per soddisfare la domanda. I falsi negativi - rispondere "nuovo" quando la risposta dovrebbe essere "non nuova" - sono ammessi qui, quindi non tutti i dati devono essere conservati.
Patrick87,

1
Questa risposta è perfettamente ragionevole e sembra indirizzare la lettera della mia domanda, ma forse non lo spirito.
András Salamon,

@DW Grazie per aver dedicato del tempo per aggiornare la risposta. Sono propenso a lasciare questo come una risposta ora, anche se mi oppongo ancora al linguaggio usato quando descrivo l'inefficienza dei filtri anti-bloom, oltre a pensare che sarebbe meglio elaborare un po 'di più sui "dettagli" a cui si fa riferimento. .. lasciando il -1 per ora. Ripuliti alcuni commenti obsoleti.
Patrick87,

@DW Con "falso negativo", intendo rispondere "nuovo" quando la risposta avrebbe dovuto essere "non nuova". (In qualche modo controintuitivamente, "non nuovo" è il caso positivo qui.) Non è necessario salvare "tutti i dati di sempre" per farcela, anche se sono propenso a credere che sia necessario salvare interi elementi (solo non tutti gli elementi - a meno che tu non sia disposto ad accettare una possibilità ipoteticamente significativa di errore, come per l'altra risposta alla domanda qui.)
Patrick87

6

Che ne dici di una tabella hash? Quando vedi un nuovo elemento, controlla la tabella hash. Se il punto dell'articolo è vuoto, restituisci "nuovo" e aggiungi l'articolo. Altrimenti, controlla per vedere se il posto dell'oggetto è occupato dall'oggetto. In tal caso, restituire "non nuovo". Se il punto è occupato da un altro oggetto, restituisci "nuovo" e sovrascrivi il punto con il nuovo oggetto.

Avrai sicuramente sempre "Nuovo" se non hai mai visto l'hash dell'elemento prima. Avrai sicuramente sempre "Non nuovo" se hai visto l'hash dell'oggetto solo quando hai visto lo stesso oggetto. L'unica volta che otterrai "Nuovo" quando la risposta corretta è "Non nuovo" è se vedi l'articolo A, quindi vedi l'articolo B, quindi vedi di nuovo l'articolo A e entrambi gli hash A e B sulla stessa cosa. È importante sottolineare che non è mai possibile ottenere "Not New" in modo errato.


1
Suppongo che questo tipo di ignori il problema dell'efficienza dello spazio, o meglio, sia significativamente meno efficiente di un filtro di fioritura, dal momento che un filtro di fioritura ha davvero bisogno solo di un po 'per secchio, e questo ha bisogno di tanto spazio per secchio quanto occupa spazio rappresentano gli articoli. Oh bene ... a meno che l'universo non sia finito (come nella risposta di Wandering Logic) Penso che probabilmente non puoi avvicinarti molto all'efficienza dello spazio di un filtro bloom.
Patrick87,

Personalmente, penso che la tua risposta sia molto migliore della mia. Un filtro di fioritura non è solo un po 'per secchio se si desidera una probabilità superiore al 50%. Inoltre ha una dimensione fissa e una volta riempito per più della metà la probabilità di falsi positivi aumenta precipitosamente. Non esiste un modo conveniente per espanderlo, nessun modo conveniente per usarlo come cache e nessun modo conveniente per eliminare elementi. Prenderò una tabella di hash ogni volta.
Wandering Logic,

@WanderingLogic L'uso di un piccolo contatore di saturazione anziché di un singolo bit consente di supportare la cancellazione (a costo di capacità e solo se il contatore non è al massimo, ovviamente).
Paul A. Clayton,

4

Nel caso in cui l'universo degli oggetti sia finito, allora sì: basta usare un filtro bloom che registra quali elementi sono fuori dal set, piuttosto che nel set. (Vale a dire, utilizzare un filtro bloom che rappresenta il complemento dell'insieme di interesse.)

Un luogo in cui ciò è utile è consentire una forma limitata di cancellazione. Mantieni due filtri di fioritura. Iniziano vuoti. Quando si inseriscono elementi, li si inserisce nel filtro bloom A. Se in seguito si desidera eliminare un elemento, si inserisce tale elemento nel filtro bloom B. Non è possibile annullare l'eliminazione. Per effettuare una ricerca, devi prima cercare nel filtro Bloom A. Se non trovi alcuna corrispondenza, l'elemento non è mai stato inserito (con probabilità 1). Se trovi una corrispondenza, l'elemento potrebbe (o meno) essere stato inserito. In tal caso, esegui una ricerca nel filtro bloom B. Se non trovi alcuna corrispondenza, l'elemento non viene mai eliminato. Se trovi una corrispondenza nel filtro bloom B, l'elemento è stato probabilmente inserito e quindi eliminato.

Questo in realtà non risponde alla tua domanda, ma, in questo caso limitato, il filtro bloom B sta eseguendo esattamente il comportamento del "filtro anti-bloom" che stai cercando.

I ricercatori del filtro Real Bloom usano modi molto più efficienti di rappresentare la cancellazione, vedi la pagina della pubblicazione di Mike Mitzenmacher .


In questa domanda, stiamo elaborando articoli e non ci sono cancellazioni. Non esiste un modo significativo per archiviare il complimento senza dover rimuovere gli elementi dal filtro Bloom
Joe,

1
@Joe: sono d'accordo sul fatto che il problema è insolubile in generale, quindi ho limitato la mia risposta al caso in cui il complemento era limitato e limitato.
Wandering Logic,

1

Voglio solo aggiungere qui, che se sei nella situazione fortunata, che conosci tutti i valori vioche potresti vedere; quindi è possibile utilizzare un filtro bloom conteggio.

Un esempio potrebbe essere rappresentato dagli indirizzi IP e vuoi sapere ogni volta che uno appare che non hai mai visto prima. Ma è ancora un set finito, quindi sai cosa puoi aspettarti.

La soluzione effettiva è semplice:

  1. Aggiungi tutti i tuoi articoli al filtro fioritura conteggio.
  2. Quando vedi un nuovo oggetto, avrà dei valori 1 in tutte le slot.
  3. Dopo aver visto un nuovo oggetto reale, sottralo dal filtro.

Quindi potresti avere valori di "falsi positivi" che in realtà erano vecchi, ma riconosciuti come nuovi. Tuttavia non otterrai mai "non nuovo" per un nuovo valore, dal momento che il suo valore sarà ancora in tutti gli slot, e nessun altro avrebbe potuto toglierlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.