浅谈流处理算法 (1) – 蓄水池采样

2,544 阅读7分钟

浅谈流处理算法 (1) – 蓄水池采样

现如今,“大数据 ”已经不是什么新概念,“一千个人眼中有一千个大数据”。社交网络,智能穿戴设备,智能家居,传感器,机器人等每一个热门的词汇背后都是大量的数据。抛开各种噱头和概念,相信每个人都能看到数据的价值,且能感受到数据规模的爆炸式增长。大规模的数据本身并不产生什么价值,只有通过理解数据,发现知识,避免“Garbage In Garbage Out” 才能发挥数据的价值。

在形式多样的大数据问题中,有一类问题输入以数据流的方式提供。我们需要在有限空间内通过若干遍访问数据流(很多时候可能仅仅允许访问一遍)完成数据处理并提取有价值的信息。我们称处理这类问题的算法为“流处理算法”(Streaming Algorithm)

Wikipedia定义:

In computer science, streaming algorithms are algorithms for processing data streams in which the input is presented as a sequence of items and can be examined in only a few passes (typically just one). These algorithms have limited memory available to them (much less than the input size) and also limited processing time per item.

一些典型的例子:

  • 根据用户访问日志,统计用户来源分布?

  • 根据用户访问日志,统计新增用户数目?

  • 根据用户访问日志,统计Unique User数目?

  • 根据用户访问日志,统计某个页面访问次数?

  • ......

这些问题在数据规模并不大且事先确定的情况下,采取直接的统计方案并不难实现。但,在数据流场景下,直接的方案并不能满足实际需求。

数据流问题有以下特点:

  • 数据规模大

  • 存储空间有限。保存完整的数据集合并不现实。

  • 分发速度快。如果处理不及时,没有足够资源保存数据。

  • 允许数据遍历极少次数,一般仅支持遍历一次。

  • 空间复杂度sub-linear

  • 时间复杂度linear

  • 可以接受近似解


曾几何时,拜摩尔定律和硬件技术所赐,软件开发脱离在64k内存中扣扣索索的日子,进入没事开个大数组的印度模式时代(道听途说,如有雷同,概不负责)。软件体量一个比一个吓人。然而,面对上述特点的数据流问题时,我们再一次深刻地体会到日新月异的计算机技术背后总有一个背景音 - “New Wine In Old Bottles”。

  • 数据库大牛Jim Gray多年前就指示我们:Tape is Dead, Disk is Tape, Flash is Disk, RAM locality is King.

  • TalkingData的Bitmap引擎这不又在扣扣索索了嘛

这个小系列,就是跟大家一起品尝一下“新酒”, 以酒会友:-)

言归正传,针对上述数据流问题特点,流处理算法的一般模式有:

  • 采样/过滤 – 减少规模

    • 如:蓄水池采样 Reservoir Sampling

  • 随机映射(Random Projection) – 降维

  • 统计直方图(Histogram)

  • 滑动窗口

这些算法模式都是希望通过样本采样或者特征采样,使用有限数据保持原整体数据的某些特性。

后续我们计划聊聊蓄水池采样, 存在性查询,基数估计,频率估计,频繁项估计等问题的流处理方法。

蓄水池采样

蓄水池采样是一类采样算法的总称,广泛使用在从数据流中按一定需求抽样出部分样本的场景中。蓄水池,可以看作小段缓存,协助存储数据流的部分信息。

下面我们从三个具体例子看看什么是蓄水池采样。

问题1. 年度大奖将从参加年会的同事中随机选择,如何维护一个获奖者名字,不管何时老大宣布获奖者,所有参会者获奖概率一致?

假如老大宣布获奖者时,参会同事人数为N,我们需要保证每个参会者获奖概率为1/N。

蓄水池采样算法是这样的:

  • 记录第一个到达的同学到获奖者名单上 (早起的鸟儿有虫吃 :-) )

  • 当第位同学到达年会会场的时候():

    • 的概率使用第位同学替换获奖者名单上的同学

    • 的概率保持获奖者名单不变 (Yeah!守擂成功!)

