机器学习之八——Apriori算法

在写这篇文章之前不得不先吐槽一下《机器学习实战》一书。Apriori是个很有趣也很简单的算法,这本书却把它解释得晦涩难懂,很多地方的讲解很容易把人带偏,我费了好大劲才读懂。这篇文章里会对比较难理解的地方重点讲解,如有错误,还望大家不吝赐教。

一、关联分析

关联分析就是寻找数据之间的关联,这里直接用《机器学习实战》书中的例子来举例吧:

交易号码商品

0

1

2

3

4

豆奶,莴苣

莴苣,尿布,葡萄酒,甜菜

豆奶,尿布,葡萄酒,橙汁

莴苣,豆奶,尿布,葡萄酒

莴苣,豆奶,尿布,橙汁

这里有五条交易记录,人们想要知道可否根据这五条交易记录发现一些数据的关系,比如尿布和葡萄酒经常一起出现,那么就可以认为买尿布的人大概率会买葡萄酒。知道这些信息后,商家就可以做出更加准确的营销,而了解这些信息的过程就叫关联分析。

关联分析需要知道几个概念:

1. 频繁项集:经常一起出现的物品的集合,如{尿布,葡萄酒}

2. 关联规则:两个物品之间的关系,如尿布-->葡萄酒意味着买了尿布的人大概率会买葡萄酒

3. 支持度:支持度是针对频繁项集而言,一个频繁项集的支持度被定义为该数据集中包括该频繁项集的比例,如{尿布,葡萄酒}的支持度为0.6

4. 可信度:可信度是针对关联规则而言,衡量的是一个关联规则,如尿布-->葡萄酒在多大程度上是可信的,其计算方式为 {尿布,葡萄酒}的支持度/尿布的支持度

二、Apriori原理

对于一组数据,其可能的频繁项集是非常多的。例如在上面的例子中,一共有6个商品,频繁项集数却有2^6-1个,要对每一个频繁项集都计算支持度是非常麻烦的,不过好在Apriori原理可以帮助我们减少运算。Apriori原理说的是如果某个项集是频繁的,那么其子集一定是频繁的。如{0,1}是频繁的,那么{0}和{1}也是频繁的,这个很好理解。其逆否命题更加有用:如果某个项集是非频繁的,那么其超集也是非频繁的。基于这个逆否命题,我们就可以更快的检索频繁集。

代码如下:

def loadDataSet():
    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 tid in D:
        for can in Ck:
            if can.issubset(tid):
                ssCnt[can] = ssCnt.get(can,0) + 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

上面的代码中,loadDataSet()生成了一个测试数据,这个可以不用管。createC1()生成的是元素数目为1的项集和集合,比如共有5个元素,那么C1就是 [{0}, {1}, {2}, {3}, {4}]。然后算法会对C1中的项目进行扫描,找到支持度大于最小支持度的项集,这些项集的集合构成L1。比如L1为 [{0}, {1},  {4}],接下来又会试者将L1中的项集合并,判断合并后的项集支持度是否大于最小支持度,满足这个条件的集合构成C2......如此循环往复直到无法生成新的项集为止。注意在createC1()中使用了frozenset类型,frozenset是不可修改的集合,并且可以用来作为字典的键,而set不行,所以程序中使用了frozenset。

scanD就是对原始数据D进行扫描,观察其是否存在Ck中的项集,如果存在,就令其出现的次数+1.这里用了一个字典ssCnt来保存项集以及其出现的次数。最后对字典中的每一个项集,计算其支持度,如果大于最小支持度,就添加到retList中。

上面只是给出了扫描和生成C1的代码,完整的apriori算法代码如下:

def aprioriGen(Lk,k):   #根据Lk创建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:
        Ck = aprioriGen(L[k-2],k)
        Lk,supK = scanD(D,Ck,minSupport)
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L,supportData

函数aprioriGen()是根据Lk生成Ck,也就是说,对于已有的频繁项目集,将其组合生成新的频繁项目集,其中,每一个新频繁项目集的元素个数为k。例如,Lk = [ {0}, {1}, {2} ]时,Ck = [ {0,1}, {0,2}, {1,2} ] 。函数中的双层循环意思是,对于Lk中的每两个频繁项目集,会比较它们的前k-2个元素,如果前k-2个元素相同,就合并这两个频繁项目集。至于为什么是前k-2个频繁项目集,考虑Lk = [ {0,1}, {0,2}, {1,2} ],如果直接两两合并的话,那么会得到 [ {0,1,2}, {0,1,2}, {0,1,2} ] ,有两个重复的项目集。如果是按照前k-2个元素相同再来合并的话,就只会有一个项目集,避免了重复。

