L’algorithme HyperLogLog est l’algorithme HyperLogLog que je découvre et qui me semble très intéressant. L’apprentissage de différents algorithmes me semble très intéressant. Il permet d’estimer le nombre d’éléments uniques dans une liste.
C’était particulièrement intéressant pour moi car cela me ramena à mes jours dans MySQL quand j’ai vu cette valeur de "cardinalité" (que j’avais toujours supposé jusqu’à récemment que cette valeur était calculée et non estimée).
Je sais donc comment écrire un algorithme en [~ # ~] o [~ # ~] ( n ) qui calculera le nombre d'éléments uniques contenus dans un tableau. J'ai écrit ceci en JavaScript:
function countUniqueAlgo1(arr) {
var Table = {};
var numUnique = 0;
var numDataPoints = arr.length;
for (var j = 0; j < numDataPoints; j++) {
var val = arr[j];
if (Table[val] != null) {
continue;
}
Table[val] = 1;
numUnique++;
}
return numUnique;
}
Mais le problème est que mon algorithme, tandis que [~ # ~] o [~ # ~] ( n ), utilise beaucoup de mémoire (stockage des valeurs dans Table
).
Je lis ce document comment compter les doublons dans une liste dans [~ # ~] o [~ # ~] ( n ) temps et en utilisant une mémoire minimale.
Il explique qu'en hachant et en comptant des bits ou quelque chose, on peut estimer avec une certaine probabilité (en supposant que la liste soit distribuée de manière égale) le nombre d'éléments uniques dans une liste.
J'ai lu le journal, mais je n'arrive pas à comprendre. Quelqu'un peut-il donner une explication plus profane? Je sais ce que sont les hachages, mais je ne comprends pas comment ils sont utilisés dans cet algorithme HyperLogLog.
L'astuce principale de cet algorithme est que si vous observez un flux d'entiers aléatoires et que vous voyez un entier dont la représentation binaire commence par un préfixe connu, il y a plus de chance que la cardinalité du flux soit égale à 2 ^ (taille du préfixe) .
Autrement dit, dans un flux aléatoire d'entiers, environ 50% des nombres (en binaire) commencent par "1", 25% commencent par "01", 12,5% commencent par "001". Cela signifie que si vous observez un flux aléatoire et voyez un "001", il y a plus de chance que ce flux ait une cardinalité de 8.
(Le préfixe "00..1" n'a pas de signification particulière. Il existe simplement parce qu'il est facile de trouver le bit le plus significatif dans un nombre binaire dans la plupart des processeurs).
Bien sûr, si vous observez un seul entier, la probabilité que cette valeur soit fausse est élevée. C'est pourquoi l'algorithme divise le flux en "m" sous-flux indépendants et conserve la longueur maximale d'un préfixe "00 ... 1" vu de chaque sous-flux. Ensuite, estime la valeur finale en prenant la valeur moyenne de chaque sous-flux.
C'est l'idée principale de cet algorithme. Il manque certains détails (la correction pour les valeurs d'estimation basses, par exemple), mais tout est bien écrit dans le document. Désolé pour le terrible anglais.
Un HyperLogLog est un structure de données probabiliste . Il compte le nombre d'éléments distincts dans une liste. Mais par rapport à une manière simple de le faire (avoir un ensemble et ajouter des éléments à celui-ci), il procède de manière approximative.
Avant de voir comment l’algorithme HyperLogLog fait cela, il faut comprendre pourquoi vous en avez besoin. Le problème avec un moyen simple, c'est qu'il consomme O(distinct elements)
de l'espace. Pourquoi existe-t-il une grande notation O ici au lieu de simplement des éléments distincts? En effet, les éléments peuvent être de tailles différentes. Un élément peut être 1
Un autre élément "is this big string"
. Donc, si vous avez une liste énorme (ou un énorme flux d'éléments), il faudra beaucoup de mémoire.
Comptage probabiliste
Comment peut-on obtenir une estimation raisonnable d'un nombre d'éléments uniques? Supposons que vous avez une chaîne de longueur m
composée de {0, 1}
Avec une probabilité égale. Quelle est la probabilité pour que cela commence par 0, avec 2 zéros, avec k zéros? C'est 1/2
, 1/4
Et 1/2^k
. Cela signifie que si vous avez rencontré une chaîne avec k
zéros, vous avez approximativement examiné les éléments 2^k
. C'est donc un bon point de départ. En ayant une liste d'éléments uniformément répartis entre 0
Et 2^k - 1
, Vous pouvez compter le nombre maximum du plus grand préfixe de zéros dans la représentation binaire, ce qui vous donnera une estimation raisonnable.
Le problème est qu’il est trop difficile de supposer que les nombres distribués de manière égale entre 0
T 2^k-1
Mais avec un bonne fonction de hachage , vous pouvez supposer que les bits de sortie seraient répartis de manière uniforme et que la plupart des fonctions de hachage ont des sorties comprises entre 0
et 2^k - 1
( SHA1 vous donne des valeurs comprises entre 0
Et 2^160
). Nous avons donc réussi jusqu'ici à estimer le nombre d'éléments uniques avec la cardinalité maximale de k
bits en stockant un seul nombre de taille log(k)
bits. L’inconvénient est que nous avons une énorme variance dans notre estimation. Une chose sympa que nous avons presque créée Comptage probabiliste de 1984 papier (c'est un peu plus intelligent avec l'estimation, mais nous sommes toujours proches).
LogLog
Avant d'aller plus loin, nous devons comprendre pourquoi notre première estimation n'est pas si bonne. La raison en est qu’une occurrence aléatoire d’élément préfixe 0 à haute fréquence peut tout gâcher. Une façon de l’améliorer consiste à utiliser de nombreuses fonctions de hachage, à compter max pour chacune des fonctions de hachage et à moyen terme. C'est une excellente idée, ce qui améliorera l'estimation, mais ( Papier LogLog a utilisé une approche légèrement différente (probablement parce que le hachage est genre de cher).
Ils ont utilisé un hash mais l'ont divisé en deux parties. L'un s'appelle un seau (le nombre total de seaux est 2^x
) Et un autre - est fondamentalement le même que notre hash. J'ai eu du mal à comprendre ce qui se passait, alors je vais donner un exemple. Supposons que vous avez deux éléments et que votre fonction de hachage qui donne les valeurs de la forme 0
À 2^10
Produit 2 valeurs: 344
Et 387
. Vous avez décidé d'avoir 16 seaux. Donc vous avez:
0101 011000 bucket 5 will store 1
0110 000011 bucket 6 will store 4
En ayant plus de compartiments, vous réduisez la variance (vous utilisez un peu plus d'espace, mais il reste minuscule). À l'aide de compétences en mathématiques, ils ont pu quantifier l'erreur (qui est 1.3/sqrt(number of buckets)
).
HyperLogLog
HyperLogLog n'introduit aucune nouvelle idée, mais utilise principalement beaucoup de mathématiques pour améliorer l'estimation précédente. Les chercheurs ont constaté que si vous retirez 30% des nombres les plus gros des tranches, vous améliorez considérablement l'estimation. Ils ont également utilisé un autre algorithme pour calculer la moyenne des nombres. Le papier est lourd en maths.
Et je voudrais terminer avec un article récent, qui montre un version améliorée de l'algorithme hyperLogLog (jusqu'à présent, je n'avais pas le temps de bien le comprendre, mais j'améliorerai peut-être plus tard cette réponse).
L'intuition est que si votre entrée est un grand ensemble de nombres aléatoires (par exemple, des valeurs hachées), ils doivent être répartis uniformément sur une plage. Supposons que la plage va jusqu'à 10 bits pour représenter la valeur jusqu'à 1024. Ensuite, observez la valeur minimale. Disons qu'il est 10. Ensuite, la cardinalité sera d'environ 100 (10 × 100 1024).
Lisez le papier pour la vraie logique du cours.
Une autre bonne explication avec un exemple de code peut être trouvée ici:
Algorithmes Damn Cool: Estimation de la cardinalité - Le blog de Nick