1. 程式人生 > >閉頻繁項集的挖掘——Closet演算法

閉頻繁項集的挖掘——Closet演算法

 Closet演算法有很大一部分涉及到了FP-Growth演算法,但是FP-Growth什麼的大牛們都寫了很多就不多贅述了吧。

  話不多說直接上方法。

  首先,對事務資料庫進行掃描,得到一個根據項的支援度從大到小排序的項集合F_list,將不頻繁的項刪除。

  然後根據F_list對資料集進行劃分對每一個劃分出來的資料集遞迴的進行閉項集的挖掘,直到劃分出的子集中F_list為空。

舉個例子具體說明CLOSET演算法的具體流程:

事物的資料集如圖所示,假設支援度的閾值為2

  第一遍掃描資料庫,得到了一個F_list: <c:4,e:4,f:4,a:3,d:2> 。將所有事務中的專案集按照

F_list中的順序進行排序

然後,將資料集進行劃分:

包含有d的模式集

包含a不包含d的模式集

包含f不包含ad的模式集

得到的是一些投影資料庫,比如d的投影資料庫如圖所示,d的投影資料庫,就是篩選出所有包含d事務中,只保留所有在F_list中排在d之前的項得到的事務集,形成了一個新的事務集,就是d的投影資料庫。

  其它的投影資料庫以此類推,保證得到的劃分是包含d的模式集,包含a但不包含d的模式集等。

  得到了某個項投影資料庫之後,首先看是否有在投影資料庫中的每個事務中都出現的項,如果有的話。它們和這個項的並集就是一個閉頻繁項集,比如在這個例子中,c,f,a出現在了每一個事務中,所以{e,f,a,d}

就可以作為一個閉頻繁項集被挖掘。

  再觀察a的投影資料庫:首先,.沒有發現有的項出現在了所有的模式中,那麼說明a本身就是一個閉頻繁項集。


  接著,遞迴地對這個投影資料庫進行掃描,得到一個F_list= <c:2,e:2,f:2>對這個投影資料庫進行遞迴挖掘,繼續劃分,得到一個包含f的模式集,包含e但不包含f的模式集,包含c但不包含ef的模式集,對f,e,c的投影資料庫繼續進行掃描,如果發現沒有頻繁項,那麼最終得到的F_list應該為空,則當前在對哪個項集進行投影,那個項集就是閉頻繁項集,函式可以返回。

  最後還要考慮的一點就是,如果當前挖掘出的項集的超集已經作為閉頻繁項集被挖掘出,並且它們的支援度相同,那麼當前挖掘出的項集不是閉頻繁項集。比如對於

a的投影資料庫,a的支援度大於所有項的支援度,ac,af,都是efad的子集並且支援度相同,而ea的投影資料庫為空,所以ea是一個閉頻繁項集,a也是一個閉頻繁項集。對剩下的閉頻繁項集的挖掘以此類推。

  CLOSET演算法的流程可以描述為如下圖所示(F.C.I表示Frequent closed itemsets):


可以發現,CLOSET演算法可以利用FP樹進行空間劃分和閉頻繁項集的遞迴挖掘演算法,所以演算法是可以基於FP樹實現的。

接下來,放程式碼~哈哈~有些重點需要在程式碼裡說明一下~

  根據FP-Growth演算法改編的CLOSET演算法,程式碼實現的關鍵點是一定要保證專案排序一致。規定F_list按照支援度排序,當支援度相同時,就需要保證在支援度相同的情況下,F_list中的項是按照字典序排列的,這樣才能夠保證遞迴建立的FP樹始終按照一致的順序進行建立並進行挖掘,否則會導致程式碼的出錯。比如d的投影資料庫的F_list<a2,e2,f2>。建樹時,是按照a節點離根節點更近,f節點離根節點更遠的規則建立的,而如果挖掘的時候不是從f節點開始挖掘而是從a節點開始挖掘,會導致先加入{a,d}作為閉頻繁項集,再加入{a,d,f}作為閉頻繁項集。

# -*- coding: utf-8 -*-
"""
Created on Sun Dec 24 21:15:51 2017

@author: Administrator
"""

class treeNode:
    def __init__(self,nameValue,numOccur,parentNode):
        #節點名稱
        self.name=nameValue
        #節點在該路徑總共出現次數
        self.count=numOccur
        #nodeLink:下一個同名節點的地址,構成整個頭表的鏈,方便對FP樹的快速訪問
        self.nodeLink=None
        #節點的前一個節點(父節點)
        self.parent=parentNode
        #節點的孩子節點,因為孩子節點的個數不確定,並非什麼二叉樹,所以用一個字典存放,key:子節點的名稱,value:子節點的地址
        self.children={}

    def inc(self,numOccur):
        self.count+=numOccur



