关联规则和Apriori算法
Apriori算法两大定理:
1.A subset of a frequent itemset must be frequent:
频繁项集的所有非空子集都是频繁项目集
2.The supersets of any infrequent itemset cannot be frequent
任何不频繁项集的超集都是非频繁项目集
Apriori算法例子
// An highlighted block
Set.of("1","3","4")
Set.of("2","3","5")
Set.of("1","2","3","5")
Set.of("2","5")
minSupport=50%
minConfidence=80%
用Java简单实现Apriori算法
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class Apriori {
List<Set<String>> allFrequentSet=new ArrayList<Set<String>>();
List<Set<String>> data=new ArrayList<Set<String>>();
double minSupport;
double confidence;
public Apriori(List<Set<String>> data,double minSupport,double confidence) {
this.data=data;
this.minSupport=minSupport;
this.confidence=confidence;
List<Set<String>> itemSet = createItemSet(data);
List<Set<String>> sc=null;
int k=0;
while(!itemSet.isEmpty()) {
System.out.println("K \t:"+(++k));
System.out.println("itemSet:"+itemSet);
Map<Set<String>, Integer> frequent = frequentSet(itemSet,this.data);
System.out.println("frequent:"+frequent);
sc = scan(frequent,this.minSupport);
System.out.println("scan"+sc);
itemSet=candidateSet(sc);
}
associationRules(sc);
}
//关联规则
public void associationRules(List<Set<String>> sc) {
System.out.println(" lk\t\t 关联规则 \t\t confidence \t support \t 是否是强规则");
sc.forEach(set->{
List<Set<String>> subset = nonEmptySubset(set);
int y=support(set,data);
double support = 1.0*y/data.size();
subset.forEach(sub->{
int x=support(sub,data);
HashSet<String> target = new HashSet<String>(set);
target.removeAll(sub);
double conf = 1.0*y/x;
System.out.println(set+"\t"+sub+"-->"+target+
"\t"+y+"/"+x+"("+String.format("%.2f", conf)+")\t"+
y+"/"+data.size()+"("+String.format("%.2f", support)+
")\t"+(conf>this.confidence));
});
});
}
//计算频繁项目集的支持度
public Integer support(Set<String> set,List<Set<String>> data){
return (int) data.stream()
.filter(d->d.containsAll(set))
.count();
}
//生成候选项集
//安算法来讲应该是将候选项集按一定规则排序 将两个集合只有最后一个元素不同的合并
//个人理解:
//对任意频繁集A B属于S 若 C = A 并 B 是频繁的 则 D=C- (A 交 B) 也是频繁的
public List<Set<String>> candidateSet(List<Set<String>> list) {//候选项集
List<Set<String>> candidate=new ArrayList<Set<String>>();
for(int i=0;i<list.size();i++) {
for(int j=i+1;j<list.size();j++) {
Set<String> item = list.get(i);
Set<String> compare = list.get(j);
if(item.size()==1){
Set<String> unionn=new HashSet<String>(item);
unionn.addAll(compare);
candidate.add(unionn);
}else{
Set<String> intersection=new HashSet<String>(item);
intersection.retainAll(compare);
if(!intersection.isEmpty()) {
Set<String> unionn=new HashSet<String>(item);
unionn.addAll(compare);
Set<String> difference =new HashSet<String>(unionn);
difference.removeAll(intersection);
if(allFrequentSet.contains(difference)) {
candidate.add(unionn);
}
}
}
}
}
candidate = candidate.stream().distinct().collect(Collectors.toList());
return candidate;
}
//扫描满足最小支持度的频繁项目集
public List<Set<String>> scan(Map<Set<String>, Integer> frequent,double minSupport) {
List<Set<String>> list=new ArrayList<Set<String>>();
for(Set<String> key :frequent.keySet()) {
if(frequent.get(key)>=minSupport*data.size()) {
list.add(key);
allFrequentSet.add(key);
}
}
return list;
}
//建立频繁项目集与支持度的映射关系
public Map<Set<String>, Integer> frequentSet(List<Set<String>> candidate,List<Set<String>> data) {//频繁项集
Map<Set<String>,Integer> frequent=new HashMap();
candidate.forEach(can->{
Integer sup = support(can,data);
frequent.put(can,sup);
});
return frequent;
}
//创建事务集
public List<Set<String>> createItemSet(List<Set<String>> data) {
List<Set<String>> itemSet=new ArrayList< Set<String> >();
Set<String> set =new HashSet<String>();
data.forEach(set::addAll);
set.forEach(e->itemSet.add(new HashSet<String>() {{add(e);}}));
return itemSet;
}
//获取给定集合的所有非空真子集
public List<Set<String>> nonEmptySubset(Set<String> set){
List<Set<String>> list =new ArrayList<Set<String>>();
List<HashSet<String>> arr = set.stream()
.map(s->new HashSet<String>() {{add(s);}})
.collect(Collectors.toList());
//子集个数位2^n
//遍历二进制序列的每一位,若为“1”则保留
int max=(int) Math.pow(2,arr.size());
for(int i=0;i<max;i++) {
Set<String> tmp =new HashSet<String>();
String bt = Integer.toBinaryString(i);
while(arr.size()>bt.length()) {
bt="0"+bt;
}
for(int j=0;j<arr.size()&&j<bt.length();j++) {
if(bt.charAt(j)=='1') {
tmp.addAll(arr.get(j));
}
}
if(!tmp.isEmpty()&&!tmp.containsAll(set)) {
list.add(tmp);
}
}
return list;
}
}
测试数据
public static void main(String[] args) {
List<Set<String>> data=new ArrayList<Set<String>>();
data.add(Set.of("1","3","4"));
data.add(Set.of("2","3","5"));
data.add(Set.of("1","2","3","5"));
data.add(Set.of("2","5"));
Apriori t=new Apriori(data,0.5,0.8);
}
控制台输出:
K :1
itemSet:[[1], [2], [3], [4], [5]]
frequent:{[1]=2, [2]=3, [3]=3, [4]=1, [5]=3}
scan[[1], [2], [3], [5]]
K :2
itemSet:[[1, 2], [1, 3], [1, 5], [2, 3], [2, 5], [3, 5]]
frequent:{[1, 2]=1, [1, 3]=2, [2, 3]=2, [1, 5]=1, [2, 5]=3, [3, 5]=2}
scan[[1, 3], [2, 3], [2, 5], [3, 5]]
K :3
itemSet:[[2, 3, 5]]
frequent:{[2, 3, 5]=2}
scan[[2, 3, 5]]
lk 关联规则 confidence support 是否是强规则
[2, 3, 5] [5]-->[2, 3] 2/3(0.67) 2/4(0.50) false
[2, 3, 5] [3]-->[2, 5] 2/3(0.67) 2/4(0.50) false
[2, 3, 5] [3, 5]-->[2] 2/2(1.00) 2/4(0.50) true
[2, 3, 5] [2]-->[3, 5] 2/3(0.67) 2/4(0.50) false
[2, 3, 5] [2, 5]-->[3] 2/3(0.67) 2/4(0.50) false
[2, 3, 5] [2, 3]-->[5] 2/2(1.00) 2/4(0.50) true
Apriori算法有两个致命的性能瓶颈:
1.多次扫描事务数据库,需要很大的I/O负载
2.可能产生庞大的侯选集
主要的改进方法有:
1.基于数据分割(Partition)的方法:基本原理是“在一个划分中的支持度小于最小支持度的k-项集不 可能是全局频繁的”。
2.基于散列(Hash)的方法:基本原理是“在一个hash桶内支持度小于最小支持度的k-项集不可能是全局频繁的”。
3.基于采样(Sampling)的方法:基本原理是“通过采样技术,评估被采样的子集中,并依次来估计k-项集的全局频度”。
4.其他:如,动态删除没有用的事务:“不包含任何Lk的事务对未来的扫描结果不会产生影响,因而可以删除”。