Apriori算法简介及其用C++模拟实现

3 篇文章 0 订阅
3 篇文章 0 订阅

原文链接:http://blog.csdn.net/qq675927952/article/details/6707704

http://blog.csdn.net/pupingpp/article/details/8264734

关联规则的目的在于在一个数据集中找出项之间的关系,也称之为购物蓝分析 (market basketanalysis)。例如,购买鞋的顾客,有10%的可能也会买袜子,60%的买面包的顾客,也会买牛奶。这其中最有名的例子就是"尿布和啤酒"的故事了。

关联规则的应用场合。在商业销售上,关联规则可用于交叉销售,以得到更大的收入;在保险业务方面,如果出现了不常见的索赔要求组合,则可能为欺诈,需要作进一步的调查。在医疗方面,可找出可能的治疗组合;在银行方面,对顾客进行分析,可以推荐感兴趣的服务等等。

Apriori algorithm是关联规则里一项基本算法。由Rakesh Agrawal 在 1994年提出的,详细的介绍请猛击这里《Fast Algorithms for Mining Association Rules》。

首先我们来看,什么是规则?规则形如"如果…那么…(If…Then…)",前者为条件,后者为结果。例如一个顾客,如果买了可乐,那么他也会购买果汁。

如何来度量一个规则是否够好?有两个量,置信度(Confidence)和支持度(Support)。假设有如下表的购买记录。

顾客

项目

1

orangejuice, coke

2

milk,orange juice, window cleaner

3

orangejuice, detergent

4

orangejuice, detergent, coke

5

windowcleaner

将上表整理一下,得到如下的一个2维表

  

Orange

WinCl

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

上表中横栏和纵栏的数字表示同时购买这两种商品的交易条数。如购买有Orange的交易数为4,而同时购买Orange和Coke的交易数为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 andB

0.25

A andC

0.2

B andC

0.15

A,BandC

0.05

可得到下述规则

规则

置信度

If B and C thenA

0.05/0.15*100%=33.33%

If A and C thenB

0.05/0.20*100%=25%

If A and B thenC

0.05/0.25*100%=20%

上述的三条规则,哪一条规则有用呢?

对于规则" If B and CthenA",同时购买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(IfB and C The A)=0.05/(0.15*0.45)=0.74。而Lift(If A thenB)=0.25/(0.45*0.42)=1.32。也就是说对买了A的人进行推荐B,购买概率是随机推荐B的1.32倍。lift(A->B) = P(AB)/(P(A)P(B))

如何产生规则呢。可以分两步走。

首先找出频繁集(frequentitemset)。所谓频繁集指满足最小支持度或置信度的集合。其次从频繁集中找出强规则(strongrules)。强规则指既满足最小支持度又满足最小置信度的规则。

我们来看如何产生频繁集。

这其中有一个定理。即频繁集的子集也一定是频繁集。比如,如果{A,B,C}是一个3项的频繁集,则其子集{A,B},{B,C},{A,C}也一定是2项的频繁集。为方便,可以把含有k项的集合称之为k-itemsets.

下面以迭代的方式找出频繁集。首先找出1-itemsets的频繁集,然后使用这个1-itemsets,进行组合,找出2-itemsets的频繁集。如此下去,直到不再满足最小支持度或置信度的条件为止。这其中重要的两步骤分别是连接(join 连接的规则就是:两个第K级的频繁项要是可以连接形成第K+1级候选频繁项,当且仅当:两个第K级频繁项的前K-1项都相同,只是第K项不同!!) 和剪枝(prune).即从(k-1)-itemsets中的项进行组合,产生备选集(Candidateitemsets)。再从备选集中,将不符合最小支持度或置信度的项删去。例如

Frequent2-itemsets

 

Candidate3-itemsets

 

Frqquent3-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

 

对C2进行扫描,计算支持度。

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" 和"Canonprinter"一起可能很难产生有效规则。因为恰好一起买这两个牌子的产品的顾客太少。但不妨将Thinkpadnotebook放到Notebook这一层次上考虑,而Canonprinter放到printer这一去层次上考虑。这样的话,一起买notebook和printer的顾客就较多了。也即Multilevelassociation rules。


