首页 后端开发 Python教程 用Python制作简单的朴素基数估计器的教程

用Python制作简单的朴素基数估计器的教程

Jun 10, 2016 pm 03:16 PM
python

假设你有一个很大的数据集,非常非常大,以至于不能全部存入内存。这个数据集中有重复的数据,你想找出有多少重复的数据,但数据并没有排序,由于数据量太大所以排序是不切实际的。你如何来估计数据集中含有多少无重复的数据呢?这在许多应用中是很有用的,比如数据库中的计划查询:最好的查询计划不仅仅取决于总共有多少数据,它也取决于它含有多少无重复的数据。

在你继续读下去之前,我会引导你思考很多,因为今天我们要讨论的算法虽然很简单,但极具创意,它不是这么容易就能想出来的。
一个简单的朴素基数估计器

让我们从一个简单的例子开始吧。假定某人以下列方式来生成数据:

  •     生成 n 个充分分散的随机数
  •     任意地从中选择一些数字,使其重复某次
  •     打乱这些数字

我们怎么估计结果数据集中有多少非重复的数字呢?了解到原来的数据集是随机数,且充分分散,一个非常简单的方法是:找出最小的数字。如果最大的可能的数值是 m,最小的值是 x,我们 可以估计大概有 m/x 个非重复的数字在数据集里面。举个例子,如果我们扫描一个数字在 0 到 1 之间的数据集,发现最小的数字是 0.01。我们有理由猜想可能数据集里大概有 100 个非重复的数字。如果我们找到一个更小的最小值的话,可能包含的数据个数可能就更多了。请注意不管每个数字重复了多少次都没关系,这是很自然的,因为重复多少次并不会影响?min?的输出值.

这个过程的优点是非常直观,但同时它也很不精确。不难举出一个反例:一个只包含少数几个非重复数字的数据集里面有一个很小的数。同样的一个含有许多非重复数字的数据集含有一个比我们想像中更大的最小值,用这种估计方法也会很不精确。最后,很少有数据充分分散充分随机的数据集。但是这个算法原型给了我们一些灵感使得我们有可能达到我们的目的,我们需要更精致一些的算法.
基于概率的计数

第一处改进来来自 Flajolet 和 Martin 的论文 Probabilistic Counting Algorithms for Data Base Applications。 进一步的改进来自 Durand-Flajolet 的论文 LogLog counting of large cardinalities 和 Flajolet et al 的论文 HyperLogLog:The analysis of a near-optimal cardinality estimation algorithm。从一篇论文到另一篇论文来观察想法的产生和改进很有趣,但我的方法稍有不同,我会演示如何从头开始构建并改善一个解决方法,省略了一些原始论文中的算法。有兴趣的读者可以读一下那三篇论文,论文里面包含了大量的数学知识,我这里不会详细探讨.

首先,Flajolet 和 Martin 发现对于任意数据集,我们总可以给出一个好的哈希函数,使得哈希后的数据集可以是我们需要的任意一种排列。甚至充分分散的(伪)随机数也是如此。通过这个简单的灵感,我们可以把我们之前产生的数据集转化为我们想要的数据集,但是这远远还不够.

接下来,他们发现存在更好的估计非重复数个数的方法。部分方法比记录最小的哈希值表现得更好。Flajolet 和 Martin 用的估计方法是计算哈希后的值的首部的 0 字的个数。显然在一个随机的数据集中,平均每 2^k 个元素就出现一个长度为 k 的全为 0 的比特序列。我们要做的就是找出这些序列并记录最长的来估计非重复元素的个数。然而这仍然不是一个很棒的估计器。它最多只能给我们一个 2 的幂的数量的估计。而且不像基于最小值的估计方法,这个方法的方差很大。但在另一个方面,我们的估计需要的空间非常小:为了记录最长 32 比特的前导 0 比特序列,我们只需要一个 5 比特的数字就可以了.

附注:Flajolet-Martin 原先的论文在这里继续讨论了一种基于 bitmap 的过程来获得一个更精确的估计。我不会讨论这个细节因为它马上就会在随后的方法中得到改进。更多细节对于有兴趣的读者可以阅读原论文。

现在我们得到了一个确实比较糟糕的比特式估计方法。我们能做出一些什么改进呢?一个直接的想法是使用多个独立的哈希函数。如果每个哈希函数?输出它自己的随机数据集,我们可以记录最长的前导 0 比特序列。然后在最后我们就可以对其求一个平均值以得到一个更精确的估计。

