机器学习实战(Machine Learning in Action)学习笔记————07.使用Apriori算法进行关联分析

机器学习实战(Machine Learning in Action)学习笔记————07.使用Apriori算法进行关联分析

关键字:Apriori、关联规则挖掘、频繁项集
作者:米仓山下
时间:2018-11-2
机器学习实战(Machine Learning in Action,@author: Peter Harrington)
源码下载地址:https://www.manning.com/books/machine-learning-in-action
git@github.com:pbharrin/machinelearninginaction.git

*************************************************************
一、Apriori算法——生成频繁项集

Apriori算法原理:
原理:如果某个项集是频繁的,那么它所有的子集也是频繁的。反过来,如果一个项集是非频繁集,那么他所有的超集也是非频繁的
--------------------------------------------------------------
#创建测试数据集,包含四个列表的列表:[[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
loadDataSet()
#根据测试数据集生成第一个频繁项集,频繁项为单个
createC1(dataSet)
#根据给定的频繁项集Ck,支持度minSupport,计算在数据集D中Ck各频繁项的支持度,并返回支持度大于minSupport的频繁项(数组),及所有频繁项的支持度(频繁项为key,支持度为value的字典)
scanD(D, Ck, minSupport)
--------------------------------------------------------------
#根据给定频繁项集生成下Lk,通过集合并集运算,得到频繁项集Lk+1。

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: #if first k-2 elements are equal
                retList.append(Lk[i] | Lk[j]) #set union
    return retList

#注:这里强调一下为什么只比较前k-2元素,书中强调的原因是可以减少循环次数,但并没有进一步解释这样做的逻辑。示例数据中,两项的频繁项集为[{2,3}, {3,5}, {2,5}, {1,3}],所有两两并集为[{2,3,5},{1,3,5}]。但是如果仅比较前k-2个元素相同的进行合并为[{2,3,5}],之所以没有{1,3,5}是因为{1,5}不存在,即它不是频繁项集,按照Apriori的原理“某个子项集不是频繁的,那么它的超集也不是频繁的”。因此比较前k-2个元素既可以减少循环次数又不会漏掉频繁项。

--------------------------------------------------------------
#不断扫描数据集,由单元素频繁项不断合并,并求解它们的支持度,记录下符合条件的频繁项,以及频繁项的支持度

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

--------------------------------------------------------------
测试:

>>> import apriori
>>> data=apriori.loadDataSet()
>>> data
[[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
>>> L,supportData=apriori.apriori(data)
>>> L
[[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])], [frozenset([1, 3]), frozenset([2, 5]), frozenset([2, 3]), frozenset([3, 5])], [frozenset([2, 3, 5])], []]
>>> supportData
{frozenset([5]): 0.75, frozenset([3]): 0.75, frozenset([2, 3, 5]): 0.5, frozenset([1, 2]): 0.25, frozenset([1, 5]): 0.25, frozenset([3, 5]): 0.5, frozenset([4]): 0.25, frozenset([2, 3]): 0.5, frozenset([2, 5]): 0.75, frozenset([1]): 0.5, frozenset([1, 3]): 0.5, frozenset([2]): 0.75}
>>>

*************************************************************
二、Apriori算法——从频繁项集中挖掘关联规则

置信度:
  置信度(confidence)揭示了A出现时B是否一定出现,如果出现,则出现的概率是多大。如果A->B的置信度是100%,则说明A出现时B一定会出现(返回来不一定)。用公式表示是,物品A->B的置信度=物品{A,B}的支持度 / 物品{A}的支持度:(A称为前件,B称为后件)
Confidence(A->B) = support({A,B}) / support({A}) = P(B|A)

如果某条规则不满足最小可信度要求,那么该规则的所有子集也不会满足最小置信度要求。假设{0,1,2}->{3}不满足最小可信度的要求。那么任何{0,1,2}的子集也不会满足最小可信度的要求。可以利用该性质来减少需要测试的规则数目。

--------------------------------------------------------------
#规则生成函数

def generateRules(L, supportData, minConf=0.7):  #supportData is a dict coming from scanD
    bigRuleList = []           #存储关联规则的列表
    for i in range(1, len(L)): #取出两个或更多元素的频繁项集,L[0]为单元素频繁项集,遍历
        for freqSet in L[i]:   #遍历频繁项集L[i]中的频繁项
            H1 = [frozenset([item]) for item in freqSet]  #某个频繁项中的元素列表
            if (i > 1):        #元素大于两个的频繁项,将频繁项中的元素进行组合成后件(规则)
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else:              #i=1,两个元素的频繁项
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList         

