机器学习实战---读书笔记: 第11章 使用Apriori算法进行关联分析---2---从频繁项集中挖掘关联规则

#!/usr/bin/env python
# encoding: utf-8

'''
<<机器学习实战>> 读书笔记 第11章 使用Apriori算法进行关联分析---从频繁项集中挖掘关联规则

关键:
1 关联规则
某个元素或者某个元素集合可能会推导出另一个元素
举例: 如果有一个频繁项集{豆奶,莴苣},那么就可能有一条关联规则: 豆奶 --> 莴苣,
    这意味着如果有人购买了豆奶,那么他购买莴苣的概率较大。
关联规则的量化指标: 可信度
一条规则P --> H的可信度定义为support(P | H) / support(P)
注意: Python中,操作符 | 表示集合的并操作
P|H: 是指所有出现在集合P或者集合H中的元素

2 频繁项集产生关联规则
举例: {0, 1, 2, 3}产生的规则列表如下
                            0123->空
        123->0      023->1          013->2      012->3
    23->01  13->02  12->03          03->12  02->13  01->23
        3->012      2->013          1->023      0->123

如果: 0,1,2->3是一套低可信度规则,那么其他以3作为后件的规则的可信度也会较低
前件: 箭头左边的集合
后件: 箭头右边的集合

如果某条规则并不满足最小可信度要求-->那么该规则的所有子集也不会满足最小可信度要求

3 频繁项集到关联规则生成
算法思想:
从一个频繁项集开始,创建一个规则列表,其中规则右部值包含一个元素,然后对这些规则进行测试。
接下来合并所有剩余规则来创建一个新的规则列表,其中规则右部包含两个元素。
这种方法被称为分级法。


4 从频繁项集中挖掘关联规则算法
步骤1: 遍历频繁项集列表中的每个频繁项
       1.1 对该频繁项中每个元素进行遍历,生成每个元素组成的frozenset得到的结果作为规则右部的候选元素列表H
       1.2 如果频繁项集的元素数目只有2个,则计算该频繁项集对应规则右部的元素列表。否则,执行1.3
       1.3 表明当前频繁项集中元素数目大于2个,则判定如果当前频繁项集的长度大于规则右部候选元素列表H中H[0]的长度,
            则表明可以对规则右部候选元素列表生成H[0]长度+1的规则右部候选元素列表canH,
       1.4 计算规则右部候选元素列表为canH时,当前频繁项集对应规则右部的元素列表realH
       1.5 如果当前频繁项集对应规则右部的元素列表realH长度大于1,则使用realH作为规则右部的候选元素列表
            递归处理,转步骤1

'''

'''
作用:
输入参数: 频繁项集列表L,包含哪些频繁项集支持数据的字典supportData, 最小可信度阈值
返回结果: 返回包含可信度的规则列表
算法:
步骤1: 遍历频繁项集列表中的每个频繁项
       1.1 对该频繁项中每个元素进行遍历,生成每个元素组成的frozenset得到的结果作为规则右部的候选元素列表H
       1.2 如果频繁项集的元素数目只有2个,则计算该频繁项集对应规则右部的元素列表。否则,执行1.3
       1.3 表明当前频繁项集中元素数目大于2个,则判定如果当前频繁项集的长度大于规则右部候选元素列表H中H[0]的长度,
            则表明可以对规则右部候选元素列表生成H[0]长度+1的规则右部候选元素列表canH,
       1.4 计算规则右部候选元素列表为canH时,当前频繁项集对应规则右部的元素列表realH
       1.5 如果当前频繁项集对应规则右部的元素列表realH长度大于1,则使用realH作为规则右部的候选元素列表
            递归处理,转步骤1
'''
def generateRules(L, supportData, minConf=0.7):
    ruleList = []
    for i in range(1, len(L)):
        for freqSet in L[i]:
            candidateH = [frozenset([value]) for value in freqSet]
            if i > 1:
                rulesFromConseq(freqSet, candidateH, supportData, ruleList, minConf)
            else:
                calcConf(freqSet, candidateH, supportData, ruleList, minConf)
    return ruleList

