1. 程式人生 > 實用技巧 >機器學習實戰---使用FP-growth演算法來高效發現頻繁項集

機器學習實戰---使用FP-growth演算法來高效發現頻繁項集

一:參考資料

(一)機器學習實戰

(二)以下3篇簡單瞭解

https://baijiahao.baidu.com/s?id=1661651339862291744&wfr=spider&for=pc(不全,但簡單)

https://blog.csdn.net/qq_40587575/article/details/79997195(稍微看看)

https://blog.csdn.net/qq1010885678/article/details/45244829(排版還可以)

(三)下面PPT不錯(可以完全理解,且案例豐富)

https://wenku.baidu.com/view/5a28e351bed126fff705cc1755270722182e594b.html

(下面兩則案例來自此處)

二:案例一(FP-growth演算法原理)

三:案例二(更詳細)尤其是頻繁集挖掘

四:FP樹結構定義

#一:FP樹結構定義
class treeNode:
    def __init__(self,nameValue,nameOccur,parentNode):  #nameValue節點名稱,nameOccur計數,parentNode指向父節點
        self.name = nameValue   #節點名稱
        self.count = nameOccur  #計數器
        self.nodeLink 
= None #用來存放節點資訊,用來指向下一個同類(同名稱)的節點 self.parent = parentNode #指向父節點 self.children = {} #子節點使用字典進行存放 def inc(self,numOccur): self.count += numOccur

五:資料載入

#二:載入資料
def loadSimDat():
    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 #返回字典型別{frozenset({'j', 'h', 'z', 'r', 'p'}): 1, frozenset({'y', 'v', 'x', 'w', 'u', 's', 't', 'z'}): 1...} return retDict

六:根據createInitSet方法得到的資料集型別,建立FP樹 和 FP條件樹

#三:根據createInitSet方法得到的資料集型別,建立FP樹 和 FP條件樹
#1.建樹主函式
def createTree(dataSet,minSup=1):
    headerTable = {}    #存放頭指標表
    for trans in dataSet:   #遍歷資料,注意我們使用dataSet.items()可以遍歷(key,val)對,直接遍歷dataSet是對key的操作
        for item in trans:  #item是frozenset型別
            headerTable[item] = headerTable.get(item,0) + dataSet[trans]    #計數操作,其中dataSet[trans]返回字典對於values

    #重點:進行初次剪枝,去掉那些單個元素支援度太低的資料元素
    keys = list(headerTable.keys())
    for k in keys:
        if headerTable[k] < minSup:
            del(headerTable[k]) #達到剪枝效果

    #檢查剪枝後是否還有資料存在
    freqItemSet = set(headerTable.keys())
    if len(freqItemSet) == 0:
        return None,None    #因為我們在正常情況下,返回的資料是FP樹和頭指標表

    #修改頭指標表的格式,使得可以指向同類節點(實現連結串列操作)
    for k in headerTable:
        headerTable[k] = [headerTable[k],None]  #多出的None用來儲存後面需要的節點資訊

    #先產生根節點
    retTree = treeNode("Null Set",1,None)   #節點名稱,出現次數,父節點

    #開始建樹
    for tranSet,count in dataSet.items():
        localD = {} #用來儲存本樣本frozenset({'j', 'h', 'z', 'r', 'p'}): 1的keys是否是頻繁項集
        for item in tranSet:
            if item in freqItemSet: #是在頻繁項集中
                localD[item] = headerTable[item][0] #新增到localD儲存次數
        if len(localD) > 0: #如果在本次樣本中有資料在頻繁項集中,則開始進行更新樹---重點
            #對我們得到的臨時localD頻繁項集進行排序,將在更新樹按照出現最高順序進行更新(例如最大堆,但不是)
            orderItems = [v[0] for v in sorted(localD.items(),key=lambda p:p[1],reverse=True)]  #大到小排序
            updateTree(orderItems,retTree,headerTable,count)

    return retTree,headerTable  #返回我們需要的樹和頭指標表

#2.按照傳入的一個樣本frozenset({'j', 'h', 'z', 'r', 'p'})《注意:是排序好的,大到小》,對樹進行更新
def updateTree(items,inTree,headerTable,count):
    """
    重點:我們使用遞迴方法建樹
    :param items: 是傳入的排序好的樣本frozenset({'j', 'h', 'z', 'r', 'p'})
    :param inTree: 是我們要建立的樹
    :param headerTable: 是我們要對頭指標表進行修改節點指向,
    :param count: 是本樣本在資料集中出現的次數
    """
    if items[0] in inTree.children: #已經存在該增加計數
        inTree.children[items[0]].inc(count)    #增加計數
    else:   #開始進行新增節點
        inTree.children[items[0]] = treeNode(items[0],count,inTree)  #節點按照特定的資料結構修改
        #由於我們有新增的節點,所以我們需要更新頭指標表中的nodeLink連結串列資訊,達到連結串列將同類型的節點連結
        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)