--------------------------------------------------------------
#计算可信度
#输入:freqSet——频繁项, H——频繁项元素列表, supportData——频繁项支持度列表, brl——存储关联规则的列表, minConf——最小可信度
#输出:brl——频繁项freqSet中存在的关联规则,prunedH——返回关联规则中的后件列表

def calcConf(freqSet, H, supportData, brl, minConf=0.7):
    prunedH = []     #关联规则中的后件列表
    for conseq in H: #遍历频繁项中的元素,conseq为元素
        conf = supportData[freqSet]/supportData[freqSet-conseq] #可信度(freq-conseq)->conseq
        if conf >= minConf:                                     #可信度>minConf
            print freqSet-conseq,'-->',conseq,'conf:',conf      #打印关联规则及可信度
            brl.append((freqSet-conseq, conseq, conf))          #保存关联规则及可信度
            prunedH.append(conseq)    #保存后件(规则),为后面准备。注:不满足规则的后件,在该元素基础下添加也不会满足
    return prunedH

--------------------------------------------------------------
#最初的规则生成更多的规则
#输入:freqSet——频繁项, H——频繁项元素列表;输出:brl——频繁项freqSet中存在的关联规则

def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
    m = len(H[0])
    if (len(freqSet) > (m + 1)):     #频繁项元素数目>后件(候选规则)的元素数,一直执行。例如{1,2,3,4}当{1}-{2,3,4}以后此时H[0]长度为3
        Hmp1 = aprioriGen(H, m+1)    #合并候选后件(规则),由Hm生成Hm+1。注:不满足规则的后件,在该元素基础下添加也不会满足
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)  #返回规则后件(规则)列表
        if (len(Hmp1) > 1):          #返回后件(规则)列表包含1个以上后件(规则),递归进一步组合这些规则
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)

--------------------------------------------------------------
测试:

>>> reload(apriori)
<module 'apriori' from 'apriori.py'>
>>> L, supportData=apriori.apriori(data)
>>> L
[[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])], [frozenset([1, 3]), frozenset([2, 5]), frozenset([2, 3]), frozenset([3, 5])], [frozenset([2, 3, 5])], []]
>>> supportData
{frozenset([5]): 0.75, frozenset([3]): 0.75, frozenset([2, 3, 5]): 0.5, frozenset([1, 2]): 0.25, frozenset([1, 5]): 0.25, frozenset([3, 5]): 0.5, frozenset([4]): 0.25, frozenset([2, 3]): 0.5, frozenset([2, 5]): 0.75, frozenset([1]): 0.5, frozenset([1, 3]): 0.5, frozenset([2]): 0.75}
>>> rules=generateRules(L, supportData, minConf=0.6)
frozenset([3]) --> frozenset([1]) conf: 0.666666666667
frozenset([1]) --> frozenset([3]) conf: 1.0
frozenset([5]) --> frozenset([2]) conf: 1.0
frozenset([2]) --> frozenset([5]) conf: 1.0
frozenset([3]) --> frozenset([2]) conf: 0.666666666667
frozenset([2]) --> frozenset([3]) conf: 0.666666666667
frozenset([5]) --> frozenset([3]) conf: 0.666666666667
frozenset([3]) --> frozenset([5]) conf: 0.666666666667
frozenset([5]) --> frozenset([2, 3]) conf: 0.666666666667
frozenset([3]) --> frozenset([2, 5]) conf: 0.666666666667
frozenset([2]) --> frozenset([3, 5]) conf: 0.666666666667
>>>
>>> rules=generateRules(L, supportData, minConf=0.7)
frozenset([1]) --> frozenset([3]) conf: 1.0 #出现1集合中一定出现了3
frozenset([5]) --> frozenset([2]) conf: 1.0 #出现5集合中一定出现了2
frozenset([2]) --> frozenset([5]) conf: 1.0 #出现2集合中一定出现了5
>>>
>>> rules
[(frozenset([1]), frozenset([3]), 1.0), (frozenset([5]), frozenset([2]), 1.0), (frozenset([2]), frozenset([5]), 1.0)]
>>>


