关联分析之Apriori算法

转载地址:http://blog.csdn.net/rongyongfeikai2/article/details/40457827


1.数据挖掘与关联分析

数据挖掘是一个比较庞大的领域,它包括数据预处理(清洗去噪)、数据仓库、分类聚类、关联分析等。关联分析可以算是数据挖掘最贴近我们生活的一部分了,打开卓越亚马逊,当挑选一本《Android4高级编程》时,它会不失时机的列出你可能还会感兴趣的书籍,比如Android游戏开发、Cocos2d-x引擎等,让你的购物车又丰富了些,而钱包又空了些。

关联分析,即从一个数据集中发现项之间的隐藏关系。本篇文章Apriori算法主要是基于频繁集的关联分析,所以本文中所出现的关联分析默认都是指基于频繁集的关联分析。

有了一个感性的认识,我们来一段理性的形式化描述:

令项集I={i1,i2,...in}

且有一个数据集合D,它其中的每一条记录T,都是I的子集

那么关联规则都是形如A->B的表达式,A、B均为I的子集,且A与B的交集为空

这条关联规则的支持度:support = P(A并B)

这条关联规则的置信度:confidence = support(A并B)/suport(A)

如果存在一条关联规则,它的支持度和置信度都大于预先定义好的最小支持度与置信度,我们就称它为强关联规则。强关联规则就可以用来了解项之间的隐藏关系。所以关联分析的主要目的就是为了寻找强关联规则,而Apriori算法则主要用来帮助寻找强关联规则。

注意:因为频率=事件出现次数/总事件次数,为了方便,我们在以下都用事件出现的频数而非频率来作为支持度。

2.Apriori算法描述

Apriori算法指导我们,如果要发现强关联规则,就必须先找到频繁集。所谓频繁集,即支持度大于最小支持度的项集。如何得到数据集合D中的所有频繁集呢?

有一个非常土的办法,就是对于数据集D,遍历它的每一条记录T,得到T的所有子集,然后计算每一个子集的支持度,最后的结果再与最小支持度比较。且不论这个数据集D中有多少条记录(十万?百万?),就说每一条记录T的子集个数({1,2,3}的子集有{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3},即如果记录T中含有n项,那么它的子集个数是2^n-1)。计算量非常巨大,自然是不可取的。

所以Aprior算法提出了一个逐层搜索的方法,如何逐层搜索呢?包含两个步骤:

1.自连接获取候选集。第一轮的候选集就是数据集D中的项,而其他轮次的候选集则是由前一轮次频繁集自连接得到(频繁集由候选集剪枝得到)。

2.对于候选集进行剪枝。如何剪枝呢?候选集的每一条记录T,如果它的支持度小于最小支持度,那么就会被剪掉;此外,如果一条记录T,它的子集有不是频繁集的,也会被剪掉。

算法的终止条件是,如果自连接得到的已经不再是频繁集,那么取最后一次得到的频繁集作为结果。

需要值得注意的是:

Apriori算法为了进一步缩小需要计算支持度的候选集大小,减小计算量,所以在取得候选集时就进行了它的子集是否有非频繁集的判断。(参见《数据挖掘:概念与技术》一书)。

另外,两个K项集进行连接的条件是,它们至少有K-1项相同。

知道了这些可以方便我们写出高效的程序。

3.Apriori算法例子推导

上面的描述是不是有点抽象,例子是最能帮助理解的良方。(假设最小支持度为2,最小置信度为0.6,最小支持度和置信度都是人定的,可以根据实验结果的优劣对这两个参数进行调整)

假设初始的数据集D如下:

   

  那么第一轮候选集和剪枝的结果为:

  

可以看到,第一轮时,其实就是用的数据集中的项。而因为最小支持度是2的缘故,所以没有被剪枝的,所以得到的频繁集就与候选集相同。

第二轮的候选集与剪枝结果为:

  

可以看到,第二轮的候选集就是第一轮的频繁集自连接得到的(进行了去重),然后根据数据集D计算得到支持度,与最小支持度比较,过滤了一些记录。频繁集已经与候选集不同了。

第三轮候选集与频繁集结果为:

 

可以看到,第三轮的候选集发生了明显的缩小,这是为什么呢?

请注意取候选集的两个条件:

1.两个K项集能够连接的两个条件是,它们有K-1项是相同的。所以,(I2,I4)和(I3,I5)这种是不能够进行连接的。缩小了候选集。

