Apriori 算法关联分析_lwbeyond的博客-程序员宅基地

技术标签: 算法  机器学习  

描述:

关联分析是一种在大规模数据集中寻找有趣关系的任务。这些关系可以由两种形式:频繁项集或者关联规则。

频繁项集:是经常出现在一起的物品的集合。
关联规则:暗示两种物品之间可能存在很强的关系。


量化定义:

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

如果我们把上面的概念进行数学量化,就会得到两个相关的量化定义:
支持度(support):数据集中包含该项集的记录所占的比例,支持度是针对项集来说的。
在5条记录中,{豆奶}的支持度为 4/5,{豆奶,尿布}的支持度为3/5。

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


我们先来看频繁项计算,然后分析关联度。

一. 频繁项集:

假设我们有一家经营着4种商品(商品0,商品1,商品2和商品3)的杂货店,下图显示了所有商品之间所有的可能组合:
这里写图片描述
对于单个项集的支持度,我们可以通过遍历每条记录并检查该记录是否包含该项集来计算。对于包含N中物品的数据集共有2N−1种项集组合,重复上述计算过程是不现实的。

研究人员发现一种所谓的Apriori原理,可以帮助我们减少计算量:
Apriori原理是说如果某个项集是频繁的,那么它的所有子集也是频繁的。
更常用的是它的逆否命题,即如果一个项集是非频繁的,那么它的所有超集也是非频繁的。
这里写图片描述

具体实现步骤如下:
1. 首先构建集合C1,然后扫描数据集来判断这些只有一个元素的项集是否满足最小支持度的要求。
2. 对于满足最低要求的项集构成L1。
3. 再由L1组合成C2,C2再进一步过滤变成L2。
4. L2再次组合成C3,……直到最后!

C1是最开始的原始集合,根据最小支持度,可以过滤得到C1的 L1 集。
由只有一个元素的L1集成,通过排列组合,生成有两个元素的C2集合。
C2集合再次根据最小支持度计算,得到L2集合。
L2->C3->L3->C4……,直到最后

代码实现:

#随便一个数据集,用于测试
def loadDataSet():
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]


#构建C1,C1是大小为 1 的所有候选项集的集合
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))#use frozen set so we
                            #can use it as a key in a dict

    #输出为:[frozenset({1}), frozenset({2}), frozenset({3}), frozenset({4}), frozenset({5})]


#扫描函数,输入:测试项集,候选项集(C1,C2,Cn..),支持度.
def scanD(D, Ck, minSupport):
    #ssCnt数据字典,键就是集合,值是集合出现的次数
    ssCnt = {}
    for tid in D:
        for can in Ck:
            if can.issubset(tid):
                #if not ssCnt.has_key(can):
                if can not in ssCnt:
                    ssCnt[can]=1
                else:
                    ssCnt[can] += 1

        #对于C1来说,单一集合在测试集中的出现次数如下:
        #{ frozenset({1}):2, frozenset({2}):3, frozenset({3}):1, frozenset({4}):1, frozenset({5}):3 }

    #测试的项数
    numItems = float(len(list(D))) #len(list())
    retList = []
    supportData = {}

    for key in ssCnt:
        #计算支持度
        support = ssCnt[key]/numItems
        if support >= minSupport:
            retList.insert(0,key)
        supportData[key] = support

    #返回一个集合L1,又返回一个包含支持度的一个字典
    return retList, supportData

#辅助函数,由频繁项集列表L1及项集个数K,生成一个新的组合CK
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

#-----------------------------------
#-----------------------------------
#真正的评估函数,C1->L1, L1->C2, C2->L2, L2-C3, ...
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)#scan DB to get Lk
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L, supportData   

我们把这个过程记录下来,如下:
第一次迭代:
C1 -> L1
C1:1,2,3,4,5
L1:1,3,2,5

第二次迭代:
L1 -> C2
C2:13,12,15,23,35,25
L2:13,25,23,35

第三次迭代:
L2 -> C3
C3:132,135,235
L3:235

前面提到,关联分析的目标包括两项:发现频繁项集和发现关联规则。
首先需要找到频繁项集,然后才能获得关联规则。

现在,已经解决了频繁项集问题,下一步就可以解决相关规则问题。



