最近、暇なときにさまざまなアルゴリズムについて学んでいますが、非常に興味深いと思われるものにHyperLogLogアルゴリズムと呼ばれるものがあります。これは、リスト内の一意のアイテムの数を推定します。
「カーディナリティ」の値を見たMySQLの時代に戻ってきたので、これは特に興味深いものでした(最近まで推定されていないと計算されていた)。
だから私はO(nでアルゴリズムを書く方法を知っている)配列内にある一意のアイテムの数を計算します。私は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;
}
しかし、問題は私のアルゴリズム、O(n )、大量のメモリを使用します(Table
に値を保存)。
私は読んでいます このペーパーO(n)時間と最小メモリの使用。
ビットをハッシュおよびカウントすることで、リスト内の一意のアイテムの数を特定の確率(リストが均等に分布していると仮定)内で推定できることを説明します。
私は論文を読みましたが、理解できないようです。誰かがもっと素人の説明をすることはできますか?ハッシュとは何ですか?.
このアルゴリズムの背後にある主なトリックは、ランダムな整数のストリームを観察して、バイナリ表現が既知のプレフィックスで始まる整数を見ると、ストリームのカーディナリティが2 ^(プレフィックスのサイズ)である可能性が高いことです。 。
つまり、整数のランダムストリームでは、数の最大50%(バイナリ)が「1」で始まり、25%が「01」で始まり、12.5%が「001」で始まります。つまり、ランダムストリームを観察して「001」が表示された場合、このストリームのカーディナリティが8である可能性が高くなります。
(プレフィックス "00..1"には特別な意味はありません。ほとんどのプロセッサで2進数の最上位ビットを簡単に見つけることができるからです。)
もちろん、整数を1つだけ観察すると、この値が間違っている可能性が高くなります。これが、アルゴリズムがストリームを「m」個の独立したサブストリームに分割し、各サブストリームの「00 ... 1」プレフィックスの最大長を維持する理由です。次に、各サブストリームの平均値を取得して最終値を推定します。
それがこのアルゴリズムの主なアイデアです。いくつかの欠落した詳細(たとえば、低い推定値の修正)がありますが、それはすべて論文によく書かれています。ひどい英語でごめんね。
HyperLogLogは 確率的データ構造 です。リスト内の個別の要素の数をカウントします。しかし、それを行う簡単な方法(セットを持ち、セットに要素を追加する)と比較すると、これはおおよその方法で行われます。
HyperLogLogアルゴリズムがこれを行う方法を調べる前に、なぜそれが必要なのかを理解する必要があります。簡単な方法の問題は、O(distinct elements)
のスペースを消費することです。なぜ個別の要素ではなく、大きなO表記がここにあるのですか?これは、要素のサイズが異なる可能性があるためです。 1つの要素は1
別の要素"is this big string"
にすることができます。そのため、巨大なリスト(または要素の巨大なストリーム)がある場合、多くのメモリが必要になります。
確率的カウント
ユニークな要素の数の合理的な推定値を取得するにはどうすればよいですか?等しい確率で{0, 1}
で構成される長さm
の文字列があると仮定します。 0から始まり、2つのゼロで、k個のゼロで始まる確率はどのくらいですか? 1/2
、1/4
、および1/2^k
です。つまり、k
ゼロの文字列に遭遇した場合、おおよそ2^k
要素を調べたことになります。したがって、これは良い出発点です。 0
と2^k - 1
の間に均等に分布する要素のリストがあると、バイナリ表現でゼロの最大プレフィックスの最大数を数えることができ、合理的な見積もりが得られます。
問題は、0
t 2^k-1
から数値を均等に分配するという仮定を達成するのが難しすぎることです(遭遇したデータはほとんどが数値ではなく、ほとんど均等に分配されず、任意の値の間である可能性があります)。ただし、 良好なハッシュ関数 を使用すると、出力ビットが均等に分散され、ほとんどのハッシュ関数が0
と2^k - 1
の間に出力を持っていると仮定できます( SHA1 = 0
と2^160
の間の値を指定します。これまでに達成したことは、1つだけを保存することで、k
ビットの最大カーディナリティを持つ一意の要素の数を推定できることです。サイズlog(k)
ビットの数。マイナス面は、推定値に大きな分散があることです。ほとんど作成したクールなもの 1984の確率的カウント 紙(推定値は少し賢いですが、まだ近いです)。
LogLog
さらに先に進む前に、最初の見積もりがそれほど大きくない理由を理解する必要があります。その背後にある理由は、高頻度の0プレフィックス要素の1つのランダムな出現がすべてを損なう可能性があることです。これを改善する1つの方法は、多くのハッシュ関数を使用し、各ハッシュ関数の最大数をカウントし、最終的にそれらを平均化することです。これは優れたアイデアであり、推定を改善しますが、 LogLog paper は少し異なるアプローチを使用しました(おそらくハッシュが高価なような)。
彼らは1つのハッシュを使用しましたが、それを2つの部分に分割しました。 1つはバケットと呼ばれ(バケットの総数は2^x
です)、もう1つは基本的にハッシュと同じです。何が起こっているのかを把握するのは大変だったので、例を挙げます。 2つの要素があり、0
から2^10
に値を与えるハッシュ関数が2つの値を生成すると仮定します:344
と387
。 16個のバケットを用意することにしました。だからあなたが持っています:
0101 011000 bucket 5 will store 1
0110 000011 bucket 6 will store 4
バケットを増やすことで、分散を小さくすることができます(使用するスペースは少し増えますが、それでもまだ小さいです)。数学のスキルを使用して、エラーを定量化することができました(1.3/sqrt(number of buckets)
)。
HyperLogLog
HyperLogLog は新しいアイデアを導入しませんが、多くの数学を使用して以前の推定値を改善します。研究者は、バケットから最大数の30%を削除すると、推定が大幅に改善されることを発見しました。また、数値の平均化に別のアルゴリズムを使用しました。この論文は数学が多用されています。
そして、私は hyperLogLogアルゴリズムの改良版 (これまで完全に理解する時間がありませんでしたが、後でこの答えを改善するかもしれません)を示す最近の論文で終わりたいと思います。
直感は、入力が大量の乱数(ハッシュ値など)である場合、範囲全体に均等に分散する必要があることです。 1024までの値を表すために範囲が最大10ビットだとしましょう。次に、最小値を観察しました。 10とすると、カーディナリティは約100(10×100≈1024)と推定されます。
もちろん、本当の論理については論文を読んでください。
サンプルコードを使用した別の良い説明は、ここにあります。
ダムクールアルゴリズム:カーディナリティ推定-Nick's Blog