关联规则(二)Apriori算法

1. 先验原理

1. 性质

上次说到,Apriori算法有两个重要特性。

  • 频繁项集的非空子集一定是频繁的
  • 非频繁项集的超集一定是非频繁的

这两个性质就是Apriori算法的基础,插图解释。(插图用PPT画的,有好的画图方法,请大佬赐教)。
在这里插入图片描述

都从1-项集开始分析的,假设1,2,3,4都满足最小支持度,那么它们都是频繁1-项集。
2-项集分析时,发现34不是频繁项集,标红。其余为频繁2-项集。
3-项集分析时,134,234 都不是频繁项集,标红。
4-项集分析时,1234不是频繁4-项集。

性质1 ,频繁项集的非空子集一定是频繁的。知道123 是频繁项集,就可以得出结论,12,13,23,1,2,3肯定都是频繁项集。234同理。
性质2 ,非频繁项集的超集一定是非频繁的。分析过程中,得知34是非频繁的,那么就可以知道134,234一定也是非频繁的。

2. 算法流程

流程是死的,就不解释什么了。感兴趣的还可以看看伪代码。
输入:数据集合D,支持度阈值αα
输出:最大的频繁k项集

  1. 扫描整个数据集,得到所有出现过的数据,作为候选频繁1项集。k=1,频繁0项集为空集。
  2. 挖掘频繁k项集
    a) 扫描数据计算候选频繁k项集的支持度
    b) 去除候选频繁k项集中支持度低于阈值的数据集,得到频繁k项集。如果得到的频繁k项集为空,则直接返回频繁k-1项集的集合作为算法 结果,算法结束。如果得到的频繁k项集只有一项,则直接返回频繁k项集的集合作为算法结果,算法结束。
    c) 基于频繁k项集,连接生成候选频繁k+1项集。
  3. 令k=k+1,转入步骤2。

3. 剪枝和连接

剪枝和连接是算法的核心。通过当项集不满足支持度的时候,我们删除。
1-项集都满足最小支持度,不需要剪枝,只要连接。因此,1,2,3,4 被连接成12,13,14,23,24,34。
2-项集时,34 不满足最小支持度,因此删除。这一步删除就是剪枝,删除之后,234,134,1234 都不会再出现。而12,13,14,23,24连接成123,124。
3-项集时,123,124 运行结束。
如果数据比较多,则算法就会4-项集,5-项集,,,n-项集一直这样下去。

2. python 代码分析

from numpy import *

def loadDataSet():  ## 定义的数据集,选数字还是选字母自行选择,也可以用其他的数据集代替。
    #return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
    return [['A','B','D','E'],['E','B','D'],['B','D','E'],['E','D'],['A','B','D'],['D'],['B','E'],['E'],['A','C','D','E'],['A','B','C','D','E']]

def createC1(dataSet):  # 创造候选项集C1,C1是大小为1的所有候选项集的集合
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
    # C1.sort()     # 从大到小排序   本想用排序删除重复项,放弃了。
    #print("CCC=",C1)  
    return list(map(frozenset, C1))      #这里采用不可改变的集合(frozenset函数决定)删除重复项,然后返回去重的列表。

def scanD(D, Ck, minSupport):  # 此函数计算支持度,筛选满足要求的项集成为频繁项集Lk,D是数据集,Ck为候选项集C1或C2或C3 ...
    ssCnt = {}                  # 你会发现,就是计数,求概率。毕竟是条件概率,先验概率。
    #print("CK==",Ck)
    for tid in D:
        for can in Ck:
            if can.issubset(tid):
                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)
            retList.append(key)
        supportData[key] = support

    return retList, supportData


def aprioriGen(Lk, k):  # 这里是利用了性质的aproriGen
    #print("lk==",Lk)
    lenLk = len(Lk)
    temp_dict = {}  # 临时字典,存储
    for i in range(lenLk):
        for j in range(i + 1, lenLk):
            L1 = Lk[i] | Lk[j]  # 两两合并,执行了 lenLk!次         #算法时间复杂度高的原因
            #print("L1==",L1)
            #print("sss",Lk[i])
            if len(L1) == k:  # 如果合并后的子项元素有k个,满足要求
                if not L1 in temp_dict:  # 把符合的新项存到字典的键中,使用字典可以去重复,比如{1,2,3}和{3,1,2}是一样的项,使用了字典就可以达到去重的作用
                    temp_dict[L1] = 1

    return list(temp_dict)  # 把字典的键转化为列表

# def aprioriGen(Lk, k):  # 没有改变之前的函数,根据频繁项集Lk-1创造候选项集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:   # 比较前Lk-1中的两个项的k-2个元素是否相同,这样才能保证合成后只有k个元素
#                retList.append(Lk[i]|Lk[j])

#    return retList


def apriori(dataSet, minSupport=0.2):  # 通过循环得出[L1,L2,L3..]频繁项集列表
    C1 = createC1(dataSet)  # 创造C1
    D = list(map(set, dataSet))
    #print("DDD=",D)
    L1, supportData = scanD(D, C1, minSupport)
# 筛选出L1
    L = [L1]
    print(L)
    k = 2

    while (len(L[k - 2]) > 0):  # 创造Ck
        print("k==",k,L[k-2])
        Ck = aprioriGen(L[k - 2], k)
        Lk, supK = scanD(D, Ck, minSupport)
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L, supportData


if __name__ == "__main__":
    dataSet = loadDataSet()
    L, suppData = apriori(dataSet)

代码注释写在了程序里,有两个版本的aprioriGen()函数供选择,数据集可以自己制作,也可以用给出的例子。注意apriori()算法的逻辑。

猜你喜欢

转载自blog.csdn.net/qq_40575024/article/details/105847266