Salta al contenuto
Note per Studenti Esercizio - Count-Min sketch - tabella a mano e dimensionamento

Esercizio - Count-Min sketch - tabella a mano e dimensionamento

Teoria: Algoritmi di streaming - campionamento di riserva e conteggio degli elementi distintiModello a flusso (un passaggio, memoria molto minore di n); campionamento di riserva (reservoir sampling) con dimostrazione per induzione; contatore di Morris (stima non distorta 2^X-1, varianza n(n-1)/2); elementi distinti: idea di Flajolet-Martin e stima del k-esimo minimo (k-1)/v_k; Count-Min sketch con garanzia (epsilon, delta) e dimensionamento w=e/epsilon, d=ln(1/delta).Algoritmi di streaming - campionamento di riserva e conteggio degli elementi distinti → (Count-Min sketch), Hashing randomizzato - famiglie universali, filtri di Bloom e fingerprintHashing con catene e costo atteso 1+alfa; famiglie universali (probabilità di collisione al più 1/m) e costruzione h(x)=((ax+b) mod p) mod m con dimostrazione; hashing perfetto a due livelli (spazio lineare, ricerca O(1) nel caso peggiore); filtri di Bloom: falsi positivi (1-e^(-kn/m))^k e k ottimo (m/n) ln 2; fingerprint di stringhe e numero di bit necessari.Hashing randomizzato - famiglie universali, filtri di Bloom e fingerprint → (famiglie universali).

Testo.

  1. Uno sketch ha d=2d=2 righe e w=4w=4 colonne. Il flusso contiene aa cinque volte, bb tre volte, cc due volte, dd e ee una volta (N=12N=12 arrivi). Le funzioni hash (date) sono
aa bb cc dd ee
h1h_1 00 11 00 22 33
h2h_2 11 11 33 00 22

Costruire la tabella dei contatori e le stime f^(x)=min⁡jC[j][hj(x)]\hat f(x)=\min_jC[j][h_j(x)] per i cinque elementi. Quali sono sovrastimati e perché? Che stima dà lo sketch per un elemento mai visto zz con h1(z)=0h_1(z)=0, h2(z)=0h_2(z)=0? 2. Dimostrare che f^(x)≥f(x)\hat f(x)\ge f(x) e che, con w=⌈e/ε⌉w=\lceil e/\varepsilon\rceil e d=⌈ln⁡(1/δ)⌉d=\lceil\ln(1/\delta)\rceil, P(f^(x)>f(x)+εN)≤δP(\hat f(x)>f(x)+\varepsilon N)\le\delta. 3. Dimensionare uno sketch per ε=0,001\varepsilon=0{,}001, δ=0,01\delta=0{,}01 e per ε=0,01\varepsilon=0{,}01, δ=0,05\delta=0{,}05: quanti contatori? 4. Simulazione: flusso di N=200 000N=200\,000 arrivi su 20 00020\,000 valori con frequenze di tipo Zipf (esponente 1,11{,}1). Per i tre sketch (ε,δ)=(0,01;0,05)(\varepsilon,\delta)=(0{,}01;0{,}05), (0,001;0,01)(0{,}001;0{,}01) e (0,05;0,2)(0{,}05;0{,}2) misurare l'errore f^−f\hat f-f massimo e medio sui 20 00020\,000 valori, la frazione di valori con errore superiore a εN\varepsilon N e l'errore sull'elemento più frequente.

Soluzione