apriori()就是前面说的流程:C1-->L2-->C2-->L3-->C3...

三、从频繁项目集中构成关联规则

现在我们已经根据apriori算法成功找到了符合要求的频繁项目集,接下来要做的就是从这些频繁项目集中寻找关联规则。在这里先讲一个概念:对于关联规则A-->B,称A为前件,B为后件。然后把代码放上来:

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]    #先生成后件集合,当i=1时,后件只有一个,直接用clacConf计算可信度;当i>1时,后件可以有多个,此时要用rulesFromConseq生成新的后件
            if i > 1:
                rulesFromConseq(freqSet,H1,supportData,bigRuleList,minConf)
            else:
                calcConf(freqSet,H1,supportData,bigRuleList,minConf)

def calcConf(freqSet,H,supportData,brl,minConf=0.7):
    prunedH = []
    for conseq in H:
        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)

代码中的conseq指的就是后件,而H是后件的集合,弄清这一点非常重要!因为书中称H称为关联规则,实在是让人摸不着头脑,弄明白H的含义后,理解这段代码就简单很多了。

generateRules()是寻找关联规则的主函数,bigRuleList就是用来存放关联规则的。这个函数的第一层循环是从1开始的,因为L中的第0个元素里面的频繁项目集都只有一个元素,即L[0]是[ {0}, {1}, {2} ]这种形式的,只有一个元素的频繁项目集是无法构建关联规则的。而L[1]的形式是[ {0,1}, {0,2}, {1,2} ]这样的,这样的话,对于L[1]中的每一个项目集就能够构建关联规则了,比如对于{0,1},我们可以尝试着构成0-->1或1-->0这样的关联规则,然后计算其可信度。第二层循环中,对于L[i]中的每一个频繁项目集,用H1来存放频繁项目集中的每一个元素。之前说过,H1是后件的集合。在最开始的时候,我们先尝试着用单个元素作为后件,对于可以构成单个元素的后件,我们会对其进行组合,作为新的后件来构成关联规则。举个例子:[ {0}, {1}, {2}, {3} ]四个后件,如果成功的构成了关联规则0-->1,0-->2,我们会用新的后件{1,2}来尝试构建关联规则,而由于{3}没有关联规则,我们就不考虑{1,3},{2,3}了,原因在于某条规则不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求。举例:0,1,2-->3不满足最小可信度要求,那么0,2-->1,3也不会满足。其原因根据可信度计算公式可以很轻松地证明。说清楚了这些东西,我们又绕回来说程序。前面说到,在第二层循环中,对于L[i]中的每一个频繁项目集,用H1来存放频繁项目集中的每一个元素。当i=1的时候,每一个频繁项目集里只有两个元素,此时后件必然只能有一个元素,因此就直接利用函数calcConf()计算可信度。当i>1时,每一个频繁项目集里只有三个以上元素,那么就有可能生成有多个元素的后件,这样的后件用函数rulesFromConseq()来生成。于是我们就直接讲这个函数了,calcConf()比较简单,就不讲解了。

在rulesFromConseq()中,先计算H中每一个频繁项目集中的元素个数,在最开始,m自然就等于1,不过如果合成了新的后件后,m就可能在2以上。为什么判断条件是len(freqSet) > (m+1)呢?举一个例子,freqSet = [{2, 3}, {3, 5}],这样的话m=2,len(freqSet)=2,是不满足判断条件的,此时如果我们对其进行合并,那么合并之后就是{2,3,5},无法再生成新的关联规则了。满足判断条件之后,就用aprioriGen()生成新的后件组合,然后用calcConf()计算新后件组合的可信度。如果最后Hmp1中的元素数量有两个及以上,那么可能还可以合并,于是再一次调用rulesFromConseq()。

至此,整个算法就介绍完了,希望对大家有帮助~,如果还是有读不懂的地方,可以留言告诉我~

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值