FP-Growth算法

项目代码

FP-Growth算法

FP-Growth算法用来高效发现频繁项集,但不能用于发现关联规则。
FP-Growth算法只需要对数据进行两次扫描,而Apriori算法对于每个潜在的频繁项集都会扫描数据集判定给定模式是否频繁,因此FP-Growth算法的速度要比Apriori算法快。
FP-Growth只会扫描数据集两次,它发现频繁项集的基本过程如下:
(1) 构建FP树
(2)从FP树中挖掘频繁项集
FP-Growth算法将数据存储在一种称为FP树的紧凑数据结构中。FP代表频繁模式(Frequent Pattern)。一棵FP树看上去与计算机科学中的其他树结构类似,但是它通过链接(link)来连接相似元素,被连起来的元素项可以看成一个链表。如图:
在这里插入图片描述
一个元素项可以在一棵FP树中出现多次。FP树会存储项集的出现频率,而每个项集会以路径的方式存储在树中。存在相似元素的集合会共享树的一部分。只有当集合之间完全不同时,树才会分叉。 树节点上给出集合中的单个元素及其在序列中的出现次数,路径会给出该序列的出现次数。
看个例子:
在这里插入图片描述
上表中,元素项z出现了5次 ,集合{r,z}出现了1次。于是可以得出结论:z一定是自己本身或者和其他符号一起出现了4次。我们再看下z其他可能性。集 合{t,s,y,x,z}出现了2次 ,集合{t,r,y,x,z}出现了1次。元素项z的右边标的是5,表示z出现了5次 ,其中刚才已经给出了4次出现,所以它一定单独出现过1次。通过观察表12-1看看刚才的结论是否正确。前面提到{t,r,y,x,z}只出现过1次 ,在事务数据集中我们看到005号记录上却是{y,r,x,z,q,t,p}。那么,q和p去哪儿了呢?
这里使用第11章给出的支持度定义,该指标对应一个最小阈值,低于最小阈值的元素项被认为是不频繁的。如果将最小支持度设为3,然后应用频繁项分析算法,就会获得出现3次或3次以上的项集。上面在生成图12-1中的FP树时,使用的最小支持度为3,因此q和p并没有出现在最后的树中。
FP-Growth算法的工作流程: 首先构建FP树 ,然后利用它来挖掘频繁项集。为构建FP树 ,需要对原始数据集扫描两遍。第一遍对所有元素项的出现次数进行计数。记住第11章中给出Apriori原理,即如果某元素是不频繁的,那么包含该元素的超集也是不频繁的,所以就不需要考虑这些超集。数据集的第一遍扫描用来统计出现的频率,而第二遍扫描中只考虑那些频繁元素。

构建FP树

定义树结构

在第二次扫描数据集时会构建一棵树。为构建一棵树,需要一个容器来保存树。
代码实现:

class treeNode:
    def __init__(self, name_value, num_occur, parent_node):
        self.name = name_value
        self.count = num_occur
        self.node_link = None
        self.parent = parent_node
        self.children = {}

    def increase(self, num_occur):
        self.count += num_occur

    def display(self, ind=1):
        print(' ' * ind, self.name, ' ', self.count)
        for child in self.children.values():
            child.display(ind + 1)

构建FP树

要构建FP树还需要一个头指针表来指向给定类型的第一个实例。利用头指针表 ,可以快速访问FP树中一个给定类型的所有元素。如图:
在这里插入图片描述
这里使用一个字典作为数据结构,来保存头指针表。除了存放指针外,头指针表还可以用来保存FP树中每类元素的总数。
第一次遍历数据集会获得每个元素项的出现频率。接下来,去掉不满足最小支持度的元素项。再下一步构建FP树。在构建时,读人每个项集并将其添加到一条已经存在的路径中。如果该路径不存在,则创建一条新路径。每个事务就是一个无序集合。假设有集合{z,x,y}和{y,z,r} ,那么在FP树中 , 相同项会只表示一次。为了解决此问题,在将集合添加到树之前,需要对每个集合进行排序。排序基于元素项的绝对出现频率来进行。
数据进行过滤、重排序后的数据显示如图:
在这里插入图片描述

实现代码
from st_tree import treeNode


def create_tree(data, min_support=1): # 创建 FP树
    header_table = {} # 初始化头指针列表
    for trans in data:
        for item in trans:
            header_table[item] = header_table.get(item, 0) + data[trans]  # 第一次扫描数据集,统计所有项的频度
    for k in list(header_table.keys()): # 删除频度小于给定值的项
        if header_table[k] < min_support:
            del header_table[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 = treeNode('Null tree', 1, None) # 创建 FP的树根
    for tran_set, count in data.items():  # 第二次遍历数据集,
        local_D = {}
        for item in tran_set:
            if item in freq_item_set: # 只考虑那些频繁项
                local_D[item] = header_table[item][0]
        if len(local_D) > 0:
            ordered_item = [v[0] for v in sorted(local_D.items(), key=lambda p: p[1], reverse=True)] # 排序
            update_tree(ordered_item, 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]].increase(count)
    else:
        in_tree.children[items[0]] = treeNode(items[0], count, in_tree)
        if header_table[items[0]][1] is 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(node2test, target_node):
    """确保节点连接指向树中该元素项的每个实例"""
    while node2test.node_link is not None:
        node2test = node2test.node_link
    node2test.node_link = target_node
 