*************************************************************
三、示例:发现毒蘑菇的相似特征
数据集:mushroom.dat
说明:mushroom数据集是关于肋形蘑菇的23种特征的数据集,每个特征都包含一个标称数据值。
mushroom.dat前几列数据为:
1 3 9 13 23 25 34 36 38 40 52 54 59 63 67 76 85 86 90 93 98 107 113
2 3 9 14 23 26 34 36 39 40 52 55 59 63 67 76 85 86 90 93 99 108 114
2 4 9 15 23 27 34 36 39 41 52 55 59 63 67 76 85 86 90 93 99 108 115
第一个特征表示有毒(2)或则可食用(1),下一特征为蘑菇伞的形状,六种可能的值3-8表示。

#将数据读取转换为集合
>>> mushDataSet=[line.split() for line in open('mushroom.dat').readlines()]
>>> mushDataSet[0]
['1', '3', '9', '13', '23', '25', '34', '36', '38', '40', '52', '54', '59', '63', '67', '76', '85', '86', '90', '93', '98', '107', '113']
>>> len(mushDataSet)
8124

#生成频繁项集
>>> L, supportData=apriori.apriori(mushDataSet,minSupport = 0.3)
>>> len(L)
10
>>> L[9]
[]
>>> L[8]
[frozenset(['39', '59', '23', '36', '34', '2', '93', '86', '85']), frozenset(['39', '59', '23', '36', '34', '90', '93', '86', '85']), frozenset(['59', '23', '36', '34', '63', '90', '93', '86', '85']), frozenset(['39', '59', '23', '36', '34', '63', '2', '86', '85'])]
>>>

#所有特征的标称性数值并不重复,打印长度为8的所有有毒(2)的频繁项
>>> for item in L[7]:
...   if item.intersection('2'):print item
...
frozenset(['39', '59', '23', '36', '34', '2', '93', '85'])
frozenset(['39', '59', '36', '34', '63', '2', '86', '85'])
frozenset(['39', '59', '23', '34', '63', '2', '86', '85'])
frozenset(['39', '59', '36', '34', '2', '93', '86', '85'])
frozenset(['39', '59', '23', '36', '34', '2', '93', '86'])
frozenset(['39', '59', '23', '36', '2', '93', '86', '85'])
frozenset(['59', '23', '34', '2', '90', '93', '86', '85'])
frozenset(['39', '28', '53', '34', '2', '90', '86', '85'])
frozenset(['59', '23', '36', '34', '63', '2', '86', '85'])
frozenset(['59', '23', '36', '34', '2', '93', '86', '85'])
frozenset(['39', '59', '23', '36', '34', '2', '86', '85'])
frozenset(['39', '59', '23', '36', '34', '63', '2', '86'])
frozenset(['59', '36', '34', '2', '90', '93', '86', '85'])
frozenset(['39', '23', '36', '34', '2', '93', '86', '85'])
frozenset(['39', '59', '23', '36', '63', '2', '86', '85'])
frozenset(['39', '59', '23', '34', '2', '93', '86', '85'])
frozenset(['39', '59', '23', '36', '34', '63', '2', '85'])
frozenset(['39', '23', '36', '34', '63', '2', '86', '85'])
>>>

#这样就可以看到有哪些特征的蘑菇大概率是毒蘑菇。但:并不表示没有这些特诊的蘑菇就没有毒

*************************************************************
四、总结
关联分析用于发现大规模数据集中元素间有趣关系。通过两种两种方式量化这些有趣关系:第一种是使用频繁项集,给出经常一起出现的元素项;第二种是关联规则,每条关联规则意味着元素之间“如果……那么”的关系。

减少扫描频繁项统计扫描次数的方法就是Apriori。Apriori原理是说如果一个元素项是不频繁的那么包含该元素的超集也是不频繁的。Apriori算法从单元素项集合开始,通过组合满足最小支持度要求的项集来形成更大的集合。支持度用来度量一个集合在原始数据中出现的频率。

原始数据中每条数据是集合,但并不要求每条数据具有相同的长度

缺点:每次增加频繁项集的大小,Apriori算法都会扫描整个数据集。数据集很大时会降低频繁项集发现的速度。相比,12章中FPgrowth算法只需要对数据库进行两次扫描,能够显著加快频繁项集发现速度


参考文献:
https://blog.csdn.net/wanghao109/article/details/39452303
https://www.cnblogs.com/bigmonkey/p/7405555.html
https://blog.csdn.net/zllnau66/article/details/81534368




posted on 2018-11-03 15:12  米仓山下  阅读(598)  评论(0编辑  收藏  举报

导航