机器学习实战---读书笔记: 第11章 使用Apriori算法进行关联分析

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

'''
<<机器学习实战>> 读书笔记 第11章 使用Apriori算法进行关联分析

关键:
1 关联分析
含义:从大规模数据集中寻找物品之间的隐含关系
主要问题: 暴力搜索物品的不同组合很困难

2 Apriori算法
优点: 容易编码实现
缺点: 在大数据集上可能较慢
适用数据类型: 数值型或标称型数据

关系形式: 频繁项集或者关联规则
频繁项集: 经常出现在一块的物品的集合
关联规则: 暗示两种物品之间可能存在很强的关系

例子:
交易号码    商品
0           豆奶,莴苣
1           莴苣,尿布,葡萄酒,甜菜
2           豆奶,尿布,葡萄酒,橙汁
3           莴苣,豆奶,尿布,  葡萄酒
4           莴苣,豆奶,尿布,橙汁

支持度: 数据集中包含该项集额记录所占的比例。
上述例子中,{豆奶}支持度为4/5
5条交易记录中有3条: {豆奶,尿布},{豆奶,尿布}的支持度为3/5
支持度特点: 可定义最小支持度

可信度/置信度:
含义: 是指针对一条诸如{尿布}->{葡萄酒}的关联规则来定义的。
规则可信度被定义为:  "支持度({尿布,葡萄酒})/支持度({尿布})
由于{尿布,葡萄酒}支持度为3/5, {尿布}支持度为4/5,
所以 尿布 -> 葡萄酒 的可信度=3/4=0.75
意味着,对于包含尿布的所有记录,我们的规则对其中75%的记录都适用。

3 Apriori原理
作用: 帮我们减少感兴趣的项集。
原理: 如果某个项集是频繁的,那么它的所有子集也是频繁的。
      如果一个项集是非频繁集,那么它的所有超集也是非频繁的。
    举个例子:如果{0, 1}是频繁的,那么{0}和{1}也一定是频繁的。
apriori: 来自以前 。 定义问题时,会使用先验知识或假设,这被称作是一个先验。
        例如: 贝叶斯统计。
        先验知识可能来自领域知识,先前的一些测量结果等。
我的理解: 实际Apriori的原理就是类似剪枝,减小搜索规模。


4 使用Apriori算法来发现频繁项集
关联分析的目标: 发现频繁项集和发现关联规则。
处理过程: 先发现频繁项集,然后获得关联规则。
本节只关注发现频繁项集。

Apriori算法:
作用: 发现频繁项集
算法思想:
步骤1: 生成所有单个物品的项集列表
步骤2:扫描交易记录来查看哪些项集满足最小支持度要求,过滤不满足的
步骤3: 对剩余集合进行组合来生成包含两个元素的项集
步骤4:重新扫描交易记录,去掉不满足最小支持度的项集。
步骤5: 重复上述步骤,直到所有项集被去掉。

5 完整的Apriori算法
思想:
当集合项中的个数大于0时
    构建一个k个项组成的候选项集的列表
    确保每个项集都是频繁的
    保留频繁项集并构建k+1项组成的候选项集的列表

apriori算法完整过程:
输入参数: 数据集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 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)
    # C1 = createC1(dataSet)
    # print "C1: {C1}".format(C1=C1)
    # D = map(set, dataSet)
    # print "D: {D}".format(D=D)
    # minSupport = 0.5
    # L1, canToSupport = scanD(D, C1, minSupport)
    # print "L1: {L1},canToSupport: {canToSupport}".format(L1=L1, canToSupport=canToSupport)



if __name__ == "__main__":
    process()

猜你喜欢

转载自blog.csdn.net/qingyuanluofeng/article/details/86988615
今日推荐