# 测试结果
#  Null tree   1
#    z   5
#     r   1
#     x   3
#      t   3
#       s   2
#        y   2
#       r   1
#        y   1
#    x   1
#     s   1
#      r   1

FP树中挖掘频繁项集

有了FP树之后,就可以抽取频繁项集了。首先从单元素项集合开始,然后在此基础上逐步构建更大的集合。当然这里将利用FP树来做实现上述过程 ,不再需要原始数据集了。
从FP树中抽取频繁项集的三个基本步骤如下:
(1)从FP树中获得条件模式基;
(2)利用条件模式基,构建一个条件FP树 ;
(3)迭代重复步骤(1)步骤( 2 ) ,直到树包含一个元素项为止。
接下来重点关注第(1)步 ,即寻找条件模式基的过程。之后,为每一个条件模式基创建对应的条件FP树。最后需要构造少许代码来封装上述两个函数,并从FP树中获得频繁项集。

抽取条件模式基

条件模式基是以所查找元素项为结尾的路径集合。每一条路径其实都是一条前辍路径(prefix path)。简而言之,一条前缀路径是介于所査找元素项与树根节点之间的所有内容。
如第一个图所示,符号r的前缀路径是{x,s}、{z,x,y} 和{z}。每一条前缀路径都与一个计数值关联。该计数值等于起始元素项的计数值,该计数值给了每条路径上的数目。下图列出了上例当中每一个频繁项的所有前缀路径。
在这里插入图片描述
为了获得这些前缀路径,可以对树进行穷举式搜索,直到获得想要的频繁项为止,或者使用一个更有效的方法来加速搜索过程。可以利用先前创建的头指针表来得到一种更有效的方法。头指针表包含相同类型元素链表的起始指针。一旦到达了每一个元素项,就可以上溯这棵树直到根节点为止。

实现代码
from fpgrowth import create_tree


def ascend_tree(leaf_node, prefixPath):  # ascends from leaf node to root
    if leaf_node.parent is not None:
        prefixPath.append(leaf_node.name)
        ascend_tree(leaf_node.parent, prefixPath)

def find_prefix_path(base_pattern, header_table):  # treeNode comes from header table
    tree_node = header_table[base_pattern][1]
    condPats = {}
    while tree_node is not None:
        prefix_path = []
        ascend_tree(tree_node, prefix_path)
        if len(prefix_path) > 1:
            condPats[frozenset(prefix_path[1:])] = tree_node.count
        tree_node = tree_node.node_link
    return condPats

# 测试结果
#  condPats = find_prefix_path('x',header_table)
#     {frozenset({'z'}): 3}
#  condPats = find_prefix_path('z',header_table)
#     {}
#  condPats = find_prefix_path('r',header_table)
#     {frozenset({'z'}): 1, frozenset({'x'}): 1, frozenset({'y', 'z', 'x'}): 1}
创建条件FP树

对于每一个频繁项,都要创建一棵条件FP树。我们会为z、x以及其他频繁项构建条件树。可以使用刚才发现的条件模式基作为输入数据,并通过相同的建树代码来构建这些树。然后 ,我们会递归地发现频繁项、发现条件模式基,以及发现另外的条件树。举个例子来说,假定为频繁项测建一个条件FP树,然 后对{t,y}、{t,x}、…重复该过程。元素项t的条件FP树的构建过程如图所示:
在这里插入图片描述
注意到元素项s以及r是条件模式基的一部分,但是它们并不属于条件FP树。原因是什么?如果讨论s以及r的话,它们难道不是频繁项吗?实际上单独来看它们都是频繁项,但是在t的条件树中,它们却不是频繁的,也就是说,{t,r}及{t,s}是不频繁的。
接下来,对集{t,z}、{t,x}以及{t,y}来挖掘对应的条件树。这会产生更复杂的频繁项集。该过程重复进行,直到条件树中没有元素为止,然后就可以停止了。

实现代码
def mine_tree(header_table, min_support, pre_fix, freq_item_list):
    bigL = [v[0] for v in sorted(header_table.items(), key=lambda p: p[1][0])]
    for basePat in bigL:
        new_freq_set = pre_fix.copy()
        new_freq_set.add(basePat)
        freq_item_list.append(new_freq_set)
        cond_patt_bases = find_prefix_path(basePat, header_table)
        myCondTree, myHead = create_tree(cond_patt_bases, min_support)
        if myHead is not None:
            print('conditional tree for: ',new_freq_set)
            myCondTree.display()
            mine_tree(myHead, min_support, new_freq_set, freq_item_list)

# 测试结果
# conditional tree for:  {'y'}
#   Null tree   1
#    z   3
#     x   3
# conditional tree for:  {'y', 'x'}
#   Null tree   1
#    z   3
# conditional tree for:  {'t'}
#   Null tree   1
#    y   3
#     z   3
#      x   3
# conditional tree for:  {'t', 'z'}
#   Null tree   1
#    y   3
# conditional tree for:  {'t', 'x'}
#   Null tree   1
#    y   3
#     z   3
# conditional tree for:  {'t', 'z', 'x'}
#   Null tree   1
#    y   3
# conditional tree for:  {'s'}
#   Null tree   1
#    x   3
# conditional tree for:  {'x'}
#   Null tree   1
#    z   3
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值