2.如果一个项集是频繁集,那么它不存在不是子集的频繁集。比如(I1,I2)和(I1,I4)得到(I1,I2,I4),而(I1,I2,I4)存在子集(I1,I4)不是频繁集。缩小了候选集。

第三轮得到的2个候选集,正好支持度等于最小支持度。所以,都算入频繁集。

这时再看第四轮的候选集与频繁集结果:

可以看到,候选集和频繁集居然为空了!因为通过第三轮得到的频繁集自连接得到{I1,I2,I3,I5},它拥有子集{I2,I3,I5},而{I2,I3,I5}不是频繁集,不满足:频繁集的子集也是频繁集这一条件,所以被剪枝剪掉了。所以整个算法终止,取最后一次计算得到的频繁集作为最终的频繁集结果:

也就是:['I1,I2,I3', 'I1,I2,I5']

那么,如何得到强规则呢?

比如对于:I1,I2,I3这个频繁集,我们可以得到它的子集:{I1},{I2},{I3},{I1,I2},{I1,I3},{I2,I3},那么可以得到的规则如下:

I1->I3^I2 0.333333333333
I2->I1^I3 0.285714285714
I3->I1^I2 0.333333333333
I1^I2->I3 0.5
I1^I3->I2 0.5
I2^I3->I1 0.5
左边是规则,右边是置信度。conf(I1->I3^I2) = support(I1,I2,I3)/support(I1)

与最小置信度相比较,我们就可以得到强规则了。

所以对于频繁集['I1,I2,I3', 'I1,I2,I5']

我们得到的规则为:

I1->I3^I2 0.333333333333
I2->I1^I3 0.285714285714
I3->I1^I2 0.333333333333
I1^I2->I3 0.5
I1^I3->I2 0.5
I2^I3->I1 0.5
I1->I2^I5 0.333333333333
I2->I1^I5 0.285714285714
I5->I1^I2 1.0
I1^I2->I5 0.5

I1^I5->I2 1.0
 I2^I5->I1 1.0

从而得到强规则为:

I5->I1^I2 : 1.0
I1^I5->I2 : 1.0
I2^I5->I1 : 1.0

意味着如果用户买了商品I5,则极有可能买商品I1,I2;买了I1和I5,则极有可能买I2,如果买了I2,I5,则极有可能买I1。所以,你就知道在页面上该如何推荐了。

4.Apriori算法源码及输出

为了加强对于Apriori算法的了解,我用Python写了个简单的程序(时间仓促,没有考虑时间空间复杂度,也没有非常严谨的测试正确性),希望以后能对此程序进行修改和优化。

算法源码如下:

