Apriori详解

关联规则是数据挖掘领域的一个重要分支。随着大量数据不停地收集和存储,人们对挖掘数据间的关联规则越来越感兴趣,Apriori算法就是经典的关联挖掘算法,文章分析了Apriori的算法思想、算法描述及实际应用。

1    关联规则挖掘用于寻找给定数据集中项之间的有趣的关联或相关关系。关联规则揭示了数据项间的未知的依赖关系,根据所挖掘的关联关系,可以从一个数据对象的信息来推断另一个数据对象的信息。例如购物篮分析。Fresh milk bread[support=3%confidence=40%]   支持度support=3%意味3%顾客同时购买新鲜牛奶和面包。置信度40%意味购买新鲜牛奶的顾客40%也购买面包。规则的支持度和置信度是两个规则兴趣度度量,它们分别反映发现规则的有用性和确定性。关联规则是有趣的,如果它满足最小支持度阈值和最小置信度阈值。这些阈值可以由用户或领域专家设定。

我们先来认识几个相关的定义:

 定义1:支持度(support)   支持度s是事务数据库D中包含A U B的事务百分比,它是概率PA U B),即supportA B=PA U B),它描述了AB这两个物品集的并集在所有的事务中出现的概率。    定义2:可信度(confidence)   可信度为事务数据库D中包含A的事务中同时也包含B的百分比,它是概率PB|A),即confidenceA B=PB|A)。     定义3:频繁项目集   支持度不小于用户给定的最小支持度阈值(minsup)的项集称为频繁项目集(简称频集),或者大项目集。所有的频繁1-项集记为L1

2 Apriori算法思想   1994年由Agrawal等人提出的关联规则挖掘Apriori的算法从其产生到现在,对关联规则挖掘方面的研究有着很大的影响。Apriori算法是一种最有影响的挖掘布尔关联规则频繁项集的算法。算法基于这样的事实:算法使用频繁项集性质的先验知识。Apriori使用一种称作逐层搜索的迭代方法,k-项集用于探索(k+1)-项集。首先,找出频繁1-项集的集合。该集合记作L1L1用于找频繁2-项集的集合L2,而L2用于找L3,如此下去,直到不能找到频繁k-项集。找每个Lk需要一次数据库扫描。为提高频繁项集逐层产生的效率,一种称作Apriori性质的重要性质用于压缩搜索空间。为了提高频繁项目集逐层产生的效率,Apriori算法利用了两个重要的性质用于压缩搜索空间:   l)若X是频繁项集,则x的所有子集都是频繁项集。   2)若x是非频繁项集,则X的所有超集都是非频繁项集。

3 算法描述   算法: Apriori算法,使用逐层迭代找出频繁项集。    

输入:事务数据库D;最小支持度阈值min_sup   

输出:D中的频繁项集L    

L1 =find_frequent_1_itemsetsD);    

for k = 2 Lk-1 ≠ k++

     {    

      Ck = aproiri_genLk-1min_sup);   

              for eachtransaction t D

              { //scan D for count    

                    Ct = subsetCkt); //get subsetsof t that are candidates    

                    for eachcandidate c Ct    

                    c.count++    

             }   

             Lk={c Ck | c.count ≥min_sup}    

       }    

return L =kLk

4   apriori算法实例   现举例说明:如表1所示为事务数据库D,设最小支持度为20%,挖掘频繁项集的具体过程如表1所示。

1 事务数据库D




图1所示为Apriori算法挖掘频繁集的过程,其中最小支持度为20%


1Apriori算法的执行流程
第一步,经过算法的第一次迭代,对事务数据库进行一次扫描,计算出D中所包含的每      个项目出现的次数,生成候选1-项集的集合C1  

第二步,根据设定的最小支持度,从C1中确定频繁1-项集L1   

第三步,由L1产生候        选2-项集C2,然后扫描事务数据库对C2中的项集进行计数。   

第四步,根据最小支持度,从候选集C2中确定频繁集L2   

第五步,由频繁2-项集L2生成候选3-项集C3,生成的候选3-项集的集合C3={{123}{135}{235}},根据Apriori的性质剪枝:所有的频繁项集的子集都是频繁的,项集{123}的子集{12}不包含在频繁2-项集L2中,故删除{123}。项集{135}的子集{15}也不包含在频繁2-项集L2中,故删除{135},项集{235}的所有子集都是L2的元素,故保留。

