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'}]