#3.更新頭指標表
def updateHeader(headerNode,newNode):
    while headerNode.nodeLink != None:
        headerNode = headerNode.nodeLink
    headerNode.nodeLink = newNode

七:開始從上面獲取的FP樹中,獲取各個資料的條件模式基

#四:開始從上面獲取的FP樹中,獲取各個資料的條件模式基
#1.從FP樹種獲取頭指標表中的各個資料的條件模式基(即找字首路徑,設定計數為當前節點計數值)
def findPrefixPath(treeNode):   #書本上多了一個引數??這裡直接傳我們指定的節點,查詢字首路徑
    condPats = {}
    while treeNode != None:
        prefixPath = []
        ascendTree(treeNode,prefixPath) #獲取字首路徑
        #儲存字首路徑,和計數值
        if len(prefixPath) > 1: #保證除了本身節點之外,還存在其他字首
            condPats[frozenset(prefixPath[1:])] = treeNode.count    #注意:使用frozenset型別作為key,並且儲存時不含有本身節點
        treeNode = treeNode.nodeLink    #遍歷下一個同類型節點

    return condPats #返回所有的字首路徑

#2.向上遞迴遍歷樹,找字首節點資訊
def ascendTree(treeNode,prefixPath):    #將結果儲存在prefixPath中
    while treeNode.parent != None:  #除了根節點(無用)之外,都進行新增節點
        prefixPath.append(treeNode.name)
        treeNode = treeNode.parent

八:開始查詢頻繁集(注意:上面只對單個數據進行了一次支援度裁剪,下面會對各個組合進行裁剪)

#五:開始查詢頻繁集(注意:上面只對單個數據進行了一次支援度裁剪,下面會對各個組合進行裁剪)
#根據條件基模型來構建條件FP樹
def mineTree(headerTable,minSup,preFix,freqItemList):
    """
    :param headerTable: 是我們生成FP樹時,生成的頭指標表
    :param minSup: 最小支援度,用來對複合的資料進行篩選
    :param preFix: 首次為空集合,用來儲存當前字首
    :param freqItemList: 首次為空列表,用來儲存所有的頻繁項集
    """
    #對頭指標表中的元素按照支援度(出現頻率)從小到大排序
    bigL = [v[0] for v in sorted(headerTable.items(),key=lambda p:p[1][0])]
    #從滿足最小支援度的頭指標表的最小元素開始,遍歷頭指標表,挖掘頻繁資料項
    for basePat in bigL:    #最好只思考一次迴圈資料
        #儲存當前字首路徑
        newFreqSet = preFix.copy()
        #將當前頻繁元素加入頻繁集合中(這裡就是將頭指標表元素加入---已經是滿足的)
        newFreqSet.add(basePat) #都是frozenset型別---------注意:
        #1.newFreqSet.add(basePat)這一步我們可以看案例I5推導,這裡先加入I5,再獲取條件FP樹,
        #之後根據HeaderTable判斷是否為None
        #由案例可知,返回非None,
        #2.對headerTable中I2 I1建樹,newFreqSet中含有I5,再分別加上I1,I2
        #2.1先加上I1,即得到I1 I5
        #之後根據HeaderTable判斷是否為None
        #由案例可知,返回非None,
        #2.1.1對headerTable中I2建樹,newFreqSet中含有I1 I5,再加上I2 即可得到I2 I1 I5
        #2.2再在I5基礎上,加上I2,即可得到I2 I5
        #之後根據HeaderTable判斷是否為None
        #由案例可知,返回None,退出
        #得到I2 I5, I1 I5,    I2 I1 I5

        #將每個頻繁元素新增到頻繁項集列表中
        freqItemList.append(newFreqSet)
        #將每個頻繁項,都呼叫findPrefixPath函式,找到字首路徑
        condPattBases = findPrefixPath(headerTable[basePat][1])
        #重點:根據當前元素項生成的字首路徑和最小支援度,生成條件FP樹(內部會增加計數值)
        myCondTree,myHead = createTree(condPattBases,minSup)    #從這裡可以看出,我們要儲存原始資料和字首路徑發現函式返回相同資料型別
        #如果條件FP樹中有元素項,則可以再次遞迴生成條件樹
        if myHead != None:  #所以對於我們新的myHead必須在使用條件模型基時,滿足最小支援度
            #遞迴挖掘該條件樹,每次向上推一個節點(條件模型基都不包含本節點,所以一直在向上走)
            mineTree(myHead,minSup,newFreqSet,freqItemList)

九:結果測試

