用于计算数百万对频率的算法和工具集

时间:2016-05-24 10:04:46

标签: python mysql algorithm numpy

更新26-May-16 :尝试了新算法。见底部。

我正在寻找算法和工具集的建议来获取项目对的频率计数。对于那些熟悉它的人来说,这类似于"市场篮子"问题("啤酒和尿布"型号),除了我需要发生的每一对的频率计数

我有大约500万条记录。每条记录是10到300个项目的列表。这些项目的整数范围从1到大约250,000。所以,例如:

1: [85708, 28302, 1045, 20395]
2: [20382, 3092, 2933, 20993, 58585, 4855, 112393, 38347, 20447, 33892]
3: [118082, 30282, 2859, 585, 1045, 20395, 2383, 85855, 182582, 223]

我想生成一个表来回答这个问题:

对于任何一对2项,它们在同一记录中出现了多少次?

例如,记录1生成对:(85708,28302),(85708,1045),(85708,20395),(28302,1045),(28302,20395)和(1045,20395)。我想计算整个数据集中每个对的频率。 [顺序并不重要]。

要了解它需要处理的大小:记录的平均长度为85项。对于该长度的记录,需要计算的是3655(= 86 * 85/2)对项目。对于那个长度的500万条记录,需要计算180亿对项目。在大多数运行中,记录的中位数长度远低于平均值(大多数记录包含< 18项,而少数记录包含更多),因此实际的对数可能不会达到180亿,但是它绝对可能是几十亿。

单个项目的频率分布遵循幂律,具有一些高频项目和许多低频项目;在最近一次大于正常尺寸的跑步中,最终有大约20亿个不同的物品对,其频率大于0。绝大多数潜在的对组合都不会发生;每次运行都不同,但我估计最多可能会出现15%的配对组合,并且在大多数情况下,会发生不到2%。

我有一个准确无误的程序,但速度很慢。我现在想要优化速度。它是使用Python和MySql的强力手段:

  • 在Python中,获取一批1,000条记录的项目。
  • 使用python' s itertools.combinations,逐个记录循环并生成每个记录所有项目的组合。
  • 将结果存储在sql db中。我在数据库中有一个包含3个字段的表:item1 (int), item2 (int), frequency (int), primary key (item1, item2)。对于我们计算的每一对项目组合,执行insert... on duplicate key update:即,如果该表中不存在该对,则插入频率为1的对。如果该对存在,则增加频率那对由1。
  • 为下一批1,000条记录重复循环。

处理需要大约15个小时。当我刚才写这篇文章时,时间并不重要,我只需要运行一次就可以获得永远不需要更新的静态结果。但现在输入记录将会发生变化,我需要进行优化,以便每天至少重新生成一次结果。结果需要采用一种形式,可以用于非常快速地查找项目对的频率;我认为像索引的数据库表。

我基本上改变了暴力程序,通过玩读写批次来提高效率;处理时间的大部分发生在"如果它不存在/增加对频率计数(如果它存在")则插入该对。我的小调整使处理时间减少了大约15%。

另一个调整是因为我已经拥有每个单项的频率,所以我可以试着预先播种"具有最可能频繁组合的数据库(例如,前5,000 x 5,000),然后在Python中将我发现的对组合根据其项目编号分成两组:"肯定在db"并且"不知道它是否在数据库中。"它可以为数据库节省一些时间,但代价是让Python需要跟踪频繁的项目并将它们分开....

所以我可以继续做这样的调整,并在这里和那里节省几个百分点,但是我想做正确的事情,现在用一个好的算法和好的工具重新编写程序,而不是浪费时间调整一个糟糕的过程,这个过程很快拼凑起来进行一次性使用,并且从未计划过效率。

它必须运行在用户的单一独立桌面(标准规格)上,无需外部存储或分布式计算。

理想情况下,我想从python运行该过程。 Numpy,scipy,blas / lapack都可以。 (我看了python&collections.counter每{{}}}一个相关的问题,但我认为我的大小太大了;告诉我这是不是错了{{1}可能是有效的)。