二. 从频繁集中挖掘相关规则:

要找到关联规则,我们首先从一个频繁项集开始。从杂货店的例子可以得到,如果有一个频繁项集{豆奶, 莴苣},那么就可能有一条关联规则“豆奶➞莴苣”。这意味着如果有人购买了豆奶,那么在统计上他会购买莴苣的概率较大。注意这一条反过来并不总是成立,也就是说,可信度(“豆奶➞莴苣”)并不等于可信度(“莴苣➞豆奶”)。

前文也提到过,一条规则P➞H的可信度定义为support(P | H)/support(P),其中“|”表示P和H的并集。可见可信度的计算是基于项集的支持度的。

下图给出了从项集{0,1,2,3}产生的所有关联规则,其中阴影区域给出的是低可信度的规则。可以发现如果{0,1,2}➞{3}是一条低可信度规则,那么所有其他以3作为后件(箭头右部包含3)的规则均为低可信度的。

这里写图片描述

可以观察到,如果某条规则并不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求。以上图为例,假设规则{0,1,2} ➞ {3}并不满足最小可信度要求,那么就知道任何左部为{0,1,2}子集的规则也不会满足最小可信度要求。
可以利用关联规则的上述性质属性来减少需要测试的规则数目,类似于Apriori算法求解频繁项集。

实现原理:
我们已经得到所有的频繁率列表了,那么我们将运用置信率公式来计算相关的置信度。

频繁项表如下:
L= { {1,3,2,5}, {13,25,23,35}, {235} }

从 L[1] 开始计算:
support(13)/support(1) —- 由1 –>3的置信度
support(13)/support(3) —- 由3 –>1的置信度
support(25)/support(2) —- 由2 –>5的置信度
support(25)/support(5) —- 由5 –>2的置信度
……
support(235)/support(5) —- 由5 –>23的置信度
support(235)/support(2) —- 由2 –>35的置信度
support(235)/support(3) —- 由3 –>25的置信度

代码:

#输入频繁项集,包含支持度的频繁项集,最小可信度
def generateRules(L, supportData, minConf=0.7):  #supportData is a dict coming from scanD
    #所有的规则,放在规则表中
    bigRuleList = []
    for i in range(1, len(L)):#only get the sets with two or more items
        for freqSet in L[i]:
            H1 = [frozenset([item]) for item in freqSet]
            if (i > 1):
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else:
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList         

这个函数是主函数,调用其他两个函数。其他两个函数是 rulesFromConseq() 和 calcConf(),分别用于生成候选规则集合以及对规则进行评估(计算支持度)。

函数generateRules()有3个参数:频繁项集列表L、包含那些频繁项集支持数据的字典supportData、最小可信度阈值minConf。
函数最后要生成一个包含可信度的规则列表bigRuleList,后面可以基于可信度对它们进行排序。L和supportData正好为函数apriori()的输出。
该函数遍历L中的每一个频繁项集,并对每个频繁项集构建只包含单个元素集合的列表H1。代码中的i指示当前遍历的频繁项集包含的元素个数,freqSet为当前遍历的频繁项集(回忆L的组织结构是先把具有相同元素个数的频繁项集组织成列表,再将各个列表组成一个大列表,所以为遍历L中的频繁项集,需要使用两层for循环)。

# 辅助函数——计算规则的可信度,并过滤出满足最小可信度要求的规则
def calcConf(freqSet, H, supportData, brl, minConf=0.7):
    prunedH = [] #create new list to return
    for conseq in H:
        # support(P|H)/support(P),表示由P->H的支持度
        conf = supportData[freqSet]/supportData[freqSet-conseq] #calc confidence

        if conf >= minConf: 
            print (freqSet-conseq,'-->',conseq,'conf:',conf)
            brl.append((freqSet-conseq, conseq, conf))
            prunedH.append(conseq)

    return prunedH

计算规则的可信度以及找出满足最小可信度要求的规则。函数返回一个满足最小可信度要求的规则列表,并将这个规则列表添加到主函数的bigRuleList中(通过参数brl)。返回值prunedH保存规则列表的右部,这个值将在下一个函数rulesFromConseq()中用到。

