
(2011-07-18 11:28:52)
首先我们来看,什么是规则?规则形如”如果…那么…(If…Then…)”,前者为条件,后者为结果。关联规则挖掘用于寻找给定数据集中项之间的有趣的关联或相关关系。关联规则揭示了数据项间的未知的依赖关系,根据所挖掘的关联关系,可以从一个数据对象的信息来推断另一个数据对象的信息。例如购物篮分析。牛奶⇒ 面包 [支持度:3%,置信度:40%]
支持度3%意味3%顾客同时购买牛奶和面包。置信度40%意味购买牛奶的顾客40%也购买面包。规则的支持度和置信度是两个规则兴趣度度量,它们分别反映发现规则的有用性和确定性。关联规则是有趣的,如果它满足最小支持度阈值和最小置信度阈值。这些阈值可以由用户或领域专家设定。
我们先来认识几个相关的定义:
定义1:支持度(support)
支持度s是事务数据库D中包含A U B的事务百分比,它是概率P(A U B),即support(A B)=P(A U B),它描述了A和B这两个物品集的并集在所有的事务中出现的概率。
定义2:置信度(confidence)
可信度为事务数据库D中包含A的事务中同时也包含B的百分比,它是概率P(B|A),即confidence(A B)=P(B|A)。
定义3:频繁项目集
支持度不小于用户给定的最小支持度阈值(minsup)的项集称为频繁项目集(简称频集),或者大项目集。所有
的频繁1-项集记为L1。
假设有如下表的购买记录。
| 顾客 | 项目 |
| 1 | orange juice, coke |
| 2 | milk, orange juice, window cleaner |
| 3 | orange juice, detergent |
| 4 | orange juice, detergent, coke |
| 5 | window cleaner |
| Orange | Win Cl | Milk | Coke | Detergent | |
| Orange | 4 | 1 | 1 | 2 | 2 |
| WinCl | 1 | 2 | 1 | 0 | 0 |
| Milk | 1 | 1 | 1 | 0 | 0 |
| Coke | 2 | 0 | 0 | 2 | 1 |
| Detergent | 1 | 0 | 0 | 0 | 2 |
置信度表示了这条规则有多大程度上值得可信。设条件的项的集合为A,结果的集合为B。置信度计算在A中,同时也含有B的概率。即Confidence(A==>B)=P(B|A)。例如计算"如果Orange则Coke"的置信度。由于在含有Orange的4条交易中,仅有2条交易含有Coke.其置信度为0.5。
支持度计算在所有的交易集中,既有A又有B的概率。例如在5条记录中,既有Orange又有Coke的记录有2条。则此条规则的支持度为2/5=0.4。现在这条规则可表述为,如果一个顾客购买了Orange,则有50%的可能购买Coke。而这样的情况(即买了Orange会再买Coke)会有40%的可能发生。
再来考虑下述情况。
| 项 | 支持度 |
| A | 0.45 |
| B | 0.42 |
| C | 0.4 |
| A and B | 0.25 |
| A and C | 0.2 |
| B and C | 0.15 |
| A,B,and C | 0.05 |
| 规则 | 置信度 |
| If B and C then A | 0.05/0.15*100%=33.33% |
| If A and C then B | 0.05/0.20*100%=25% |
| If A and B then C | 0.05/0.25*100%=20% |
对于规则" If B and C then A",同时购买B和C的人中,有33.33%会购买A。而单项A的支持度有0.45,也就是说在所有交易中,会有45%的人购买A.看来使用这条规则来进行推荐,还不如不推荐,随机对顾客进荐好了。
为此引入另外一个量,即提升度(Lift),以度量此规则是否可用。描述的是相对于不用规则,使用规则可以提高多少。有用的规则的提升度大于1。计算方式为Lift(A==>B)=Confidence(A==>B)/Support(B)=Support(A==>B)/(Support(A)*Support(B))。在上例中,Lift(If B and C The A)=0.05/(0.15*0.45)=0.74。而Lift(If A then B)=0.25/(0.45*0.42)=1.32。也就是说对买了A的人进行推荐B,购买概率是随机推荐B的1.32倍。
如何产生规则呢。可以分两步走。
首先找出频繁集(frequent itemset)。所谓频繁集指满足最小支持度或置信度的集合。其次从频繁集中找出强规则(strong rules)。强规则指既满足最小支持度又满足最小置信度的规则。
我们来看如何产生频繁集。
这其中有一个定理。即频繁集的子集也一定是频繁集。比如,如果{A,B,C}是一个3项的频繁集,则其子集{A,B},{B,C},{A,C}也一定是2项的频繁集。为方便,可以把含有k项的集合称之为k-itemsets.
下面以迭代的方式找出频繁集。首先找出1-itemsets的频繁集,然后使用这个1-itemsets,进行组合,找出2-itemsets的频繁集。如此下去,直到不再满足最小支持度或置信度的条件为止。这其中重要的两步骤分别是连接(join)和剪枝(prune).即从(k-1)-itemsets中的项进行组合,产生备选集(Candidate itemsets)。再从备选集中,将不符合最小支持度或置信度的项删去。例如
| Frequent 2-itemsets | Candidate 3-itemsets | Frqquent 3-itemsets | ||
| I1,I2 | ==> | I1,I2,I4 | ==> | I1,I2,I4 |
| I1,I4 | I2,I3,I4 | |||
| I2,I3 | ||||
| I2,I4 |
设最小支持度为2,以Ck表示k-itemsets备选集,以Lk表示k-itemsets频繁集。
| ID | Items | Itemset | Sup. count | Itemset | Itemset | |||
| 100 | I1,I2,I5 | I1 | 6 | I1 | I1,I2 | |||
| 200 | I2,I4 | ==>C1: | I2 | 7 | ==>L1: | I2 | ==>C2 | I1,I3 |
| 300 | I2,I3 | I3 | 6 | I3 | I1,I4 | |||
| 400 | I1,I2,I4 | I4 | 2 | I4 | I1,I5 | |||
| 500 | I1,I3 | I5 | 2 | I5 | I2,I3 | |||
| 600 | I2,I3 | I2,I4 | ||||||
| 700 | I1,I3 | I2,I5 | ||||||
| 800 | I1,I2,I3,I5 | I3,I4 | ||||||
| 900 | I1,I2,I3 | I3,I5 | ||||||
| I4,I5 |
| Itemset | Sup. count | Itemset | Itemset | Sup. count | Itemset | |||
| I1,I2 | 4 | ==> L2: | I1,I2 | ==> C3 | I1,I2,I3 | 2 | ==> L3: | I1,I2,I3 |
| I1,I3 | 4 | I1,I3 | I1,I2,I5 | 2 | I1,I2,I5 | |||
| I1,I4 | 1 | I1,I5 | ||||||
| I1,I5 | 2 | I2,I3 | ||||||
| I2,I3 | 4 | I2,I4 | ||||||
| I2,I4 | 2 | I2,I5 | ||||||
| I2,I5 | 2 | |||||||
| I3,I4 | 0 | |||||||
| I3,I5 | 1 | |||||||
| I4,I5 | 0 |
对于频繁集中的每一项k-itemset,可以产生非空子集,对每一个子集,可以得到满足最小置信度的规则了。例如考虑{I1,I2,I5}。其子集有{I1,I2}, {I1,I5}, {I2,I5}, {I1}, {I2}, {I5}。可以产生规则,{I1,I2} => {I5} (50%), {I1,I5} => {I2} (100%), {I2,I5} =>{I1} (100%),{I1} => {I2,I5} (33%), {I2} =>{I1,I5} (29%), {I5} =>{I1,I2} (100%)。
也不是每个数据集都有产生强规则。例如"Thinkpad notebook" 和"Canon printer"一起可能很难产生有效规则。因为恰好一起买这两个牌子的产品的顾客太少。但不妨将Thinkpad notebook放到Notebook这一层次上考虑,而Canon printer放到printer这一去层次上考虑。这样的话,一起买notebook和printer的顾客就较多了。也即Multilevel association rules。
自1994年由Agrawal等人提出的关联规则挖掘Apriori的算法从其产生到现在,对关联规则挖掘方面的研究有着很大的影响。Apriori算法是一种最有影响的挖掘布尔关联规则频繁项集的算法。算法基于这样的事实:算法使用频繁项集性质的先验知识。Apriori使用一种称作逐层搜索的迭代方法,k-项集用于探索(k+1)-项集。首先,找出频繁1-项集的集合。该集合记作L1。L1 用于找频繁2-项集的集合L2,而L2 用于找L3,如此下去,直到不能找到频繁k-项集。找每个Lk 需要一次数据库扫描。为提高频繁项集逐层产生的效率,一种称作Apriori性质的重要性质用于压缩搜索空间。
为了提高频繁项目集逐层产生的效率,Apriori算法利用了两个重要的性质用于压缩搜索空间:
(l)若X是频繁项集,则x的所有子集都是频繁项集。
(2)若x是非频繁项集,则X的所有超集都是非频繁项集。
算法:Apriori算法,使用逐层迭代找出频繁项集。
输入:事务数据库D;最小支持度阈值min_sup。
输出:D 中的频繁项集L。
1) L1 = find_frequent_1_itemsets(D);
2) for (k = 2; Lk-1 ≠ ; k++) {
3)Ck = aproiri_gen(Lk-1,min_sup);
4) for each transaction t D{ //scan D for count
5) Ct = subset(Ck,t); //get subsets of t that are candidates
6) for each candidate c Ct
7)c.count++;
8) }
9)Lk={c Ck | c.count ≥ min_sup}
10) }
11) return L = ∪kLk;
现举例说明:如表1所示为事务数据库D,设最小支持度为20%,挖掘频繁项集的具体过程如表1所示。
表1 事务数据库D
图1所示为Apriori算法挖掘频繁集的过程,其中最小支持度为20%。
图1 Apriori算法的执行流程
第一步,经过算法的第一次迭代,对事务数据库进行一次扫描,计算出D中所包含的每个项目出现的次数,生成候选1-项集的集合C1。
第二步,根据设定的最小支持度,从C1中确定频繁1-项集L1。
第三步,由L1产生候选2-项集C2,然后扫描事务数据库对C2中的项集进行计数。
第四步,根据最小支持度,从候选集C2中确定频繁集L2。
第五步,由频繁2-项集L2生成候选3-项集C3,生成的候选3-项集的集合C3={{1,2,3},{1,3,5},{2,3,5}},根据Apriori的性质剪枝:所有的频繁项集的子集都是频繁的,项集{1,2,3}的子集{1,2}不包含在频繁2-项集L2中,故删除{1,2,3}。项集{1,3,5}的子集{1,5}也不包含在频繁2-项集L2中,故删除{1,3,5},项集{2,3,5}的所有子集都是L2的元素,故保留。
Apriori算法的效率分析:
(1)Apriori性质能显著减少候选集的数目。事务数据库TDB总共有4个项目,因此可能的2-项集有 =6个。正如本例所示,利用Apriori性质,我们只需要检查4个候选2-项集的支持度。Apriori算法在2项集这个层次上剪枝率达33.3%。随着候选集的长度逐渐增大,可能的组合数目也急剧增大,因此Apriori算法的剪枝效率也越来越高。
(2)尽管Apriori能对大量候选集剪枝,但是在大型的事务数据库中,仍然可能有大量的候选集需要处理,并且这种操作相当耗时。例如,如果事务数据库包含106个项目,并且只有1%(即104)的项目是频繁的,Apriori需要产生超过107个候选2-项集,扫描数据库计算它们的支持度,生成L2以产生候选3-项集。
(3)反复地扫描数据库、计算候选集的支持度,再生成新的长度加1的候选集,Apriori算法是冗长乏味的,尤其是当存在长模式的时候。Apriori是一种产生候选集,然后检测其支持度的算法。为挖掘频集X ={x1,x2…,x100} . Apriori需要扫描数据库100次。
针对Apriori算法存在的缺点,人们对Apriori算法进行了多方面的改进,希望能够找出一个高效、可靠的挖掘频繁项集的算法。这些算法大多是以Apriori为核心,或是其变体,或是其扩展。如增量更新算法、并行算法等
下面是使用Java语言实现的Apriori算法,实现了AprioriAlgorithm类,包含了频繁项集的挖掘过程和频繁关联规则的挖掘过程。
另外,有一个辅助类ProperSubsetCombination用于计算一个频繁项集的真子集,采用组合原理,基于数值编码原理实现的组合求解集合的真子集。
算法实现
(一)核心类
Apriori算法的核心实现类为AprioriAlgorithm,实现的Java代码如下所示:
packageorg.shirdrn.datamining.association;
importjava.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class AprioriAlgorithm {
private Map private Float minSup; // 最小支持度 private Float minConf; // 最小置信度 private Integer txDatabaseCount; // 事务数据库中的事务数 private Map private Map public AprioriAlgorithm(Map this.txDatabase = txDatabase; this.minSup = minSup; this.minConf = minConf; this.txDatabaseCount = this.txDatabase.size(); freqItemSet = new TreeMap assiciationRules = new HashMap } public Map Map Map Iterator while(it.hasNext()) { Map.Entry // 计算支持度 Float supported = new Float(entry.getValue().toString())/new Float(txDatabaseCount); if(supported>=minSup) { freq1ItemSetMap.put(entry.getKey(), supported); } } return freq1ItemSetMap; } public Map Map Iterator // 统计支持数,生成候选频繁1-项集 while(it.hasNext()) { Map.Entry Set for(String item : itemSet) { Set key.add(item.trim()); if(!candFreq1ItemSetMap.containsKey(key)) { Integer value = 1; candFreq1ItemSetMap.put(key, value); } else { Integer value = 1+candFreq1ItemSetMap.get(key); candFreq1ItemSetMap.put(key, value); } } } return candFreq1ItemSetMap; } public Set Set Iterator Set while(it.hasNext()) { originalItemSet = it.next(); Iterator while(itr.hasNext()) { Set identicalSet.addAll(originalItemSet); Set identicalSet.retainAll(set); // identicalSet中剩下的元素是identicalSet与set集合中公有的元素 if(identicalSet.size() == m-1) { // (k-1)-项集中k-2个相同 Set differentSet.addAll(originalItemSet); differentSet.removeAll(set); // 因为有k-2个相同,则differentSet中一定剩下一个元素,即differentSet大小为1 differentSet.addAll(set); // 构造候选k-项集的一个元素(set大小为k-1,differentSet大小为k) candFreqKItemSet.add(differentSet); // 加入候选k-项集集合 } } } return candFreqKItemSet; } private Iterator Iterator while(it.hasNext()) { if(itemSet.equals(it.next())) { break; } } return it; } public Map Map // 调用aprioriGen方法,得到候选频繁k-项集 Set // 扫描事务数据库 Iterator // 统计支持数 while(it.hasNext()) { Map.Entry Iterator while(kit.hasNext()) { Set Set set.addAll(kSet); set.removeAll(entry.getValue()); // 候选频繁k-项集与事务数据库中元素做差元算 if(set.isEmpty()) { // 如果拷贝set为空,支持数加1 if(candFreqKItemSetMap.get(kSet) == null) { Integer value = 1; candFreqKItemSetMap.put(kSet, value); } else { Integer value = 1+candFreqKItemSetMap.get(kSet); candFreqKItemSetMap.put(kSet, value); } } } } // 计算支持度,生成频繁k-项集,并返回 return support(candFreqKItemSetMap); } public Map Map Iterator while(it.hasNext()) { Map.Entry // 计算支持度 Float supportRate = new Float(entry.getValue().toString())/new Float(txDatabaseCount); if(supportRate } else { freqKItemSetMap.put(entry.getKey(), supportRate); } } return freqKItemSetMap; } public void mineFreqItemSet() { // 计算频繁1-项集 Set freqItemSet.put(1, freqKItemSet); // 计算频繁k-项集(k>1) int k = 2; while(true) { Map if(!freqKItemSetMap.isEmpty()) { this.freqItemSet.put(k, freqKItemSetMap.keySet()); freqKItemSet = freqKItemSetMap.keySet(); } else { break; } k++; } } public void mineAssociationRules() { freqItemSet.remove(1); // 删除频繁1-项集 Iterator while(it.hasNext()) { Map.Entry for(Set // 对每个频繁项集进行关联规则的挖掘 mine(itemSet); } } } public void mine(Set int n = itemSet.size()/2; // 根据集合的对称性,只需要得到一半的真子集 for(int i=1; i<=n; i++) { // 得到频繁项集元素itemSet的作为条件的真子集集合 Set // 对条件的真子集集合中的每个条件项集,获取到对应的结论项集,从而进一步挖掘频繁关联规则 for(Set Set conclusionSet.addAll(itemSet); conclusionSet.removeAll(conditionSet); // 删除条件中存在的频繁项 confide(conditionSet, conclusionSet); // 调用计算置信度的方法,并且挖掘出频繁关联规则 } } } public void confide(Set // 扫描事务数据库 Iterator // 统计关联规则支持计数 intconditionToConclusionCnt = 0; // 关联规则(条件项集推出结论项集)计数 intconclusionToConditionCnt = 0; // 关联规则(结论项集推出条件项集)计数 intsupCnt = 0; // 关联规则支持计数 while(it.hasNext()) { Map.Entry Set Set Set set1.addAll(conditionSet); set1.removeAll(txSet); // 集合差运算:set-txSet if(set1.isEmpty()) { // 如果set为空,说明事务数据库中包含条件频繁项conditionSet // 计数 conditionToConclusionCnt++; } set2.addAll(conclusionSet); set2.removeAll(txSet); // 集合差运算:set-txSet if(set2.isEmpty()) { // 如果set为空,说明事务数据库中包含结论频繁项conclusionSet // 计数 conclusionToConditionCnt++; } if(set1.isEmpty() && set2.isEmpty()) { supCnt++; } } // 计算置信度 Float conditionToConclusionConf = new Float(supCnt)/new Float(conditionToConclusionCnt); if(conditionToConclusionConf>=minConf) { if(assiciationRules.get(conditionSet) == null) { // 如果不存在以该条件频繁项集为条件的关联规则 Set conclusionSetSet.add(conclusionSet); assiciationRules.put(conditionSet, conclusionSetSet); } else { assiciationRules.get(conditionSet).add(conclusionSet); } } Float conclusionToConditionConf = new Float(supCnt)/new Float(conclusionToConditionCnt); if(conclusionToConditionConf>=minConf) { if(assiciationRules.get(conclusionSet) == null) { // 如果不存在以该结论频繁项集为条件的关联规则 Set conclusionSetSet.add(conditionSet); assiciationRules.put(conclusionSet, conclusionSetSet); } else { assiciationRules.get(conclusionSet).add(conditionSet); } } } public Map return freqItemSet; } public Map return assiciationRules; } } (二)辅助类 ProperSubsetCombination类是一个辅助类,在挖掘频繁关联规则的过程中,用于生成一个频繁项集元素的非空真子集,实现代码如下: packageorg.shirdrn.datamining.association; import java.util.BitSet; import java.util.HashSet; import java.util.Set; public class ProperSubsetCombination { private static String[] array; private static BitSetstartBitSet; // 比特集合起始状态 private static BitSetendBitSet; // 比特集合终止状态,用来控制循环 private static Set public static Set String[] array = new String[itemSet.size()]; ProperSubsetCombination.array = itemSet.toArray(array); properSubset = new HashSet startBitSet = new BitSet(); endBitSet = new BitSet(); // 初始化startBitSet,左侧占满1 for (int i=0; i } // 初始化endBit,右侧占满1 for (int i=array.length-1; i>=array.length-n; i--) { endBitSet.set(i, true); } // 根据起始startBitSet,将一个组合加入到真子集集合中 get(startBitSet); while(!startBitSet.equals(endBitSet)) { intzeroCount = 0; // 统计遇到10后,左边0的个数 intoneCount = 0; // 统计遇到10后,左边1的个数 intpos = 0; // 记录当前遇到10的索引位置 // 遍历startBitSet来确定10出现的位置 for (int i=0; i zeroCount++; } if (startBitSet.get(i) && !startBitSet.get(i+1)) { pos = i; oneCount = i - zeroCount; // 将10变为01 startBitSet.set(i, false); startBitSet.set(i+1, true); break; } } // 将遇到10后,左侧的1全部移动到最左侧 int counter = Math.min(zeroCount, oneCount); intstartIndex = 0; intendIndex = 0; if(pos>1 && counter>0) { pos--; endIndex = pos; for (int i=0; i startBitSet.set(endIndex, false); startIndex = i+1; pos--; if(pos>0) { endIndex = pos; } } } get(startBitSet); } return properSubset; } private static void get(BitSetbitSet) { Set for(int i=0; i set.add(array[i]); } } properSubset.add(set); } } 测试用例 对上述Apriori算法的实现进行了简单的测试,测试用例如下所示: packageorg.shirdrn.datamining.association; importjava.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; importorg.shirdrn.datamining.association.AprioriAlgorithm; importjunit.framework.TestCase; public class TestAprioriAlgorithm extends TestCase { private AprioriAlgorithmapriori; private Map private Float minSup = new Float("0.50"); private Float minConf = new Float("0.70"); @Override protected void setUp() throws Exception { create(); // 构造事务数据库 apriori = new AprioriAlgorithm(txDatabase, minSup, minConf); } public void create() { txDatabase = new HashMap Set set1.add("A"); set1.add("B"); set1.add("C"); set1.add("E"); txDatabase.put(1, set1); Set set2.add("A"); set2.add("B"); set2.add("C"); txDatabase.put(2, set2); Set set3.add("C"); set3.add("D"); txDatabase.put(3, set3); Set set4.add("A"); set4.add("B"); set4.add("E"); txDatabase.put(4, set4); } public void testFreq1ItemSet() { System.out.println("挖掘频繁1-项集 : " + apriori.getFreq1ItemSet()); } public void testAprioriGen() { System.out.println( "候选频繁2-项集: " + this.apriori.aprioriGen(1, this.apriori.getFreq1ItemSet().keySet()) ); } public void testGetFreq2ItemSet() { System.out.println( "挖掘频繁2-项集:" + this.apriori.getFreqKItemSet(2, this.apriori.getFreq1ItemSet().keySet()) ); } public void testGetFreq3ItemSet() { System.out.println( "挖掘频繁3-项集:" + this.apriori.getFreqKItemSet( 3, this.apriori.getFreqKItemSet(2, this.apriori.getFreq1ItemSet().keySet()).keySet() ) ); } public void testGetFreqItemSet() { this.apriori.mineFreqItemSet(); // 挖掘频繁项集 System.out.println("挖掘频繁项集:" + this.apriori.getFreqItemSet()); } public void testMineAssociationRules() { this.apriori.mineFreqItemSet(); // 挖掘频繁项集 this.apriori.mineAssociationRules(); System.out.println("挖掘频繁关联规则:" + this.apriori.getAssiciationRules()); } } 测试结果: 挖掘频繁1-项集 : {[E]=0.5, [A]=0.75, [B]=0.75, [C]=0.75} 候选频繁2-项集: [[E, C], [A, B], [B, C], [A, C], [E, B], [E, A]] 挖掘频繁2-项集:{[A, B]=0.75, [B, C]=0.5, [A, C]=0.5, [E, B]=0.5, [E, A]=0.5} 挖掘频繁3-项集:{[E, A, B]=0.5, [A, B, C]=0.5} 挖掘频繁项集:{1=[[E], [A], [B], [C]], 2=[[A, B], [B, C], [A, C], [E, B], [E, A]], 3=[[E, A, B], [A, B, C]]} 挖掘频繁关联规则:{[E]=[[A], [B], [A, B]], [A]=[[B]], [B]=[[A]], [B, C]=[[A]], [A, C]=[[B]], [E, B]=[[A]], [E, A]=[[B]]} 从测试结果看到,使用Apriori算法挖掘得到的全部频繁项集为: {1=[[E], [A], [B], [C]], 2=[[A, B], [B, C], [A, C], [E, B], [E, A]], 3=[[E, A, B], [A, B, C]]} 使用Apriori算法挖掘得到的全部频繁关联规则为: {E}→{A}、{E}→{B}、{E}→{A,B}、{A}→{B}、{B}→{A}、{B,C}→{A}、{A,C}→{B}、{B,E}→{A}、{A,E}→{B}。 本文内容来自网上文章的综合整理,感谢原文作者:Stentor、顾红其、Shirdrn
