リザーバ サンプリングは、未知のサイズnの母集団からk 個のアイテムを1 回のパスで非置換の単純ランダム サンプルとして選択するランダム化アルゴリズムのファミリです。母集団nのサイズはアルゴリズムにはわかりません。通常、 n個のアイテムすべてをメイン メモリに収めるには大きすぎます。母集団は時間の経過とともにアルゴリズムに公開され、アルゴリズムは以前のアイテムをさかのぼることはできません。どの時点でも、アルゴリズムの現在の状態は、これまでに確認された母集団の一部からサイズkの非置換の単純ランダム サンプルを抽出できる必要があります。
モチベーション
アイテムのシーケンスを 1 つずつ見ているとします。メモリに 10 個のアイテムを保持し、シーケンスからランダムに選択するとします。アイテムの総数nがわかっていて、アイテムに任意にアクセスできる場合、解決方法は簡単です。1からnまでの間の 10 個の異なるインデックスi を等確率で選択し、i番目の要素を保持します。問題は、正確なn が常に事前にわかっているわけではないことです。
シンプル: アルゴリズム R
シンプルで人気があるが遅いアルゴリズムであるアルゴリズムRは、ジェフリー・ヴィッターによって作成されました。[1]
入力の最初のk項目を含む、から までのインデックスを持つ配列を初期化します。これがリザーバです。
それぞれの新しい入力 に対して、内で一様に乱数j を生成します。 の場合、 を設定します。それ以外の場合は を破棄します。
すべての入力が処理された後に戻ります。
このアルゴリズムは 上の帰納法によって機能します。
の場合、アルゴリズム R はすべての入力を返すため、数学的帰納法による証明の基礎が提供されます。
ここで、帰納的仮説は、特定の入力が- 番目の入力が処理される直前にリザーバーに含まれる確率は であり、特定の入力が - 番目の入力が処理された直後にリザーバーに含まれる確率は であることを示す必要があるというものです。
アルゴリズム R を- 番目の入力に適用します。入力は、アルゴリズムの定義により、確率 で含まれます。他の任意の入力 については、帰納法の仮説により、- 番目の入力が処理される直前にリザーバに含まれている確率は です。 が処理された後もリザーバに含まれている確率(つまり、 がに置き換えられない確率) は です。後者は、整数が一様にランダムに生成されるという仮定から得られます。置き換えが実際に発生することが明らかになると、特に が に置き換えられる確率は です。
新しい入力がリザーバーに入る確率は、リザーバー内の既存の入力が保持される確率に等しいことを示しました。したがって、数学的帰納法の原理により、アルゴリズム R は実際に入力の均一なランダム サンプルを生成するという結論が導き出されます。
このアルゴリズムは概念的には単純で理解しやすいですが、破棄される項目も含め、入力の各項目に対して乱数を生成する必要があります。したがって、アルゴリズムの漸近的実行時間は です。この程度の乱数と線形実行時間を生成すると、入力母集団が大きい場合にアルゴリズムが不必要に遅くなります。
これはアルゴリズム R であり、次のように実装されています。
(* S にはサンプリングする項目があり、R にはその結果が含まれます *)
ReservoirSample ( S [ 1 .. n ] , R [ 1 .. k ]) // リザーバー配列を埋めるfor i := 1 to k R [ i ] := S [ i ] end
// 徐々に確率が下がる要素を置き換えます。
for i := k + 1 to n (* randomInteger(a, b) は、{a, ..., b} を含む範囲から均一な整数を生成します。 *) j := randomInteger ( 1 , i ) if j <= k R [ j ] := S [ i ] end end end
最適: アルゴリズム L
乱数を独立に生成すると、そのうちの最小の乱数のインデックスはの k 部分集合の均一なサンプルになります。
このプロセスは、次のことを知らなくても実行できます。
これまでに見たの最小値と、それらの最大値のインデックス を保持します。新しい ごとに、それを と比較します。 の場合、 を破棄し、 を保存して、 をそれらの最大値のインデックス に設定します。それ以外の場合は、 を破棄してを設定します。
これを入力のストリームと組み合わせます。 一部が受け入れられるたびに、対応する を保存します。 一部が破棄されるたびに、対応する を破棄します。
このアルゴリズムでは依然として乱数が必要なので時間がかかります。しかし、簡略化することは可能です。
最初の簡略化:次の承認が で発生する確率は 、つまり承認の間隔は幾何分布 に従うため、新しいものを 1 つずつテストする必要はありません。
2 つ目の簡略化:これまでに見た の最小の配列全体を覚える必要はなく、その中で最大の だけを覚えればよい。これは次の 3 つの観察に基づいています。
- ストレージに入力する新しいエントリが選択されるたびに、ストレージ内の均一にランダムなエントリが破棄されます。
- は と同じ分布を持ち、すべては独立しています。これは、まず をサンプリングし、次に を取ることでサンプリングできます。
これはアルゴリズムL [2]であり、以下のように実装される。
(* S にはサンプリングする項目があり、R にはその結果が含まれます *)
ReservoirSample ( S [ 1 .. n ] , R [ 1 .. k ]) // リザーバー配列を埋めるfor i = 1 to k R [ i ] := S [ i ] end
(* random() は均一な (0,1) 乱数を生成します *)
W := exp ( log ( random ()) / k )
i <= nの場合i := i + floor ( log ( random ()) / log ( 1 - W )) + 1 if i <= n (* リザーバーのランダムなアイテムをアイテム i に置き換えます *) R [ randomInteger ( 1 , k )] := S [ i ] // 1 から k までの範囲のランダムなインデックスW := W * exp ( log ( random ()) / k ) end end end
このアルゴリズムは、リザーバの一部となるアイテムごとに3つの乱数を計算し、リザーバの一部とならないアイテムには時間を費やしません。したがって、予想される実行時間は[ 2]であり、最適です。[1] 同時に、効率的に実装するのは簡単で、エキゾチックな分布や計算が難しい分布からのランダムな偏差に依存しません。
ランダムソート
入力の各項目に均一に生成された乱数を関連付けると、関連付けられた値が最大(または最小)のk項目が単純ランダムサンプルを形成します。 [3] 単純なリザーバサンプリングでは、現在関連付けられている値が最大であるk項目が優先キューに保持されます。
(*
S はサンプルするアイテムのストリームです。
S.Current はストリーム内の現在のアイテムを返します。
S.Next はストリームを次の位置に進めます。
min-priority-queue は以下をサポートします:
Count -> 優先キュー内のアイテム数
Minimum -> 全アイテムの最小キー値を返します
Extract-Min() -> 最小キーを持つアイテムを削除します
Insert(key, Item) -> 指定されたキーを持つアイテムを追加します
* )
ReservoirSample ( S [ 1 .. ? ] )
H := new min - priority - queue while S has data r := random ( ) // 0 から 1 の間の均一ランダム (排他的) if H.Count < k H.Insert ( r , S.Current ) else //最大の関連キーを持つ k 個のアイテムを保持if r > H.Minimum H.Extract - Min ( ) H.Insert ( r , S.Current ) end S.Next end H内のアイテムを返しますend
このアルゴリズムの予想される実行時間は、重みのあるアイテムに簡単に拡張できるため、主に重要です。
加重ランダムサンプリング
前のセクションで紹介した方法では、事前に固定された包含確率を得ることはできません。一部のアプリケーションでは、アイテムのサンプリング確率が各アイテムに関連付けられた重みに応じている必要があります。たとえば、検索エンジンでクエリを重みを実行回数としてサンプリングして、サンプルのユーザーエクスペリエンスへの全体的な影響を分析する必要がある場合があります。アイテムiの重みを、すべての重みの合計をWとします。セット内の各アイテムに割り当てられた重みを解釈する方法は 2 つあります。[4]
- 各ラウンドで、選択されていないすべてのアイテムがそのラウンドで選択される確率は、選択されていないすべてのアイテムの重みに対するそのアイテムの重みに比例します。X が現在のサンプルの場合、現在のラウンドでアイテムが選択される確率は です。
- ランダムサンプルに含まれる各項目の確率は、その相対的な重みに比例します。つまり、 です。ただし、この解釈は、たとえば などの場合には実現できない可能性があることに注意してください。
アルゴリズム A-Res
エフライミディスとスピラキスは、解釈1を用いた次のアルゴリズムを提示した。[5]
(*
S はサンプリングする項目のストリームです。
S.Current はストリーム内の現在の項目を返します。
S.Weight はストリーム内の現在の項目の重みを返します。
S.Next はストリームを次の位置へ進めます。
累乗演算子は ^ で表されます。
min-priority-queue は以下をサポートします:
Count -> 優先キュー内の項目数
Minimum() -> 全項目の最小キー値を返します
Extract-Min() -> 最小キーを持つ項目を削除します
Insert(key, Item) -> 指定されたキーを持つ項目を追加します
*)
ReservoirSample ( S [ 1 .. ? ])
H := new min - priority - queue while Sにデータがありますr := random () ^ ( 1 / S . Weight ) // random() は (0,1) の範囲で一様に乱数を生成します。if H . Count < k H . Insert ( r , S . Current ) else // 最大の関連キーを持つ k 個の項目を保持します。 if r > H . Minimum H . Extract - Min () H . Insert ( r , S . Current ) end end S .次の終了H終了の項目を返す
このアルゴリズムは、アイテムのキーの生成を除いて、ランダムソートによるリザーバサンプリングで示されたアルゴリズムと同一です。このアルゴリズムは、各アイテムにキーを割り当て、rを乱数として、最大のキーを持つk個のアイテムを選択することと同等です。同様に、このアルゴリズムのより数値的に安定した定式化では、キーを次のように計算し、最小のキーを持つk個のアイテムを選択します。[6] [検証失敗]
アルゴリズムA-ExpJ
以下のアルゴリズムはA-Resのより効率的なバージョンであり、これもエフライミディスとスピラキスによって提案されたものである。[5]
(*
S はサンプリングする項目のストリームです。
S.Current はストリーム内の現在の項目を返します。
S.Weight はストリーム内の現在の項目の重みを返します。
S.Next はストリームを次の位置へ進めます。
べき乗演算子は ^ で表されます。
min-priority-queue は以下をサポートします:
Count -> 優先キュー内の項目数
Minimum -> 優先キュー内の任意の項目の最小キー
Extract-Min() -> 最小キーを持つ項目を削除
Insert(Key, Item) -> 指定されたキーを持つ項目を追加します
*)
ReservoirSampleWithJumps ( S [ 1 .. ? ])
H := new min - priority - queue while Sにデータがあり、H . Count < k r := random () ^ ( 1 / S . Weight ) // random() は (0,1) 内の一様乱数を生成します。 H . Insert ( r , S . Current ) S .次へend X := log ( random ()) / log ( H . Minimum ) // これは、 Sにデータがある間に飛び越える必要がある重みの量です。 X := X - S . Weight if X <= 0 t := H . Minimum ^ S . Weight r := random ( t , 1 ) ^ ( 1 / S . Weight ) // random(x, y) は、(x, y) で均一な乱数を生成します。H . Extract - Min () H . Insert ( r , S . Current )
X := log ( random ()) / log ( H . Minimum ) end S . Next end H内の項目を返すend
このアルゴリズムは、A-Res で使用されるのと同じ数学的特性に従いますが、各アイテムのキーを計算してそのアイテムを挿入するかどうかをチェックする代わりに、挿入される次のアイテムへの指数関数的なジャンプを計算します。これにより、コストがかかる可能性がある各アイテムのランダム変数を作成する必要がなくなります。必要なランダム変数の数は、期待値で から に削減されます。ここではリザーバのサイズ、 はストリーム内のアイテム数です。[5]
アルゴリズムA-Chao
警告: 以下の説明は間違っています。Chao の元の論文とここでの議論を参照してください。
以下のアルゴリズムはMT Chaoによって解釈2で示されました: [7]およびTillé (2006)。[8]
(*
S にはサンプリングするアイテムがあり、R には結果が含まれます
S[i].Weight には各アイテムの重みが含まれます
*)
WeightedReservoir - Chao ( S [ 1 .. n ] 、R [ 1 .. k ]) WSum := 0 // リザーバー配列を埋めるfor i := 1 to k R [ i ] := S [ i ] WSum := WSum + S [ i ] . Weight end for i := k + 1 to n WSum := WSum + S [ i ] . Weight p := S [ i ] . Weight / WSum // このアイテムの確率j := random () ; // 0 から 1 の間で均一にランダムif j <= p // 確率に従ってアイテムを選択R [ randomInteger ( 1 , k )] := S [ i ] // 置換用のリザーバー内の均一な選択end end end
各アイテムの相対的な重みが計算され、そのアイテムをリザーバーに追加するかどうかをランダムに決定するために使用されます。アイテムが選択されると、リザーバーの既存のアイテムの 1 つが均一に選択され、新しいアイテムに置き換えられます。ここでの秘訣は、リザーバー内のすべてのアイテムの確率がすでに重みに比例している場合、どのアイテムを置き換えるかを均一に選択することで、置き換え後もすべてのアイテムの確率が重みに比例したままになるということです。
Chao は最初のk個の要素をサンプリングする方法を指定していないことに注意してください。彼は、重みに比例して要素を選択する別の方法があると単純に想定しています。Chao: 「時刻AにおけるS_kに関する固定サイズのサンプリング プランがあり、そのX_tの 1 次包含確率がπ(k; i)であると仮定します」。
ジャンプ付きアルゴリズムA-Chao
j他のアルゴリズムと同様に、ランダムな重みを計算し、アイテムの確率質量値を減算してスキップすることでj > 0、生成しなければならない乱数の数を減らすことができます。 [4]
(*
S にはサンプリングする項目があり、R には結果
S[i] が含まれます。Weight には各項目の重みが含まれます
*)
WeightedReservoir - Chao ( S [ 1 .. n ] 、R [ 1 .. k ]) WSum := 0 // リザーバー配列を埋めますfor i := 1 to k R [ i ] := S [ i ] WSum := WSum + S [ i ] . Weight end j := random () // 0 から 1 の間の均一ランダムpNone := 1 // これまで (このジャンプで) 項目が選択されていない確率for i := k + 1 to n WSum := WSum + S [ i ] . Weight p := S [ i ] . Weight / WSum // このアイテムの確率j -= p * pNone pNone := pNone * ( 1 - p ) if j <= 0 R [ randomInteger ( 1 , k )] := S [ i ] // 置換のためのリザーバー内の均一選択j = random () pNone := 1 end end end
フィッシャー・イェーツ・シャッフルとの関係
トランプのデッキからランダムにk枚のカードを取りたいとします。自然な方法は、デッキをシャッフルして、上からk枚のカードを取ることです。一般的なケースでは、デッキのカードの枚数が事前にわからなくてもシャッフルが機能する必要がありますが、この条件はフィッシャー・イェーツ・シャッフルの裏返しバージョンによって満たされます。[9]
(* S には入力があり、R には出力順列が含まれます *)
Shuffle ( S [ 1 .. n ] , R [ 1 .. n ]) R [ 1 ] := S [ 1 ] for i from 2 to n do j := randomInteger ( 1 , i ) // 包含範囲R [ i ] := R [ j ] R [ j ] := S [ i ] end end
残りのカードはシャッフルされますが、現在のコンテキストでは最初のk 枚だけが重要であることに注意してください。したがって、配列R はシャッフルの実行中に最初のk位置のカードを追跡するだけでよく、必要なメモリの量を削減できます。R を長さkに切り捨てると、アルゴリズムはそれに応じて変更されます。
(* S にはサンプリングする項目があり、R にはその結果が含まれます *)
ReservoirSample ( S [ 1 .. n ] , R [ 1 .. k ]) R [ 1 ] := S [ 1 ] for i from 2 to k do j := randomInteger ( 1 , i ) // 包含範囲R [ i ] := R [ j ] R [ j ] := S [ i ] end for i from k + 1 to n do j := randomInteger ( 1 , i ) // 包含範囲if ( j <= k ) R [ j ] := S [ i ] end end end
最初のk枚のカードの順序は重要ではないため、最初のループを削除し、R を入力の最初のk個の項目に初期化することができます。これにより、アルゴリズム R が生成されます。
制限事項
リザーバサンプリングでは、目的のサンプルがメインメモリに収まるという仮定が立てられ、 kはnに依存しない定数であることがしばしば示唆されます。入力リストの大きなサブセット(たとえば3分の1)を選択したいアプリケーションでは、他の方法を採用する必要があります。この問題に対する分散実装が提案されています。[10]
参考文献
- ^ ab Vitter, Jeffrey S. (1985 年 3 月 1 日). 「リザーバーによるランダムサンプリング」(PDF) . ACM Transactions on Mathematical Software . 11 (1): 37–57. CiteSeerX 10.1.1.138.784 . doi :10.1145/3147.3165. S2CID 17881708.
- ^ ab Li, Kim-Hung (1994 年 12 月 4 日). 「時間計算量 O(n(1+log(N/n))) のリザーバーサンプリングアルゴリズム」. ACM Transactions on Mathematical Software . 20 (4): 481–493. doi : 10.1145/198429.198435 . S2CID 15721242.
- ^ Fan, C.; Muller, ME; Rezucha, I. (1962). 「逐次(項目ごと)選択手法とデジタルコンピュータを使用したサンプリング計画の開発」アメリカ統計学会誌. 57 (298): 387–402. doi :10.1080/01621459.1962.10480667. JSTOR 2281647.
- ^ ab Efraimidis, Pavlos S. (2015). 「データストリーム上の加重ランダムサンプリング」。アルゴリズム、確率、ネットワーク、ゲーム。コンピュータサイエンスの講義ノート。第9295巻。pp. 183–195。arXiv : 1012.0256。doi : 10.1007/ 978-3-319-24024-4_12。ISBN 978-3-319-24023-7. S2CID 2008731。
- ^ abc Efraimidis, Pavlos S.; Spirakis, Paul G. (2006-03-16). 「リザーバーによる加重ランダムサンプリング」. Information Processing Letters . 97 (5): 181–185. doi :10.1016/j.ipl.2005.11.003.
- ^ Arratia, Richard (2002). Bela Bollobas (ed.). 「一様ランダム整数の素因数分解における依存性の量について」. Contemporary Combinatorics . 10 : 29–91. CiteSeerX 10.1.1.745.3975 . ISBN 978-3-642-07660-2。
- ^ Chao, MT (1982). 「汎用不等確率サンプリング計画」Biometrika . 69 (3): 653–656. doi :10.1093/biomet/69.3.653.
- ^ Tillé, Yves (2006).サンプリングアルゴリズム. Springer. ISBN 978-0-387-30814-2。
- ^ National Research Council (2013). Frontiers in Massive Data Analysis. The National Academies Press. p. 121. ISBN 978-0-309-28781-4。
- ^ MapReduce におけるリザーバーサンプリング