#辅助函数——根据当前候选规则集H生成下一层候选规则集
def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
    m = len(H[0])
    if (len(freqSet) > (m + 1)): #try further merging
        Hmp1 = aprioriGen(H, m+1)#create Hm+1 new candidates
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
        if (len(Hmp1) > 1):    #need at least two sets to merge
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)

从最初的项集中生成更多的关联规则。该函数有两个参数:频繁项集freqSet,可以出现在规则右部的元素列表H。其余参数:supportData保存项集的支持度,brl保存生成的关联规则,minConf同主函数。函数先计算H中的频繁项集大小m。接下来查看该频繁项集是否大到可以移除大小为m的子集。如果可以的话,则将其移除。使用函数aprioriGen()来生成H中元素的无重复组合,结果保存在Hmp1中,这也是下一次迭代的H列表。



三. 关于rulesFromConseq()函数的问题

1. 问题的提出

频繁项集L的值前面提到过。我们在其中计算通过{2, 3, 5}生成的关联规则,可以发现关联规则{3, 5}➞{2}和{2, 3}➞{5}的可信度都应该为1.0的,因而也应该包括在当minConf = 0.7时的rules中——但是这在前面的运行结果中并没有体现出来。minConf = 0.5时也是一样,{3, 5}➞{2}的可信度为1.0,{2, 5}➞{3}的可信度为2/3,{2, 3}➞{5}的可信度为1.0,也没有体现在rules中。

通过分析程序代码,我们可以发现:

当i = 1时,generateRules()函数直接调用了calcConf()函数直接计算其可信度,因为这时L[1]中的频繁项集均包含两个元素,可以直接生成和判断候选关联规则。比如L[1]中的{2, 3},生成的候选关联规则为{2}➞{3}、{3}➞{2},这样就可以了。
当i > 1时,generateRules()函数调用了rulesFromConseq()函数,这时L[i]中至少包含3个元素,如{2, 3, 5},对候选关联规则的生成和判断的过程需要分层进行(图4)。这里,将初始的H1(表示初始关联规则的右部,即箭头右边的部分)作为参数传递给了rulesFromConseq()函数。
例如,对于频繁项集{a, b, c, …},H1的值为[a, b, c, …](代码中实际为frozenset类型)。如果将H1带入计算可信度的calcConf()函数,在函数中会依次计算关联规则{b, c, d, …}➞{a}、{a, c, d, …}➞{b}、{a, b, d, …}➞{c}……的支持度,并保存支持度大于最小支持度的关联规则,并保存这些规则的右部(prunedH,即对H的过滤,删除支持度过小的关联规则)。

当i > 1时没有直接调用calcConf()函数计算通过H1生成的规则集。在rulesFromConseq()函数中,首先获得当前H的元素数m = len(H[0])(记当前的H为Hm)。当Hm可以进一步合并为m+1元素数的集合Hm+1时(判断条件:len(freqSet) > (m + 1)),依次:

生成Hm+1:Hmpl = aprioriGen(H, m + 1)
计算Hm+1的可信度:Hmpl = calcConf(freqSet, Hmpl, …)
递归计算由Hm+1生成的关联规则:rulesFromConseq(freqSet, Hmpl, …)
所以这里的问题是,在i>1时,rulesFromConseq()函数中并没有调用calcConf()函数计算H1的可信度,而是直接由H1生成H2,从H2开始计算关联规则——于是由元素数>3的频繁项集生成的{a, b, c, …}➞{x}形式的关联规则(图4中的第2层)均缺失了。由于代码示例数据中的对H1的剪枝prunedH没有删除任何元素,结果只是“巧合”地缺失了一层。正常情况下如果没有对H1进行过滤,直接生成H2,将给下一层带入错误的结果(如图4中的012➞3会被错误得留下来)。

2. 对问题代码的修改

在i>1时,将对H1调用calcConf()的过程加上就可以了。比如可以这样:

def generateRules2(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]
            if (i > 1):
                # 三个及以上元素的集合
                H1 = calcConf(freqSet, H1, supportData, bigRuleList, minConf)
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else:
                # 两个元素的集合
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList

这里就只需要修改generateRules()函数。这样实际运行效果中,刚才丢失的那几个关联规则就都出来了。

