从FP树中挖掘频繁项集
从FP树中抽取频繁项集的三个基本步骤如下:
- 从FP树中获得条件模式基
- 利用条件模式基,构建一个条件FP树
- 重复步骤1、2,直到树包含一个元素项为止
抽取条件模式基
条件模式基(conditional pattern base)是以所查找元素项为结尾的路径集合。每一条路径是一条前缀路径(prefix path)。
前缀路径是介于所查找元素与根节点之间的所有内容。
以上图的树为例,元素项
r
的前缀路径有{z}
、{z,x,y}
和{x,s}
。
下表列出树中每一个频繁项的所有前缀路径。
频繁项 | 前缀路径 |
---|---|
z | {}5 |
r | {x,s}1, {z,x,y}1, {z}1 |
x | {z}3, {}1 |
y | {z,x}3 |
s | {z,x,y}2, {x}1 |
t | {z,x,y,s}2, {z,x,y,r}1 |
为了快速获得这些前缀路径,可以利用头指针表。头指针表包含相同类型元素链表的起始指针。一旦到某个元素项,就可以上溯到根节点为止。
# 上溯到根节点
def ascendTree(leafNode, prefixPath):
if leafNode.parent != None:
prefixPath.append(leafNode.name)
# 迭代上溯
ascendTree(leafNode.parent, prefixPath)
def findPrefixPath(basePat, treeNode):
condPats = {} # 存储条件模式基
while treeNode != None:
prefixPath = []
ascendTree(treeNode, prefixPath)
if len(prefixPath) > 1:
condPats[frozenset(prefixPath[1:])] = treeNode.count
treeNode = treeNode.nodeLink
return condPats
代码很简单,就是利用头指针表的链接访问同类型的每个元素项,对每个元素项都会调用ascendTree()
函数来上溯到根节点。下面看看实际运行效果。
可以对照输出结果跟上表。
创建条件FP树
有了条件模式基后,就可以创建条件FP树了。
下面用元素项t
的条件FP树构建做例子。
t
的条件模式基:{z,x,y,s}:2, {z,x,y,r}:1
设最小支持度为3,那么s
和r
在这里不符合条件,被去掉。那么t
的条件FP树如下:
note:
s
和r
单独来看是频繁项,但在t
的条件树中是非频繁的。也就是说{t, r}
和{t,s}
是不频繁的。
然后继续对{t,z}
、{t,x}
和{t,y}
构建条件树,过程重复进行到条件树中没有元素为止。
# 递归查找频繁项集
def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
# 从小到大排序
bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1][0])]
for basePat in bigL:
newFreqSet = preFix.copy()
newFreqSet.add(basePat)
freqItemList.append(newFreqSet)
condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
# 从条件模式基构建条件FP树
myCondTree, myHead = createTree(condPattBases, minSup)
# 挖掘条件FP树
if myHead != None:
print('conditional tree for: ',newFreqSet)
myCondTree.disp(1)
mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)
看下实际运行效果。
freqItems = []
mineTree(FPTree, headerTable, 3, set([]), freqItems)
output:
conditional tree for: {'y'}
根节点 0
z 3
x 3
conditional tree for: {'y', 'x'}
根节点 0
z 3
conditional tree for: {'t'}
根节点 0
y 3
z 3
x 3
conditional tree for: {'t', 'z'}
根节点 0
y 3
conditional tree for: {'t', 'x'}
根节点 0
y 3
z 3
conditional tree for: {'t', 'z', 'x'}
根节点 0
y 3
conditional tree for: {'s'}
根节点 0
x 3
conditional tree for: {'x'}
根节点 0
z 3
查看下返回的频繁项集。
freqItems
output:
[{'r'},
{'y'},
{'y', 'z'},
{'x', 'y'},
{'x', 'y', 'z'},
{'t'},
{'t', 'y'},
{'t', 'z'},
{'t', 'y', 'z'},
{'t', 'x'},
{'t', 'x', 'y'},
{'t', 'x', 'z'},
{'t', 'x', 'y', 'z'},
{'s'},
{'s', 'x'},
{'x'},
{'x', 'z'},
{'z'}]
到这里就完成了完整的FP-growth算法。从用数据集构建FP树,到用FP树得到频繁项集。
示例
文件kosarak.dat
包含将近100万条记录,每一条记录包含某个用户浏览过的新闻。接下来将用FP-growth算法,找出至少被10万人浏览过的新闻。
# 加载数据
data = transToDict([line.split() for line in open('kosarak.dat').readlines()])
# 构建FP树
FPtree, headerTable = createTree(data, 100000)
# 找出频繁项集
freqList = []
mineTree(FPtree, headerTable, 100000, set([]), freqList)
output:
conditional tree for: {'1'}
根节点 0
6 107404
conditional tree for: {'3'}
根节点 0
6 186289
11 117401
11 9718
conditional tree for: {'11', '3'}
根节点 0
6 117401
conditional tree for: {'11'}
根节点 0
6 261773
有兴趣看下运行时间可以用%%timeit
。
%%timeit
FPtree, headerTable = createTree(data, 100000)
freqList = []
mineTree(FPtree, headerTable, 100000, set([]), freqList)
在我的电脑运行时间如下:
4.4 s ± 238 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
扫描100万条记录、构建树到挖掘频繁项集,只需要4秒左右。可以看出FP-growth的高效。
接下来看看哪些新闻或报道集合被10万+的人浏览过。
freqList
output:
[{'1'},
{'1', '6'},
{'3'},
{'11', '3'},
{'11', '3', '6'},
{'3', '6'},
{'11'},
{'11', '6'},
{'6'}]