数据挖掘中的模式发现(二)Apriori算法

来源:互联网 发布:音乐视频剪辑软件 编辑:程序博客网 时间:2024/06/06 13:17

基本概念

对于AB

  1. 支持度(support):

    P(AB),既有A又有B的概率

  2. 置信度(Confidence Strength):

    conf(AB)=sup(AB)sup(A)=P(B|A)

    即,在A发生的事件中同时发生B的概率

    例如购物篮分析:牛奶面包

    例子:[支持度:3%,置信度:40%]

    支持度3%:意味着3%顾客同时购买牛奶和面包

    置信度40%:意味着购买牛奶的顾客40%也购买面包

  3. 候选集(Candidate itemset):

    通过向下合并得出的项集。

    定义为C[k]。

  4. 频繁集(Frequent itemset):
    支持度大于等于特定的最小支持度(Minimum Support/minsup)的项集。

    表示为L[k]。

  5. 提升比率(提升度Lift):

    lift(XY)=lift(YX)=conf(XY)supp(Y)=conf(YX)supp(X)=P(XY)P(X)P(Y)

  6. 向下封闭属性(Downward Closure Property)

    如果一个项集满足某个最小支持度要求,那么这个项集的任何非空子集必须都满足这个最小支持度。

Apriori算法简介

Apriori算法是一种挖掘关联规则的频繁项集算法,其核心思想是通过候选集生成向下封闭、检测两个阶段来挖掘频繁项集。

Apriori算法应用广泛,可用于消费市场价格分析,猜测顾客的消费习惯;网络安全领域中的入侵检测技术;可用在用于高校管理中,根据挖掘规则可以有效地辅助学校管理部门有针对性的开展贫困助学工作;也可用在移动通信领域中,指导运营商的业务运营和辅助业务提供商的决策制定。

挖掘步骤:

1.依据支持度找出所有频繁项集(频度)

2.依据置信度产生关联规则(强度)

实现步骤

Apriori使用一种称作逐层搜索的迭代方法,“K-1项集”用于搜索“K项集”。

首先,扫描整个事务,找出频繁1-项集的集合,该集合记作L1。L1用于找频繁“2项集”的集合L2,而L2用于找L3。如此下去,直到不能找到“K项集”。找每个Lk都需要一次数据库扫描。

核心思想是:连接步和剪枝步。连接步是自连接,原则是保证前k-2项相同,并按照字典顺序连接。剪枝步,是使任一频繁项集的所有非空子集也必须是频繁的。反之,如果某个候选的非空子集不是频繁的,那么该候选肯定不是频繁的,从而可以将其从CK中删除。

简单的讲,1、发现频繁项集,过程为(1)扫描(2)计数(3)比较(4)产生频繁项集(5)连接、剪枝,产生候选项集 重复步骤(1)~(5)直到不能发现更大的频集

产生关联规则

根据前面提到的置信度的定义,关联规则的产生如下:

(1)对于每个频繁项集L,产生L的所有非空子集;

(2)对于L的每个非空子集S,如果

P(L)P(S)min_conf

则输出规则 SLS

注:L-S表示在项集L中除去S子集的项集

这里写图片描述

伪代码

伪代码描述:   // 找出频繁 1 项集       L1 =find_frequent_1-itemsets(D);        For(k=2;Lk-1 !=null;k++){  // 产生候选,并剪枝          Ck =apriori_gen(Lk-1 );   // 扫描 D 进行候选计数          For each 事务t  in D{               Ct =subset(Ck,t); // 得到 t 的子集              For each 候选 c 属于 Ct                  c.count++;          }          //返回候选项集中不小于最小支持度的项集          Lk ={c 属于 Ck | c.count>=min_sup}  }  Return L= 所有的频繁集;  第一步:连接(joinProcedure apriori_gen (Lk-1 :frequent(k-1)-itemsets)        For each 项集 l1 属于 Lk-1           For each 项集 l2 属于 Lk-1              If( (l1 [1]=l2 [1])&&( l1 [2]=l2 [2])&& ……&& (l1 [k-2]=l2 [k-2])&&(l1 [k-1]<l2 [k-1]) )   then{                      c = l1 连接 l2    // 连接步:产生候选                    //若k-1项集中已经存在子集c则进行剪枝                     if has_infrequent_subset(c, Lk-1 ) then                         delete c; // 剪枝步:删除非频繁候选                     else add c to Ck;                     }            Return Ck;  第二步:剪枝(prune)    Procedure has_infrequent_sub (c:candidate k-itemset; Lk-1 :frequent(k-1)-itemsets)           For each (k-1)-subset s of c              If s 不属于 Lk-1 then                 Return true;          Return false;  