进一步修改:当i=1时的else部分并没有独特的逻辑,这个if语句可以合并,然后再修改rulesFromConseq()函数,保证其会调用calcConf(freqSet, H1, …):

def generateRules3(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]
            rulesFromConseq2(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList

def rulesFromConseq2(freqSet, H, supportData, brl, minConf=0.7):
    m = len(H[0])
    if (len(freqSet) > m): # 判断长度改为 > m,这时即可以求H的可信度
        Hmpl = calcConf(freqSet, H, supportData, brl, minConf)
        if (len(Hmpl) > 1): # 判断求完可信度后是否还有可信度大于阈值的项用来生成下一层H
            Hmpl = aprioriGen(Hmpl, m + 1)
            rulesFromConseq2(freqSet, Hmpl, supportData, brl, minConf) # 递归计算,不变

运行结果和generateRules2相同。

进一步修改:消除rulesFromConseq2()函数中的递归项。这个递归纯粹是偷懒的结果,没有简化任何逻辑和增加任何可读性,可以直接用一个循环代替:

def rulesFromConseq3(freqSet, H, supportData, brl, minConf=0.7):
    m = len(H[0])
    while (len(freqSet) > m): # 判断长度 > m,这时即可求H的可信度
        H = calcConf(freqSet, H, supportData, brl, minConf)
        if (len(H) > 1): # 判断求完可信度后是否还有可信度大于阈值的项用来生成下一层H
            H = aprioriGen(H, m + 1)
            m += 1
        else: # 不能继续生成下一层候选关联规则,提前退出循环
            break

另一个主要的区别是去掉了多余的Hmpl变量。运行的结果和generateRules2相同。

至此,一个完整的Apriori算法就完成了。



四. 小结

关联分析是用于发现大数据集中元素间有趣关系的一个工具集,可以采用两种方式来量化这些有趣的关系。第一种方式是使用频繁项集,它会给出经常在一起出现的元素项。第二种方式是关联规则,每条关联规则意味着元素项之间的“如果……那么”关系。

发现元素项间不同的组合是个十分耗时的任务,不可避免需要大量昂贵的计算资源,这就需要一些更智能的方法在合理的时间范围内找到频繁项集。能够实现这一目标的一个方法是Apriori算法,它使用Apriori原理来减少在数据库上进行检查的集合的数目。Apriori原理是说如果一个元素项是不频繁的,那么那些包含该元素的超集也是不频繁的。Apriori算法从单元素项集开始,通过组合满足最小支持度要求的项集来形成更大的集合。支持度用来度量一个集合在原始数据中出现的频率。

关联分析可以用在许多不同物品上。商店中的商品以及网站的访问页面是其中比较常见的例子。

每次增加频繁项集的大小,Apriori算法都会重新扫描整个数据集。当数据集很大时,这会显著降低频繁项集发现的速度。下面会介绍FP-growth算法,和Apriori算法相比,该算法只需要对数据库进行两次遍历,能够显著加快发现频繁项集的速度。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lwbeyond/article/details/74932996

智能推荐

python写的本地搜索小工具0.9_iteye_5014的博客-程序员宅基地

#coding=utf-8 #smallsearchtoolbytkinter #testplatform:WindowsXPChinese #version0.9 #author:zhouhh #filename:find.py #date:2008.12.16 #email:ablozhou#gmail.com #note...

绿皮书再版!ECMAScript 5th有讲呵!_iteye_2733的博客-程序员宅基地

终于可以发博来讲这本书了。哈哈哈,终于印出来了哇!从去年9月一直等到现在,同志们啊,我被催得好那个悲【3月19日补充】各大网上书店开始有售:当当:http://product.dangdang.com/product.aspx?product_id=22628482china-pub:http://product.china-pub.com/199103亚马路(^^.)...

灯光数据_iteye_721的博客-程序员宅基地

因为只有一个网页内容,所以StockPriceWebUrlList类没有用到。在编写代码的时候遇到过很多这样那样的问题,主要还是要运用好自己手头的工具,不会的地方先要先查jdk或者看书,懒的时候选择直接百度,很多问题都是可以在百度上找到的,找不到的问题暂时是不存在的,一个大问题,可以分解成小问题,一个很有意思的地方是,当我在百度知道里面找到部分关于自己遇到的问题的解答时,往往还能在下面的“...

java通过月日判断星座_iteye_20313的博客-程序员宅基地

根据月日判断星座:[code="java"]/** * 根据月日判断星座 * @param month * @param day * @return int */ public static String getConstellation(int m,int d){ final Strin...

去电影院看《悲惨世界》,真的很好看的!_iteye_4185的博客-程序员宅基地

音乐剧吧,很好看的,那首多次响起的音乐很好听!只是不知道叫啥名字!

Lucene:基于Java的全文检索引擎简介(四)_iteye_2156的博客-程序员宅基地

索引过程优化索引一般分2种情况,一种是小批量的索引扩展,一种是大批量的索引重建。在索引过程中,并不是每次新的DOC加入进去索引都重新进行一次索引文件的写入操作(文件I/O是一件非常消耗资源的事情)。Lucene先在内存中进行索引操作,并根据一定的批量进行文件的写入。这个批次的间隔越大,文件的写入次数越少,但占用内存会很多。反之占用内存少,但文件IO操作频繁,索引速度会很慢。在IndexWr...

随便推点

Task1:Python基础和Numpy基础_xxl_lei的博客-程序员宅基地

一、python基础下面的python基础涉及python的基本数据结构等知识,本来想着在这里补充上的,试了一下发现并没有网上一些教程写的好,本着不重复造轮子的想法(其实就是懒)也就不加了,如果需要的话可参考廖雪峰的python教程1、列表推导式和条件赋值列表推导式:按照一定的语法帮助简化操作,可以较容易的获取一个列表,语法为[A for i in B],其中A为函数名(需要注意的是别忘了要带参数比如:A(i)),B是一个迭代的对象。具体例子如下所示:def my_func(x): retu

程序员如何提高工作效率_iteye_4515的博客-程序员宅基地

在网上看到这文章<<开发人员间的效率差在哪里>>,觉得挺好的,把自己晒晒,做做总结:<!-- [if gte mso 10]><mce:style><!-- /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-...

考新交规的经验分享(倒库,S型,窄路掉头,直角弯,侧方,坡道启动,隧道)..._iteye_4921的博客-程序员宅基地

一、倒库实际经验总结(认真领悟,3次就搞定了)  一个平时主要写程序的IT技术人员,第二次上车,第一次学倒库。按道理对于一个头脑较发达的人,四肢应该不是很灵活,开车应该不是很有天赋,但是第一次真正开车,师傅却说我很有领悟力,很快就学会倒库了。 对于手动档的车,其实开车无外乎就是会到方向盘,会根据速度的变化适时的变化踩离合器的轻重。而倒库是很多师傅让学车人员开始学车便开始学习...

pktgen的安装与使用_iteye_5282的博客-程序员宅基地

pktgen的安装与使用系统环境:fefora core 12如果你和我一样,在pktgen面前是个新人。是不是也曾遇到下面的问题:(1)以为pktgen和tcpdump一样是Linux下的工具软件;(2)后来,明白了pktgen不是工具,而是内核模块,但是不知道如何加载;(3)加载后,却不会使用,并且一度一位pktgen无法与网络接口eth0建立映射关系...

JDK 1.6 API 中英文版 在线版、下载地址_iteye_16520的博客-程序员宅基地

JDK 1.6 API(全)* HTML 格式(在线英文) http://download.oracle.com/javase/6/docs/api/* HTML 格式(在线中文) http://download.java.net/jdk/jdk-api-localizations/jdk-api-zh-cn/publish/1.6.0/html/zh_CN/api/index.htm...

初步了解数字电视与IPTV_iteye_271的博客-程序员宅基地

   最近业务需要不小心接触到数字电视、IPTV,对这个只有一知半解,上网搜索相关内容补补课学习下。最后把学习总结拿出来,与有需要的同学共享。数字电视概念数字电视是相对模拟电视而言的,数字电视从演播室到发射、传输、接收的所有环节都是使用数字电视信号来传播,传播速率是每秒19.39兆字节,大大高于现在的宽带网络速度。因为全过程均采用数字技术处理,因此,信号损失小,接收效果好。数字电视...