从实验统计上来看这给了我们一个相当好的结果,但哈希的代价的是很高的。一个更好的方式是一个叫做随机平均的方法。相比使用多个哈希函数,我们仅仅使用一个哈希函数。但是把它的输出进行分割然后使用它的一部分作为桶序号来放到许多桶中一个桶里去。假设我们需要 1024 个值,我们可以使用哈希函数的前 10 个比特值作为桶的序号,然后使用剩下的哈希值来计算前导 0 比特序列。这个方法并不会损失精确度,但是节省了大量的哈希计算.

把我们目前学到的应用一下,这里有一个简单的实现。这和 Durand-Flajolet 的论文中的算法是等价的,为了实现方便和清晰所以我计算的是尾部的 0 比特序列。结果是完全等价的。
 

def trailing_zeroes(num):
 """Counts the number of trailing 0 bits in num."""
 if num == 0:
  return 32 # Assumes 32 bit integer inputs!
 p = 0
 while (num >> p) & 1 == 0:
  p += 1
 return p
 
def estimate_cardinality(values,k):
 """Estimates the number of unique elements in the input set values.
 
 Arguments:
  values:An iterator of hashable elements to estimate the cardinality of.
  k:The number of bits of hash to use as a bucket number; there will be 2**k buckets.
 """
 num_buckets = 2 ** k
 max_zeroes = [0] * num_buckets
 for value in values:
  h = hash(value)
  bucket = h & (num_buckets - 1) # Mask out the k least significant bits as bucket ID
  bucket_hash = h >> k
  max_zeroes[bucket] = max(max_zeroes[bucket],trailing_zeroes(bucket_hash))
 return 2 ** (float(sum(max_zeroes)) / num_buckets) * num_buckets * 0.79402
登录后复制

这很漂亮就像我们描述的一样:我们保持一个计算前导(或尾部)0个数的数组,然后在最后对个数求平均值,如果我们的平均值是 x,我们的估计就是 2^x 乘以桶的个数。前面没有说到 的是这个魔术数 0.79402。数据统计表明我们的程序存在一个可预测的偏差,它会给出一个比实际更大的估计值。这个在 Durand-Flajolet 的论文中导出的魔术常数是用来修正这个偏差的。实际上这个数字随着使用的桶的个数(最大2^64)而发生变化,但是对于更多数目的桶数,它会收敛到我们上面用到的算法的估计数字。大量更多的信息请看完整的论文,包括那个魔术数是怎么导出的。

这个程序给了我们一个非常好的估计,对于 m 个桶来说,平均错误率大概在 1.3/sqrt(m) 左右。所以1024个桶时(),我们大概会有 4% 的期望错误率。为了估计每篇最多 2^27 个数据的数据集每个桶仅需要 5 比特就够了。少于 1 kb 内存,这真的很赞(1024 * 5 = 5120,即 640 字节)!

让我们在一些随机的数据上测试一下它:

>>> [100000/estimate_cardinality([random.random() for i in range(100000)],10) for j in range(10)]
[0.9825616152548807,0.9905752876839672,0.979241749110407,1.050662616357679,0.937090578752079,0.9878968276629505,0.9812323203117748,1.0456960262467019,0.9415413413873975,0.9608567203911741]
登录后复制

结果不坏,一些估计超过 4% 的预期偏差,但总而言之结果都很好。如果你自己再尝试一遍这个实验,请注意:Python 内建的 hash() 函数将整数哈希为它们本身。导致运行像 estimate_cardinality(range(10000),10) 这样的会给出偏差很大的结果,因为此时的 hash() 不是一个好的哈希函数。当然使用上述例子中的随机数是没有问题的.
改进准确度:SuperLogLog 和 HyperLogLog

虽然我们已经得到了一个非常好的估计,但它有可能做到更好。Durand 和 Flajolet 发现极端数值会很大地影响估计结果的准确度。通过在求平均前舍弃一些最大值,准确度可以得到提高。特别地,舍弃前 30% 大的桶,仅仅计算 70% 的桶的平均值,精确度可以用 1.30/sqrt(m) 提高到 1.05/sqrt(m)! 这意味着在我们之前的例子中,用 640 字节的状态,平均错误率从 4% 变成了大约 3.2%。但并没增加空间的使用.

