使用FP-growth算法发现频繁项集

源码如下:

#coding=utf-8


'''
Created on Jun 14, 2011
FP-Growth FP means frequent pattern
the FP-Growth algorithm needs: 
1. FP-tree (class treeNode)
2. header table (use dict)

This finds frequent itemsets similar to apriori but does not 
find association rules.  

@author: Peter

使用FP-growth算法发现频繁项集

FP-growth只会扫描数据集两次,它发现频繁项集的基本过程如下:
    (1)构建FP树
    (2)从FP树中挖掘频繁项集

                            FP-growth算法
优点:一般要快于Apriorio
缺点:实现比较困难,在某些数据集上性能会下降。
适用数据类型:标称型数据。


    FP-growth算法将数据存储在一种称为FP树的紧凑数据结构中。FP代表频繁模式(Frequent
Pattern )。一棵FP树看上去与计算机科学中的其他树结构类似,但是它通过链接(link)来连接相
似元素,被连起来的元素项可以看成一个链表。


    同搜索树不同的是,一个元素项可以在一棵FP树中出现多次。FP树会存储项集的出现频率,
而每个项集会以路径的方式存储在树中。存在相似元素的集合会共享树的一部分。只有当集合之
间完全不同时,树才会分叉。树节点上给出集合中的单个元素及其在序列中的出现次数,路径
会给出该序列的出现次数。

FP-growth的一般流程
(1)收集数据:使用任意方法。
(2)准备数据:由于存储的是集合,所以需要离散数据。如果要处理连续数据,需要将它们
  量化为离散值。
(3)分析数据:使用任意方法。
(’)训练算法:构建一个FP树,并对树进行挖据。
(5)测试算法:没有测试过程。
(6)使用算法卜可用于识别经常出现的元素项,从而用于制定决策、推荐元素或进行预测
  等应时。


创建FP树的数据结构

 上面的程序给出了FP树中节点的类定义。类中包含用于存放节点名字的变量和1个计数值,
nodeLink变量用于链接相似的元素项(参考图12-I中的虚线)。类中还使用了父变量parent来
指向当前节点的父节点。通常情况下并不需要这个变量,因为通常是从上往下迭代访问节点的。
本章后面的内容中需要根据给定叶子节点上溯整棵树,这时就需要指向父节点的指针。最后,类
中还包含一个空字典变量,用于存放节点的子节点。
    程序清单12-1中包括两个方法,其中inc()对count变量增加给定值,而另一个方法disp()
用于将树以文本形式显示。后者对于树构建来说并不是必要的,但是它对于调试非常有用。


总结:
    FP-growth算法是一种用于发现数据集中频繁模式的有效方法。FP-growth算法利用Apriori原
则,执行更快。Apriori算法产生候选项集,然后扫描数据集来检查它们是否频繁。由于只对数据
集扫描两次,因此FP-growth算法执行更快。在FP-growth算法中,数据集存储在一个称为FP树的
结构中。FP树构建完成后,可以通过查找元素项的条件基及构建条件FP树来发现频繁项集。该
过程不断以更多元素作为条件重复进行,直到FP树只包含一个元素为止。
    可以使用FP-growth算法在多种文本文档中查找频繁单词。Twitter网站为开发者提供了大量的
API来使用他们的服务。利用Python模块Python-Twitter可以很容易访问Twitter。在Twitter源
上对某个话题应用FP-growth算法,可以得到一些有关该话题的摘要信息。频繁项集生成还有其
他的一些应用,比如购物交易、医学诊断及大气研究等。

'''
class treeNode:
    def __init__(self, nameValue, numOccur, parentNode):
        self.name = nameValue
        self.count = numOccur
        self.nodeLink = None
        self.parent = parentNode      #needs to be updated
        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树。在构建时,读人每个项集并将其添加到一条已经存在的路径中。如果该路径
