一、概念
FP-Growth(Frequent Pattern Growth)算法是一种用于从事务数据库中挖掘频繁项集的高效算法。它是关联规则挖掘中的一种重要方法,能够在不产生候选项集的情况下找到频繁项集。FP-Growth算法通过构建一种称为FP-Tree(频繁模式树)的数据结构来实现这一目标。
二、原理
1. FP-Tree构建
FP-Growth算法的第一步是构建FP-Tree。FP-Tree是一种紧凑的数据结构,用于存储事务数据库中的频繁项集。构建FP-Tree的步骤如下:
- 扫描事务数据库:第一次扫描事务数据库,计算每个项的频次(支持度)。
- 移除不频繁项:根据预定义的最小支持度阈值,移除不频繁的项。
- 项排序:对每个事务中的项按照频次从高到低排序。
- 构建FP-Tree:第二次扫描事务数据库,将每个事务插入FP-Tree中。插入时,按照排序后的顺序插入,若树中已有相同前缀路径,则共享该路径。
2. 挖掘频繁项集
在构建好FP-Tree之后,FP-Growth算法通过递归地挖掘FP-Tree来找到所有频繁项集。具体步骤如下:
- 从FP-Tree中提取条件模式基:对于每个频繁项,提取其条件模式基(即包含该项的所有路径)。
- 构建条件FP-Tree:根据条件模式基,构建条件FP-Tree。
- 递归挖掘条件FP-Tree:对条件FP-Tree进行递归挖掘,找到所有频繁项集。
三、python实现
1、手撕版
class TreeNode:
def __init__(self, name, count, parent):
# 初始化树节点
self.name = name # 节点名称
self.count = count # 节点计数
self.parent = parent # 父节点
self.children = {} # 子节点
self.nodeLink = None # 链接到相似节点
def increment(self, count):
# 增加节点计数
self.count += count
def update_header(node, targetNode):
# 更新头指针表,确保节点链接到链表的最后一个节点
while node.nodeLink is not None:
node = node.nodeLink
node.nodeLink = targetNode
def update_tree(items, inTree, headerTable, count):
# 更新FP树
if items[0] in inTree.children:
# 如果第一个元素在子节点中,增加计数
inTree.children[items[0]].increment(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:
update_header(headerTable[items[0]][1], inTree.children[items[0]])
if len(items) > 1:
# 递归更新树
update_tree(items[1:], inTree.children[items[0]], headerTable, count)
def create_tree(dataSet, minSup=1):
# 创建FP树
headerTable = {}
for trans in dataSet:
for item in trans:
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[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:
orderedItems = [v[0] for v in sorted(localD.items(), key=lambda p: p[1], reverse=True)]
update_tree(orderedItems, retTree, headerTable, count)
return retTree, headerTable
def ascend_tree(leafNode, prefixPath):
# 递归上升树,收集前缀路径
if leafNode.parent is not None:
prefixPath.append(leafNode.name)
ascend_tree(leafNode.parent, prefixPath)
def find_prefix_path(basePat, treeNode):
# 查找前缀路径
condPats = {}
while treeNode is not None:
prefixPath = []
ascend_tree(treeNode, prefixPath)
if len(prefixPath) > 1:
condPats[frozenset(prefixPath[1:])] = treeNode.count
treeNode = treeNode.nodeLink
return condPats
def mine_tree(inTree, headerTable, minSup, preFix, freqItemList):
# 挖掘FP树
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 = find_prefix_path(basePat, headerTable[basePat][1])
myCondTree, myHead = create_tree(condPattBases, minSup)
if myHead is not None:
mine_tree(myCondTree, myHead, minSup, newFreqSet, freqItemList)
def load_simp_dat():
simp_dat = [['牛奶', '面包', '啤酒'],
['牛奶', '面包'],
['牛奶', '啤酒'],
['面包', '啤酒'],
['牛奶', '面包', '啤酒', '鸡蛋'],
['面包', '鸡蛋']]
return simp_dat
def create_init_set(dataSet):
retDict = {}
for trans in dataSet:
retDict[frozenset(trans)] = 1
return retDict
simp_dat = load_simp_dat()
init_set = create_init_set(simp_dat)
my_fp_tree, my_header_tab = create_tree(init_set, 2)
freq_items = []
mine_tree(my_fp_tree, my_header_tab, 2, set([]), freq_items)
print("频繁项集:")
for itemset in freq_items:
print(itemset)
2、工程实现
import pyfpgrowth
transactions = [['牛奶', '面包', '啤酒'],
['牛奶', '面包'],
['牛奶', '啤酒'],
['面包', '啤酒'],
['牛奶', '面包', '啤酒', '鸡蛋'],
['面包', '鸡蛋']]
min_sup = 2 # 绝对支持度计数
# 自动处理数据预处理
patterns = pyfpgrowth.find_frequent_patterns(transactions, min_sup)
rules = pyfpgrowth.generate_association_rules(patterns, 0.7) # 置信度阈值
# 结果展示
print("频繁项集:")
for itemset, support in patterns.items():
print(f"{set(itemset)}: {support}")
print("\n关联规则:")
for ante, cons in rules.items():
print(f"{set(ante)} => {set(cons[0])} [conf={cons[1]:.2f}]")