FP-growth算法
1.原理
- 相较于Apriori算法,FP-growth算法在发现频繁项集上有更快的速度。
- FP-growth算法将数据存储在FP树的紧凑数据结构中。与搜索树不同的是,一个元素可以在FP树中出现多次。FP树会储存项集的出现频率,每个项集以路径的方式储存在树中,并通过link连接相似元素。
- 构建FP树需要对原始数据集扫描两遍。第一次遍历数据集会获得每个元素项的出现频率,去除不满足最小支持率的元素项。第二次扫描只需要考虑频繁元素构建FP树。构建树时,将每个项集根据元素项的绝对出现频率进行排序,之后读入每个项集并将其添加到已存在的路径中,若该路径不存在则创建新路径。
2.代码
(1)创建FP树
# 用于保存树的节点
class TreeNode:
def __init__(self, nameValue, occurNum, parentNode):
self.name = nameValue # 节点名字
self.count = occurNum # 计数值
self.nodeLink = None # 链接相似的元素
self.parent = parentNode # 当前节点的父节点
self.children = {} # 当前节点的所有子节点
def increase(self, occurNum):
self.count += occurNum # 增加计数值
# 将树以文本形式显示
def display(self, space=1):
print(' ' * space, self.name, " ", self.count)
for child in self.children.values():
child.display(space + 1)
def createTree(dataset, minSup=1):
headerTable = {}
# 遍历整个数据集,对每个项集的每个元素项计数
for trans in dataset:
for item in trans:
"""get()方法返回字典中item对应的值,若找不到则返回0
item出现一次,计数+1"""
headerTable[item] = headerTable.get(item, 0) + dataset[trans]
for k in list(headerTable.keys()):
if headerTable[k] < minSup:
del (headerTable[k]) # 剔除不满足最小支持度的元素项
freqItemSet = set(headerTable.keys()) # 得到频繁项的非重复集合
if len(freqItemSet) == 0: return None, None # 若所有元素项均不存在满足最小支持度,则退出
for k in headerTable:
# 扩展headerTable值,以便同时保存每个频繁项的频数和该频繁项第一次出现的节点(将该节点作为指针)
headerTable[k] = [headerTable[k], None]
retTree = TreeNode('Null Set', 1, None) # 创建根节点
for tranSet, count in dataset.items(): # 第二次遍历(每条交易记录以及该交易记录出现的次数)
localD = {}
for item in tranSet:
if item in freqItemSet: # 过滤掉不符要求的项
localD[item] = headerTable[item][0] # 得到每个元素项的计数值
if len(localD) > 0:
# 生成一个按出现频率由大到小排列元素项的列表
"""sorted()函数按照可选参数key进行排序,reverse=True表示从大到小。
lambda函数用于封装简单的过程,冒号后是返回值,下面的过程表示返回y第二维的值
因此按照localD.items()的第二维值(计数值)从大到小排序,排序后返回localD的键,即元素项"""
orderItems = [x[0] for x in sorted(localD.items(), key=lambda y: y[1], reverse=True)]
updateTree(orderItems, retTree, headerTable, count) # 更新FP树
return retTree, headerTable
# 根据每条进行过滤排序后的交易记录构建FP树
def updateTree(items, inTree, headerTable, count):
if items[0] in inTree.children: # 第一个元素项在树的子节点中是否存在
inTree.children[items[0]].increase(count) # 若存在,则该节点的计数值增加count个
else:
inTree.children[items[0]] = TreeNode(items[0], count, inTree) # 若不存在,则创建新节点
if headerTable[items[0]][1] is None: # 该元素项是否有指向它的指针
headerTable[items[0]][1] = inTree.children[items[0]] # 没有则将新节点添加为指针
else: # 如果已经有指向该元素项的指针,则更新该新节点的链接
updateHeader(headerTable[items[0]][1], inTree.children[items[0]])
if len(items) > 1: # 继续对items列表中剩余元素项构建FP树
# items从第二个元素开始切片,后一个冒号表示步长默认为1。后面的元素在前一元素的子节点上进行更新
updateTree(items[1::], inTree.children[items[0]], headerTable, count)
# 更新指针链接
def updateHeader(node, targetNode):
while node.nodeLink is not None: # 指针从开始一直向下,直到链接到末尾的节点
node = node.nodeLink
node.nodeLink = targetNode # 将末尾节点链接到指定节点上
if __name__ == "__main__":
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']]
dict1 = {}
for i in simpdat:
dict1[frozenset(i)] = 1
tree, header=createTree(dict1,minSup=3)
tree.display()
# 结果
>>> Null Set 1
z 5
r 1
x 3
s 2
y 2
t 2
r 1
y 1
t 1
x 1
r 1
s 1
(2) 从构建好的FP树中抽取频繁项集
- 步骤:
(1)在FP树中获得指定项的条件模式基(条件模式基指所查找元素项为结尾的路径集合,即介于所查找元素项与根节点之间的内容)
(2)根据条件模式基,构建条件FP树
(3)重复上面两个步骤,直到树包含一个元素项 - 获得指定项的条件模式基↓↓↓
# 上溯整棵树
def ascendTree(leafNode, prefixPath):
if leafNode.parent is not None: # 若存在父节点
prefixPath.append(leafNode.name) # 将当前节点添加到前缀路径中
ascendTree(leafNode.parent, prefixPath) # 调用自身,直到找到最上层的节点
# 寻找指定项的路径
def findPrefixPath(treeNode):
condPat = {} # 存放条件模式基
while treeNode is not None:
prefixPath = [] # 前缀路径
ascendTree(treeNode, prefixPath) # 追溯到最顶端的节点,并把经过的节点保存在prefixPath中
if len(prefixPath) > 1: # 若存在前缀路径
# 将路径(不包含根节点)与当前节点的计数值保存在字典中
condPat[frozenset(prefixPath[1:])] = treeNode.count
# 根据节点的链接,到下一个相同元素项的节点
treeNode = treeNode.nodeLink
return condPat
if __name__ == "__main__":
tree, header = createTree(dict1, 3)
x = findPrefixPath(header['x'][1])
print(x)
>>> {frozenset({'z'}): 3}
- 构建条件FP树:对于每个频繁的元素项以及频繁的元素项的组合构建条件FP树(如:频繁项‘x’可以构建FP树,如果’x’与‘t’的组合也是频繁的,那么需要也对[‘x’,‘t’]构建FP树)
# 创建条件FP树
def mineTree(headerTable, minSupp, preFix, freqItemList):
# 得到按照频率从小到大排序的元素项
bigL = [y[0] for y in sorted(headerTable.items(), key=lambda x: x[1][0])]
for basePatt in bigL: # 遍历所有频繁项
newFreqSet = preFix.copy()
newFreqSet.add(basePatt) # 存放当前频繁项
freqItemList.append(newFreqSet) # 储存所有频繁项(包括组合的频繁项)
condPatBases = findPrefixPath(headerTable[basePatt][1]) # 找到当前频繁项的所有前缀路径
mycondTree, myHead = createTree(condPatBases, minSupp) # 根据前缀路径建立条件FP树
if myHead is not None: # 如果FP树中存在元素项,则显示并继续调用自身
print('conditional tree for:', newFreqSet)
mycondTree.display(space=1)
mineTree(myHead, minSupp, newFreqSet, freqItemList)
if __name__ == "__main__":
freq = []
mineTree(header, 3, set([]), freq)
print('freqItems:', freq)
>>> conditional tree for: {'s'}
Null Set 1
x 3
conditional tree for: {'y'}
Null Set 1
x 3
z 3
conditional tree for: {'y', 'z'}
Null Set 1
x 3
conditional tree for: {'t'}
Null Set 1
y 3
x 3
z 3
……………………省略部分结果
freqItems: [{'r'}, {'s'}, {'s', 'x'}, {'y'}, {'y', 'x'}, {'y', 'z'}, {'y', 'x', 'z'}, {'t'}, {'t', 'y'}, {'t', 'x'}, {'t', 'x', 'y'}, {'t', 'z'}, {'t', 'z', 'y'}, {'t', 'x', 'z'}, {'t', 'x', 'z', 'y'}, {'x'}, {'x', 'z'}, {'z'}]