不存在,则创建一条新路径。每个事务就是一个无序集合。假设有集合{z,x,y}和{{y,z,r},那么在
FP树中,相同项会只表示一次。为了解决此问题,在将集合添加到树之前,需要对每个集合进行
排序。排序基于元素项的绝对出现频率来进行。

上述代码中包含3个函数。第一个函数createTree()使用数据集以及最小支持度作为参数
来构建FP树。树构建过程中会遍历数据集两次。第一次遍历扫描数据集并统计每个元素项出现的
频度。这些信息被存储在头指针表中。接下来,扫描头指针表删掉那些出现次数少于minSup的
项.。如果所有项都不频繁,就不需要进行下一步处理O。接下来,对头指针表稍加扩展以便可
以保存计数值及指向每种类型第一个元素项的指针。然后创建只包含空集合。的根节点。最后,
再一次遍历数据集,这次只考虑那些频繁项O。这些项已经如表12-2所示那样进行了排序,然后
调用updateTree()方法0。接下来讨论函数updateTree()o
    为了让FP树生长①,需调用updateTree,其中的输人参数为一个项集。图12-3给出了
updateTree()中的执行细节。该函数首先测试事务中的第一个元素项是否作为子节点存在。如
果存在的话,则更新该元素项的计数;如果不存在,则创建一个新的treeNode并将其作为一个
子节点添加到树中。这时,头指针表也要更新以指向新的节点。更新头指针表需要调用函数
updateHeader(),接下来会讨论该函数的细节。updateTree()完成的最后一件事是不断迭代
调用自身,每次调用时会去掉列表中第一个元素)o
程序清单12-2中的最后一个函数是updateHeader(),
它确保节点链接指向树中该元素项的
每一个实例。
从头指针表的nodeLink开始,一直沿着nodeLink直到到达链表末尾。
这就是一个
链表。当处理树的时候,一种很自然的反应就是迭代完成每一件事。
当以相同方式处理链表时可
能会遇到一些间题,原因是如果链刻良长可能会遇到迭代调用的次数限制。
在运行上例之前,还需要一个真正的数据集。这可以从代码库中获得,
人。
loadSimpDat()函数会返回一个事务列表。这和表12-1中的事务相同。
或者直接手工输
后面构建树时会
使用
createTree()函数,而该函数的输人数据类型不是列表。其需要的是一部字典,其中
项集为字典中的键,
而频率为每个键对应的取值。createInitSet()用于实现上述从列表
到字典的类型转换过程。将下列代码添加到fpGrowth.py文件中。

从FP树中抽取频繁项集的三个基本步骤如下:
(1)从FP树中获得条件模式基;
(2)利用条件模式基,构建一个条件FP树;
(3)迭代重复步骤(1)步骤(2),直到树包含一个元素项为止。

'''
def createTree(dataSet, minSup=1): #create FP-tree from dataset but don't mine
    headerTable = {}
    #go over dataSet twice
    for trans in dataSet:#first pass counts frequency of occurance
        for item in trans:
            headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
    for k in headerTable.keys():  #remove items not meeting minSup
        if headerTable[k] < minSup: 
            del(headerTable[k])
    freqItemSet = set(headerTable.keys())
    #print 'freqItemSet: ',freqItemSet
    if len(freqItemSet) == 0: return None, None  #if no items meet min support -->get out
    for k in headerTable:
        headerTable[k] = [headerTable[k], None] #reformat headerTable to use Node link 
    #print 'headerTable: ',headerTable
    retTree = treeNode('Null Set', 1, None) #create tree
    for tranSet, count in dataSet.items():  #go through dataset 2nd time
        localD = {}
        for item in tranSet:  #put transaction items in order
            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)#populate tree with ordered freq itemset
    return retTree, headerTable #return tree and header table

def updateTree(items, inTree, headerTable, count):
    if items[0] in inTree.children:#check if orderedItems[0] in retTree.children
        inTree.children[items[0]].inc(count) #incrament count
    else:   #add items[0] to inTree.children
        inTree.children[items[0]] = treeNode(items[0], count, inTree)
        if headerTable[items[0]][1] == None: #update header table 
            headerTable[items[0]][1] = inTree.children[items[0]]
        else:
            updateHeader(headerTable[items[0]][1], inTree.children[items[0]])
    if len(items) > 1:#call updateTree() with remaining ordered items
        updateTree(items[1::], inTree.children[items[0]], headerTable, count)
        
def updateHeader(nodeToTest, targetNode):   #this version does not use recursion
    while (nodeToTest.nodeLink != None):    #Do not use recursion to traverse a linked list!
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode
'''
发现以给定元素项结尾的所有路径的函数
    上述程序中的代码用于为给定元素项生成一个条件模式基,这通过访问树中所有包含给定元
素项的节点来完成。当创建树的时候,使用头指针表来指向该类型的第一个元素项,该元素项也
会链接到其后续元素项。函数findPrefixPath()遍历链表直到到达结尾。每遇到一个元素项都
会调用ascendTree()来上溯FP树,并收集所有遇到的元素项的名称.。该列表返回之后添加到
条件模式基字典condPats中。

'''        
def ascendTree(leafNode, prefixPath): #ascends from leaf node to root
    if leafNode.parent != None:
        prefixPath.append(leafNode.name)
        ascendTree(leafNode.parent, prefixPath)
    
def findPrefixPath(basePat, treeNode): #treeNode comes from header table
    condPats = {}
    while treeNode != None:
        prefixPath = []
        ascendTree(treeNode, prefixPath)
        if len(prefixPath) > 1: 
            condPats[frozenset(prefixPath[1:])] = treeNode.count
        treeNode = treeNode.nodeLink
    return condPats
'''
    对于每一个频繁项,都要创建一棵条件FP树。我们会为z, x以及其他频繁项构建条件树。可
以使用刚才发现的条件模式基作为输人数据,并通过相同的建树代码来构建这些树。然后,我们
会递归地发现频繁项、发现条件模式基,以及发现另外的条件树。举个例子来说,假定为频繁项
t}}建一个条件FP树,然后对{f t}Y} " ft}x} ,…重复该过程。元素项t的条件FP树的构建过程如图12-4
所示。