def createTree(dataSet,minSup):
    # 首先我們思考一下建立FP樹的過程

    '''
    一般我們分兩步:
    第一步:先掃描一遍資料集,過濾掉非頻繁項,得到頻繁1項集,同時需要儲存它們出現的次數,因為第二遍掃描記錄時,需要根據
    第二步:再一次掃描資料集,對每一條記錄,去掉非頻繁項,然後對記錄中的項按出現次數從高到底排序,然後插入FP樹
    '''
    # 第一步
    headerTable={}
    for T in dataSet:
        for item in T:
            headerTable[item]=headerTable.get(item,0)+dataSet[T]
    #去掉不滿足最小支援度的item

    for k in headerTable.keys():
        if headerTable[k]<minSup:
            del headerTable[k]

    freqItemSet=set(headerTable.keys())
    if len(freqItemSet)==0:
        return None,None

    '''
    頭指標表:FP-growth演算法還需要頭表,儲存每個節點鏈的第一個節點地址,每個節點通過nodeLink指向下一個節點,
    這樣就構成一個單鏈表
    '''
    # 初始化頭表
    for k in headerTable:
        headerTable[k]=[headerTable[k],None]
    
    root=treeNode('Null Set',0,None)
    #第二次掃描資料集,對每一條記錄先過濾掉非頻繁項,後排序,再插入到FP樹
    dataSet_tuple=dataSet.items()
    for tranSet,count in dataSet_tuple:
        localD={}
        for item in tranSet:
            if item in freqItemSet:
                localD[item]=headerTable[item][0]
        #item in localD are ordered
        #元素按照鍵值從大到小,關鍵字按照字典序排列
        if len(localD)>0:
            orderedItems = [v[0] for v in sorted(localD.items(),key = lambda p:(-p[1],p[0]))]
            # 插入到FP樹中,每一條記錄都是從根節點插入
            insert_treeNode(orderedItems,root,headerTable,count)
    return root,headerTable



def insert_treeNode(items, into_treeNode, headerTable, count):
    '''
    Create the FP-Tree
    Insert the node and update the HeaderTable
    '''
    if items[0] in into_treeNode.children:
        into_treeNode.children[items[0]].inc(count)
    else:
        into_treeNode.children[items[0]] = treeNode(items[0], count, into_treeNode) #初始化要插入節點,並插入到當前節點

        if headerTable[items[0]][1] == None:
            headerTable[items[0]][1] = into_treeNode.children[items[0]]
        else:
            updateHeader(headerTable[items[0]][1], into_treeNode.children[items[0]]) #headerTable[items[0]][1]是一個樹節點

    if len(items) > 1: 
        insert_treeNode(items[1:], into_treeNode.children[items[0]], headerTable, count) #此時插入的的位置從into_treeNode.children[items[0]]開始了


def updateHeader(nodeToTest, targetNode):
    while (nodeToTest.nodeLink != None):
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode

    
def loadDat():
    '''
    Load the Data
    '''
    datalist = []
    file = open('test.txt')
    diction = {}
    
    while 1:
        line = file.readline()
        l = line[:-2].split(' ')
        l = l[2:]
        if l:
            datalist.append(l)
        for i in l:
            if i not in diction:
                diction[i] = 1
            else:
                diction[i] += 1
        if not line:
            break
        pass
    
    file.close() 
    return datalist
     
def createInitSet(dataSet):
    '''
    Create the Dict
    '''
    retDict={}
    for Tid in dataSet:
        retDict[frozenset(Tid)]=retDict.get(frozenset(Tid),0)+1
    return retDict

def findPrefixPath(basePat,treeNode):
    '''
    BasePat: the item
    TreeNode: the item in the HeadTable is the first node
    '''
    condPats = {} #condition pattern
    while treeNode != None:
        prefixPath = []
        ascendTree(treeNode, prefixPath)
        if len(prefixPath) > 1:
            #fronzenset as key!
            condPats[frozenset(prefixPath[1:])] = treeNode.count
        treeNode = treeNode.nodeLink
    return condPats

def ascendTree(treeNode, prefixPath): #找當前節點到根節點的路徑(從下而上順序),路徑按item的名字存放在prefixPath
    if treeNode.parent != None:
        prefixPath.append(treeNode.name)
        ascendTree(treeNode.parent, prefixPath)

def CLOSET(FPtree, headerTable, minSup, preFix, closedDict, support):
    #minSup:支援度,closedDict:頻繁閉項集,preFix:該項的字首,FPtree:構建的FP樹,headerTable:FP樹對應的頭表,
    #support : the preFix's support count
    #If the preFix's support is larger than all the condition pattern, it is the closed pattern
    #元素鍵值相同時,按照字典序排序
    bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p:(-p[1][0],p[0]),reverse = True)]  #從頻次出現低項開始挖掘
    #如果字首的支援度大於所有的superset的支援度,則字首本身可能就是一個CLOSED ITEMSET
    if support > headerTable[bigL[-1]][0]:
        flag = True
        for k in closedDict:
            if frozenset(preFix).issubset(k) and closedDict[k] >= support:
                flag = False
        if flag == True:
            closedDict[frozenset(preFix)]=support

    for basePat in bigL:
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        condPattBases = findPrefixPath(basePat, headerTable[basePat][1])    #求條件模式基
        myCondTree,myHead = createTree(condPattBases, minSup)
        if myHead == None:
            flag = True
            for k in closedDict:

                if frozenset(newFreqSet).issubset(k) and closedDict[k] >= headerTable[basePat][0]:
                    flag = False
            if flag == True:
                closedDict[frozenset(newFreqSet)]=headerTable[basePat][0]
    #Mine the closed frequent itemsets using CLOSET recursively
        if myHead != None:
            CLOSET(myCondTree, myHead, minSup, newFreqSet, closedDict, headerTable[basePat][0]) #FP樹中的遞迴有一個特點:沒有返回值,需要記錄的資料都放在引數中了,上層可以直接拿到資料

if __name__=='__main__':

    dataSet = loadDat()
    initSet  = createInitSet(dataSet)
    minsup = 0.4 * len(dataSet)
    FPtree,HeaderTab = createTree(initSet,minsup)
    freqDict={}
    CLOSET(FPtree,HeaderTab,minsup,set([]),freqDict, minsup)
    print freqDict