我的问题类似于Counter,它最初来自一家商店,该商店记录了顾客在一个购物篮中购买的物品(并得出了一个着名的结论,即购买尿布的人非常有可能购买啤酒) [感谢@lzcig链接到市场篮子问题的this answer]。市场篮子问题的策略过滤项目对下到最常见的对,并且不计算任何不适合主存储器的东西。但在我的情况下,我需要计算发生的每一对,即使它只发生一次。所以我需要一个算法和工具集来有效地存储和索引所有这些。我不想重新发明轮子,我真的很想找到一个能够有效处理这个问题的解决方案。

您认为什么是最佳解决方案?

更新(16-May-16): 我开发了一种解决方案,可以在2小时内精确计算出数十亿对的完整数据集。基本理念:

  • 利用幂律分布以及我已经计算出单项频率的事实。由前几千项组成的对代表了总对中的很大一部分。
  • 构建一维数组以保存最常见对的计数。 i x j 矩阵的一半值会被浪费,因为该对的顺序并不重要[(a,b)与(b)相同,a)],所以我可以通过将它们打包成单个 k -id来节省空间(将( i,j )转换为 k - i x j 矩阵的上三角形的索引。我根据单个项目的频率分布和可用内存动态调整数组的大小。我发现3,000 x 5,000(存储在一个10.5ml的数组中)效果很好。
  • 我使用原生Python数组构建了数组。与this good description类似,我发现在我正在进行的简单数组访问和增量计数器的情况下,原生Python比numpy需要更多的内存,但速度要快得多。
  • 处理每条记录。对于每对,如果项目位于最频繁的组中,则在数组中增加其id的计数器。如果没有,请将该对添加到低频对列表中。
  • 当内存变紧时,对低频对数组进行排序并将其写入新文件。
  • 在处理结束时,执行(许多)已排序文件的合并heapq以创建具有所有低频对的一个文件。通过它,获得每个唯一对的计数。最后,将高频阵列转换为对数值并将其与低频合并。结果是具有对频率的文件,按排序顺序。

这在很大程度上取决于系统内存的最大化。我一直在监视内存使用情况,试图尽可能多地获取内存。瓶颈是磁盘读/写:合并数百个巨大的文件比我认为的要强得多。因此,我已经开始使用减少文件数量的设置:合并一些大型文件比合并许多较小的文件要好。

在4GB的RAM上,处理最近一批拥有数十亿对的500万条记录需要2小时的时间。它肯定比我最初的15个小时好,但它感觉非常hacky,我确信必须有更好的方法来计算对。如果您有任何想法,请告诉我。

1 个答案:

答案 0 :(得分:1)

您可以打印出每条记录的所有不同元素对,然后利用任何Unix中可用的精心设计的sort命令将相同的对组合在一起,最后计算每个相同块中的行数uniq -c

perl -lne '($_) = /\[(.*)\]/ or die; @x = sort { $a <=> $b } split /, /; for ($i = 0; $i < @x - 1; ++$i) { for ($j = $i + 1; $j < @x; ++$j) { print "$x[$i] $x[$j]"; } }' | sort -g | uniq -c > outfile

这需要很长时间才能达到180亿行,但它应该比重复更新B * -tree更快,这是SQL数据库很可能在内部进行的。 (换句话说:如果更新B * -tree实际 比这更快,那么sort的所有实现也将在内部做到这一点。)你将不得不尝试它出来看看。

要查询此数据库&#34;,您只需二进制搜索outfile - 无需将整个内容加载到内存中。 (您可能希望首先将其转换为更紧凑的二进制格式,但这实际上并不是必需的 - 您仍然可以在纯文本文件上执行二进制搜索,只需始终向前读取,直到您点击{{1}在每次搜索之后。一旦你搜索的范围变得足够小,你可能想要将它完整地读入内存并继续在内存中进行二进制搜索。)

如果您不关心Perl,我确信您可以使用Python或任何其他语言编写第一部分代码。