1、說在前面
- Alias采樣是時間復雜度為o(1)的離散采樣方式
- 論文地址:http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.675.8158&rep=rep1&type=pdf
2、詳細介紹
問題
比如一個隨機事件包含四種情況,每種情況發生的概率分別為: 1/2,1/3,1/12,1/12問怎么用產生符合這個概率的采樣方法。
最容易想到的方法
我之前有在【數學】均勻分布生成其他分布的方法中寫過均勻分布生成其他分布的方法,這種方法就是產生0~1之間的一個隨機數,然后看起對應到這個分布的CDF中的哪一個,就是產生的一個采樣。比如落在0~ 1/2之間就是事件A,落在1/2~5/6之間就是事件B,落在5/6~11/12之間就是事件C,落在11/12~1之間就是事件D。
但是這樣的復雜度,如果用BST樹來構造上面這個的話,時間復雜度為O(logN),有沒有時間復雜度更低的方法。
一個Naive的辦法
1. 可以像上圖這樣采樣,將四個事件排成4列:1~4,扔兩次骰子,第一次扔1~4之間的整數,決定落在哪一列。
2. 如上如所示,將其按照最大的那個概率進行歸一化。在1步中決定好哪一列了之后,扔第二次骰子,0~1之間的任意數,如果落在了第一列上,不論第二次扔幾,都采樣時間A,如果落在第二列上,第二次扔超過2/3則采樣失敗,重新采樣,如果小於2/3則采樣時間B成功,以此類推。
3. 這樣算法復雜度最好為O(1)最壞有可能無窮次,平均意義上需要采樣O(N)
那怎么去改進呢?
Alias Method
還是如上面的那樣的思路,但是如果我們不按照其中最大的值去歸一化,而是按照其均值歸一化。即按照1/N(這里N是情況的種數,這里四種)歸一化,即為所有概率乘以N,得到如下圖:
其總面積為N,然后可以將其分成一個1*N的長方形,如下圖:
將前兩個多出來的部分補到后面兩個缺失的部分中。
先將1中的部分補充到4中:
這時如果,將1,2中多出來的部分,補充到3中,就麻煩了,因為又陷入到如果從中采樣超過2個以上的事件這個問題中,所以Alias Method一定要保證:每列中最多只放兩個事件
所以此時需要將1中的補滿3中去:
再將2中的補到1中:
至此整個方法大功告成
Alias Method具體算法如下:
1. 按照上面說的方法,將整個概率分布拉平成為一個1*N的長方形即為Alias Table,構建上面那張圖之后,儲存兩個數組,一個里面存着第ii列對應的事件ii矩形站的面積百分比【也即其概率】,上圖的話數組就為Prab[2/3, 1, 1/3, 1/3],另一個數組里面儲存着第ii列不是事件ii的另外一個事件的標號,像上圖就是Alias[1 0 0 0]
2.產生兩個隨機數,第一個產生1~N 之間的整數i,決定落在哪一列。扔第二次骰子,0~1之間的任意數,判斷其與Prab[i]大小,如果小於Prab[i],則采樣i,如果大於Prab[i],則采樣Alias[i]
3、算法實現
import numpy as np def alias_table(prob_table): n = len(prob_table) prob_norm = np.array(prob_table) * n small, large = [], [] for i, prob in enumerate(prob_norm): if prob < 1: small.append(i) else: large.append(i) accept, alias = [0] * n, [0] * n while small and large: small_idx, large_idx = small.pop(), large.pop() # 存放第i列對應的事件i的概率; accept[small_idx] = prob_norm[small_idx] # 存放不是事件i的另外事件的標號; alias[small_idx] = large_idx prob_norm[large_idx] = prob_norm[large_idx] - (1 - prob_norm[small_idx]) if prob_norm[large_idx] < 1.0: small.append(large_idx) else: large.append(large_idx) while small: small_idx = small.pop() accept[small_idx] = 1 while large: large_idx = large.pop() accept[large_idx] = 1 print(accept) print(alias) return accept, alias def alias_sample(accept, alias): n = len(accept) i = int(np.random.random() * n) r = np.random.random() if r < accept[i]: return i else: return alias[i] if __name__ == '__main__': prob_table = [1 / 2, 1 / 3, 1 / 12, 1 / 12] ac, a = alias_table(prob_table) i = alias_sample(ac, a) print(i)
————————————————
版權聲明:本文為CSDN博主「哈樂笑」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/haolexiao/article/details/65157026