递归查找频繁项集的mineTree函数

    创建条件树、前缀路径以及条件基的过程听起来比较复杂,但是代码起来相对简单。程序首
先对头指针表中的元素项按照其出现频率进行排序。(记住这里的默认顺序是按照从小到大。)O
然后,将每一个频繁项添加到频繁项集列表freqItemList中。接下来,递归调用程序清单12-4
中的findPrefixPa七h()函数来创建条件基。该条件基被当成一个新数据集输送给create-
Tree ( )函数。O这里为函数createTree()添加了足够的灵活性,以确保它可以被重用于构建
条件树。最后,如果树中有元素项的话,递归调用mineTree()函数
'''
def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
    bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1])]#(sort header table)
    for basePat in bigL:  #start from bottom of header table
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        #print 'finalFrequent Item: ',newFreqSet    #append to set
        freqItemList.append(newFreqSet)
        condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
        #print 'condPattBases :',basePat, condPattBases
        #2. construct cond FP-tree from cond. pattern base
        myCondTree, myHead = createTree(condPattBases, minSup)
        #print 'head from conditional tree: ', myHead
        if myHead != None: #3. mine cond. FP-tree
            #print 'conditional tree for: ',newFreqSet
            #myCondTree.disp(1)            
            mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)

def loadSimpDat():
    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 createInitSet(dataSet):
    retDict = {}
    for trans in dataSet:
        retDict[frozenset(trans)] = 1
    return retDict
'''
发现twitter源中的共现词(CO-oocurring word)

(1)收集数据:
(2)准备数据:
  单词集合。
(3)分析数据:
(4)训练算法:
(5)侧试算法:
(6)使用算法:
使用Python-twitter模块来访问推文。
编写一个函数来去掉URL、去掉标点、转换成小写并从字符串中建立一个
在Python提示符下查看准备好的数据,确保它的正确性。
使用本章前面开发的。reateTree()与mineTree()函数执}TFP-growth算法。
这里不适用。
本例中没有包含具体应用,可以考虑用于情感分析或者查询推荐领域。

'''
import twitter
from time import sleep
import re
'''
    函数getLotsOfTweets()处理认证然后创建一个空列表。搜索API可以一次获得100条推
文。每100条推文作为一页,而Twitte吮许一次访问14页。在完成搜索调用之后,有一个6秒钟的
睡眠延迟,这样做是出于礼貌,避免过于频繁的访问请求。print语句用于表明程序仍在执行没
有死掉。
文本解析及合成代码

    我们还记得FP树的构建是通过每次应用一个实例的方式来完成的。这里假设已经获得了所有
数据,所以刚才是直接遍历所有的数据来构建FP树的。实际上可以重写。reateTree()函数,每
次读人一个实例,并随着Twitter流的不断输人而不断增长树。FP-growth算法还有一个map-reduce
版本的实现,它也很不错,可以扩展到多台机器上运行。Google使用该算法通过遍历大量文本来
发现频繁共现词,其做法和我们刚才介绍的例子非常类似①。

'''
def textParse(bigString):
    urlsRemoved = re.sub('(http:[/][/]|www.)([a-z]|[A-Z]|[0-9]|[/.]|[~])*', '', bigString)    
    listOfTokens = re.split(r'\W*', urlsRemoved)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]

def getLotsOfTweets(searchStr):
    CONSUMER_KEY = ''
    CONSUMER_SECRET = ''
    ACCESS_TOKEN_KEY = ''
    ACCESS_TOKEN_SECRET = ''
    api = twitter.Api(consumer_key=CONSUMER_KEY, consumer_secret=CONSUMER_SECRET,
                      access_token_key=ACCESS_TOKEN_KEY, 
                      access_token_secret=ACCESS_TOKEN_SECRET)
    #you can get 1500 results 15 pages * 100 per page
    resultsPages = []
    for i in range(1,15):
        print "fetching page %d" % i
        searchResults = api.GetSearch(searchStr, per_page=100, page=i)
        resultsPages.append(searchResults)
        sleep(6)
    return resultsPages

def mineTweets(tweetArr, minSup=5):
    parsedList = []
    for i in range(14):
        for j in range(100):
            parsedList.append(textParse(tweetArr[i][j].text))
    initSet = createInitSet(parsedList)
    myFPtree, myHeaderTab = createTree(initSet, minSup)
    myFreqList = []
    mineTree(myFPtree, myHeaderTab, minSup, set([]), myFreqList)
    return myFreqList

#minSup = 3
#simpDat = loadSimpDat()
#initSet = createInitSet(simpDat)
#myFPtree, myHeaderTab = createTree(initSet, minSup)
#myFPtree.disp()
#myFreqList = []
#mineTree(myFPtree, myHeaderTab, minSup, set([]), myFreqList)


你可能感兴趣的:(使用FP-growth算法发现频繁项集)