机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)

机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)

  • 1、关联分析与频繁项集介绍
  • 2、Apriori和FP-Growth算法数学原理
  • 3、Apriori和FP-Growth算法及Python实现
  • 4、小结

1、关联分析和频繁项集介绍

关联分析:从大规模数据集中寻找物品间的隐含关系被称作关联分析(association analysis)或者关联规则学习,典型的例子有“尿布和啤酒”。
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第1张图片
频繁项集:频繁项集是指那些经常出现在一起的物品集合。例如上图中的集合{葡萄酒,尿布,豆奶}就是一个频繁项集。

2、Apriori和FP-Growth算法数学原理

首先需要说明的是这两种算法都是为了寻找频繁项集,Apriori算法能够帮助快速进行关联分析,减少计算量,而FP-Growth算法主要用来寻找频繁项集,速度优于Apriori算法。
支持度:数据集中包含该项集的记录所占的比例,例如上图中{豆奶}的支持度为4/5。
可信度:是针对一条诸如{尿布} → {\rightarrow} {葡萄酒}的关联规则来定义的,这条规则的可信度被定义为“支持度({尿布,葡萄酒})/支持度({尿布})”。根据上图可以得到“尿布 → {\rightarrow} 葡萄酒”这个规则的可信度为(3/5)/(4/5)=0.75。这意味着对于包含尿布的所有记录中,我们的规则对其中75%的记录都适用。
Apriori原理:通过对所有物品进行所有可能的组合,通过遍历每条交易记录,检查包含该集合的记录条数,计算支持度。下图是集合{0,1,2,3}中所有可能的项集组合。
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第2张图片
由图可以发现随着商品数目的增加,组合数目会呈爆炸式增长,为了降低所需的计算时间,研究发现一种所谓的Apriori原理,即如果某个项集是频繁的,那么它的所有子集也是频繁的,反过来也成立,即如果说一个项集是非频繁集,那么它的所有超集也是非频繁的
假如已知阴影项集{2,3}是非频繁的,则可以进行一些剪枝
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第3张图片
关联规则:关联规则首先从一个频繁项集开始,我们可以为每个频繁项集产生许多规则,以频繁项集{0,1,2,3}为例,所有的关联规则如下图所示
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第4张图片
同样为了对树进行剪枝,我们有如下规则:如果某条规则不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求,已上图为例,假设0,1,2 → \rightarrow 3并不满足最小可信度,则任何左部分为{0,1,2}子集的规则也不会满足最小可信度的要求(图中阴影部分即是不满足最小可信度的项集)
FP树:FP-Growth算法将数据存储在一种称为FP树的紧凑数据结构中,FP代表频繁模式(Frequent Pattern),下图是一个FP树的例子
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第5张图片
根据上述事务数据样例生成的FP树(数字代表出现次数),如下图所示
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第6张图片
构建FP树:这里需要遍历两次数据集,第一次遍历数据集会获得每个元素的出现频率,接下来,去掉不满足最小支持度的元素项,再下一步构建FP树,在构建时,读入每个项集并将其添加到一条已经存在的路径中,如果该路径不存在则创建一条路径(从树根节点到下方节点即为一条路径)
下图是根据每个元素项的出现频率由高到低进行重排后的结果(这里频繁项集的最小支持度为3,这里移除了非频繁项)
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第7张图片
在对事物记录过滤和排序之后,就可以构建FP树了,从空集开始,向其中不断添加频繁项集。过滤排序后的事物一次添加到树中,如果树中已存在现有元素,则增加现有元素值,如果现有元素不存在,则向树中添加一个分支。
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第8张图片

3、Apriori和FP-Growth算法及Python实现

Apriori算法
当集合中项的个数大于0时
构建一个K个项组成的候选项集的列表
检查数据以确认每个项集都是频繁的
保留频繁项集并构建K+1项组成的候选项集的列表
FP-Growth
从FP树中抽取频繁项集的三个基本步骤如下:
(1)从FP树中获得条件模式集;
(2)利用条件模式基,构建一个条件FP树;
(3)迭代重复步骤(1)步骤(2),直到树包含一个元素项为止
Apriori算法Python实现