代码

#coding:utf-8  samples = [      ["I1","I2","I5"],      ["I2","I4"],      ["I2","I3"],      ["I1","I2","I4"],      ["I1","I3"],      ["I2","I3"],      ["I1","I3"],      ["I1","I2","I3","I5"],      ["I1","I2","I3"]  ]  min_support = 2  min_confidence = 0.6  fre_list = list()  def get_c1():      global record_list      global record_dict      new_dict = dict()      for row in samples:          for item in row:              if item not in fre_list:                  fre_list.append(item)                  new_dict[item] = 1              else:                  new_dict[item] = new_dict[item] + 1      fre_list.sort()      print "candidate set:"      print_dict(new_dict)      for key in fre_list:          if new_dict[key] < min_support:              del new_dict[key]      print "after pruning:"      print_dict(new_dict)      record_list = fre_list      record_dict = record_dict  def get_candidateset():      new_list = list()      #自连接      for i in range(0,len(fre_list)):          for j in range(0,len(fre_list)):              if i == j:                  continue              #如果两个k项集可以自连接,必须保证它们有k-1项是相同的              if has_samesubitem(fre_list[i],fre_list[j]):                  curitem = fre_list[i] + ',' + fre_list[j]                  curitem = curitem.split(",")                  curitem = list(set(curitem))                  curitem.sort()                  curitem = ','.join(curitem)                  #如果一个k项集要成为候选集,必须保证它的所有子集都是频繁的                  if has_infresubset(curitem) == False and already_constains(curitem,new_list) == False:                      new_list.append(curitem)      new_list.sort()      return new_list  def has_samesubitem(str1,str2):      str1s = str1.split(",")      str2s = str2.split(",")      if len(str1s) != len(str2s):          return False      nums = 0      for items in str1s:          if items in str2s:              nums += 1              str2s.remove(items)      if nums == len(str1s) - 1:          return True      else:          return False  def judge(candidatelist):      # 计算候选集的支持度      new_dict = dict()      for item in candidatelist:          new_dict[item] = get_support(item)      print "candidate set:"      print_dict(new_dict)      #剪枝      #频繁集的支持度要大于最小支持度      new_list = list()      for item in candidatelist:          if new_dict[item] < min_support:              del new_dict[item]              continue          else:              new_list.append(item)      global fre_list      fre_list = new_list      print "after pruning:"      print_dict(new_dict)      return new_dict  def has_infresubset(item):      # 由于是逐层搜索的,所以对于Ck候选集只需要判断它的k-1子集是否包含非频繁集即可      subset_list = get_subset(item.split(","))      for item_list in subset_list:          if already_constains(item_list,fre_list) == False:              return True      return False  def get_support(item,splitetag=True):      if splitetag:          items = item.split(",")      else:          items = item.split("^")      support = 0      for row in samples:          tag = True          for curitem in items:              if curitem not in row:                  tag = False                  continue          if tag:              support += 1      return support  def get_fullpermutation(arr):      if len(arr) == 1:          return [arr]      else:          newlist = list()          for i in range(0,len(arr)):              sublist = get_fullpermutation(arr[0:i]+arr[i+1:len(arr)])              for item in sublist:                  curlist = list()                  curlist.append(arr[i])                  curlist.extend(item)                  newlist.append(curlist)          return newlist  def get_subset(arr):      newlist = list()      for i in range(0,len(arr)):          arr1 = arr[0:i]+arr[i+1:len(arr)]          newlist1 = get_fullpermutation(arr1)          for newlist_item in newlist1:              newlist.append(newlist_item)      newlist.sort()      newlist = remove_dumplicate(newlist)      return newlist  def remove_dumplicate(arr):      newlist = list()      for i in range(0,len(arr)):          if already_constains(arr[i],newlist) == False:              newlist.append(arr[i])      return newlist  def already_constains(item,curlist):      import types      items = list()      if type(item) is types.StringType:          items = item.split(",")      else:          items = item      for i in range(0,len(curlist)):          curitems = list()          if type(curlist[i]) is types.StringType:              curitems = curlist[i].split(",")          else:              curitems = curlist[i]          if len(set(items)) == len(curitems) and len(list(set(items).difference(set(curitems)))) == 0:              return True      return False  def print_dict(curdict):      keys = curdict.keys()      keys.sort()      for curkey in keys:          print "%s:%s"%(curkey,curdict[curkey])  # 计算关联规则的方法  def get_all_subset(arr):      rtn = list()      while True:          subset_list = get_subset(arr)          stop = False          for subset_item_list in subset_list:              if len(subset_item_list) == 1:                  stop = True              rtn.append(subset_item_list)          if stop:              break      return rtn  def get_all_subset(s):      from itertools import combinations      return sum(map(lambda r: list(combinations(s, r)), range(1, len(s)+1)), [])  def cal_associative_rule(frelist):      rule_list = list()      rule_dict = dict()      for fre_item in frelist:          fre_items = fre_item.split(",")          subitem_list = get_all_subset(fre_items)          for subitem in subitem_list:              # 忽略为为自身的子集              if len(subitem) == len(fre_items):                  continue              else:                  difference = set(fre_items).difference(subitem)                  rule_list.append("^".join(subitem)+"->"+"^".join(difference))      print "The rule is:"      for rule in rule_list:          conf = cal_rule_confidency(rule)          print rule,conf          if conf >= min_confidence:              rule_dict[rule] = conf      print "The associative rule is:"      for key in rule_list:          if key in rule_dict.keys():              print key,":",rule_dict[key]  def cal_rule_confidency(rule):      rules = rule.split("->")      support1 = get_support("^".join(rules),False)      support2 = get_support(rules[0],False)      if support2 == 0:          return 0      rule_confidency = float(support1)/float(support2)      return rule_confidency  if __name__ == '__main__':      record_list = list()      record_dict = dict()      get_c1()      # 不断进行自连接和剪枝,直到得到最终的频繁集为止;终止条件是,如果自连接得到的已经不再是频繁集      # 那么取最后一次得到的频繁集作为结果      while True:          record_list = fre_list          new_list = get_candidateset()          judge_dict = judge(new_list)          if len(judge_dict) == 0:              break          else:              record_dict = judge_dict      print "The final frequency set is:"      print record_list      # 根据频繁集计算关联规则      cal_associative_rule(record_list)  

