FP_growth算法
本文基于机器学习实战这本书记录一下学习路程,就当是笔记了,我是机器实战的搬运工,各位大爷不喜勿喷~~~
1、Apriori与FP_growth的区别
Aprori算法利用频繁集的两个特性,过滤了很多无关的集合,效率提高不少,但是我们发现Apriori算法是一个候选消除算法,每一次消除都需要扫描一次所有数据记录,造成整个算法在面临大数据集时显得无能为力。今天我们介绍一个新的算法挖掘频繁项集,效率比Aprori算法高很多。
FpGrowth算法通过构造一个树结构来压缩数据记录,使得挖掘频繁项集只需要扫描两次数据记录,而且该算法不需要生成候选集合,所以效率会比较高
2、FP_growth优缺点
优点:一般快于Apriori算法
缺点:实现比较困难,在某些数据集上性能会下降
适用数据类型:标称型数据
3、构建FP树
3.1构建过程1
第一遍对所有元素项的出现次数进行计数,去掉不满足最小支持度的元素项
第二遍扫描中只需要考虑那些频繁元素,读入每个项集将其添加到一条已经存在的路径中,若该路径不存在则创建一条新路径。
事务ID | 事务中的元素 |
---|---|
001 | r,z,h,j,p |
002 | z,y,x,w,v,u,t,s |
003 | z |
004 | r,x,n,o,s |
005 | y,r,x,z,q,t,p |
006 | y,z,x,e,q,s,t,m |
根据上面的实例构建fp树
这里没有出现p,q,的原因是因为我们实战的有最小支持度的阀值,当小于这个阀值的时候我们认为其是不频繁的。
3.2构建过程2
除了给出FP树之外还需要一个头指针表来指向给定的类型的第一个实例。利用头指针表,快速访问FP中一个给定类型的所有元素。
这里给出前两个事务的构建过程
fp树伪代码
传入:数据集dataSet, 最小尺度minSup
第一次遍历数据集统计各元素出现的次数,在这同时创建头指针表
过滤掉不满足的元素
创建根节点
第二次遍历数据集
根据全局频率进行排序
更新树节点(在这写一个updateTree函数)
返回 创建的树,头指针表
4、从一颗FP树中挖掘频繁项集
4.1从FP树中抽取频繁项集分为三个步骤
从FP树中获得条件模式基
利用条件模式基,构建一颗条件FP树
迭代重复步骤(1),(2),直到树包含一个元素项为止
4.2 条件模式基
1、条件模式基:条件模式基是所查找元素项为结尾的路径集合。
2、前缀路径:每一条到所查找元素的路径,也就是条件模式基里面的一条路径,一条前缀路径是介于所查找元素项与树根节点的内容(利用头指针表可以求前缀路径,前缀路径将被用来构建条件FP树)
4.3创建条件FP树
对于每一个频繁项,都要创建一个条件FP树。
递归查找频繁项集伪代码
传入:头指针表(headtable),最小阀值(minSup),用来存储前缀路径的集合(preFix),存储频繁集的列表(freItemList)
1.将头指针表中的元素按照从小到大的顺序排列好存储到bigL列表里面
2.遍历bigL每个元素basePat:
复制前缀路径到新的频繁集newFreSet里面
将basePat添加到newFreSet里面
并将newFreSet添加到freItemList列表里面
寻找到basePat的条件基
利用basePat条件基构建basePat的条件树
如果构建basePat的条件树有头指针表,那么就继续递归调用本身的发掘频繁集函数
给一波代码先瞅瞅……
#inTree为生成的Fp树,头指针表headerTable, preFix空集合Set()
#freItemList保存生成的频繁集
def mineTree(inTree, headerTable, minSup, preFix, freItemList):
# 对头指针出现的元素按照出现的频率从小到大进行排序
#遍历头指针表,挖掘频繁集
bigL = [v[0] for v in sorted(headerTable.items(), key = lambda p:p[1])]
for basePat in bigL:
#保存当前前缀路径basePat
newSet = preFix.copy()
newSet.add(basePat)
#将每个频繁项添加到频繁项集列表freqItemList
freItemList.append(newSet)
#递归调用findPrefixPath函数找到到元素项basePat的前缀路径
condPattBases= findPrefixPath(basePat, headerTable[basePat][1])
#根据当前元素项生成的前缀路径和最小支持度生成条件树
myCondTree, myHead = createTree(condPattBases, 3)
#若条件fp树有元素项,可以再次递归生成条件树
if myHead != None:
# print newSet
#递归挖掘该条件树
mineTree(myCondTree, myHead, 2, newSet, freItemList)
例如我们为t创建一个条件FP树
我们已经挖掘到了为t创建了条件树,那么接下来就开始挖为{t,z},{t,x},{t,y}在创建……重复这个过程
直到条件树没有元素可以挖掘,如果看不懂那么就看下面这张图,这张图很好诠释发掘t所有频繁集的过程。
5、python代码
#encoding:utf-8
import numpy as py
from numpy import *
from audioop import reverse
#fp树的定义
class treeNode:
def __init__(self, nameValue, numOccur, parentNode):
self.name = nameValue#树节点的名字
self.count = numOccur#树节点的计数
self.nodeLink = None#链接下一棵树的线索,好像指针
self.parent = parentNode#该树节点的的父亲
self.children={}#树节点的孩子
def inc(self, numOccur):#计算该树节点出现的支持度数,就是出现了几次
self.count += numOccur
def disp(self, ind = 1):#遍历这颗树
print " "*ind,self.name," ", self.count
for child in self.children.values():
child.disp(ind+1)
#构建fp树
def createTree(dataSet, minSup = 1):
headerTable = {}#头结点的字典
#1.统计元素出现的次数
for trans in dataSet:#字典中的每条记录 网上俗称的事务
for item in trans:#遍历的是事务中的每个元素
#dict.get(key, default = None ) 有过有该键值就返回对应的价值,如果不存在键值就返回用户指定的值
headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
#2 删除小于该支持度的元素项
for k in headerTable.keys():
if headerTable[k] < minSup:
del(headerTable[k])
#3 对元素项去重得到频繁集
freqItemSet =set(headerTable.keys())
#如果为空 则返回无需进行下一项
if len(freqItemSet) == 0:
return None, None
#在头指针中 保留计数的数值以及指向每种类型第一个指针
for k in headerTable:
headerTable[k]=[headerTable[k], None]
#根节点为空 并且出现的次数为一
reTree = treeNode('NUll Set', 1, None)
'''第二次遍历数据集 建立fp树'''
for transet, count in dataSet.items():#transSet代表事务(一条物品的组合) 也就是一个集合啦 dataSet的键值,count代表出现的次数
localID={}#key每项物品,count 商品出现的次数
for trans in transet:
if trans in freqItemSet:
localID[trans] = headerTable[trans][0]#记录商品出现的次数
# print "loacal",localID
if len(localID) > 0:
#对这个事务(一条物品的组合)按照出现的度数 从大到小进行排序 为插入进行准备
orderedItems = [v[0] for v in sorted(localID.items(), key=lambda p:p[1], reverse = True)]
#构建树
updateTree(orderedItems, reTree,headerTable, count)
return reTree, headerTable#返回FP树结构,头指针
'''已经排好序的物品items,构建fp树'''
def updateTree(items, inTree,headerTable, count):
if items[0] in inTree.children:#若该项已经在树中出现则计数加一, 好像字典树有木有………………
inTree.children[items[0]].inc(count)
else:#如果没有这个元素项,那么创建一个子节点
inTree.children[items[0]] = treeNode(items[0], count, inTree)
if headerTable[items[0]][1] == None:#如果头指针没有指向任何元素,那么指向该节点
headerTable[items[0]][1] = inTree.children[items[0]]
else:#如果已经指向,那么就继续加入这个链表 updateHeader这个函数的作用就是让已经加入的该链表的最后一项,指向这个新的节点
updateHeader(headerTable[items[0]][1], inTree.children[items[0]])
#对剩下的元素项迭代调用,updateTree
#不断调用自身,每次调用就会去掉列表的第一个元素
#通过items[1::]实现
if len(items) > 1:
updateTree(items[1::], inTree.children[items[0]], headerTable, count)
'''更新相似元素的链表,相当于延长这个链表啦'''
def updateHeader(nodeToTest, targetNode):
while(nodeToTest.nodeLink != None):
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode
def loadSimpDat():
simpDat = [['r', 'z', 'h', 'j', 'p'],
['z', 'y', 'x', 'w', 'v', 'u', 't', 's'],
['z'],
['r', 'x', 'n', 'o', 's'],
['y', 'r', 'x', 'z', 'q', 't', 'p'],
['y', 'z', 'x', 'e', 'q', 's', 't', 'm']]
return simpDat
#对数据进行格式化处理转化成字典类型,<交易记录,count = 1>
def createInitSet(dataSet):
retDict={}
for transSet in dataSet:
retDict[frozenset(transSet)] = 1
return retDict
'''挖掘频繁集'''
#向上搜索,寻找到leafNode当前节点的路径
def ascentTree(leafNode, prefixPath):
if leafNode.parent != None:#如果父节点不为空就继续向上寻找并记录
prefixPath.append(leafNode.name)
ascentTree(leafNode.parent, prefixPath)#递归向上查找
return
'''为给定的元素项找到前缀路径(条件模式基)'''
def findPrefixPath(basePat, treeNode):#basePat 要发掘的元素 treeNode发掘的节点
condPats = {}#存放条件模式基,即含元素项basePat的前缀路径以及计数
#<key,value> key:前缀路径 value:路径计数
while treeNode != None:
prefixPath = []#存放不同路线的 前缀路径 包含basePat自身 在下面会去掉自身
ascentTree(treeNode, prefixPath)
if len(prefixPath) > 1:
condPats[frozenset(prefixPath[1:])] = treeNode.count
treeNode = treeNode.nodeLink
return condPats
#inTree为生成的Fp树,头指针表headerTable, preFix空集合Set()
#freItemList保存生成的频繁集
def mineTree(inTree, headerTable, minSup, preFix, freItemList):
# 对头指针出现的元素按照出现的频率从小到大进行排序
#遍历头指针表,挖掘频繁集
bigL = [v[0] for v in sorted(headerTable.items(), key = lambda p:p[1])]
for basePat in bigL:
#保存当前前缀路径basePat
newSet = preFix.copy()
newSet.add(basePat)
#将每个频繁项添加到频繁项集列表freqItemList
freItemList.append(newSet)
#递归调用findPrefixPath函数找到到元素项basePat的前缀路径
condPattBases= findPrefixPath(basePat, headerTable[basePat][1])
#根据当前元素项生成的前缀路径和最小支持度生成条件树
myCondTree, myHead = createTree(condPattBases, 3)
#若条件fp树有元素项,可以再次递归生成条件树
if myHead != None:
# print newSet
#递归挖掘该条件树
mineTree(myCondTree, myHead, 2, newSet, freItemList)
if __name__ == '__main__':
# dataSet = loadSimpDat()
# iniSet = createInitSet(dataSet)
# myFpTree,myHeaderTab = createTree(iniSet, 3)
# freItemList=[]
# mineTree(myFpTree, myHeaderTab, 3, set([]), freItemList)
# print mat(freItemList)
#------------从新闻网站站点点击流中挖掘---------------
paresedDat = [line.split() for line in open('kosarak.dat').readlines()]
initSet = createInitSet(paresedDat)
myFpTree, myHeaderTab = createTree(initSet, 100000)
freItemList=[]
mineTree(myFpTree, myHeaderTab, 100000 , set([]), freItemList)
print mat(freItemList)
6、kosarak.dat数据集
上传在了我的资源里面