结束语   Apriori算法的效率分析:   1Apriori性质能显著减少候选集的数目。事务数据库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 ={x1x2x100}. Apriori需要扫描数据库100次。   针对Apriori算法存在的缺点,人们对Apriori算法进行了多方面的改进,希望能够找出一个高效、可靠的挖掘频繁项集的算法。这些算法大多是以 Apriori 为核心,或是其变体,或是其扩展。如增量更新算法[、并行算法等。


源代码

#include<iostream>
#include<string>
#include<map>
using namespace std;
#define N 10  //N是项目的个数
#define T 10  //T是交易的总数
#define sup_min 2  //定义最小支持度
string item[N];   //item用来存储项目  项目有的可能不能用整数值来表示 故用string类型来存储
class Trade{
public:
    int num;  //每条交易包含的项目数
string t_item[N];//用来记录每条交易中的每一个项目
};//定义交易的类型 用来寻找频繁集合
class Data{
public:
string itemset;
int sup;
};
class Frequence
{
public:
int k;//表示k项频繁集有几个
Data data_f[N];//交易里的频繁集合
};
class Candidate
{
public:
int k_c;//表示k项候选集有几个
Data data_c[N];//用来记录k项候选集的每一个项目
}; 
Trade trade[T];
Frequence L[N];  //用来记录频繁集
Candidate C[N];
void find_frequence_1_itemset(Trade tt[],int x);  //发现1项频繁集
void out_f_1();//输出1项频繁集合
void apriori_gen(int x);  //根据x频繁集产生x+1候选集
bool connect(int xx,int p,int q);
int t;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
   //getline(cin,item[i]);
cin>>item[i];   //对所有的交易统计就能够得到item数组
}
cin>>t;
for(int i=1;i<=t;i++)//输入每一个交易
{
   cin>>trade[i].num;
for(int j=1;j<=trade[i].num;j++)
{
//getline(cin,trade[i].t_item[j]);
   cin>>trade[i].t_item[j];//输入每个交易中的每一个项目
}
}
//产生1项目频繁集L[1]
find_frequence_1_itemset(trade,t);
cout<<"1 项频繁集是 :"<<endl;
out_f_1();//输出1项频繁集合
for(int k=2;k<10;k++)
{
    //根据k-1项频繁集产生k项候选集
apriori_gen(k-1);  //
}
system("pause");
    return 0;
}
void find_frequence_1_itemset(Trade tt[],int x)  //发现1项频繁集
{
map<string,int> m;
    for(int i=1;i<=x;i++)//输入每一个交易
{
for(int j=1;j<=trade[i].num;j++)
{
m[tt[i].t_item[j]]++;
}
}
map<string,int>::iterator it;
int num_1=0;
for(it=m.begin();it!=m.end();it++)
{
   if(it->second>=sup_min)
{
L[1].data_f[num_1].itemset=it->first+",";
L[1].data_f[num_1].sup=it->second;
num_1++;
}
}
L[1].k=num_1;
cout<<"1项目频繁集合的数目:"<<L[1].k<<endl;
}
void out_f_1()//输出1项频繁集合
{
for(int i=0;i<L[1].k;i++)
{
cout<<"{"<<L[1].data_f[i].itemset<<"}   "<<L[1].data_f[i].sup<<endl;
}
}
void apriori_gen(int x)  //L[x]频繁项
{
//cout<<"产生 "<<x+1<<" 项候选集"<<endl;

for(int i=0;i<L[x].k;i++)
{
for(int j=i+1;j<L[x].k;j++)
{   //判断两个x项频繁集是否相差一项
  // cout<<x<<" 项频繁集合有 "<<L[x].k<<" 个"<<endl;
if(connect(x,i,j))//d第i个x项频繁集和第j个频繁集做连接操作,只相差一项时才能做连接
   {//连接并且产生了x+1候选集合
       //subset(x);
//产生候选集合
     cout<<i<<" 和 "<<j<<" 只相差一项 可以产生候选集"<<endl;
   } 
}
}/*cout<<x<<" 项集产生了 "<<C[x+1].k_c<<" 个候选项 "<<endl<<"分别是:";
for(int i=0;i<C[x+1].k_c;i++)
cout<<C[x+1].data_c[i].itemset<<"  ";
cout<<endl;*/
//在 x+1 候选集中剪枝 找到 x+1 项频繁集合
int Lx=0;
for(int i=0;i<C[x+1].k_c;i++)  
{  //对于每一个子集先分离出所有的项目 ,存放在s数组中
int xi=0;string s[N]={""};
for(int j=0;j<C[x+1].data_c[i].itemset.size();j++)
{
   if(C[x+1].data_c[i].itemset[j]==',')
{
   xi++;
}
else
{  //sb[qi]=sb[qi]+L[xx].data_f[q].itemset[l];
  s[xi]=s[xi]+C[x+1].data_c[i].itemset[j];
}
}

/*cout<<"从 "<<x+1<<" 候选项中分离出 "<<xi<<" 个项目,分别是: ";
   for(int j=0;j<xi;j++)
{
   cout<<s[j]<<" ";
}cout<<endl;*/
//下面对产生的x+1候选项进行剪枝 产生x+1频繁集
        //重新扫描每一笔交易

for(int k=1;k<=t;k++)
{//第k笔交易
int flag=0;
for(int j=0;j<xi;j++)  
{ 
for(int kk=1;kk<=trade[k].num;kk++)
{
if(s[j]==trade[k].t_item[kk])
{
flag++;break;
}
}
}
if(flag==xi)  //这个项集在这个交易里
{  
C[x+1].data_c[i].sup++; 
}
}
//求出了每个候选集的支持度
//cout<<"第 "<<x+1<<" 个候选集的支持度是: "<<C[x+1].data_c[i].sup<<endl;

if(C[x+1].data_c[i].sup>=sup_min)
{
L[x+1].data_f[Lx].itemset=C[x+1].data_c[i].itemset;
L[x+1].data_f[Lx].sup=C[x+1].data_c[i].sup;
   //会产生重复的频繁项问题在这里
cout<<x+1<<" 频繁集有:{"<<L[x+1].data_f[Lx].itemset<<"}"<<"支持度是:"<<L[x+1].data_f[Lx].sup<<endl;
   L[x+1].k++;
Lx++;
}
} 
}
bool connect(int xx,int p,int q)  //判断两个xx项频繁集 p和q 是否能做连接 构成x+1项候选集
{
string sa[N],sb[N];
int pi=0,qi=0;
    for(int l=0;l<L[xx].data_f[p].itemset.size();l++)
{
if(L[xx].data_f[p].itemset[l]==',')
{
pi=pi+1; continue;
}
else
{
sa[pi]=sa[pi]+L[xx].data_f[p].itemset[l];
}
}
for(int l=0;l<L[xx].data_f[q].itemset.size();l++)
{
if(L[xx].data_f[q].itemset[l]==',')
{
qi=qi+1;continue;
}
else
{
sb[qi]=sb[qi]+L[xx].data_f[q].itemset[l];
}
} 
//cout<<xx<<" 项目频繁集有 "<<p<<" 项子集分离出 "<<pi<<" 个物品"<<endl;

int f=0;
for(int ii=0;ii<pi;ii++)
{   bool flag=0;
   for(int jj=0;jj<qi;jj++)
{
   if(sa[ii]==sb[jj])
{
   flag=1;break;
}
}
if(flag==1)
f++;
}
//cout<<p<<" 和 "<<q<<" 有 "<<f<<"个相同的"<<endl;
if(f==pi-1)  //可以做连接  并找出不一样的那个做连接 
{
   int no_same=0; 
for(int ii=0;ii<qi;ii++)
{   bool flag=0;
for(int jj=0;jj<pi;jj++)
{
if(sb[ii]==sa[jj])
{
flag=1;break;
}
}
if(flag==0)
no_same=ii;
}
//cout<<xx<<" 项集 "<<p<<" 子集是 "<<L[xx].data_f[p].itemset<<"   缺少"<<q<<" 子集中的 "<<sb[no_same]<<"  "<<no_same<<endl;
C[xx+1].data_c[C[xx+1].k_c].itemset=L[xx].data_f[p].itemset+sb[no_same]+",";
   C[xx+1].k_c++;
   return true;
}
return false;
}
/*
5
1 2 3 4 5
4
3 1 3 4
3 2 3 5
4 1 2 3 5
2 2 5
*/


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值