[python]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #coding:utf-8  
  2. samples = [  
  3.     ["I1","I2","I5"],  
  4.     ["I2","I4"],  
  5.     ["I2","I3"],  
  6.     ["I1","I2","I4"],  
  7.     ["I1","I3"],  
  8.     ["I2","I3"],  
  9.     ["I1","I3"],  
  10.     ["I1","I2","I3","I5"],  
  11.     ["I1","I2","I3"]  
  12. ]  
  13. min_support = 2  
  14. min_confidence = 0.6  
  15. fre_list = list()  
  16. def get_c1():  
  17.     global record_list  
  18.     global record_dict  
  19.     new_dict = dict()  
  20.     for row in samples:  
  21.         for item in row:  
  22.             if item not in fre_list:  
  23.                 fre_list.append(item)  
  24.                 new_dict[item] = 1  
  25.             else:  
  26.                 new_dict[item] = new_dict[item] + 1  
  27.     fre_list.sort()  
  28.     print "candidate set:"  
  29.     print_dict(new_dict)  
  30.     for key in fre_list:  
  31.         if new_dict[key] < min_support:  
  32.             del new_dict[key]  
  33.     print "after pruning:"  
  34.     print_dict(new_dict)  
  35.     record_list = fre_list  
  36.     record_dict = record_dict  
  37. def get_candidateset():  
  38.     new_list = list()  
  39.     #自连接  
  40.     for i in range(0,len(fre_list)):  
  41.         for j in range(0,len(fre_list)):  
  42.             if i == j:  
  43.                 continue  
  44.             #如果两个k项集可以自连接,必须保证它们有k-1项是相同的  
  45.             if has_samesubitem(fre_list[i],fre_list[j]):  
  46.                 curitem = fre_list[i] + ',' + fre_list[j]  
  47.                 curitem = curitem.split(",")  
  48.                 curitem = list(set(curitem))  
  49.                 curitem.sort()  
  50.                 curitem = ','.join(curitem)  
  51.                 #如果一个k项集要成为候选集,必须保证它的所有子集都是频繁的  
  52.                 if has_infresubset(curitem) == False and already_constains(curitem,new_list) == False:  
  53.                     new_list.append(curitem)  
  54.     new_list.sort()  
  55.     return new_list  
  56. def has_samesubitem(str1,str2):  
  57.     str1s = str1.split(",")  
  58.     str2s = str2.split(",")  
  59.     if len(str1s) != len(str2s):  
  60.         return False  
  61.     nums = 0  
  62.     for items in str1s:  
  63.         if items in str2s:  
  64.             nums += 1  
  65.             str2s.remove(items)  
  66.     if nums == len(str1s) - 1:  
  67.         return True  
  68.     else:  
  69.         return False  
  70. def judge(candidatelist):  
  71.     # 计算候选集的支持度  
  72.     new_dict = dict()  
  73.     for item in candidatelist:  
  74.         new_dict[item] = get_support(item)  
  75.     print "candidate set:"  
  76.     print_dict(new_dict)  
  77.     #剪枝  
  78.     #频繁集的支持度要大于最小支持度  
  79.     new_list = list()  
  80.     for item in candidatelist:  
  81.         if new_dict[item] < min_support:  
  82.             del new_dict[item]  
  83.             continue  
  84.         else:  
  85.             new_list.append(item)  
  86.     global fre_list  
  87.     fre_list = new_list  
  88.     print "after pruning:"  
  89.     print_dict(new_dict)  
  90.     return new_dict  
  91. def has_infresubset(item):  
  92.     # 由于是逐层搜索的,所以对于Ck候选集只需要判断它的k-1子集是否包含非频繁集即可  
  93.     subset_list = get_subset(item.split(","))  
  94.     for item_list in subset_list:  
  95.         if already_constains(item_list,fre_list) == False:  
  96.             return True  
  97.     return False  
  98. def get_support(item,splitetag=True):  
  99.     if splitetag:  
  100.         items = item.split(",")  
  101.     else:  
  102.         items = item.split("^")  
  103.     support = 0  
  104.     for row in samples:  
  105.         tag = True  
  106.         for curitem in items:  
  107.             if curitem not in row:  
  108.                 tag = False  
  109.                 continue  
  110.         if tag:  
  111.             support += 1  
  112.     return support  
  113. def get_fullpermutation(arr):  
  114.     if len(arr) == 1:  
  115.         return [arr]  
  116.     else:  
  117.         newlist = list()  
  118.         for i in range(0,len(arr)):  
  119.             sublist = get_fullpermutation(arr[0:i]+arr[i+1:len(arr)])  
  120.             for item in sublist:  
  121.                 curlist = list()  
  122.                 curlist.append(arr[i])  
  123.                 curlist.extend(item)  
  124.                 newlist.append(curlist)  
  125.         return newlist  
  126. def get_subset(arr):  
  127.     newlist = list()  
  128.     for i in range(0,len(arr)):  
  129.         arr1 = arr[0:i]+arr[i+1:len(arr)]  
  130.         newlist1 = get_fullpermutation(arr1)  
  131.         for newlist_item in newlist1:  
  132.             newlist.append(newlist_item)  
  133.     newlist.sort()  
  134.     newlist = remove_dumplicate(newlist)  
  135.     return newlist  
  136. def remove_dumplicate(arr):  
  137.     newlist = list()  
  138.     for i in range(0,len(arr)):  
  139.         if already_constains(arr[i],newlist) == False:  
  140.             newlist.append(arr[i])  
  141.     return newlist  
  142. def already_constains(item,curlist):  
  143.     import types  
  144.     items = list()  
  145.     if type(item) is types.StringType:  
  146.         items = item.split(",")  
  147.     else:  
  148.         items = item  
  149.     for i in range(0,len(curlist)):  
  150.         curitems = list()  
  151.         if type(curlist[i]) is types.StringType:  
  152.             curitems = curlist[i].split(",")  
  153.         else:  
  154.             curitems = curlist[i]  
  155.         if len(set(items)) == len(curitems) and len(list(set(items).difference(set(curitems)))) == 0:  
  156.             return True  
  157.     return False  
  158. def print_dict(curdict):  
  159.     keys = curdict.keys()  
  160.     keys.sort()  
  161.     for curkey in keys:  
  162.         print "%s:%s"%(curkey,curdict[curkey])  
  163. # 计算关联规则的方法  
  164. def get_all_subset(arr):  
  165.     rtn = list()  
  166.     while True:  
  167.         subset_list = get_subset(arr)  
  168.         stop = False  
  169.         for subset_item_list in subset_list:  
  170.             if len(subset_item_list) == 1:  
  171.                 stop = True  
  172.             rtn.append(subset_item_list)  
  173.         if stop:  
  174.             break  
  175.     return rtn  
  176. def get_all_subset(s):  
  177.     from itertools import combinations  
  178.     return sum(map(lambda r: list(combinations(s, r)), range(1, len(s)+1)), [])  
  179. def cal_associative_rule(frelist):  
  180.     rule_list = list()  
  181.     rule_dict = dict()  
  182.     for fre_item in frelist:  
  183.         fre_items = fre_item.split(",")  
  184.         subitem_list = get_all_subset(fre_items)  
  185.         for subitem in subitem_list:  
  186.             # 忽略为为自身的子集  
  187.             if len(subitem) == len(fre_items):  
  188.                 continue  
  189.             else:  
  190.                 difference = set(fre_items).difference(subitem)  
  191.                 rule_list.append("^".join(subitem)+"->"+"^".join(difference))  
  192.     print "The rule is:"  
  193.     for rule in rule_list:  
  194.         conf = cal_rule_confidency(rule)  
  195.         print rule,conf  
  196.         if conf >= min_confidence:  
  197.             rule_dict[rule] = conf  
  198.     print "The associative rule is:"  
  199.     for key in rule_list:  
  200.         if key in rule_dict.keys():  
  201.             print key,":",rule_dict[key]  
  202. def cal_rule_confidency(rule):  
  203.     rules = rule.split("->")  
  204.     support1 = get_support("^".join(rules),False)  
  205.     support2 = get_support(rules[0],False)  
  206.     if support2 == 0:  
  207.         return 0  
  208.     rule_confidency = float(support1)/float(support2)  
  209.     return rule_confidency  
  210. if __name__ == '__main__':  
  211.     record_list = list()  
  212.     record_dict = dict()  
  213.     get_c1()  
  214.     # 不断进行自连接和剪枝,直到得到最终的频繁集为止;终止条件是,如果自连接得到的已经不再是频繁集  
  215.     # 那么取最后一次得到的频繁集作为结果  
  216.     while True:  
  217.         record_list = fre_list  
  218.         new_list = get_candidateset()  
  219.         judge_dict = judge(new_list)  
  220.         if len(judge_dict) == 0:  
  221.             break  
  222.         else:  
  223.             record_dict = judge_dict  
  224.     print "The final frequency set is:"  
  225.     print record_list  
  226.       
  227.     # 根据频繁集计算关联规则  
  228.     cal_associative_rule(record_list)  