dataSet = createInitSet(loadSimDat())
tree,headertable = createTree(dataSet,3)    #注意:因為我們建樹的時候,支援度相同的節點不止一個,比如t:3,s:3,所以建樹結果順序可能不同
freqItemList = []
mineTree(headertable,3,set([]),freqItemList)
print(freqItemList)
[{'r'}, {'s'}, {'x', 's'}, {'y'}, {'z', 'y'}, {'x', 'y'}, {'z', 'x', 'y'}, {'t'}, {'z', 't'}, {'x', 't'}, {'z', 'x', 't'}, {'y', 't'}, {'z', 'y', 't'}, {'y', 't', 'x'}, {'z', 'y', 't', 'x'}, {'x'}, {'z', 'x'}, {'z'}]

全部程式碼:

#一:FP樹結構定義
class treeNode:
    def __init__(self,nameValue,nameOccur,parentNode):  #nameValue節點名稱,nameOccur計數,parentNode指向父節點
        self.name = nameValue   #節點名稱
        self.count = nameOccur  #計數器
        self.nodeLink = None    #用來存放節點資訊,用來指向下一個同類(同名稱)的節點
        self.parent = parentNode    #指向父節點
        self.children = {}  #子節點使用字典進行存放

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

#二:載入資料
def loadSimDat():
    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

    #返回字典型別{frozenset({'j', 'h', 'z', 'r', 'p'}): 1, frozenset({'y', 'v', 'x', 'w', 'u', 's', 't', 'z'}): 1...}
    return retDict

#三:根據createInitSet方法得到的資料集型別,建立FP樹 和 FP條件樹
#1.建樹主函式
def createTree(dataSet,minSup=1):
    headerTable = {}    #存放頭指標表
    for trans in dataSet:   #遍歷資料,注意我們使用dataSet.items()可以遍歷(key,val)對,直接遍歷dataSet是對key的操作
        for item in trans:  #item是frozenset型別
            headerTable[item] = headerTable.get(item,0) + dataSet[trans]    #計數操作,其中dataSet[trans]返回字典對於values

    #重點:進行初次剪枝,去掉那些單個元素支援度太低的資料元素
    keys = list(headerTable.keys())
    for k in keys:
        if headerTable[k] < minSup:
            del(headerTable[k]) #達到剪枝效果

    #檢查剪枝後是否還有資料存在
    freqItemSet = set(headerTable.keys())
    if len(freqItemSet) == 0:
        return None,None    #因為我們在正常情況下,返回的資料是FP樹和頭指標表

    #修改頭指標表的格式,使得可以指向同類節點(實現連結串列操作)
    for k in headerTable:
        headerTable[k] = [headerTable[k],None]  #多出的None用來儲存後面需要的節點資訊

    #先產生根節點
    retTree = treeNode("Null Set",1,None)   #節點名稱,出現次數,父節點

    #開始建樹
    for tranSet,count in dataSet.items():
        localD = {} #用來儲存本樣本frozenset({'j', 'h', 'z', 'r', 'p'}): 1的keys是否是頻繁項集
        for item in tranSet:
            if item in freqItemSet: #是在頻繁項集中
                localD[item] = headerTable[item][0] #新增到localD儲存次數
        if len(localD) > 0: #如果在本次樣本中有資料在頻繁項集中,則開始進行更新樹---重點
            #對我們得到的臨時localD頻繁項集進行排序,將在更新樹按照出現最高順序進行更新(例如最大堆,但不是)
            orderItems = [v[0] for v in sorted(localD.items(),key=lambda p:p[1],reverse=True)]  #大到小排序
            updateTree(orderItems,retTree,headerTable,count)

    return retTree,headerTable  #返回我們需要的樹和頭指標表

#2.按照傳入的一個樣本frozenset({'j', 'h', 'z', 'r', 'p'})《注意:是排序好的,大到小》,對樹進行更新
def updateTree(items,inTree,headerTable,count):
    """
    重點:我們使用遞迴方法建樹
    :param items: 是傳入的排序好的樣本frozenset({'j', 'h', 'z', 'r', 'p'})
    :param inTree: 是我們要建立的樹
    :param headerTable: 是我們要對頭指標表進行修改節點指向,
    :param count: 是本樣本在資料集中出現的次數
    """
    if items[0] in inTree.children: #已經存在該增加計數
        inTree.children[items[0]].inc(count)    #增加計數
    else:   #開始進行新增節點
        inTree.children[items[0]] = treeNode(items[0],count,inTree)  #節點按照特定的資料結構修改
        #由於我們有新增的節點,所以我們需要更新頭指標表中的nodeLink連結串列資訊,達到連結串列將同類型的節點連結
        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)

#3.更新頭指標表
def updateHeader(headerNode,newNode):
    while headerNode.nodeLink != None:
        headerNode = headerNode.nodeLink
    headerNode.nodeLink = newNode