1. Tabella. Riga 11 (somma per colonna di h1h_1): colonna 00: a+c=5+2=7a+c=5+2=7; colonna 11: b=3b=3; colonna 22: d=1d=1; colonna 33: e=1e=1. Riga 22: colonna 00: d=1d=1; colonna 11: a+b=5+3=8a+b=5+3=8; colonna 22: e=1e=1; colonna 33: c=2c=2. C=(73111812).C=\begin{pmatrix}7&3&1&1\\1&8&1&2\end{pmatrix}. Stime:

  • f^(a)=min⁡(C[1][0],C[2][1])=min⁡(7,8)=7\hat f(a)=\min(C[1][0],C[2][1])=\min(7,8)=\boxed7 (vero 55: sovrastimato di 22, cioè f(c)f(c): nella riga 11 collide con cc, nella riga 22 con bb, e il minimo è l'errore minore).
  • f^(b)=min⁡(C[1][1],C[2][1])=min⁡(3,8)=3\hat f(b)=\min(C[1][1],C[2][1])=\min(3,8)=3 ✓ (la riga 11 non ha collisioni).
  • f^(c)=min⁡(7,C[2][3])=min⁡(7,2)=2\hat f(c)=\min(7,C[2][3])=\min(7,2)=2 ✓; f^(d)=min⁡(1,1)=1\hat f(d)=\min(1,1)=1 ✓; f^(e)=min⁡(1,1)=1\hat f(e)=\min(1,1)=1 ✓.
  • Elemento mai visto zz con h1(z)=0h_1(z)=0, h2(z)=0h_2(z)=0: min⁡(C[1][0],C[2][0])=min⁡(7,1)=1\min(C[1][0],C[2][0])=\min(7,1)=\boxed1 (vero 00). Se invece fosse h1(z)=0h_1(z)=0, h2(z)=1h_2(z)=1 si avrebbe min⁡(7,8)=7\min(7,8)=7: gli errori additivi possono essere grandi per gli elementi rari. Controllo: ogni riga somma a NN (riga 11: 7+3+1+1=127+3+1+1=12; riga 22: 1+8+1+2=121+8+1+2=12).

2. Garanzia. Nella riga jj la cella di xx contiene C[j][hj(x)]=f(x)+YjC[j][h_j(x)]=f(x)+Y_j con Yj=∑y≠xf(y)1[hj(y)=hj(x)]≥0Y_j=\sum_{y\ne x}f(y)\mathbf 1[h_j(y)=h_j(x)]\ge0, quindi ogni cella è ≥f(x)\ge f(x) e il minimo anche: f^(x)≥f(x)\hat f(x)\ge f(x). Per l'universalità P(hj(y)=hj(x))≤1wP(h_j(y)=h_j(x))\le\frac1w e per la linearità E[Yj]≤Nw≤εNeE[Y_j]\le\frac Nw\le\frac{\varepsilon N}e (con w≥eεw\ge\frac e\varepsilon). Markov (con Yj≥0Y_j\ge0): P(Yj>εN)≤1eP(Y_j>\varepsilon N)\le\frac1e. Le righe usano funzioni indipendenti, quindi P(f^(x)>f(x)+εN)=P(∀j: Yj>εN)≤e−d≤δP(\hat f(x)>f(x)+\varepsilon N)=P(\forall j:\ Y_j>\varepsilon N)\le e^{-d}\le\delta se d≥ln⁡1δd\ge\ln\frac1\delta.

3. Dimensionamento.

  • ε=0,001\varepsilon=0{,}001, δ=0,01\delta=0{,}01: w=⌈e/0,001⌉=⌈2718,3⌉=2719w=\lceil e/0{,}001\rceil=\lceil2718{,}3\rceil=2719, d=⌈ln⁡100⌉=⌈4,61⌉=5d=\lceil\ln100\rceil=\lceil4{,}61\rceil=5: 13 595\boxed{13\,595} contatori (circa 5454 KB con contatori a 3232 bit).
  • ε=0,01\varepsilon=0{,}01, δ=0,05\delta=0{,}05: w=272w=272, d=⌈ln⁡20⌉=⌈3,0⌉=3d=\lceil\ln20\rceil=\lceil3{,}0\rceil=3 (ln⁡20=2,996\ln20=2{,}996): 816\boxed{816} contatori.
  • ε=0,05\varepsilon=0{,}05, δ=0,2\delta=0{,}2: w=55w=55, d=⌈ln⁡5⌉=2d=\lceil\ln5\rceil=2: 110110 contatori.

4. Risultati della simulazione (indipendenti dal numero di valori 20 00020\,000):

(ε,δ)(\varepsilon,\delta) w×dw\times d εN\varepsilon N errore max errore medio frazione con errore >εN>\varepsilon N elemento più frequente (f=29 285f=29\,285)
(0,01; 0,05)(0{,}01;\,0{,}05) 272×3272\times3 20002000 13571357 258,4258{,}4 00 stima 29 49229\,492 (errore 207207)
(0,001; 0,01)(0{,}001;\,0{,}01) 2719×52719\times5 200200 4444 7,27{,}2 00 stima 29 28829\,288 (errore 33)
(0,05; 0,2)(0{,}05;\,0{,}2) 55×255\times2 10 00010\,000 31 13731\,137 21692169 0,25%0{,}25\% stima 31 13731\,137 (errore 18521852)

La frazione di valori che violano il limite (00, 00, 0,25%0{,}25\%) è molto sotto δ\delta (5%5\%, 1%1\%, 20%20\%): il limite è prudente. Il terzo sketch, piccolissimo, ha un errore massimo di 31 13731\,137 (maggiore di εN=10 000\varepsilon N=10\,000) su un valore le cui celle sono contaminate dai valori più frequenti: con 5555 colonne e 22 righe le collisioni sono inevitabili. L'errore additivo è però trascurabile sull'elemento più frequente (≈6%\approx6\% della sua frequenza al massimo): per questo lo sketch serve a trovare gli elementi frequenti, non i rari.

Codice di verifica

python
import random
from math import ceil, e, log

h1 = {'a': 0, 'b': 1, 'c': 0, 'd': 2, 'e': 3}
h2 = {'a': 1, 'b': 1, 'c': 3, 'd': 0, 'e': 2}
f = {'a': 5, 'b': 3, 'c': 2, 'd': 1, 'e': 1}
C = [[0] * 4 for _ in range(2)]
for x, c in f.items():
    C[0][h1[x]] += c
    C[1][h2[x]] += c
print(C, {x: min(C[0][h1[x]], C[1][h2[x]]) for x in f})
print(ceil(e / 0.001), ceil(log(100)), ceil(e / 0.01), ceil(log(20)), ceil(e / 0.05), ceil(log(5)))

def simula(eps, delta, N=200_000, U=20_000, seed=0):
    rnd = random.Random(seed)
    P = (1 << 61) - 1
    w, d = ceil(e / eps), ceil(log(1 / delta))
    ab = [(rnd.randrange(1, P), rnd.randrange(P)) for _ in range(d)]
    flusso = rnd.choices(range(U), weights=[1 / (i + 1) ** 1.1 for i in range(U)], k=N)
    C, fr = [[0] * w for _ in range(d)], {}
    for x in flusso:
        fr[x] = fr.get(x, 0) + 1
        for j, (a, b) in enumerate(ab):
            C[j][((a * x + b) % P) % w] += 1
    err = [min(C[j][((a * x + b) % P) % w] for j, (a, b) in enumerate(ab)) - fr.get(x, 0) for x in range(U)]
    top = max(fr, key=fr.get)
    return w * d, max(err), sum(err) / U, sum(r > eps * N for r in err) / U, fr[top], err[top]

for eps, delta in ((0.01, 0.05), (0.001, 0.01), (0.05, 0.2)):
    print(eps, delta, simula(eps, delta))

Risultato: la matrice [[7, 3, 1, 1], [1, 8, 1, 2]] con stime {'a': 7, 'b': 3, 'c': 2, 'd': 1, 'e': 1}, le dimensioni 2719 5 272 3 55 2, e le tre righe (celle, errore max, errore medio, frazione, f max, errore su f max) della tabella (per esempio 13595 44 7.2497 0.0 29285 3).

Versione ripasso

Teoria collegata