下面就用C++来模拟一下,当然此算法是数据挖掘中很经典的一个算法,基于数据库的,但是我的程序就是模拟一下算法,用STL容器存储事务,代替数据库作为模拟!


[cpp]  view plain copy
  1. #include<iostream>  
  2. #include<string>  
  3. #include <vector>  
  4. #include <map>  
  5. #include <algorithm>  
  6. using namespace std;  
  7.   
  8. class Apriori  
  9. {  
  10. public:  
  11.     Apriori(size_t is =0,unsigned int mv=0)  
  12.     {  
  13.         item_size = is;  
  14.         min_value = mv;  
  15.     }  
  16.     //~Apriori() {};  
  17.     void getItem();  
  18.     map< vector<string>,unsigned int> find_freitem();//求事务的频繁项  
  19.     //连接连个k-1级频繁项,得到第k级频繁项  
  20.     map< vector<string>,unsigned int > apri_gen(unsigned int K , map< vector<string>,unsigned int > K_item);  
  21.     //展示频繁项集  
  22.     void showAprioriItem(unsigned int K,map< vector<string>,unsigned int > showmap);  
  23. private:  
  24.     map< int , vector<string> > item;//存储所有最开始的事务及其项  
  25.     map< vector<string>,unsigned int > K_item;//存储频繁项集  
  26.     size_t item_size;//事务数目  
  27.     unsigned  int min_value;//最小阈值  
  28. };  
  29.   
  30. void Apriori::getItem()//用户输入最初的事务集  
  31. {  
  32.     int ci = item_size;  
  33.     for (int i=0;i<ci;i++)  
  34.     {  
  35.         string str;  
  36.         vector<string> temp;  
  37.         cout<<"请输入第 "<<i+1<<"个事务的项集(123 end):";  
  38.         while (cin>>str && str !="123")  
  39.         {  
  40.             temp.push_back(str);  
  41.         }  
  42.         sort(temp.begin(),temp.end());  
  43.         pair< map<int ,vector<string> >::iterator , bool> ret = item.insert(make_pair(i+1 ,temp));   
  44.         if (!ret.second)  
  45.         {  
  46.             --i;  
  47.             cout<<"你输入的元素已存在!请重新输入!"<<endl;  
  48.         }  
  49.     }  
  50.     cout<<"-------------运行结果如下:--------------"<<endl;  
  51. }  
  52.   
  53. map< vector<string>,unsigned int> Apriori::find_freitem()  
  54. {  
  55.     unsigned int i = 1;  
  56.     bool isEmpty = false;  
  57.     map< int , vector<string> >::iterator mit ;  
  58.     for (mit=item.begin();mit != item.end();mit++)  
  59.     {  
  60.         vector<string> vec = mit->second;  
  61.         if (vec.size() != 0)  
  62.             break;  
  63.     }  
  64.     if (mit == item.end())//事务集为空  
  65.     {  
  66.         isEmpty = true;  
  67.         cout<<"事务集为空!程序无法进行..."<<endl;  
  68.         map< vector<string>,unsigned int> empty;  
  69.         return empty;  
  70.     }  
  71.     while(1)  
  72.     {  
  73.         map< vector<string>,unsigned int > K_itemTemp = K_item;  
  74.   
  75.         K_item = apri_gen(i++,K_item);  
  76.   
  77.         if (K_itemTemp == K_item)  
  78.         {  
  79.             i = UINT_MAX;  
  80.             break;  
  81.         }  
  82.         //判断是否需要进行下一次的寻找  
  83.         map< vector<string>,unsigned int > pre_K_item = K_item;  
  84.         size_t Kitemsize = K_item.size();  
  85.         //存储应该删除的第K级频繁项集,不能和其他K级频繁项集构成第K+1级项集的集合  
  86.         if (Kitemsize != 1 && i != 1)  
  87.         {  
  88.             vector< map< vector<string>,unsigned int >::iterator > eraseVecMit;  
  89.             map< vector<string>,unsigned int >::iterator pre_K_item_it1 = pre_K_item.begin() , pre_K_item_it2;  
  90.             while (pre_K_item_it1 != pre_K_item.end() )  
  91.             {  
  92.                 map< vector<string>,unsigned int >::iterator mit = pre_K_item_it1;  
  93.                 bool isExist = true;  
  94.                 vector<string> vec1;  
  95.                 vec1 = pre_K_item_it1->first;  
  96.                 vector<string> vec11(vec1.begin(),vec1.end()-1);  
  97.                 while (mit != pre_K_item.end())  
  98.                 {  
  99.                     vector<string> vec2;  
  100.                     vec2 = mit->first;                 
  101.                     vector<string> vec22(vec2.begin(),vec2.end()-1);  
  102.                     if (vec11 == vec22)  
  103.                         break;  
  104.                     ++mit;  
  105.                 }  
  106.                 if (mit == pre_K_item.end())  
  107.                     isExist = false;  
  108.                 if (!isExist && pre_K_item_it1 != pre_K_item.end())  
  109.                     eraseVecMit.push_back(pre_K_item_it1);//该第K级频繁项应该删除  
  110.                 ++pre_K_item_it1;             
  111.             }  
  112.             size_t eraseSetSize = eraseVecMit.size();  
  113.             if (eraseSetSize == Kitemsize)  
  114.                 break;  
  115.             else  
  116.             {  
  117.                 vector< map< vector<string>,unsigned int >::iterator >::iterator currentErs = eraseVecMit.begin();  
  118.                 while (currentErs != eraseVecMit.end())//删除所有应该删除的第K级频繁项  
  119.                 {  
  120.                     map< vector<string>,unsigned int >::iterator eraseMit = *currentErs;  
  121.                     K_item.erase(eraseMit);  
  122.                     ++currentErs;  
  123.                 }  
  124.             }  
  125.         }  
  126.         else  
  127.             if(Kitemsize == 1 )  
  128.                 break;  
  129.     }  
  130.     cout<<endl;  
  131.     showAprioriItem(i,K_item);  
  132.     return K_item;  
  133. }  
  134.   
  135. map< vector<string>,unsigned int > Apriori::apri_gen(unsigned int K , map< vector<string>,unsigned int > K_item)  
  136. {  
  137.     if (1 == K)//求候选集C1  
  138.     {  
  139.         size_t c1 = item_size;  
  140.         map< int , vector<string> >::iterator mapit = item.begin();  
  141.         vector<string> vec;  
  142.         map<string,unsigned int> c1_itemtemp;  
  143.         while (mapit != item.end() )//将原事务中所有的单项统计出来  
  144.         {  
  145.   
  146.             vector<string> temp = mapit->second;  
  147.             vector<string>::iterator vecit = temp.begin();  
  148.             while (vecit != temp.end() )  
  149.             {  
  150.                 pair< map<string,unsigned int>::iterator , bool > ret = c1_itemtemp.insert(make_pair(*vecit++ , 1));  
  151.                 if (!ret.second)  
  152.                 {  
  153.                     ++ ret.first->second;  
  154.                 }  
  155.             }  
  156.             ++mapit;  
  157.         }  
  158.         map<string,unsigned int>::iterator item_it = c1_itemtemp.begin();  
  159.         map< vector<string>,unsigned int > c1_item;  
  160.         while (item_it != c1_itemtemp.end() )//构造第一级频繁项集  
  161.         {  
  162.             vector<string> temp;  
  163.             if ( item_it->second >= min_value)  
  164.             {  
  165.                 temp.push_back(item_it->first);  
  166.                 c1_item.insert(make_pair(temp , item_it->second) );  
  167.             }  
  168.             ++item_it;  
  169.         }  
  170.         return c1_item;  
  171.     }  
  172.     else  
  173.     {  
  174.         cout<<endl;         
  175.         showAprioriItem(K-1,K_item);  
  176.         map< vector<string>,unsigned int >::iterator ck_item_it1 = K_item.begin(),ck_item_it2;  
  177.         map< vector<string>,unsigned int > ck_item;  
  178.         while (ck_item_it1 != K_item.end() )  
  179.         {  
  180.             ck_item_it2 = ck_item_it1;  
  181.             ++ck_item_it2;  
  182.             map< vector<string>,unsigned int >::iterator mit = ck_item_it2;  
  183.   
  184.             //取当前第K级频繁项与其后面的第K级频繁项集联合,但要注意联合条件  
  185.             //联合条件:连个频繁项的前K-1项完全相同,只是第K项不同,然后两个联合生成第K+1级候选频繁项  
  186.             while(mit != K_item.end() )  
  187.             {  
  188.                 vector<string> vec,vec1,vec2;  
  189.                 vec1 = ck_item_it1->first;  
  190.                 vec2 = mit->first;  
  191.                 vector<string>::iterator vit1,vit2;  
  192.   
  193.                 vit1 = vec1.begin();  
  194.                 vit2 = vec2.begin();  
  195.                 while (vit1 < vec1.end() && vit2 < vec2.end() )  
  196.                 {  
  197.                     string str1 = *vit1;  
  198.                     string str2 = *vit2;  
  199.                     ++vit1;  
  200.                     ++vit2;  
  201.                     if ( K ==2 || str1 == str2 )  
  202.                     {  
  203.                         if (vit1 != vec1.end() && vit2 != vec2.end() )  
  204.                         {  
  205.                             vec.push_back(str1);  
  206.                         }  
  207.                           
  208.                     }  
  209.                     else  
  210.                         break;  
  211.                 }  
  212.                 if (vit1 == vec1.end() && vit2 == vec2.end() )  
  213.                 {  
  214.                     --vit1;  
  215.                     --vit2;  
  216.                     string str1 = *vit1;  
  217.                     string str2 = *vit2;  
  218.                     if (str1>str2)  
  219.                     {  
  220.                         vec.push_back(str2);  
  221.                         vec.push_back(str1);  
  222.                     }  
  223.                     else  
  224.                     {  
  225.                         vec.push_back(str1);  
  226.                         vec.push_back(str2);  
  227.                     }  
  228.                     map< int , vector<string> >::iterator base_item = item.begin();  
  229.                     unsigned int Acount = 0 ;  
  230.                     while (base_item != item.end() )//统计该K+1级候选项在原事务集出现次数  
  231.                     {  
  232.                         unsigned int count = 0 ,mincount = UINT_MAX;  
  233.                         vector<string> vv = base_item->second;  
  234.                         vector<string>::iterator vecit , bvit ;  
  235.                         for (vecit = vec.begin();vecit < vec.end();vecit++)  
  236.                         {  
  237.                             string t = *vecit;  
  238.                             count = 0;  
  239.                             for (bvit=vv.begin();bvit < vv.end();bvit++)  
  240.                             {  
  241.                                 if (t == *bvit)  
  242.                                     count++;  
  243.                             }  
  244.                             mincount = (count < mincount ? count : mincount );  
  245.                         }  
  246.                         if (mincount >=1 && mincount != UINT_MAX)  
  247.                             Acount += mincount;  
  248.                         ++base_item;  
  249.                     }  
  250.                     if (Acount >= min_value && Acount != 0)  
  251.                     {  
  252.                         sort(vec.begin(),vec.end());  
  253.                         //该第K+1级候选项为频繁项,插入频繁项集  
  254.                         pair< map< vector<string>,unsigned int >::iterator , bool> ret = ck_item.insert(make_pair(vec,Acount));  
  255.                         if (! ret.second)  
  256.                         {  
  257.                             ret.first->second += Acount;  
  258.                         }  
  259.                     }  
  260.                 }  
  261.                 ++mit;  
  262.             }  
  263.             ++ck_item_it1;  
  264.         }  
  265.         if (ck_item.empty())//该第K+1级频繁项集为空,说明调用结束,把上一级频繁项集返回  
  266.             return K_item;  
  267.         else  
  268.             return ck_item;  
  269.     }  
  270. }  
  271. void Apriori::showAprioriItem(unsigned int K,map< vector<string>,unsigned int > showmap)  
  272. {  
  273.     map< vector<string>,unsigned int >::iterator showit = showmap.begin();  
  274.     if (K != UINT_MAX)  
  275.         cout<<endl<<"第 "<<K<<" 级频繁项集为:"<<endl;  
  276.     else  
  277.         cout<<"最终的频繁项集为:"<<endl;      
  278.     cout<<"项  集"<<"  \t  "<<"频率"<<endl;  
  279.     while (showit != showmap.end() )  
  280.     {  
  281.         vector<string> vec = showit->first;  
  282.         vector<string>::iterator vecit = vec.begin();  
  283.         cout<<"{ ";  
  284.         while (vecit != vec.end())  
  285.         {  
  286.             cout<<*vecit<<"  ";  
  287.             ++vecit;  
  288.         }  
  289.         cout<<"}"<<"\t";  
  290.         cout<<showit->second<<endl;  
  291.         ++showit;  
  292.     }  
  293. }  
  294.   
  295. unsigned int parseNumber(const char * str)//对用户输入的数字进行判断和转换  
  296. {  
  297.     if (str == NULL)  
  298.         return 0;     
  299.     else  
  300.     {  
  301.         unsigned int num = 0;  
  302.         size_t len = strlen(str);  
  303.         for (size_t i=0;i<len;i++)  
  304.         {  
  305.             num *= 10;  
  306.             if (str[i]>= '0' && str[i] <= '9')  
  307.                 num += str[i] - '0';  
  308.             else  
  309.                 return 0;             
  310.         }  
  311.         return num;  
  312.     }  
  313. }  
  314.   
  315. void main()  
  316. {  
  317.     //Apriori a;  
  318.     unsigned int itemsize = 0;  
  319.     unsigned int min;  
  320.       
  321.     do   
  322.     {  
  323.         cout<<"请输入事务数:";  
  324.         char * str = new char;  
  325.         cin>>str;  
  326.         itemsize = parseNumber(str);  
  327.         if (itemsize == 0)  
  328.         {  
  329.             cout<<"请输入大于0正整数!"<<endl;  
  330.         }  
  331.     } while (itemsize == 0);  
  332.       
  333.     do   
  334.     {  
  335.         cout<<"请输入最小阈值:";  
  336.         char * str = new char;  
  337.         cin>>str;  
  338.         min = parseNumber(str);  
  339.         if (min == 0)  
  340.         {  
  341.             cout<<"请输入大于0正整数!"<<endl;  
  342.         }  
  343.     } while (min == 0);  
  344.       
  345.     Apriori a(itemsize,min);  
  346.     a.getItem();  
  347.     map< vector<string>,unsigned int> AprioriMap = a.find_freitem();  
  348.     //a.showAprioriItem(UINT_MAX,AprioriMap);  
  349.     system("pause");  
  350. }  