#四:開始從上面獲取的FP樹中,獲取各個資料的條件模式基
#1.從FP樹種獲取頭指標表中的各個資料的條件模式基(即找字首路徑,設定計數為當前節點計數值)
def findPrefixPath(treeNode):   #書本上多了一個引數??這裡直接傳我們指定的節點,查詢字首路徑
    condPats = {}
    while treeNode != None:
        prefixPath = []
        ascendTree(treeNode,prefixPath) #獲取字首路徑
        #儲存字首路徑,和計數值
        if len(prefixPath) > 1: #保證除了本身節點之外,還存在其他字首
            condPats[frozenset(prefixPath[1:])] = treeNode.count    #注意:使用frozenset型別作為key,並且儲存時不含有本身節點
        treeNode = treeNode.nodeLink    #遍歷下一個同類型節點

    return condPats #返回所有的字首路徑

#2.向上遞迴遍歷樹,找字首節點資訊
def ascendTree(treeNode,prefixPath):    #將結果儲存在prefixPath中
    while treeNode.parent != None:  #除了根節點(無用)之外,都進行新增節點
        prefixPath.append(treeNode.name)
        treeNode = treeNode.parent

#五:開始查詢頻繁集(注意:上面只對單個數據進行了一次支援度裁剪,下面會對各個組合進行裁剪)
#根據條件基模型來構建條件FP樹
def mineTree(headerTable,minSup,preFix,freqItemList):
    """
    :param headerTable: 是我們生成FP樹時,生成的頭指標表
    :param minSup: 最小支援度,用來對複合的資料進行篩選
    :param preFix: 首次為空集合,用來儲存當前字首
    :param freqItemList: 首次為空列表,用來儲存所有的頻繁項集
    """
    #對頭指標表中的元素按照支援度(出現頻率)從小到大排序
    bigL = [v[0] for v in sorted(headerTable.items(),key=lambda p:p[1][0])]
    #從滿足最小支援度的頭指標表的最小元素開始,遍歷頭指標表,挖掘頻繁資料項
    for basePat in bigL:    #最好只思考一次迴圈資料
        #儲存當前字首路徑
        newFreqSet = preFix.copy()
        #將當前頻繁元素加入頻繁集合中(這裡就是將頭指標表元素加入---已經是滿足的)
        newFreqSet.add(basePat) #都是frozenset型別---------注意:
        #1.newFreqSet.add(basePat)這一步我們可以看案例I5推導,這裡先加入I5,再獲取條件FP樹,
        #之後根據HeaderTable判斷是否為None
        #由案例可知,返回非None,
        #2.對headerTable中I2 I1建樹,newFreqSet中含有I5,再分別加上I1,I2
        #2.1先加上I1,即得到I1 I5
        #之後根據HeaderTable判斷是否為None
        #由案例可知,返回非None,
        #2.1.1對headerTable中I2建樹,newFreqSet中含有I1 I5,再加上I2 即可得到I2 I1 I5
        #2.2再在I5基礎上,加上I2,即可得到I2 I5
        #之後根據HeaderTable判斷是否為None
        #由案例可知,返回None,退出
        #得到I2 I5, I1 I5,    I2 I1 I5

        #將每個頻繁元素新增到頻繁項集列表中
        freqItemList.append(newFreqSet)
        #將每個頻繁項,都呼叫findPrefixPath函式,找到字首路徑
        condPattBases = findPrefixPath(headerTable[basePat][1])
        #重點:根據當前元素項生成的字首路徑和最小支援度,生成條件FP樹(內部會增加計數值)
        myCondTree,myHead = createTree(condPattBases,minSup)    #從這裡可以看出,我們要儲存原始資料和字首路徑發現函式返回相同資料型別
        #如果條件FP樹中有元素項,則可以再次遞迴生成條件樹
        if myHead != None:  #所以對於我們新的myHead必須在使用條件模型基時,滿足最小支援度
            #遞迴挖掘該條件樹,每次向上推一個節點(條件模型基都不包含本節點,所以一直在向上走)
            mineTree(myHead,minSup,newFreqSet,freqItemList)

dataSet = createInitSet(loadSimDat())
tree,headertable = createTree(dataSet,3)    #注意:因為我們建樹的時候,支援度相同的節點不止一個,比如t:3,s:3,所以建樹結果順序可能不同
freqItemList = []
mineTree(headertable,3,set([]),freqItemList)
print(freqItemList)
View Code

十:再回頭去看案例二,更好的理解八中挖掘頻繁集

https://wenku.baidu.com/view/5a28e351bed126fff705cc1755270722182e594b.html

本文較為簡略,後面需要再次理解。最近時間緊,任務重....,沒有結合關聯分析