'''
作用: 如果频繁项集种玫瑰只有两个元素,则可用函数calcConf来计算可信度
输入参数:
    频繁项集freqSet,
    出现在规则右部的元素列表H,
    频繁项集对应的支持度信息的字典supportData,
    最小可信度minConf
输出参数:包含规则左部,规则右部,可信度的元组作为元素的列表ruleList
返回结果: 可以作为当前频繁项集对应关联规则右部的元素列表
算法:
步骤1: 创建一个空列表prunedH
步骤2: 遍历H中所有项集并计算他们的可信度值
        2.1 如果某条规则满足最小可信度值,那么将规则打印,并将规则加入到结果列表中
'''
def calcConf(freqSet, H, supportData, ruleList, minConf=0.7):
    if not freqSet:
        print "freqSet is empty"
        return
    prunedH = list()
    for consequence in H:
        conf = supportData[freqSet] / supportData[freqSet - consequence]
        if conf >= minConf:
            print "{left}->{right}, conf: {conf}".format(
                left=freqSet - consequence,
                right=consequence,
                conf=conf
            )
            ruleList.append((freqSet - consequence, consequence, conf))
            prunedH.append(consequence)
    return prunedH



'''
作用: 如果频繁项集的元素数目超过2,会对它做进一步合并
输入参数:
    频繁项集freqSet,
    出现在规则右部的元素列表H,
    频繁项集对应的支持度信息的字典supportData,
    最小可信度minConf
输出参数:包含规则左部,规则右部,可信度的元组作为元素的列表ruleList
返回结果:
算法:
步骤1: 先计算H中的频繁集大小m
步骤2: 查看该频繁项集是否达到可以移除大小为m的子集。
       2.1 如果可以的话,将其移除。可以使用aprioriGen生成H中元素的无重复组合Hmp1
           Hmp1是下一次迭代的H列表 ,包含所有可能的规则
       2.2 计算该规则的可信度,看是否满足要求;
            如果不止一条规则满足要求,使用Hmp1递归调用rulesFromConseq来判断是否可以进一步组合这些规则

'''
def rulesFromConseq(freqSet, H, supportData, ruleList, minConf=0.7):
    hLen = len(H[0])
    fLen = len(freqSet)
    if fLen > hLen + 1:
        candidateHList = aprioriGen(H, hLen + 1)
        realHList = calcConf(freqSet, candidateHList, supportData, ruleList, minConf)
        # 这意味着规则右部多于一条,就可以合并规则右部生成新的规则右部,继续处理
        if len(realHList) > 1:
            rulesFromConseq(freqSet, realHList, supportData, ruleList, minConf)


def loadDataSet():
    return [ [1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5] ]

'''
作用: 创建大小为1的所有候选项集的集合
算法:
步骤1: 解析数组中每个元素
步骤2: 构建每个元素组成的数组;
        2.1 如果该数组不在结果数组中,就放入到结果数组中;
步骤3: 对结果数组进行排序,并转换为fronzenset并返回
'''
def createC1(dataSet):
    if not dataSet:
        return frozenset()
    result = []
    for arr in dataSet:
        for value in arr:
            if [value] not in result:
                result.append([value])
    result.sort()
    '''
    之所以使用frozenset:冻结集合是因为,它们不可改变,需要将这些集合作为
    字典键值使用。
    '''
    return map(frozenset, result)


'''
作用: 用于从C1生成L1。
输入参数: 数据集D, 候选项列表Ck, 感兴趣项集的最小支持度minSupport
返回值: 返回一个包含支持度的字典
算法:
步骤1: 先创建一个空字典ssCnt
步骤2: 遍历数据集中的所有交易记录以及C1中的所有候选集。
        2.1 如果C1中的集合是记录中的一部分,则增加字典中对应的计数值。
步骤3: 构建一个空列表作为满足做小支持度的集合。
步骤4: 遍历步骤1中的字典ssCnt中每个元素,计算其支持度。
        4.1 如果支持度满足最小支持度要求,则将字典元素放到结果列表中
        4.2 更新频繁项集的支持度字典
步骤5: 返回频繁项集的结果列表,返回最频繁项集的支持度

总结: 扫描算法其实就是两个过程。
第一个过程就是根据候选项集找到包含候选项的超集,并统计该候选项集出现的次数
第二个过程就是根据候选项集的出现次数,计算候选项的支持度,将满足最小支持度的候选项加入到最终结果列表中。
'''
def scanD(D, Ck, minSupport):
    if not D or not Ck:
        print "D is empty or Ck is empty, D is: {D} , Ck is: {Ck}".format(D=D, Ck=Ck)
    canToTimes = {}
    for tid in D:
        for can in Ck:
            if can.issubset(tid):
                if canToTimes.has_key(can):
                    canToTimes[can] += 1
                else:
                    canToTimes[can] = 1
    result = []
    canToSupport = {}
    length = len(D)
    for can, times in canToTimes.iteritems():
        support = float(times) / length
        if support >= minSupport:
            result.insert(0, can)
        canToSupport[can] = support
    return result, canToSupport


