用FP-growth算法发现频繁项集(一)

概述

  • 优点:一般要快于Apriori
  • 缺点:实现比较困难,在某些数据集上性能会下降
  • 适用数据类型:标称型数据

FP-growth算法将数据存储在一种称为FP树的紧凑数据结构中。FP代表频繁模式(Frequent Pattern)

FP树与其他树结构类似。但它会把相似元素连接起来,被连起来的元素项可以看作是链表。如下图所示。


图1 一棵FP树

一个元素项可以在一棵FP树出现多次。

FP树会存储项集的出现频率,而每个项集会以路径的方式存储在树中。

相似项之间的链接即节点链接(node link),用于快速发现相似项的位置。

下面再简单说明一下。

事务ID 事务中的元素项 最小支持度过滤后
001 r,z,h,j,p r,z
002 z,y,x,w,v,u,t,s z,y,x,t,s
003 z z
004 r,x,n,o,s r,x,s
005 y,r,x,z,q,t,p y,r,x,z,t
006 y,z,x,e,q,s,t,m y,z,x,s,t

上表是生成上面FP树的数据。第二列“事务中的元素项”是原数据,然后经过最小支持度(关于支持度可以看之前Apriori算法的文章)过滤后,留下频繁项集。

图1树中节点z:5说明元素项出现5次。从z:5出发,沿着路径x:3y:3,说明{z,x,y}出现3次。z:5 -> r:1说明{z,r}出现1次。到这里两条路径,z一共被使用4次,所以剩下的一次就是它自身{z}。通过观察上表最后一列“最小支持度过滤后”,可以知道上述结论的正确性。以此类推,就能大概知道FP树和频繁项集的对应关系了。

构建FP树

FP树的数据结构

因为FP树比较复杂,所以定义一个类来保存节点信息。

class treeNode:
    def __init__(self, name, count, parentNode):
        self.name = name                 # 节点名字
        self.count = count               # 计数值
        self.nodeLink = None             # 用于链接相似元素项
        self.parent = parentNode
        self.children = {} 
    
    def inc(self, numOccur):
        self.count += numOccur
        
    # 将树以文本形式显示
    def disp(self, ind=1):
        print('  '*ind, self.name, ' ', self.count)
        for child in self.children.values():
            child.disp(ind+1)

测试一下。


开始构建

除了FP树外,还需要一个头指针表来指向给定类型的第一个实例。利用头指针表可以快速访问FP树中一个给定类型的所有元素。如下图。


图2 带头指针表的FP树

集合是无序的,假设有{z,x,y}{y,z,x},那么在FP树中只会表示一条路径。为了解决这个问题,在将集合添加到树前,先基于元素项的出现频率进行排序。如下表。

事务ID 事务中的元素项 最小支持度过滤后 排序后
001 r,z,h,j,p r,z z,r
002 z,y,x,w,v,u,t,s z,y,x,t,s z,x,y,s,t
003 z z z
004 r,x,n,o,s r,x,s x,s,r
005 y,r,x,z,q,t,p y,r,x,z,t z,x,y,r,t
006 y,z,x,e,q,s,t,m y,z,x,s,t z,x,y,s,t

进行过滤和排序之后,就可以构建FP树了。从空集开始,不断添加频繁项集。如下图。


图3 FP树构建过程示意图

大致了解FP树构建过程后,接下来就是代码实现了。

def createTree(dataSet, minSup=1):
    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())
    print('freqItemSet: ',freqItemSet)
    # 没有满足最小支持度的项,则退出
    if len(freqItemSet) == 0: 
        return None, None
    # 扩展头指针表以便后面的链接
    for k in headerTable:
        headerTable[k] = [headerTable[k], None] 
    print('headerTable: ',headerTable)
    # 创建树
    retTree = treeNode('根节点', 0, 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)]
            # 使用排序后的频繁项集对树进行填充
            updateTree(orderedItems, retTree, headerTable, count)
    return retTree, headerTable

def updateTree(items, inTree, headerTable, count):
    # 检查items[0]是否已在子节点
    if items[0] in inTree.children:
        inTree.children[items[0]].inc(count) # 是的话直接增加count
    else:   # 否则items[0]加到inTree.children
        inTree.children[items[0]] = treeNode(items[0], count, inTree)
        # 更新头指针表
        if headerTable[items[0]][1] == None:
            headerTable[items[0]][1] = inTree.children[items[0]]
        else:
            updateHeader(headerTable[items[0]][1], inTree.children[items[0]])
    # 递归填充剩下的项
    if len(items) > 1:
        updateTree(items[1::], inTree.children[items[0]], headerTable, count)
        
def updateHeader(nodeToTest, targetNode):   
    while (nodeToTest.nodeLink != None):  
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode

第一个函数createTree()有两个参数,数据集和最小支持度。这里支持度的定义跟Apriori算法的不一样,是元素项出现的次数。树构建过程中一共遍历数据集两次。

为了FP树生长(growth),需调用updateTree()进行填充。也就是图3的过程。

updateHeader()函数用于更新头指针表,确保节点链接指向树中该元素项的每一个实例。

接下来,模拟一个简单数据集,并实际构建FP树。

def createSimpDat():
    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']]
    return simpDat

def transToDict(dataSet):
    retDict = {}
    for trans in dataSet:
        retDict[frozenset(trans)] = 1
    return retDict

data = transToDict(createSimpDat())
FPTree, headerTable = createTree(data, 3)

看一下data

也就是前面表中的数据。

再看一下FP树


对照最开始的图1,可以发现是一样的结构。

构建好FP树后,就可以使用它来进行频繁项集挖掘了。


画图不易吖~都看到最后了,要不~点个赞?加波关注?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容