博主初次接触数据挖掘方面的研究,从最经典最基础的Apriori算法编起,不过这是博主前几个月写的了,所以现在也是凭印象写的,有些粗糙,还请谅解。
下面讲解部分为转载
1 Apriori介绍
Apriori算法使用频繁项集的先验知识,使用一种称作逐层搜索的迭代方法,k项集用于探索(k+1)项集。首先,通过扫描事务(交易)记录,找出所有的频繁1项集,该集合记做L1,然后利用L1找频繁2项集的集合L2,L2找L3,如此下去,直到不能再找到任何频繁k项集。最后再在所有的频繁集中找出强规则,即产生用户感兴趣的关联规则。
其中,Apriori算法具有这样一条性质:任一频繁项集的所有非空子集也必须是频繁的。因为假如P(I)< 最小支持度阈值,当有元素A添加到I中时,结果项集(A∩I)不可能比I出现次数更多。因此A∩I也不是频繁的。
2 连接步和剪枝步
在上述的关联规则挖掘过程的两个步骤中,第一步往往是总体性能的瓶颈。Apriori算法采用连接步和剪枝步两种方式来找出所有的频繁项集。
1) 连接步
为找出Lk(所有的频繁k项集的集合),通过将Lk-1(所有的频繁k-1项集的集合)与自身连接产生候选k项集的集合。候选集合记作Ck。设l1和l2是Lk-1中的成员。记li[j]表示li中的第j项。假设Apriori算法对事务或项集中的项按字典次序排序,即对于(k-1)项集li,li[1] 小于li[2]<……….li[k-1]。将Lk-1与自身连接,如果(l1[1]=l2[1])&&( l1[2]=l2[2])&&……..&& (l1[k-2]=l2[k-2])&&(l1[k-1]小于l2[k-1]),那认为l1和l2是可连接。连接l1和l2 产生的结果是{l1[1],l1[2],……,l1[k-1],l2[k-1]}。
2) 剪枝步
CK是LK的超集,也就是说,CK的成员可能是也可能不是频繁的。通过扫描所有的事务(交易),确定CK中每个候选的计数,判断是否小于最小支持度计数,如果不是,则认为该候选是频繁的。为了压缩Ck,可以利用Apriori性质:任一频繁项集的所有非空子集也必须是频繁的,反之,如果某个候选的非空子集不是频繁的,那么该候选肯定不是频繁的,从而可以将其从CK中删除。
(Tip:为什么要压缩CK呢?因为实际情况下事务记录往往是保存在外存储上,比如数据库或者其他格式的文件上,在每次计算候选计数时都需要将候选与所有事务进行比对,众所周知,访问外存的效率往往都比较低,因此Apriori加入了所谓的剪枝步,事先对候选集进行过滤,以减少访问外存的次数。)
3 Apriori算法实例
交易ID 商品ID列表
T100 I1,I2,I5
T200 I2,I4
T300 I2,I3
T400 I1,I2,I4
T500 I1,I3
T600 I2,I3
T700 I1,I3
T800 I1,I2,I3,I5
T900 I1,I2,I3
上图为某商场的交易记录,共有9个事务,利用Apriori算法寻找所有的频繁项集的过程如下:
详细介绍下候选3项集的集合C3的产生过程:从连接步,首先C3={{I1,I2,I3},{I1,I2,I5},{I1,I3,I5},{I2,I3,I4},{I2,I3,I5},{I2,I4,I5}}(C3是由L2与自身连接产生)。根据Apriori性质,频繁项集的所有子集也必须频繁的,可以确定有4个候选集{I1,I3,I5},{I2,I3,I4},{I2,I3,I5},{I2,I4,I5}}不可能时频繁的,因为它们存在子集不属于频繁集,因此将它们从C3中删除。注意,由于Apriori算法使用逐层搜索技术,给定候选k项集后,只需检查它们的(k-1)个子集是否频繁。
3. Apriori伪代码
算法:Apriori
输入:D - 事务数据库;min_sup - 最小支持度计数阈值
输出:L - D中的频繁项集
方法:
L1=find_frequent_1-itemsets(D); // 找出所有频繁1项集
For(k=2;Lk-1!=null;k++){
Ck=apriori_gen(Lk-1); // 产生候选,并剪枝
For each 事务t in D{ // 扫描D进行候选计数
Ct =subset(Ck,t); // 得到t的子集
For each 候选c 属于 Ct
c.count++;
}
Lk={c属于Ck | c.count>=min_sup}
}
Return L=所有的频繁集;
Procedure 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]) then{
c=l1连接l2 //连接步:产生候选
if has_infrequent_subset(c,Lk-1) then
delete c; //剪枝步:删除非频繁候选
else add c to Ck;
}
Return Ck;
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;
4. 由频繁项集产生关联规则
Confidence(A->B)=P(B|A)=support_count(AB)/support_count(A)
关联规则产生步骤如下:
1) 对于每个频繁项集l,产生其所有非空真子集;
2) 对于每个非空真子集s,如果support_count(l)/support_count(s)>=min_conf,则输出 s->(l-s),其中,min_conf是最小置信度阈值。
例如,在上述例子中,针对频繁集{I1,I2,I5}。可以产生哪些关联规则?该频繁集的非空真子集有{I1,I2},{I1,I5},{I2,I5},{I1 },{I2}和{I5},对应置信度如下:
I1&&I2->I5 confidence=2/4=50%
I1&&I5->I2 confidence=2/2=100%
I2&&I5->I1 confidence=2/2=100%
I1 ->I2&&I5 confidence=2/6=33%
I2 ->I1&&I5 confidence=2/7=29%
I5 ->I1&&I2 confidence=2/2=100%
如果min_conf=70%,则强规则有I1&&I5->I2,I2&&I5->I1,I5 ->I1&&I2。
JAVA代码原创,不过写的时候也是参照了部分前辈的代码,所以可能会有相似的地方,附带注释,希望大家能看懂
//Apriori频繁集生成和关联规则生成文件
package test;
import java.util.*;
import java.util.Map.Entry;
/*
* *主要用来实现Apriori算法的频繁集挖掘与关联规则产生
* author——郭川
*/
public class Apriori_data {
private int countDatabase;//事务数据库中事务数量
private Integer minSup; //最小支持度
private Float minCon; //最小置信度
private Map<Integer, Set<String>> database;//事务数据库
private Map<Set<String>, Integer> frequItemSets;//频繁项集
private Map<Integer,Map<Set<String>, Integer>> freshFrequItemSets;//备用频繁项集
private HashMap<Set<String>, Set<Set<String>>> associationRules;//产生的关联规则
/*
* 构造函数
*/
public Apriori_data( Map<Integer, Set<String>> database,
Integer minSup,
Float minCon ) {
this.minSup = minSup;
this.minCon = minCon;
this.database = database;
countDatabase = database.size();
frequItemSets = new HashMap<Set<String>, Integer>();
associationRules = new HashMap<Set<String>, Set<Set<String>>>();
freshFrequItemSets = new HashMap<Integer, Map<Set<String>, Integer>>();
}
/*
* 扫描事务集,计算频繁集1-
* frequItemSet1存储第一个频繁项集
* candiItemSet1存储第一个候选集
*/
public HashMap<Set<String>, Integer> genFrequItemSets1(){
HashMap<Set<String>, Integer>frequItemSet1 = new HashMap<Set<String>, Integer>();
HashMap<Set<String>, Integer>candiItemSet1 = new HashMap<Set<String>, Integer>();
candiItemSet1 = genCandiFrequ1();
//System.out.println(candiItemSet1);
Iterator<Map.Entry<Set<String>,Integer>>it = candiItemSet1.entrySet().iterator();
while(it.hasNext()){
Entry<Set<String>, Integer> entry=it.next();
Integer v = entry.getValue();
if(v>=minSup){
frequItemSet1.put(entry.getKey(), v);
}
}
return frequItemSet1;
}
/*
* 产生频繁集1-的候选集
* candiItemSet1存储候选集1-
*/
public HashMap<Set<String>, Integer> genCandiFrequ1(){
HashMap<Set<String>,Integer>candiItemSet1 = new HashMap<Set<String>, Integer>();
Iterator<Map.Entry<Integer,Set<String>>>it = database.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Integer, Set<String>>s = it.next();
Set<String>item = s.getValue();
for(String key:item){
Set<String>value = new HashSet<String>();
value.add(key);
if(!candiItemSet1.containsKey(value)){
Integer k=1;
candiItemSet1.put(value, k);
//System.out.println(value);
}
else if(candiItemSet1.containsKey(value)){
Integer k=1+candiItemSet1.get(value);
candiItemSet1.put(value,k);
}
}
}
//System.out.println(candiItemSet1);
return candiItemSet1;
}
/*
* 根据频繁(k-1)-集产生候选k-集
* m=k-1;
* freqMItemSet2为频繁(k-1)-项集
* candiItemSet为生成的候选k-项集
*/
public Set<Set<String>> genCandiFrequen(int m, Map<Set<String>, Integer> frequItemSets2)
{
Set<Set<String>>candiItemSet =new HashSet<Set<String>>();
//System.out.println(frequItemSets2);
Iterator<Map.Entry<Set<String>, Integer>>originIt = frequItemSets2.entrySet().iterator();
while(originIt.hasNext()){
Set<String> item1 = originIt.next().getKey();
Iterator<Map.Entry<Set<String>, Integer>>currentIt = getIteratorF(item1,frequItemSets2);
while(currentIt.hasNext()){
Set<String> item2 = currentIt.next().getKey();
Set<String> symmetriSet = new HashSet<String>();
//System.out.println(item1);
//System.out.println(item2);
symmetriSet.addAll(item1);
//System.out.println(symmetriSet);
symmetriSet.retainAll(item2);
//System.out.println(symmetriSet.size());
if(symmetriSet.size()==m-1){
//System.out.println(item2);
Set<String> differentSet = new HashSet<String>();
differentSet.addAll(item1);
//System.out.println(differentSet);
differentSet.removeAll(item2);
differentSet.addAll(item2);
if(!candiItemSet.contains(differentSet)){
candiItemSet.add(differentSet);
}
}
}
}
//System.out.println(candiItemSet);
return candiItemSet;
}
/*
* 判断某一项可否被剪枝
* judgedItem为待检查的k项
* frequItem为(k-1)-频繁项集
* 若该judgedItem可被添加,则返回false,否则剪枝成功,返回true
*/
public boolean judgeForInvolve(Set<String> judgedItem, Set<Set<String>> frequItem)
{
//System.out.println(frequItem);
Set<String> temp = new HashSet<String>();
temp.addAll(judgedItem);
for(String item:judgedItem){
//System.out.println(item);
temp.remove(item);
if(frequItem.contains(temp)){
temp.add(item);
//System.out.println("1");
}
else {
return true;
}
}
return false;
}
/*
* 通过剪枝操作进一步获得候选集
* candiItem为待剪枝的候选集
* frequItem为(k-1)-项频繁集
*/
public Set<Set<String>> pruneForCandiItem(Set<Set<String>>candiItem, Set<Set<String>>frequItem)
{
//System.out.println(candiItem);
Set<Set<String>> tempSet = new HashSet<Set<String>>();
tempSet.addAll(candiItem);
Iterator<Set<String>>it = candiItem.iterator();
while(it.hasNext()){
Set<String>set = it.next();
//System.out.println(set);
boolean flag = judgeForInvolve(set, frequItem);
//System.out.println(flag);
if(flag)
tempSet.remove(set);
}
//System.out.println(tempSet);
return tempSet;
}
/*
* 获得剪枝后的候选集中各个项的支持度
* candiItem为剪枝后的候选集
*/
public Map<Set<String>, Integer> caculateSupport(Set<Set<String>>candiItem)
{
HashMap<Set<String>, Integer>candiItemMap=new HashMap<Set<String>, Integer>();
Iterator<Set<String>>itForSet = candiItem.iterator();
while(itForSet.hasNext()){
Iterator<Set<String>>itForMap = database.values().iterator();
Set<String>itemForSet = itForSet.next();
Integer value=0;
while(itForMap.hasNext()){
if(itForMap.next().containsAll(itemForSet))
value++;
}
candiItemMap.put(itemForSet, value);
}
//System.out.println(candiItemMap);
return candiItemMap;
}
/*
* 根据(k-1)-频繁项集生成k-频繁项集
* m=k-1
* frequItem为(k-1)-频繁集
*/
public Map<Set<String>, Integer> genFrequItemK(int m, Map<Set<String>, Integer> frequItemSets2)
{
//System.out.println(frequItemSets2);
Map<Set<String>, Integer>frequItemk = new HashMap<Set<String>, Integer>();
Set<Set<String>> item = frequItemSets2.keySet();
Set<Set<String>> candiItem = genCandiFrequen(m,frequItemSets2);//获得候选集
//System.out.println(candiItem);
Set<Set<String>> newCandiItem=pruneForCandiItem(candiItem,item);//对候选集进行剪枝
//System.out.println(newCandiItem);
Map<Set<String>, Integer> freshCandiItem= caculateSupport(newCandiItem);//计算候选集中各个项的支持度
Iterator<Map.Entry<Set<String>, Integer>>it = freshCandiItem.entrySet().iterator();
while(it.hasNext()){
Entry<Set<String>,Integer>entry = it.next();
Set<String> set = entry.getKey();
Integer value = entry.getValue();
if(value>=minSup)
frequItemk.put(set,value);
}
return frequItemk;
}
/*
* 获得fre中从Item开始之后的迭代器
*/
public Iterator<Map.Entry<Set<String>, Integer>> getIteratorF(Set<String> Item,Map<Set<String>, Integer> frequItemSets2)
{
Iterator<Map.Entry<Set<String>, Integer>>it = frequItemSets2.entrySet().iterator();
while(it.hasNext()){
if(Item.equals(it.next().getKey()))
break;
}
return it;
}
/*
* 挖掘从1-到k-的所有频繁集
*/
public void miningAllFreque(){
Map<Set<String>, Integer>frequ=genFrequItemSets1();
//System.out.println(frequ);
int k=1;
boolean flag = true;
while(flag){
frequItemSets=frequ;
freshFrequItemSets.put(k,frequItemSets);
frequ=genFrequItemK(k,frequItemSets);
k++;
if(frequ.isEmpty())
flag = false;
}
}
/*
* 获得每一个频繁项集,并分别调用关联规则函数
*/
public void mineWholeAssociation()
{
freshFrequItemSets.remove(1);
//System.out.println(freshFrequItemSets);
Iterator<Map.Entry<Integer, Map<Set<String>, Integer>>>itForMap = freshFrequItemSets.entrySet().iterator();
while(itForMap.hasNext())
{
Entry<Integer,Map<Set<String>, Integer>> entry = itForMap.next();
Iterator<Map.Entry<Set<String>, Integer>>itForIterate =entry.getValue().entrySet().iterator();
while(itForIterate.hasNext())
{
mineSingleAssociation(itForIterate.next());
}
}
}
/*
* 根据某一个频繁项集计算他的关联规则
* 如果计算成功则计入关联规则中
*/
public void mineSingleAssociation(Entry<Set<String>, Integer> factor)
{
//System.out.println(factor);
Set<String> frequentSet = factor.getKey();
Integer support = factor.getValue();
Map<Set<String>,Set<String>> subset =divergeToSubsets(frequentSet);
//System.out.println(subset);
Iterator<Map.Entry<Set<String>, Set<String>>> it = subset.entrySet().iterator();
while(it.hasNext()){
Integer value = 0;
Entry<Set<String>, Set<String>> entry = it.next();
Set<String> subsetPart1 = entry.getKey();
Set<String> subsetPart2 = entry.getValue();
Iterator<Set<String>> itForSet = database.values().iterator();
while(itForSet.hasNext()){
Set<String> temp = itForSet.next();
if(temp.containsAll(subsetPart1))
{
value++;
}
}
//System.out.println(support+" "+value);
//System.out.println(subsetPart1+" "+subsetPart2);
if((support.floatValue()/value.floatValue())>=minCon)
{
if(!(associationRules.containsKey(subsetPart1))){
Set<Set<String>> subsetPart2Set = new HashSet<Set<String>>();
subsetPart2Set.add(subsetPart2);
associationRules.put(subsetPart1, subsetPart2Set);
}else{
associationRules.get(subsetPart1).add(subsetPart2);
}
}
}
}
/*
* 对于给定的一个频繁项集,将其分解成可数个两个互斥的子集
*/
public Map<Set<String>, Set<String>> divergeToSubsets(Set<String> item)
{
//System.out.println(item);
Map<Set<String>, Set<String>>subsets = new HashMap<Set<String>, Set<String>>();
Integer count = item.size();
Integer in=count/2;
//System.out.println(in);
Integer k=1;
QueueL<Set<String>> queue = new QueueL<Set<String>>();
Set<Set<String>> temp = new HashSet<Set<String>>();
for(String tempIt:item){
Set<String> tempSet = new HashSet<String>();
tempSet.add(tempIt);
queue.push(tempSet);
temp.add(tempSet);
}
//System.out.println(queue.top());
while(k<in){
Set<String> tempSet = new HashSet<String>();
tempSet.addAll(queue.pop());
for(String iterate:item){
if(!(tempSet.contains(iterate)))
{
tempSet.add(iterate);
temp.add(tempSet);
queue.push(tempSet);
tempSet.remove(iterate);
}
}
System.out.println(queue.top());
k=queue.top().size();
}
Iterator<Set<String>> itForCombine = temp.iterator();
while(itForCombine.hasNext()){
//System.out.println(item);
Set<String> setPart2 = new HashSet<String>();
Set<String> setPart1 = new HashSet<String>();
setPart2.addAll(item);
setPart1.addAll(itForCombine.next());
setPart2.removeAll(setPart1);
//System.out.println(item);
//System.out.println("1"+setPart1);
//System.out.println("2"+setPart2);
subsets.put(setPart1, setPart2);
subsets.put(setPart2, setPart1);
}
//System.out.println(subsets);
return subsets;
}
/*
* 获取挖掘到的频繁项集
*/
public Map<Integer, Map<Set<String>, Integer>> getFrequentSets()
{
return freshFrequItemSets;
}
/*
* 获取挖掘到的关联规则
*/
public Map<Set<String>, Set<Set<String>>> getAssociationRules()
{
return associationRules;
}
}
//辅助类,在Apriori_data文件中用到了该队列结构
package test;
import java.util.LinkedList;
class QueueL { // 构造“队列”
private LinkedList list = new LinkedList();
public void push(T o) { // 队列
list.addLast(o);
}
public T top() { // 查看队列头的元素
return list.getFirst();
}
public T pop() { // 队列
return list.removeFirst();
}
}
//测试文件
package test;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class Apriori_test {
private Apriori_data apriori;
private MapSet> database;
private Integer minSup = 2;
private Float minCon = new Float("0.5");
public static void main(String []args){
Apriori_test test = new Apriori_test();
//test.genFrequSet1();
//test.genFrequSet2();
test.genFrequSet();
test.genAssociation();
}
Apriori_test(){
database = new HashMapSet>();
Set set1 = new HashSet();
set1.add("A");
set1.add("B");
set1.add("C");
set1.add("D");
set1.add("E");
database.put(1, set1);
Set set2 = new HashSet();
set2.add("A");
set2.add("B");
set2.add("F");
database.put(2, set2);
Set set3 = new HashSet();
set3.add("C");
set3.add("D");
set3.add("E");
database.put(3, set3);
Set set4 = new HashSet();
set4.add("B");
set4.add("C");
set4.add("D");
set4.add("E");
set4.add("F");
database.put(4, set4);
Set set5 = new HashSet();
set4.add("C");
set4.add("D");
set4.add("E");
set4.add("F");
database.put(5, set5);
apriori = new Apriori_data(database, minSup, minCon);
}
/*
* 获得第一频繁项集
*/
public void genFrequSet1()
{
//System.out.println(this.database);
System.out.println("频繁项集1-"+apriori.genFrequItemSets1());
}
/*
* 获得2-频繁项集
*/
public void genFrequSet2()
{
System.out.println("频繁项集2-"+apriori.genFrequItemK(1, apriori.genFrequItemSets1()));
}
/*
* 获得所有的频繁项集
*/
public void genFrequSet()
{
apriori.miningAllFreque();
System.out.println("频繁项集"+apriori.getFrequentSets());
}
/*
* 获得所有的关联规则
*/
public void genAssociation()
{
apriori.miningAllFreque();
apriori.mineWholeAssociation();
System.out.println("关联规则"+apriori.getAssociationRules());
}
}