最后,Flajolet et al 的论文的贡献就是使用了一个不同类型的平均数。使用调和平均数而不是几何平均数。通过这么做,我们可以把错误率降到 1.04/sqrt(m),同样不增加需要的空间。当然完整的算法要更复杂一点,因为它必须修正小的和大的基数误差。有兴趣的读者应该,可能你已经猜到了,就是去阅读完整的论文.
并行化

这些方案所共有的整齐性使得它们很容易就能并行化。多台机器可以独立地运行同样的哈希函数同样数目的桶。我们在最后只需要把结果结合起来,取每个算法实例中每个桶最大的值就可以了。这不仅很好实现,因为我们最多只需要传输不到 1kb 的数据就可以了,而且和在单台机器上运行的结果是完全一模一样的.
总结

就像我们刚刚讨论过的基数排序算法,使得有可能得到一个非重复数字个数的很好的估计。通常只用不到 1kb 空间。我们可以不依赖数据的种类而使用它,并且可以分布式地在多台机器上工作,机器间的协调和数据的传输达到最小。结果估计数可以用来做许多事情,比如流量监控(多少个独立IP访问过?)和数据库查询优化(我们应该排序然后归并呢还是构造一个哈希表呢?)。

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

C语言 sum 的作用是什么? C语言 sum 的作用是什么? Apr 03, 2025 pm 02:21 PM

C语言中没有内置求和函数,需自行编写。可通过遍历数组并累加元素实现求和:循环版本:使用for循环和数组长度计算求和。指针版本:使用指针指向数组元素,通过自增指针遍历高效求和。动态分配数组版本:动态分配数组并自行管理内存,确保释放已分配内存以防止内存泄漏。

谁得到更多的Python或JavaScript? 谁得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

distinctIdistinguish有关系吗 distinctIdistinguish有关系吗 Apr 03, 2025 pm 10:30 PM

distinct 和 distinguish 虽都与区分有关,但用法不同:distinct(形容词)描述事物本身的独特性,用于强调事物之间的差异;distinguish(动词)表示区分行为或能力,用于描述辨别过程。在编程中,distinct 常用于表示集合中元素的唯一性,如去重操作;distinguish 则体现在算法或函数的设计中,如区分奇数和偶数。优化时,distinct 操作应选择合适的算法和数据结构,而 distinguish 操作应优化区分逻辑效率,并注意编写清晰可读的代码。

如何理解 C 语言中的 !x? 如何理解 C 语言中的 !x? Apr 03, 2025 pm 02:33 PM

!x 的理解!x 是 C 语言中的逻辑非运算符,对 x 的值进行布尔取反,即真变假,假变真。但要注意,C 语言中真假由数值而非布尔类型表示,非零视为真,只有 0 才视为假。因此,!x 对负数的处理与正数相同,都视为真。

H5页面制作是否需要持续维护 H5页面制作是否需要持续维护 Apr 05, 2025 pm 11:27 PM

H5页面需要持续维护,这是因为代码漏洞、浏览器兼容性、性能优化、安全更新和用户体验提升等因素。有效维护的方法包括建立完善的测试体系、使用版本控制工具、定期监控页面性能、收集用户反馈和制定维护计划。

C语言中 sum 是什么意思? C语言中 sum 是什么意思? Apr 03, 2025 pm 02:36 PM

C语言中没有内置的sum函数用于求和,但可以通过以下方法实现:使用循环逐个累加元素;使用指针逐个访问并累加元素;对于大数据量,考虑并行计算。

如何获取58同城工作页面上的实时申请和浏览人数数据? 如何获取58同城工作页面上的实时申请和浏览人数数据? Apr 05, 2025 am 08:06 AM

如何在爬虫时获取58同城工作页面的动态数据?在使用爬虫工具爬取58同城的某个工作页面时,可能会遇到这样�...

爱心代码复制粘贴 爱心代码免费复制粘贴手机 爱心代码复制粘贴 爱心代码免费复制粘贴手机 Apr 04, 2025 am 06:48 AM

复制粘贴代码并非不可行,但需谨慎对待。代码中环境、库、版本等依赖项可能与当前项目不匹配,导致错误或不可预料的结果。务必确保上下文一致,包括文件路径、依赖库和 Python 版本。此外,复制粘贴特定库的代码时,可能需要安装该库及其依赖项。常见的错误包括路径错误、版本冲突和代码风格不一致。性能优化需根据代码原用途和约束重新设计或重构。理解并调试复制的代码至关重要,切勿盲目复制粘贴。

See all articles