FP-Growth算法是这里要介绍的第三个非监督学习算法,FP(Frequent Pattern)代表频繁模式。FP-Growth算法相对于Apriori算法来说效率更高,其只需要对数据集进行两轮扫描即可发现频繁项集,而Apriori却做不到。通常情况,FP-Growth会比Apriori快两个数量级。但FP-Growth只能发现频繁项集,不能发现关联规则。
FP-Growth发现频繁项集的过程包括构建FP树和从FP树中找出频繁项集两个部分。
本节包含以下内容:
- 构建FP树
- 从FP树中发现频繁项集
部分内容引用自《Machine Learning in Action》
构建FP树
FP树和一般的树没有太大的差别,如下图所示:
树的根节点为空,其它节点包含一个节点名称(这里是英文字母)和对应的出现次数,相同节点名称之间还有一个指针链接,这样可以容易找到相同的节点。
下面数据集可对应于上面的FP树,注意,这里将最小支持度设定为3,所以出现次数小鱼3的元素将不会出现在FP树中,例如q,p等,
通过FP树可以看出,(t,s,y,x,z)出现了两次(因为叶子节点 t 的值为2),在数据集中可以找到对应的TID是002和006。(z)出现了5次,分别为(r,z)一次,(t,s,y,x,z)两次,(t,r,y,x,z)一次,(z)一次。
为了便于计算,我们还需要一个头指针表用来指向给定类型的第一个实例,
构建FP树的过程是,先计算数据集中的每个元素出现的频率,如果元素不满足最小支持度,则删掉此元素。然后对每个子集中的元素,按照元素出现的次数排序,例如:
然后开始构建FP树,从空树开始,不断增加FP树(让其生长),如果树中已经存在现有元素,则增加值,否则向树中增加一个分支,例如:
下面通过python代码来实现构建FP树的过程,创建模块 fp_tree.py,并输入以下代码:
class tree_node:
def __init__(self, name, num_occur, parent_node):
self.name = name
self.count = num_occur
self.node_link = None
self.parent = parent_node
self.children = {}
def inc(self, num_occur):
self.count += num_occur
def disp(self, ind=1):
print("%r%r%r%r" % (' ' * ind, self.name, ' ', self.count))
for child in self.children.values():
child.disp(ind + 1)
def create_tree(data_set, min_sup=1):
header_table = {}
for trans in data_set:
for item in trans:
header_table[item] = header_table.get(item, 0) + data_set[trans]
for k in list(header_table.keys()):
if header_table[k] < min_sup:
header_table.pop(k)
freq_item_set = set(header_table.keys())
if len(freq_item_set) == 0: return None, None
for k in header_table:
header_table[k] = [header_table[k], None]
ret_tree = tree_node('Null Set', 1, None)
for tran_set, count in data_set.items():
local_dataset = {}
for item in tran_set:
if item in freq_item_set:
local_dataset[item] = header_table[item][0]
if len(local_dataset) > 0:
ordered_items = [v[0] for v in sorted(local_dataset.items(), key=lambda p: p[1], reverse=True)]
update_tree(ordered_items, ret_tree, header_table, count)
return ret_tree, header_table
def update_tree(items, in_tree, header_table, count):
if items[0] in in_tree.children:
in_tree.children[items[0]].inc(count)
else:
in_tree.children[items[0]] = tree_node(items[0], count, in_tree)
if header_table[items[0]][1] == None:
header_table[items[0]][1] = in_tree.children[items[0]]
else:
update_header(header_table[items[0]][1], in_tree.children[items[0]])
if len(items) > 1:
update_tree(items[1::], in_tree.children[items[0]], header_table, count)
def update_header(node_to_test, target_node):
while (node_to_test.node_link != None):
node_to_test = node_to_test.node_link
node_to_test.node_link = target_node
def load_simp_dat():
simp_data = [['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 simp_data
def create_init_set(data_set):
ret_dict = {}
for trans in data_set:
ret_dict[frozenset(trans)] = 1
return ret_dict
if __name__ == '__main__':
data = create_init_set(load_simp_dat())
tree, table = create_tree(data, min_sup=3)
print("Head table:")
print(table)
print("FP Tree:")
tree.disp()
freqItems = []
运行结果:
D:\work\python_workspace\machine_learning\venv\Scripts\python.exe D:/work/python_workspace/machine_learning/fp_growth/fp_tree.py
Head table:
{'r': [3, <__main__.tree_node object at 0x00000000027E45E0>], 'z': [5, <__main__.tree_node object at 0x00000000026B0CD0>], 't': [3, <__main__.tree_node object at 0x00000000027C8340>], 'y': [3, <__main__.tree_node object at 0x00000000027C8280>], 's': [3, <__main__.tree_node object at 0x00000000027C8130>], 'x': [4, <__main__.tree_node object at 0x00000000027C8460>]}
FP Tree:
' ''Null Set'' '1
' ''z'' '5
' ''r'' '1
' ''x'' '3
' ''t'' '3
' ''y'' '3
' ''s'' '2
' ''r'' '1
' ''x'' '1
' ''s'' '1
' ''r'' '1
Process finished with exit code 0
注意,输出结果的FP树和上面示例中的树在结构上有一点差异,但本质上还是同一棵树。
从FP树中发现频繁项集
从FP树中发现频繁项集包含下面三个步骤:
- 从FP树中获取条件模式基
- 利用条件模式基构建一个条件FP树
- 迭代重复步骤1和步骤2,直到树包含一个元素为止
条件模式基
条件模式基是以所有查询元素项为结尾的路径集合。每一条路径都是一条前缀路径,例如:
发现条件模式基并不困难,通过头指针表即可遍历出路径上的元素。例如元素t,首先通过头指针表找到第一个元素 t 在FP树中的位置,然后向上遍历到根节点可找到一个条件模式基(z,x,y,s),接着找到第一个 t 的链接,再向上遍历即可得到第二个条件模式基(z,x,y,r),条件模式基的数值就是当前节点的数值。
创建条件FP树
对于每一个频繁项,都要创建一个条件FP树。例如,假定为频繁项 t 创建一个条件FP树,首先会得到(t,x),(t,y),(t,z),
由于s和r不满足最小支持度3,因此需要去掉,可以看出(t,s)和(t,r)的组合并不满足最小支持度,虽然单独的s或t都满足最小支持度3。得到(t,z),(t,y),(t,x)以后,需要进一步挖掘对应的条件树,这会产生更多复杂的频繁项。
下面通过python代码来实现,创建模块 frequent_items.py,并输入以下代码:
import fp_growth.fp_tree as fp_tree
def ascend_tree(leaf_node, pre_fix_path):
if leaf_node.parent != None:
pre_fix_path.append(leaf_node.name)
ascend_tree(leaf_node.parent, pre_fix_path)
def find_pre_fix_path(base_pat, tree_node):
cond_pats = {}
while tree_node != None:
pre_fix_path = []
ascend_tree(tree_node, pre_fix_path)
if len(pre_fix_path) > 1:
cond_pats[frozenset(pre_fix_path[1:])] = tree_node.count
tree_node = tree_node.node_link
return cond_pats
def mine_tree(in_tree, header_table, min_sup, pre_fix, freq_item_list):
bigL = [v[0] for v in sorted(header_table.items(), key=lambda p: p[1][0])]
for base_pat in bigL:
new_freq_set = pre_fix.copy()
new_freq_set.add(base_pat)
freq_item_list.append(new_freq_set)
cond_patt_bases = find_pre_fix_path(base_pat, header_table[base_pat][1])
my_cond_tree, my_head = fp_tree.create_tree(cond_patt_bases, min_sup)
if my_head != None:
mine_tree(my_cond_tree, my_head, min_sup, new_freq_set, freq_item_list)
if __name__ == '__main__':
data = fp_tree.create_init_set(fp_tree.load_simp_dat())
tree, table = fp_tree.create_tree(data, min_sup=3)
freqItems = []
mine_tree(tree, table, 3, set([]), freqItems)
print("Frequent items:")
print(freqItems)
运行结果:
D:\work\python_workspace\machine_learning\venv\Scripts\python.exe D:/work/python_workspace/machine_learning/fp_growth/frequent_items.py
Frequent items:
[{'r'}, {'y'}, {'y', 'z'}, {'y', 'x'}, {'y', 'z', 'x'}, {'t'}, {'y', 't'}, {'t', 'z'}, {'y', 't', 'z'}, {'t', 'x'}, {'y', 't', 'x'}, {'t', 'z', 'x'}, {'y', 't', 'z', 'x'}, {'s'}, {'s', 'x'}, {'x'}, {'z', 'x'}, {'z'}]
Process finished with exit code 0
可以看出,我们能够正确的发现频繁项集了。