def loadDataSet(dataDir=""):
    fopen = open(dataDir)
    mushDataSet = [line.split() for line in fopen.readlines()]
    return mushDataSet
#     return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
def createC1(dataSet):
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
    C1.sort()
    return list(map(frozenset,C1))
def scanD(D,Ck,minSupport):
    ssCnt = {}
    for data in D:
        for can in Ck:
            if can.issubset(data):
                if not can in ssCnt:
                    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
def aprioriGen(Lk,k): #creates Ck
    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[j])[: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 = list(map(set,dataSet))
    L1,supportData = scanD(D,C1,minSupport)
    L = [L1]
    k = 2
    while (len(L[k-2]) > 0): #判断当前最后生成的L是否为空
        Ck = aprioriGen(L[k-2],k)
        Lk,supK = scanD(D,Ck,minSupport)
        supportData.update(supK) #将新生成的集合的支持度字典更新到总字典中去
        L.append(Lk) #将新生成的频繁项集添加到总频繁项集中去
        k += 1
    return L,supportData
def generateRules(L,supportData,minConf=0.7):
    bigRuleList = []
    for i in range(1,len(L)): #只从含有两个以上元素的频繁项集
        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 = [] 
    for conseq in H:
        if supportData[freqSet-conseq]==0:
            continue
        conf = supportData[freqSet]/supportData[freqSet-conseq]
        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)):
        Hmp1 = aprioriGen(H,m+1)
        Hmp1 = calcConf(freqSet,Hmp1,supportData,brl,minConf)
        if(len(Hmp1)>1): #至少需要两个集合才能合并
            rulesFromConseq(freqSet,Hmp1,supportData,brl,minConf)

使用下面代码调用程序,数据集在这里mushroomLite.txt

dataSet = loadDataSet(dataDir="./mushroomLite.txt")
L,supportData = apriori(dataSet,minSupport=0.95)
rules = generateRules(L,supportData,minConf=0.95)
print(rules)

输出的结果如下
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第9张图片
FP-Growth算法Python实现

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 loadDataSet(dataDir=""):
    fopen = open(dataDir)
    mushDataSet = [line.split() for line in fopen.readlines()]
    return mushDataSet

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

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())
    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)
        
def updateHeader(nodeToTest, targetNode):  
    while (nodeToTest.nodeLink != None):
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode
        
def ascendTree(leafNode, prefixPath):
    if leafNode.parent != None:
        prefixPath.append(leafNode.name)
        ascendTree(leafNode.parent, prefixPath)
    
def findPrefixPath(basePat, treeNode):
    condPats = {}
    while treeNode != None:
        prefixPath = []
        ascendTree(treeNode, prefixPath)
        if len(prefixPath) > 1: 
            condPats[frozenset(prefixPath[1:])] = treeNode.count
        treeNode = treeNode.nodeLink
    return condPats

def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
    bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1])]
    for basePat in bigL: 
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        freqItemList.append(newFreqSet)
        condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
        myCondTree, myHead = createTree(condPattBases, minSup)
        if myHead != None: #3. mine cond. FP-tree       
            mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)

使用下面代码调用程序,数据集在这里mushroomLite.txt

dataSet = loadDataSet(dataDir="./mushroomLite.txt")
initSet = createInitSet(dataSet)
print(initSet)
FPtree,headerTab = createTree(initSet,3)
FPtree.disp()

输出的结果如下:
机器学习之关联分析与频繁项集(Apriori和FP-Growth算法)_第10张图片

4、小结

Apriori算法和FP-Growth都是用来发现频繁项集的算法,但Apriori算法在寻找频繁项集时需要不停的扫描数据集,而FP-Growth算法只需要扫描数据集两次,因此FP-Growth算法执行速度更快,频繁项集是进行关联分析的基础,因此能够快速高效的查找频繁项集十分重要。

你可能感兴趣的:(机器学习)