Re:从零开始的加权采样算法

re0

所有人都非常擅长均匀抽样,因为几乎所有的编程语言都内置了均匀分布中生成一个0到1的实数的方法,本文中我们将此方法记作rand(0,1)...
那如果是从一个带有权重的集合里抽取呢?往往就没有那么简单了,特别是如果集合很大,而且里面的元素会变的情况。
比如说:在一个集合里n个元素,每个元素有不同的权重,每个元素被抽中的概率为元素的权重占总权重的比例,现从这个集合中有放回的随机抽取k个元素,你会怎么做呢?

加权采样能发挥的本质,其实就是如何巧妙利用rand(0,1)来实现相应的加权效果。

拍脑袋版

先介绍几个拍脑袋就能想出来的方法,这些方法不需要任何的经验,所有人都能想到。

放大集合法

比如说我们有一个序列[a,b,c,d]中有 4个元素,权重分别是 [1,2,3,4], 我们要从这个这个序列来进行加权采样,我们完全可以将权重转化为序列中的元素,然后将权重都置为1,把序列变成[a,b,b,c,c,c,d,d,d,d], 然后就可以愉快的用int(rand(0,1) * sum([1,2,3,4])) 获取元素下标来进行采样了。
这个方法非常简单有效,但是显然它在构造新序列和求和时都需要很大的开销,时间复杂度为O(n)。不过如果序列元素是稳定的,在构建了新序列和求好和之后采样效率会非常高,时间复杂度为O(1)

累加比例法

其实和上面那种方法差不多...


累加求和二分查找

就是得到权重的累加和[1,3,6,10] ,然后愉快的来一发int(rand(0,1) *10 ) ,再通过二分查找得到元素下标即可。 很显然,因为要求和,所以复杂度也是O(n),如果序列稳定,求好了和的话就是O(log ~n).

调包法

在python标准库中提供了random.choices 这个抽样方法,是可以传入权重的,于是你十分愉快的拿起键盘一顿敲:

random.choices(population=[a,b,c,d], weights=[1,2,3,4], k=1)

十分简单的就实现了,仔细分析一下这个的实现,其实和上面的累加比例法是一样的。 当你传入累加权重 cum_weights, 时间复杂度是O(log~n), 不传的话因为要算,复杂度也为O(n).
同样不适用于动态序列的情况。

高斯分布近似法

实际上就是通过一个均值为0的高斯分布去近似这个排好序的序列.

高斯拟合

然后通过高斯分布来采样... 这样首先效率低O(n), 而且不是精确基于分布的采样,另外如果加了元素发生变化还得调整\sigma ,很是麻烦...所以肯定不用这个....

数据科学家版

np.random.choice

可能因为numpy用的太娴熟了,很多数据工作在涉及到抽样,上来就是np.random.choice ,但是这玩意的时间复杂度更上面random的方法一样是O(n), 在供选择的集合很大时同样会有效率低下的问题...
以效率著称的numpy 居然效率不行???孤为之奈何....

那么有什么方法可以在动态序列上获得O(log~n) 的效果呢?

sum tree 算法

SumTree是一种特殊的二叉树,其中父节点的值等于其子节点的值之和,如下图所示,根节点的值是所有叶子的值的和:10 = 3+7,3 = 1+2,以此类推......


SumTree

叶子节点代表元素,其上的值是元素的权重。也就是说,我们只要构造这样一棵树,就可以愉快的进行采样了。随便来一发rand(0,1)*10, 比如说是2,我们从根节点的左孩子还是查找,
发现3比2大,于是往左边查,然后发现比3的左还是1大,就往3的左孩子找,就找到了2这个节点,对应的就是b 这个元素。 在构建好这棵树后,显然,我们每次更新元素的内容,只需要更新这个节点的父节点,也就是只要修改log~n 个节点,查找过程和动态修改过程都是log~n , 于是我们终于找到了适合动态变化的集合的加权采样方法...

附上Paper原作者的SumTree实现:

import numpy

class SumTree:
    write = 0

    def __init__(self, capacity):
        self.capacity = capacity
        self.tree = numpy.zeros( 2*capacity - 1 )
        self.data = numpy.zeros( capacity, dtype=object )

    def _propagate(self, idx, change):
        parent = (idx - 1) // 2

        self.tree[parent] += change

        if parent != 0:
            self._propagate(parent, change)

    def _retrieve(self, idx, s):
        left = 2 * idx + 1
        right = left + 1

        if left >= len(self.tree):
            return idx

        if s <= self.tree[left]:
            return self._retrieve(left, s)
        else:
            return self._retrieve(right, s-self.tree[left])

    def total(self):
        return self.tree[0]

    def add(self, p, data):
        idx = self.write + self.capacity - 1

        self.data[self.write] = data
        self.update(idx, p)

        self.write += 1
        if self.write >= self.capacity:
            self.write = 0

    def update(self, idx, p):
        change = p - self.tree[idx]

        self.tree[idx] = p
        self._propagate(idx, change)

    def get(self, s):
        idx = self._retrieve(0, s)
        dataIdx = idx - self.capacity + 1

        return (idx, self.tree[idx], self.data[dataIdx])
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容