算法的输出如下:

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. candidate set:  
  2. I1:6  
  3. I2:7  
  4. I3:6  
  5. I4:2  
  6. I5:2  
  7. after pruning:  
  8. I1:6  
  9. I2:7  
  10. I3:6  
  11. I4:2  
  12. I5:2  
  13. candidate set:  
  14. I1,I2:4  
  15. I1,I3:4  
  16. I1,I4:1  
  17. I1,I5:2  
  18. I2,I3:4  
  19. I2,I4:2  
  20. I2,I5:2  
  21. I3,I4:0  
  22. I3,I5:1  
  23. I4,I5:0  
  24. after pruning:  
  25. I1,I2:4  
  26. I1,I3:4  
  27. I1,I5:2  
  28. I2,I3:4  
  29. I2,I4:2  
  30. I2,I5:2  
  31. candidate set:  
  32. I1,I2,I3:2  
  33. I1,I2,I5:2  
  34. after pruning:  
  35. I1,I2,I3:2  
  36. I1,I2,I5:2  
  37. candidate set:  
  38. after pruning:  
  39. The final frequency set is:  
  40. ['I1,I2,I3', 'I1,I2,I5']  
  41. The rule is:  
  42. I1->I3^I2 0.333333333333  
  43. I2->I1^I3 0.285714285714  
  44. I3->I1^I2 0.333333333333  
  45. I1^I2->I3 0.5  
  46. I1^I3->I2 0.5  
  47. I2^I3->I1 0.5  
  48. I1->I2^I5 0.333333333333  
  49. I2->I1^I5 0.285714285714  
  50. I5->I1^I2 1.0  
  51. I1^I2->I5 0.5  
  52. I1^I5->I2 1.0  
  53. I2^I5->I1 1.0  
  54. The associative rule is:  
  55. I5->I1^I2 : 1.0  
  56. I1^I5->I2 : 1.0  
  57. I2^I5->I1 : 1.0  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值