Apriori算法也屬於無監督學習,它強調的是“從數據X中能夠發現什麼”。從大規模的數據集中尋找物品之間隱含關係被稱爲關聯分析或者稱爲關聯規則學習。這裏的主要問題在於,尋找物品的不同組合是一項十分耗時的任務,所需的計算代價很高,蠻力搜索並不能解決這個問題。因此此處介紹使用Apriorio算法來解決上述問題。
1:簡單概念描述
(1) 頻繁項集:指經常出現在一塊的物品的集合。 關聯規則暗示兩種物品之間存在很強的關係。(這裏我們事先定義閥值,超過該閥值,證明兩者之間存在很強的關係).
(2) 一個項集的支持度(support)被定義爲數據集中包含該項集的記錄所佔的比例。我們事先需要定義一個最小支持度(minSupport),而只保留滿足最小支持度的項集。
(3) 可信度或置信度(confidence)是針對一條諸如{尿布}->{葡萄酒}的關聯規則來定義的。
(4) Apriori的原理是如果某個項集是頻繁的,那麼它的子集也是頻繁的。反過來說,如果一個項集是非頻繁的,那麼它的所有超集也是非頻繁的。比如{1,2}出現的次數已經小於最小支持度了(非頻繁的),那麼超集{0,1,2}的組合肯定也是非頻繁的了。主要包括髮現頻繁項集和挖掘關聯規則這兩步。
2:發現頻繁項集
過程是:從C1= {{0},{1},{2},{3}}開始,然後生成L1,L1是C1中項集的支持度大於等於最小支持度,比如L1 = {{0},{1},{3}}。然後由L1組合得到C2 = {{01},{03},{13}}。一直進行下去直到Ck爲空。
- # 加載數據
- def loadDataSet():
- return [[1,3,4], [2,3,5], [1,2,3,5], [2,5]]
- # 創建C1
- def createC1(dataSet):
- C1 = []
- for transaction in dataSet:
- for item in transaction:
- if not[item] in C1:
- C1.append([item])
- C1.sort()
- return map(frozenset, C1) #frozenset 可以將集合作爲字典的鍵字使用
- # 由Ck生成Lk
- def scanD(D, Ck, minSupport):
- ssCnt = {}
- for tid in D:
- for can in Ck:
- if can.issubset(tid):
- if not ssCnt.has_key(can):ssCnt[can] = 1
- else: ssCnt[can] += 1
- numItems = float(len(D))
- retList = []
- supportData = {}
- for key in ssCnt:
- support = ssCnt[key]/numItems
- if support >= minSupport:
- retList.insert(0, key) #在列表的首部插入任意新的集合
- supportData[key] = support
- return retList, supportData
- #Apriori 算法
- # 由Lk 產生Ck+1
- def aprioriGen(Lk, k):
- retList = []
- lenLk = len(Lk)
- for i in range(lenLk):
- for j in range(i+1, lenLk):
- L1 = list(Lk[i])[:k-2]; L2 = list(Lk[i])[:k-2]
- L1.sort(); L2.sort()
- if L1 == L2:
- retList.append(Lk[i] | Lk[j])
- return retList
- def apriori(dataSet, minSupport = 0.5):
- C1 = createC1(dataSet)
- D = map(set, dataSet)
- L1, supportData = scanD(D, C1, minSupport)
- L = [L1]
- k = 2
- while(len(L[k-2]) > 0):
- Ck = aprioriGen(L[k-2], k)
- Lk, supK = scanD(D,Ck, minSupport)
- supportData.update(supK)
- L.append(Lk)
- k += 1
- return L, supportData
注意:(1)C1是大小爲1的所有候選項集的集合
(2)這裏使用了python的frozenset類型。frozenset是指被“冰凍”的集合,就說它們是不可改變的,即用戶不能修改它們。這裏必須使用frozenset而不是set類型,因爲之後必須要將這些集合作爲字典鍵值使用,使用frozenset可以實現這一點,而set卻做不到。
3:從頻繁項集中發現關聯規則
- #從頻繁項集中發現關聯規則
- def generateRules(L, supportData, minConf=0.7): #supportData is a dict coming from scanD
- bigRuleList = []
- for i in range(1, len(L)):#only get the sets with two or more items
- for freqSet in L[i]:
- H1 = [frozenset([item]) for item in freqSet]
- if (i > 1):
- rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
- else:
- calcConf(freqSet, H1, supportData, bigRuleList, minConf)
- return bigRuleList
- def calcConf(freqSet, H, supportData, brl, minConf=0.7):
- prunedH = [] #create new list to return
- for conseq in H:
- conf = supportData[freqSet]/supportData[freqSet-conseq] #calc confidence
- if conf >= minConf:
- print freqSet-conseq,'-->',conseq,'conf:',conf
- brl.append((freqSet-conseq, conseq, conf))
- prunedH.append(conseq)
- return prunedH
- def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
- m = len(H[0])
- if (len(freqSet) > (m + 1)): #try further merging
- Hmp1 = aprioriGen(H, m+1)#create Hm+1 new candidates
- Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
- if (len(Hmp1) > 1): #need at least two sets to merge
- rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)
4:使用FP-growth算法來高效發現頻繁項集
每次增加頻繁項集的大小,Apriori算法都會重新掃描整個數據集。當數據集很大時,這會顯著降低頻繁項集發現的速度。而FP-growth樹只需要對數據庫進行兩次遍歷,能夠顯著加快頻繁項集的速度。但是該算法不能用於發現關聯規則。
第一遍對所有元素項的出現次數進行計數,只用來統計出現的頻率。而第二遍掃描只考慮那些頻繁元素,用來構建FP樹。
- # -*- coding: cp936 -*-
- #創建FP樹的數據結構
- class treeNode:
- def __init__(self, nameValue, numOccur, parentNode):
- self.name = nameValue
- self.count = numOccur
- 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)
- # 加載數據
- 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
- #構建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 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[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:
- 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):
- if items[0] in inTree.children:
- inTree.children[items[0]].inc(count)
- else:
- 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) #對剩下的元素項迭代調用updateTree函數
- def updateHeader(nodeToTest, targetNode):
- while(nodeToTest.nodeLink != None):
- nodeToTest = nodeToTest.nodeLink
- nodeToTest.nodeLink = targetNode