首页 > 程序开发 > 软件开发 > 其他 >

机器学习之Apriori算法进行关联分析

2017-02-18

机器学习之Apriori算法进行关联分析:上一章学习了非监督学习的聚类,聚类算法可以将不同性质的分类分开。这两天学习了apriori算法进行关联分析。

机器学习之Apriori算法进行关联分析:上一章学习了非监督学习的聚类,聚类算法可以将不同性质的分类分开。这两天学习了apriori算法进行关联分析,感觉是目前最难理解的一章了,并且书中还有个很坑爹的错误,作者存在很大的疏忽。

Apriori算法关联分析:从大规模数据集中寻找物品间的隐含关系被称作关联分析或者关联规则学习。

关联分析应用1:我们以前学习的是根据特性进行分类或者回归预测,并没有挖掘特性之间的关系,关联分析可以用于分析数据集中特性之间的关系,可以得到哪些特性频繁的共同出现或者特性之间的关系(比如出现特性A就会很大几率出现特性B),举例:如果人的身高和体重分为ABC段转化为012,那么(0, 0),(1, 1),(2,2)可能会频繁出现并且很大几率出现身高0 -> 体重0,体重2 -> 身高2,这在我们最后会有一个小应用。

关联分析应用2:是我们最常应用的一个方面,在商店中我们往往会有两种或者多种物品会被购买者共同购买的情况,比如买了西红柿就会买鸡蛋,因为要做西红柿炒鸡蛋,买了面包就会买牛奶,这样的关系还有很多(还有最著名的啤酒尿布问题),商店可以把频繁的组合商品打折一起销售,这样会更加促销,商店生意更好。商店的任务就是发掘这种频繁出现的商品组合,我们今天学的Apriori算法可以帮我们找到这种频繁的组合关系,并且进行关联分析,给我们一些关联规则,也就是买了西红柿 -> 买了鸡蛋,但是反向不一定成立,因为买了鸡蛋不一定买西红柿,鸡蛋有很多做法(扯远了。。。)。

这个算法中有好多细节需要细细推敲,很多难理解的地方,算法简短却都是精华,Apriori算法有很大的缺点,这个我们最后总结一下。接下来我们看下算法细节。

假设我们有一家经营着4种商品(商品0,商品1,商品2和商品3)的杂货店,图显示了所有商品之间所有的可能组合:

\

在关联分析中,我们只关注商品之间的组合,不关注每件商品到底购买了多少件。我们定一个规则就是商品A和商品B的合集在data集中出现超过50%,我们认为(A, B)为频繁集。我们要算出所有集合是否是频繁集这是极度麻烦的,每多一个商品它的组合集合数目就是指数级增长,我们短时间算出所有频繁集是不现实的。只有频繁集中寻找关联规则才是有意义的,不频繁的组合我们可以认为是偶然现象,不具备发掘的价值。
研究人员发现一种所谓的Apriori原理,可以帮助我们减少计算量。Apriori原理是说如果某个项集是频繁的,那么它的所有子集也是频繁的。更常用的是它的逆否命题,即如果一个项集是非频繁的,那么它的所有超集也是非频繁的。

如果商品1不是频繁的,那么所有含有1的商品组合一定不是频繁的,所以在图中含有1的组合都一定不是频繁集,所以类似于剪枝剪去了大量不需要的计算,这个剪枝在接下来两个函数中体现。

step1:

剪枝1:把不符合最小支持度的组合剪去。默认的最小支持度为0.5。

剪枝2:组合生成函数(从每个组合m个元素增加到m+1个元素)。

剪枝2中有好多细节:

1:这个组合函数用了一种特殊的方式组合更大的集合,若要生成k个元素的组合,那么比较两个k-1元素的排序后前k-2个元素,如果相同,便可以组成k个元素的新组合,如果没有这两个组合其中一个,组不成两者的新组合,因为其中一个为非频繁集,非频繁集的超集也不是频繁集。

2:这种方式避免了多个集合重复的情况,比如(0,1)和(0,2)和(1, 2)会生成3个(1, 2, 3),出现重复的情况,而这种方式只有一个情况出现,并且不会遗漏,遗漏的也是前面1中的情况为非频繁集。

3:如果出现了本为非频繁集却被合成的情况如图2中(0, 2, 3)的情况,那么他也会在下一步计算支持度中被pass。

\

#计算支持度函数
def getSupport(d, ck, minsupport):
    wordnum = {}
    for line in d:
        for c in ck:
            if c.issubset(line):
                if not wordnum.has_key(c):
                    wordnum[c] = 1
                else:
                    wordnum[c] += 1
    dnum = float(len(d))
    res = []
    support = {}
    for key in wordnum:
        tempsupport = wordnum[key] / dnum
        if tempsupport >= minsupport:
            res.insert(0, key)
        support[key] = tempsupport
    return res, support