运行结果为(例题来源于:《数据挖掘概念与技术》机械工业 2005年版,第六章153页):

请输入事务数:9
请输入最小阈值:2
请输入第 1个事务的项集(123 end):I1 I2 I5 123
请输入第 2个事务的项集(123 end):I2 I4 123
请输入第 3个事务的项集(123 end):I2 I3 123
请输入第 4个事务的项集(123 end):I1 I2 I4 123
请输入第 5个事务的项集(123 end):I1 I3 123
请输入第 6个事务的项集(123 end):I2 I3 123
请输入第 7个事务的项集(123 end):I1 I3 123
请输入第 8个事务的项集(123 end):I1 I2 I3 I5 123
请输入第 9个事务的项集(123 end):I1 I2 I3 123
-------------运行结果如下:--------------


第 1 级频繁项集为:
项  集            频率
{ I1  } 6
{ I2  } 7
{ I3  } 6
{ I4  } 2
{ I5  } 2

第 2 级频繁项集为:
项  集            频率
{ I1  I2  }     4
{ I1  I3  }     4
{ I1  I5  }     2
{ I2  I3  }     4
{ I2  I4  }     2
{ I2  I5  }     2

第 3 级频繁项集为:
项  集            频率
{ I1  I2  I3  }  2
{ I1  I2  I5  }  2

最终的频繁项集为:
项  集            频率
{ I1  I2  I3  }  2
{ I1  I2  I5  }  2
请按任意键继续. . .

当然这只是算法的模拟实现,没怎么考虑到效率,仅供参考,当然存储使用STL容器,时间效率还是蛮高的。

有什么意见和建议,欢迎留言,谢谢,互相学习,刚刚看数据挖掘,新手一枚。



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值