Apriori算法的改进

  • 基于散列(Hash)的方法

1995年,Park等提出了一种基于散列(Hash)技术产生频繁项集的算法。这种方法把扫描的项目放到不同的Hash桶中,每个频繁项最多只能放在一个特定的桶里,这样可以对每个桶中的频繁项自己进行测试,减少了候选频繁项集产生的代价

  • 事务压缩

事务压缩是指压缩未来迭代扫描的事务数。由于不包含任何频繁k-项集的事务是不可能包含任何频繁(k+1)-项集的,因此这种事务在后续的考虑中可以加上标记或者直接删除,因此产生j-项集(j>k)的数据库扫描不再需要它们。

  • 基于数据划分(Partition)的方法

Apriori算法在执行过程中首先生成候选集,然后再进行剪枝。可是生成的候选集并不都是有效的,有些候选集根本不是事务数据的项目集。因此,候选集的产生具有很大的代价。特别是内存空间不够导致数据库与内存之间不断交换数据,会使算法的效率变得很差。

使用划分方法,则挖掘频繁项集只需要进行两次扫描:

第一次:将数据划分到多个部分并找到局部频繁项集。(把大容量数据库从逻辑上分成几个互不相交的块,同时保证划分的项集能够完整地存入内存而不必进行I/O操作。每部分应用挖掘算法(如Apriori算法)生成局部的频繁项集,然后把这些局部的频繁项集作为候选的全局频繁项目集。)

第二次:评估每个候选项集的实际支持度,以确定全局频繁项集。

  • 基于采样(Sampling)的方法

基于采样的方法是Toivonen于1996年提出的,这个算法的基本思想是:选取给定数据D的随机样本S,然后在S而不是D中搜索频繁项集。用这种方法是牺牲了一些精度换取有效性。样本S的大小选取使得可以在内存搜索S中的频繁项集。这样只需要扫描一次S中的事务。由于算法只是搜索S中的数据,因此可能会丢失一些全局频繁项集。为了减少这样的情况,使用比最小支持度低的支持度阈值来找出局部于S的频繁项集(记做LS)。然后,数据库的其余部分用于计算LS中每个项集的实际频率。使用一种机制来确定是否所有的频繁项集都包含在LS中。如果LS实际包含了D中的所有频繁项集,则只需扫描一次D。否则,可以做第二次扫描来找出第一次扫描时遗漏的频繁项集。

0 0
原创粉丝点击