转载自:http://blog.sina.com.cn/s/blog_68ffc7a40100uebk.html
Sharding
这一步没什么好讲的,将数据库分成连续的大小相等的几个块,放置在不同的机器上。以Hadoop来讲,其框架本身就将整个数据库放在不同的机器上,形成不同的分区,因此在Hadoop上我们本身都不需要做什么。
F_list计算
这一步来讲也没什么好讲的,就是一个简单的频率统计,这是MapReduce最简单的一种应用。下面给出伪码,读者自己分析一下很容易看明白。
这一步难度也不大。将F_list分成几个组而已。从这一步开始,为了更好得阐述算法的详细步骤,我们举个例子来说明问题。后边的所有步骤的举例都是基于这个例子的。
还是韩老师的论文中的这个例子,假设我们在两台机器上对这个数据库进行并行化的FP Grwoth。那么经过第二步,我们得到了F_list如下:
F_list = {(f:4), (c:4), (a:3), (b:3), (m:3), (p:3)}
那么在这一步中,我们把它分成2个组,形成了一个G_list:
G_list={ {group1: (f:4), (c:4), (a:3) }, {group2: (b:3), (m:3), (p:3} )
并行化FPGrowth
直接上算法伪码。
结合例子我们来分析一下这个例子。先看maper,伪码中的G_List就是上文中提到的G_list:
G_list={ {group1: (f:4), (c:4), (a:3) }, {group2: (b:3), (m:3), (p:3} )
哈希表H则是这样的一个形式:
Key | Val |
f | group1 |
c | group1 |
a | group1 |
b | group2 |
m | group2 |
p | group2 |
HashNum就是哈希表中的value,其实就是group id。
a[]就是每一条transaction拆分成的一个一个item形成的数组。以例子来看,就有
T1(TID=100)
a[] = { f, c, a, m, p}
T2 (TID=200)
a[] = {f,c,a,b,m}
T3 (TID=300)
a[] = {f,b}
T4 (TID=400)
a[] = {c,b,p}
T5 (TID=500)
a[]={f,c,a,m,p}
假设T1,T2,T3是一个shard,T4,T5是一个shard.那么mapper的过程实际上就是这么一个过程:
查看Transaction T里的所有item分别属于哪些组,然后将T发送给相应的组。比如T1,它遍历所有元素,先看到p,根据哈希表,它属于group2,那么他就输出<group2, T1>。并将哈希表中所有val=group2的项全部删掉。那么哈希表就变成了:
Key | Val |
f | group1 |
c | group1 |
a | group1 |
接着看到m,再查哈希表,发现m的项没有了,也就是说T1在前面的遍历步骤中已经被发送到了m所在的group,因此我们没有必要再把这条记录重新再发送一遍(m本来对应的是group2,但在前面p的处理中,T1已经发送到group2了,在这一步中就不再重复发送T1到group2了),因此啥也不干,返回。继续遍历。
接着再遍历看b,哈希表中也没有,同m一样。
接下来看a,通过哈希表得知它属于group1,那么就将T1发送给group1,然后将哈希表中val=group1的所有项全部删除,也就是通知后边的步骤,T1已经发送过给group1了,后边的步骤不要再发送给group1。经过这一步,哈希表已经为空了,也就是说T1所包含的item所在的group全部已经处理完毕了。
接下来c,f的处理什么都不需要做。
根据上面详细而罗嗦的分析,其实可以看到,这一步mapper的目的很简单,把在自己机器上的所有transaction发送到合适的group上去,发送的原则就是transaction所包含的item属于哪些group,发送的目标就是这些group。通过对哈希表条目的删除,确保一条Transaction不会重复发送多次到某一group上。
其实细心的读者这时候可能会问博主了?不对,博主骗人。算法并不是把Transaction整条记录发送给不同的group,而是不同的group发送Transaction的不同的部分。因为算法伪码maper的output明显是:
Ouput<HashNum,a[0]+…+a[j]>=output<group_id, a[0]+…+a[j]>
以上面的例子来讲,T1发送给group2的item序列是
{f, c, a, b, m, p}
而发送给group1的item序列是
{f,c,a}
为什么会这样呢?这样group1的数据舍弃了一部分不是不完整了么?这样不会导致挖掘的结果不完全么?
其实如果你注意到所有的transaction里的item都是按照F_list的频率次数从高到低排列的话,你可能就明白我们为什么可以做这样的一个优化了。还是接着前面的例子,我们把{f, c, a}发送到group1的机器上,我们的目的其实就是希望能在group1的机器上,挖掘出所有包含group1的item的frequent pattern,这些frequent pattern包含一个或多个group1里的item。我们舍弃的这一部分数据都是频率比自己小的item.那么我们来看我们的group分布情况:
group1: f, c,a | group2: b, m, p
group1和group2的频率分界线是a的出现频率,在本例中分界点是3,也就是说group1里所有的item出现的频率都大于等于3,group2所有item出现的频率都小于或等于3。
通过group2的FP挖掘,我们可以得到所有包含{b,m,p}集合子集的所有item序列,比如{b:3, bm:2, pm:2, fb:3….}等等。很明显,这个序列里元素的频率都不会超过3,因为经过第二步F_list的统计, {b,m,p}出现的次数不会大于3。
同样的,group1挖掘出得到包含group1里元素集合子集的所有item序列,比如{f:4,fc:3,ca:3…},这个序列里元素的频率都不会小于3。在group1做挖掘时我们会有这样的担心:结果舍弃了包含{b,m,p}的item组合,那么如果存在类似有fb,fm,fp这样的频繁模式组合,会不会也被我们舍弃掉了?答案是不会。
如果我们设定support阈值是一个小于3的数,比如是2。那么在group2的挖掘中,就已经把fb,fm,fp这样的结果包含进去了。Group1舍弃的数据并不影响。
如果support阈值等于3,那么fb,fm,fp这样的组合的频率肯定是小于3的(因为b,m,p频率小于3),当然应该被舍掉。所以也不会造成损失。
如果阈值大于3,那么更不会造成损失了,原因也是因为fb,fm,fp这样的组合的频率肯定是小于3的(因为b,m,p频率小于3)。
所以,大家应该理解了为什么mapper做output分组时可以舍去一些数据了吧,根本原因是所有的transaction都必须按照统一的一定的顺序排列。上述的例子中分组正好是按照频率的顺序来分的,其实是个特例,便于说明而已,也可以用其他的顺序。分组其实也不需要一定把频率大的item分成一个group,频率低的item分成一个group,可以以任何策略来分组。本质上,为什么我们可以舍去一些数据的原因就是groupA^groupB的集合已经包含在先分发到groupA的transaction里了,所以groupB里不需要再包含groupA^groupB的数据。这个推论有个前提,就是所有transaction里的item排列都必须有一个唯一的一致的顺序(按什么排序都可以随意,可以按字母顺序,可以按频率高低),这个顺序的意义在于,所有的transaction都是按照一样的先后顺序来进行截断从而发送到不同的group,如下图:
I1 – grp1 , I2 –grp2, I3-grp1,…In-grp1
再看伪码中的Reducer:
每台机器上Reducer的输入就是对应某一个group_id的transaction集合。首先是获得G_list,和前面一样。nowGroup是被分配在这台机器上的所有item的集合,用上面的例子来讲,如果是在group1的机器上,那么就有nowGroup={f,c,a}.
接下来就是生成本地的FP树LocalFPTree,也很好理解。接下来,reducer针对nowGroup里的每一个item定义了一个size为K的堆HP,用来存储包含该item的frequent pattern,这个frequent pattern就是用经典的FP Growth算法挖掘出来的。(还记得堆这个数据结构吧,完全二叉树,任何一个节点都大于或小于它的子节点。这里堆存的数据是包含了指定item的按频率排的top K个frequent pattern)。最后将HP的数据输出。
实际上到了这一步,结果就已经出来了。我们每台机器上针对每个nowGroup里的item得到了top K个frequent pattern,每个frequent pattern都包含这个item。根据前面分析,我们也知道,由于所有处理的transaction都是排过序的,所以各台机器上生成结果不会有重叠覆盖的情况。按理说到这一步,就可以了。但是为了使最终的结果更好,可以再加一步处理过程:结果聚合
[1]PFP: ParallelFP-Growth for Query Recommendation
结果聚合
请看伪码如下:
这个mapreduce实际上完成了一个index的功能,把上一步的结果进行了一个处理,它把上一步得到的frequent pattern按照item做了索引,这要得到的最后结果就是某一个item对应着一组frequent pattern。它把这些frequent pattern放在一个堆里,便于按频率的高低顺序进行访问。伪码中的if-else其实就是把frequent pattern插入堆,如果堆满了,和频率最小的那个节点(也就是根节点)比较一下,如果新节点的值大的话,删掉根节点,插入新节点。这样做的目的是始终保持堆里存储着某一item频率最高的top K个frequent pattern。
这就是map reduce框架下FP Growth算法的具体实现。在apache的开源项目mathout中它已经得到了实现,大家可以直接使用。
最后再谈一点个人意见,这个FP Growth Mapreduce实现在对F_list进行分组时可以再考虑一些负载均衡的策略,因为不采用任何策略的话,有可能会导致频率高的item都