看起来很简单,我们来看看是否能满足问题的需求

  • 第一个同学P_1到达时,获奖者必定是P_1。(获奖概率 1/1 = 100%,满足)

  • 第二个同学P_2到达时,1/2概率获奖者替换为P_2, 也1/2概率保持为P_1 (满足)

  • 当第位同学到达时,1/的概率获奖者替换为P_。而的概率保持获奖者不变,也就是P_1, P_2, ..., P_(-1)获奖的概率为 1/(-1) * () = 1/ (满足)

  • 综上所述,根据归纳法,蓄水池采样搞掂了。

问题2. 项目进展很好,老大很高兴,决定年度大奖有k名,如何维护一个k个获奖者名单,保证不管何时老大宣布获奖者,所有参会者获奖概率一致?

假如老大宣布获奖者时,参会同事人数为N,我们需要保证每个参会者获奖概率为k/N。

蓄水池采样算法是这样的:

  • 记录前k位到达的同学到获奖者名单上 (早起总没错的)

  • 当第位同学到达年会会场的时候(>k):

    • 以k/的概率使用第位同学替换获奖者名单上的随机一位同学(某位同学杯具了)

    • 以1- k/的概率保持获奖者名单不变 (集体守擂成功!)

看起来跟上一个问题差不多,应该是正确的吧

  • 前k位同学到达时,获奖者必定是P_1, P_2, ..., P_k。(获奖概率 k/k = 100%,满足)

  • 当第位同学到达时( > k)

    • [Condition 1] k/概率获奖者替换为P_ (满足)

    • [Condition 2] 1 - k/的概率保持获奖者名单不变

    • 对于P_x (x < ), 在第位同学尚未到达时获奖概率为k/(-1)。她能保留在获奖名单中的概率由两部分组成 k/(-1) * [k/ * (1 - 1/k) + 1 - k/] = k/(满足)

      • [Condition 1]没有被P_踢掉 k/ * (1 - 1/k)

      • [Condition 2]P_直接出局了1 - k/

  • 综上所述,根据归纳法,蓄水池采样又搞掂了。

问题3. 为了激励绩效好的同学,每个参会同学都有一个权重,如何维护一个k个获奖者名单,保证不管何时老大宣布获奖者,所有参会者获奖概率与权重成比例?

假如老大宣布获奖者时,参会同事人数为N,我们需要保证参会者(P_, 绩效权重W_)获奖概率为W_/(W_1 + W_2 + ... + W_N)。

蓄水池采样算法是这样的:

  • 记录前k位到达的同学到获奖者名单上 (嗯,早起永远没错的)

    • 每位同学的分值S_ = random(0, 1) ^ (1/W_)

  • 当第位同学到达年会会场的时候(>k):

    • 计算第位同学的分值 S_ = random(0, 1) ^ (1/W_)

    • 如果获奖者名单中分值最小的同学P_x的分值S_x比S_小,P_x被S_替换了

    • 否则,获奖者名单不变 (集体守擂成功!)

怎么证明这样可以满足要求呢?额,“这里空白太小,我写不下了” (自己看论文去吧)

年会一年也就一次,很快来了又走了。各种各样的数据流每天都在产生,这些蓄水池采样方法可以帮忙我们获得数据流的一个样本。对于小规模样本可以保持大规模数据信息的场景,蓄水池采样帮忙我们把“大数据”的“大”给去掉了。

参考文献

1. https://en.wikipedia.org/wiki/Streaming_algorithm

2. https://en.wikipedia.org/wiki/Reservoir_sampling

3. Vitter, Jeffrey S. (1 March 1985). "Random sampling with a reservoir"

4. Efraimidis, Pavlos S.; Spirakis, Paul G. (2006-03-16). "Weighted random sampling with a reservoir"