#构造新的频繁集合
def createCk(lk, k):
    ck = []
    l = len(lk)
    for i in range(l):
        for j in range(i + 1, l):
            l1 = list(lk[i])[ : k - 2]
            l2 = list(lk[j])[ : k - 2]
            l1.sort()
            l2.sort()
            if l1 == l2:
                ck.append(lk[i] | lk[j])
    return ck
step2:

辅助函数(和上面例子无关)

#读取简单数据
def loadDataSet():
    data = [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
    return data

#创建关联词集合
def createC1(dataset):
    c1 = []
    for line in dataset:
        for i in line:
            if not [i] in c1:
                c1.append([i])
    c1.sort()
    return map(frozenset, c1)

step3:

Apriori算法,按照层次筛选频繁集,每一层递增一个组合元素,第一层1个元素,第二层2个元素,如此递推。默认的最小支持度为0.5。

细节不解释了,不难理解。

#apriori算法求频繁项集
def getApriori(dataset, minsupport = 0.5):
    c1 = createC1(dataset)
    d = map(set, dataset)
    L1, support = getSupport(d, c1, minsupport)
    L = [L1]
    k = 2
    while len(L[k - 2]) > 0:
        ck = createCk(L[k - 2], k)
        L2, sup = getSupport(d, ck, minsupport)
        L.append(L2)
        support.update(sup)
        k += 1
    return L, support

我们得到了step2中数据的频繁项集如图。

\

修改最小支持度为0.7后,频繁集明显少了。

\

step4:

前面我们完成了频繁集的计算后接下来进行关联分析:

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

下图给出了从项集{0,1,2,3}产生的所有关联规则,其中阴影区域给出的是低可信度的规则。可以发现如果{0,1,2}?{3}是一条低可信度规则,那么所有其他以3作为后件(箭头右部包含3)的规则均为低可信度的。这一点需要重要解释一下,好多地方都没有提到这点,导致费解。

原因是:可信度为p(0123)/p(012)既然为低可信度集合,那么他生成的子集中分子为p(0123),分母中的项会减少,那么分母的p值很更大,那么可信度会更低,所以为低可信度集合。这一点和Apriori中计算频繁项集很像,后项3不加入到后面更大集合的组合中,这个也用了前面的组合函数createCk(),组不成集合的为低可信度,因为组成的两个为低可信度,也没有重复出现,即使组成了也会被可信度计算函数被pass(如02 -> 13低可信度,但是生成了2 -> 013,也会被pass,符合开头说的原因)。

\

函数为:

#关联分析
def generateRules(L, support, minrules = 0.7):
    rules = []
    l = len(L)
    for i in range(1, l):
        for freqset in L[i]:
            H = [frozenset([item]) for item in freqset]
            getGenerate(freqset, H, support, rules, minrules)
            if i > 1:
                moreGenerate(freqset, H, support, rules, minrules)
    return rules

#查找关联规则
def getGenerate(freqset, H, support, rules, minrules = 0.7):
    newH = []
    for i in H:
        believe = support[freqset] / support[freqset - i]
        if believe > minrules:
            newH.append(i)
            print freqset - i, "-->", i, "believe = ", believe
            rules.append((freqset - i, i, believe))
    return newH

#增加关联规则右项
def moreGenerate(freqset, H, support, rules, minrules = 0.7):
    m = len(H[0])
    if len(freqset) > (m + 1):
        moreH = createCk(H, m + 1)
        moreH = getGenerate(freqset, moreH, support, rules, minrules)
        if len(moreH) > 1:
            moreGenerate(freqset, moreH, support, rules, minrules)
这个函数书中作者有很大的失误,他的generateRules()函数,漏下了全部ABCD.. -> X的组合。即右项为1个元素的关联规则,我改动之后得到正确的结果,这个点纠结了2天,浪费了很多时间,在其他博主看到了这个地方,确认了这个错误,我们要更相信自己,鄙视一下作者。

最后一个都蘑菇的小例子发现频繁出现的特性组合,’2‘表示毒蘑菇,发现和'2'频繁出现的特性我们都认为是毒蘑菇,但是不代表不在频繁集中出现的特性所代表的蘑菇不是毒蘑菇(也许会有’2‘)。

2个集合的频繁集(没显示完)

\

4个集合的频繁集(没显示完)

\

总结一下Apriori的缺点:我们在计算频繁项集的时候每次计算支持度每个集合都遍历了一次data集,效率极低,明天学习FP-tree的计算频繁项集的方法,只用遍历少数次data集,今天就到这里了。

相关文章
最新文章
热点推荐