1. 程式人生 > >水塘取樣(Reservoir sampling)演算法

水塘取樣(Reservoir sampling)演算法

最近看了Flink中的rangePartition使用了水塘取樣演算法,因此參考維基百科詳細瞭解了一下。

取樣的關鍵在於對每個元素的選取需要是等概率的。水塘取樣其目的在於從包含n個專案的集合S中選取k個樣本,其中n為一很大或未知的數量,尤其適用於不能把所有n個專案都存放到主記憶體的情況。
適用問題:
1.可否在一未知大小的集合中,隨機取出k個元素?
2.在不知道檔案總行數的情況下,如何從檔案中隨機的抽取k行?

取樣過程:集合中總元素個數為n,隨機選取k個元素
step1.首先將前k個元素全部選取。
step2.對於第i個元素(i>k),以概率k/i來決定是否保留該元素,如果保留該元素的話,則隨機丟棄掉原有的k個元素中的一個(即原來某個元素被丟掉的概率是1/k)。
結果:每個元素被最終被選取的概率都是k/n。

舉例說明:例子直接翻譯自維基百科。
以選取10個元素為例。
1.當總元素為11個時。每個元素保留下來的概率是10/11
證明:
對於前10個元素,一開始就會被選擇。
對於第11個元素,被選擇的概率是:10/11,即滿足。
前10個元素中某個元素t被保留下來的條件為:第11個元素被選中,且不替換掉該t元素+第11個元素不被選中。概率為:10/11x(1-1/10)+1/11=10/11。
結論:所有元素都被10/11的等概率選擇是否保留。

2.當總元素為12個時。每個元素保留下來的概率是10/12
證明:
對於前10個元素,一開始就會被選擇。
對於第11個元素,最後留下的條件為:被選中,而且不被第12個元素替換掉。概率為:10/11x(1-10/12x1/10)=10/12。其中10/11是第11個元素保留的概率,10/12是第12個元素被選中保留的概率,1/10是正好替換掉第11個元素的概率。
對於第12個元素,最後留下的條件為:被選中,即10/12。
對於前10個元素中某個元素i被保留下來的條件為:第11個元素不替換第t個元素且第12個元素不替換第t個元素。概率為:(1-10/11x1/10)x(1-10/12x1/10)=10/12。

3.當總元素為n個時,每個元素保留下來的概率是10/n
證明:
對於第i個元素,i>10。被保留下來的條件為:被保留,且不被i+1,i+2…n個元素替換掉。概率為:(10/i)x(1-10/(i+1)x 1/10)x…x(1-10/n x 1/10)=10/i x i/i+1 x … x n-1/n =10/n。
對於前十個元素中的某個,被保留下來的條件為,不被第11,12,…,n個元素替換掉。
概率為:(1-10/11 x 1/10)x(1-10/12 x 1/10)x…x(1-10/n x 1/10) = 10/11 x 11/12 x … x n-1/n = 10/n。

程式碼如下:直接抄的維基百科裡的:

(*
  S has items to sample, R will contain the result
 *)
ReservoirSample(S[1..n], R[1..k])
  // fill the reservoir array
  for i = 1 to k
      R[i] := S[i]

  // replace elements with gradually decreasing probability
  for i = k+1 to n
    j := random(1, i)   // important: inclusive range
    if j <= k
        R[j] := S[i]

參考:
https://en.wikipedia.org/wiki/Reservoir_sampling