'''
作用:
输入参数: 频繁项集列表Lk, 项集元素个数k(是输入的频繁项集列表中任意元素的长度值+1)
返回结果: 输出Ck。
举例: 该函数以{0}, {1},{2}作为输入,会生成{0, 1}, {0, 2}和{1,2}
算法:
步骤1: 每次遍历频繁项集列表中任意两个不同的频繁项集
步骤2: 如果这两个频繁项集的前k-2是相同的,则将这两个频繁项集合并,并添加到结果列表中
'''
def aprioriGen(Lk, k):
    if not Lk or k < 2:
        print "Lk is empty or k < 2, Lk: {Lk}, k: {k}".format(Lk=Lk, k=k)
        return
    length = len(Lk)
    results = []
    for i in range(length):
        for j in range(i + 1, length):
            list1 = list(Lk[i])[: k - 2]
            list2 = list(Lk[j])[: k - 2]
            list1.sort()
            list2.sort()
            if list1 == list2:
                result = Lk[i] | Lk[j]
                results.append(result)
    return results

'''
作用:
输入参数: 数据集dataSet(是一个列表, 列表中每个元素是列表); 最小支持度minSupport(浮点数)
返回结果: 频繁项集结果列表(该频繁项集结果列表中的每个元素是一个列表,列表中的每个元素是frozenset类型的结果)
         每个频繁项集对应的支持度
算法:
步骤1: 获取长度为1的候选项列表C1,具体计算过程如下:
        1.1 遍历数据集(实际是数组)中每个元素
            1.1.1 构建每个元素组成的数组;
            1.1.2 如果该数组不在结果数组中,就放入到结果数组中;
        1.2 对结果数组进行排序,并转换为fronzenset并返回
步骤2: 根据C1,数据集和最小支持度计算出满足最小支持度的频繁项集L1,以及频繁项集的支持度信息,
       具体从候选集计算得到频繁集的过程如下:
        2.1: 先创建一个空字典ssCnt
        2.2: 遍历数据集中的所有交易记录以及C1中的所有候选集。
                2.2.1 如果C1中的集合是记录中的一部分,则增加字典中对应的计数值。
        2.3: 构建一个空列表作为满足做小支持度的集合。
        2.4: 遍历步骤2.1中的字典ssCnt中每个元素,计算其支持度。
                2.4.1 如果支持度满足最小支持度要求,则将字典元素放到结果列表中
                2.4.2 更新频繁项集的支持度字典
        2.5: 返回频繁项集的结果列表,返回频繁项集的支持度信息
步骤3: 创建一个频繁项集结果列表L,设定k=2
步骤4: 只要L[k-2]的长度大于0,就进入步骤5;否则,算法结束,返回频繁项集列表L和频繁项集对应的支持度信息
步骤5:根据频繁项集列表L[k-2]和所要求出的频繁项集的长度k,计算得到候选项集Ck,具体计算过程如下:
       5.1 每次遍历频繁项集列表中任意两个不同的频繁项集
       5.2 如果这两个频繁项集的前k-2是相同的,则将这两个频繁项集合并,并添加到结果列表中
       5.3 返回结果列表,即为Ck
步骤6: 根据Ck, 数据集和最小支持度计算出频繁项集Lk和Lk频繁项集的支持度信息,并更新总的频繁项集的支持度信息
步骤7: 向频繁项集总的结果列表L中加入当前频繁项集Lk, 并令k累加

总结:
根据候选集Ck,数据集,最小支持度 --> 频繁集Lk
根据频繁集Lk, 所要求出的频繁项的长度k+1 -> 候选集Ck+1
根据候选集Ck+1, 数据集,最小支持度 --> 频繁集Lk+1
...

'''
def apriori(dataSet, minSupport=0.5):
    C1 = createC1(dataSet)
    D = map(set, dataSet)
    L1, supportDict = scanD(D, C1, minSupport)
    L = [L1]
    k = 2
    while len(L[k - 2]) > 0:
        Ck = aprioriGen(L[k-2], k)
        Lk, partialSupportDict = scanD(D, Ck, minSupport)
        supportDict.update(partialSupportDict)
        # note, Lk may be empty, if Lk is empty, it needs to end this recycle
        if not Lk:
            break
        L.append(Lk)
        k += 1
    return L, supportDict



def process():
    dataSet = loadDataSet()
    L, supportDict = apriori(dataSet)
    print "频繁项集支持度信息: {supportDict}".format(
        supportDict=supportDict
    )
    for i, arr in enumerate(L):
        print "频繁项集长度为{length}的频繁项集如下: {arr} ".format(length=i+1, arr=arr)

    rules = generateRules(L, supportDict, minConf=0.7)
    print rules


if __name__ == "__main__":
    process()

 

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