转自http://www.cnblogs.com/zhangchaoyang/articles/2198946.html
(格式复制之后有变化,建议直接点链接去博客园看原文)
python代码见https://github.com/yantijin/Lean_DataMining
FP-Tree算法的实现
在关联规则挖掘领域最经典的算法法是Apriori,其致命的缺点是需要多次扫描事务数据库。于是人们提出了各种裁剪(prune)数据集的方法以减少I/O开支,韩嘉炜老师的FP-Tree算法就是其中非常高效的一种。
名词约定
举个例子,设事务数据库为:
A E F G A F G A B E F G E F G
每一行为一个事务,事务由若干个互不相同的项目构成,任意几个项目的组合称为一个模式。
上例中一共有4个事务。
模式{A,F,G}的支持数为3,支持度为3/4。支持数大于阈值minSuport的模式称为频繁模式(Frequent Patten)。
{F,G}的支持度数为4,支持度为4/4。
{A}的支持度数为3,支持度为3/4。
{F,G}=>{A}的置信度为:{A,F,G}的支持度数 除以 {F,G}的支持度数,即3/4
{A}=>{F,G}的置信度为:{A,F,G}的支持度数 除以 {A}的支持度数,即3/3
强关联规则挖掘是在满足一定支持度的情况下寻找置信度达到阈值的所有模式。
FP-Tree算法描述
算法描述:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
算法的核心是FPGrowth函数,这是一个递归函数。CPB的全称是Conditional Pattern Base(条件模式基),我们可以把CPB理解为算法在不同阶段的事务集合。PostModel称为后缀模式,它是一个List。后文会详细讲CPB和PostModel是如何生成的,初始时令PostModel为空,令CPB就是原始的事务集合。
下面我们举个例子来详细讲解FPGrowth函数的完整实现。
事务数据库如下,一行表示一条购物记录:
牛奶,鸡蛋,面包,薯片 鸡蛋,爆米花,薯片,啤酒 鸡蛋,面包,薯片 牛奶,鸡蛋,面包,爆米花,薯片,啤酒 牛奶,面包,啤酒 鸡蛋,面包,啤酒 牛奶,面包,薯片 牛奶,鸡蛋,面包,黄油,薯片 牛奶,鸡蛋,黄油,薯片
令minSuport=3,统计每一个项目出现的次数,把次数低于minSuport的项目删除掉,剩下的项目按出现的次数降序排列,得到F1:
薯片:7 鸡蛋:7 面包:7 牛奶:6 啤酒:4
对于每一条事务,按照F1中的顺序重新排序,不在F1中的被删除掉。这样整个事务集合变为:
薯片,鸡蛋,面包,牛奶 薯片,鸡蛋,啤酒 薯片,鸡蛋,面包 薯片,鸡蛋,面包,牛奶,啤酒 面包,牛奶,啤酒 鸡蛋,面包,啤酒 薯片,面包,牛奶 薯片,鸡蛋,面包,牛奶 薯片,鸡蛋,牛奶
上面的事务集合即为当前的CPB,当前的PostModel依然为空。由CPB构建FP-Tree的步骤如下。
插入第一条事务(薯片,鸡蛋,面包,牛奶)之后
插入第二条事务(薯片,鸡蛋,啤酒)
插入第三条记录(面包,牛奶,啤酒)
估计你也知道怎么插了,最终生成的FP-Tree是:
上图中左边的那一叫做表头项,树中相同名称的节点要链接起来,链表的第一个元素就是表头项里的元素。不论是表头项节点还是FP-Tree中有节点,它们至少有2个属性:name和count。
现在我们已进行完算法描述的第10行。go on
遍历表头项中的每一项,我们拿“牛奶:6”为例。
新的PostModel为“表头项+老的PostModel”,现在由于老的PostModel还是空list,所以新的PostModel为:[牛奶]。新的PostModel就是一条频繁模式,它的支持数即为表头项的count:6,所以此处可以输出一条频繁模式<[牛奶], 6>
从表头项“牛奶”开始,找到FP-Tree中所有的“牛奶”节点,然后找到从树的根节点到“牛奶”节点的路径。得到4条路径:
薯片:7,鸡蛋:6,牛奶:1 薯片:7,鸡蛋:6,面包:4,牛奶:3 薯片:7,面包:1,牛奶:1 面包:1,牛奶:1
对于每一条路径上的节点,其count都设置为牛奶的count
薯片:1,鸡蛋:1,牛奶:1 薯片:3,鸡蛋:3,面包:3,牛奶:3 薯片:1,面包:1,牛奶:1 面包:1,牛奶:1
因为每一项末尾都是牛奶,可以把牛奶去掉,得到新的CPB:
薯片:1,鸡蛋:1 薯片:3,鸡蛋:3,面包:3 薯片:1,面包:1 面包:1
然后递归调用FPGrowth(新的CPB,新的PostModel),当发现新有CPB为空时递归就可以退出了。
几点说明
- 可以在构建FP-Tree之前就把CPB中低于minSuport的项目删掉,也可以先不删,而是在构建FP-Tree的过程当中如果遇到低于minSuport的项目不把它插入到FP-Tree中就可以了。FP-Tree算法之所以高效,就是因为它在每次FPGrowth递归时都对数据进行了这种裁剪。
- 没必要每次FPGrowth递归时都把CPB中的事务按F1做一次重排序,只需要第一次构建CPB时按F1做一次排序,以后每次构建新的CPB时保持与老的CPB各项目顺序不变就可以了。
- 对于FP-Tree已经是单枝的情况,就没有必要再递归调用FPGrowth了,直接输出整条路径上所有节点的各种组合+postModel就可了。例如当FP-Tree为:
树上只有一条路径{A-B-C},在保证A-B-C这种顺序的前提下,这三个节点的所有组合是:A,B,C,AB,AC,BC,ABC。每一种组合与postModel拼接形成一条频繁模式,模式的支持数即为表头项的计数(单枝的情况下所有表头项和所有树节点的计数都是相同的)。
Java实现
StrongAssociationRule.java
View Code
TreeNode.java
View Code
FPTree.java
View Code
输入trolley.txt
牛奶,鸡蛋,面包,薯片
鸡蛋,爆米花,薯片,啤酒
鸡蛋,面包,薯片
牛奶,鸡蛋,面包,爆米花,薯片,啤酒
牛奶,面包,啤酒
鸡蛋,面包,啤酒
牛奶,面包,薯片
牛奶,鸡蛋,面包,黄油,薯片
牛奶,鸡蛋,黄油,薯片
输出pattens.txt
模式 频数 面包,啤酒 3 鸡蛋,牛奶 4 面包,薯片 5 薯片,鸡蛋 6 啤酒 4 薯片 7 面包,薯片,鸡蛋,牛奶 3 鸡蛋,啤酒 3 面包,牛奶 5 薯片,鸡蛋,牛奶 4 面包,鸡蛋,牛奶 3 面包 7 牛奶 6 面包,薯片,鸡蛋 4 薯片,牛奶 5 鸡蛋 7 面包,鸡蛋 5 面包,薯片,牛奶 4
输出rule.txt
条件 结果 支持度 置信度
[啤酒]->面包 3 0.75
[牛奶]->鸡蛋 4 0.67
[薯片]->面包 5 0.71
[薯片]->鸡蛋 6 0.86
[薯片, 鸡蛋, 牛奶]->面包 3 0.75
[面包, 薯片, 牛奶]->鸡蛋 3 0.75
[啤酒]->鸡蛋 3 0.75
[牛奶]->面包 5 0.83
[薯片, 牛奶]->鸡蛋 4 0.8
[鸡蛋, 牛奶]->面包 3 0.75
[面包, 牛奶]->鸡蛋 3 0.6
[薯片, 鸡蛋]->面包 4 0.67
[面包, 薯片]->鸡蛋 4 0.8
[鸡蛋]->面包 5 0.71
[面包]->鸡蛋 5 0.71
[薯片, 牛奶]->面包 4 0.8
MapReduce实现
在上面的代码我们把整个事务数据库放在一个List<List<String>>里面传给FPGrowth,在实际中这是不可取的,因为内存不可能容下整个事务数据库,我们可能需要从关系关系数据库中一条一条地读入来建立FP-Tree。但无论如何 FP-Tree是肯定需要放在内存中的,但内存如果容不下怎么办?另外FPGrowth仍然是非常耗时的,你想提高速度怎么办?解决办法:分而治之,并行计算。
按照论文《FP-Growth 算法MapReduce 化研究》中介绍的方法,把以相同项目结尾的patten输出一个Reducer里面去,在Reducer中仅对这一部分patten建立FPTree,这种FPTree会小很多,一般不会占用太多的内存。另外论文中的方法不需要维护表头项。
结束语
在实践中,关联规则挖掘可能并不像人们期望的那么有用。一方面是因为支持度置信度框架会产生过多的规则,并不是每一个规则都是有用的。另一方面大部分的关联规则并不像“啤酒与尿布”这种经典故事这么普遍。关联规则分析是需要技巧的,有时需要用更严格的统计学知识来控制规则的增殖