数据挖掘之FP_Tree算法实现

转自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

输入:事务集合 List<List<String>> transactions

输出:频繁模式集合及相应的频数 Map<List<String>,Integer> FrequentPattens

 

初始化 PostModel=[],CPB=transactions

void FPGrowth(List<List<String>> CPB,List<String> PostModel){

    if CPB为空:

        return

         

    统计CPB中每一个项目的计数,把计数小于最小支持数minSuport的删除掉,对于CPB中的每一条事务按项目计数降序排列。

    由CPB构建FP-Tree,FP-Tree中包含了表头项headers,每一个header都指向了一个链表HeaderLinkList,链表中的每个元素都是FP-Tree上的一个节点,且节点名称与header.name相同。

    for header in headers:

        newPostModel=header.name+PostModel

        把<newPostModel, header.count>加到FrequentPattens中。

        newCPB=[]

        for TreeNode in HeaderLinkList:

            得到从FP-Tree的根节点到TreeNode的全路径path,把path作为一个事务添加到newCPB中,要重复添加TreeNode.count次。

        FPGrowth(newCPB,newPostModel)

算法的核心是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为空时递归就可以退出了。

几点说明

  1. 可以在构建FP-Tree之前就把CPB中低于minSuport的项目删掉,也可以先不删,而是在构建FP-Tree的过程当中如果遇到低于minSuport的项目不把它插入到FP-Tree中就可以了。FP-Tree算法之所以高效,就是因为它在每次FPGrowth递归时都对数据进行了这种裁剪。
  2. 没必要每次FPGrowth递归时都把CPB中的事务按F1做一次重排序,只需要第一次构建CPB时按F1做一次排序,以后每次构建新的CPB时保持与老的CPB各项目顺序不变就可以了。
  3. 对于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会小很多,一般不会占用太多的内存。另外论文中的方法不需要维护表头项。

结束语

在实践中,关联规则挖掘可能并不像人们期望的那么有用。一方面是因为支持度置信度框架会产生过多的规则,并不是每一个规则都是有用的。另一方面大部分的关联规则并不像“啤酒与尿布”这种经典故事这么普遍。关联规则分析是需要技巧的,有时需要用更严格的统计学知识来控制规则的增殖

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值