數據挖掘十大經典算法
國際權威的學術組織the IEEE International Conference on Data Mining
(ICDM) 2006年12月評選出了數據挖掘領域的十大經典算法:C4.5, k-Means,
SVM, Apriori, EM, PageRank, AdaBoost, kNN, Naive Bayes, and CART.
不僅僅是選中的十大算法,其實參加評選的18種算法,實際上隨便拿出一種來都
可以稱得上是經典算法,它們在數據挖掘領域都產生了極爲深遠的影響。
4. The Apriori algorithm
Apriori算法是一種最有影響的挖掘布爾關聯規則頻繁項集的算法。其核心是基於
兩階段頻集思想的遞推算法。該關聯規則在分類上屬於單維、單層、布爾關聯規
則。在這裏,所有支持度大於最小支持度的項集稱爲頻繁項集,簡稱頻集。
VC 界面的源代碼見 http://download.csdn.net/source/2963738
由Agrawal等人提出的Apriori是經典的關聯規則和頻繁項集挖掘算法,圍繞着它的改進和實現有大量的文獻。該算法是挖掘產生布爾關聯規則頻繁項目集的經典算法,從其產生到現在對關聯規則挖掘方面的研究有着很大的影響。
爲了提高頻繁項目的挖掘效率,Apriori算法利用了兩個重要的性質,用於壓縮搜索的空間。
【1】若X爲頻繁項目集,則X的所有子集都是頻繁項目集。
【2】若X爲非頻繁項目集,則X的所有超集均爲非頻繁項目集。
Apriori算法的處理流程爲:寬度優先搜索整個項集空間,從k=0開始,迭代產生長度爲k+1的候選項集的集合Ck+1。候選項集是其所有子集都是頻繁項集的項集。C1由I0中所有的項構成,在第k層產生所有長度爲k+1的項集。這由兩步完成:第一步,Fk自連接。將Fk中具有相同(k-1)-前綴的項集連接成長度爲k的候選項集。第二步是剪枝,如果項集的所有長度爲k的子集都在Fk中,該項集才能作爲候選項集被加入Ck+1中。爲了計算所有長度爲k的候選項集的支持度,在數據庫水平表示方式下,需要掃描數據庫一遍。在每次掃描中,對數據庫中的每條交易記錄,爲其中所包含的所有候選k-項集的支持度計數加1。所有頻繁的k-項集被加入Fk中。此過程直至Ck+1等於空集時結束。
算法 Apriori
Input: Transaction DataBase D,Minimum support threshold minsup。
Output: Frequent pattern L
(1) L1=search_frequent_1-itemsets( D );
(2) for(k=2;Lk-1≠φ;k++) do
(3) begin
(4) Ck=apriori-gen(Lk-1);
(5) for all transactions t D do
(6) begin
(7) Ct=subset(Ck,t);
(8) for all candidates c Ct do
(9) c.count++;
(10) end
(11) Lk ={c Ck|c.count≥minsup}
(12) end
(13) Answer L=∪kLk;
Procedure Search_frequent_1-itemsets( D )
(1) begin
(2) for all transactions t D do
(3) begin
(4) for each item ik t do
(5) ik.count++;
(6) end
(7) L1 ={ i I | i.count≥minsup}
(8) return L1;
(9) end
Procedure apriori_gen(Lk)
(1) begin
(2) for each itemset l1 Lk do
(3) for each itemset l2 Lk do
(4) begin
(5) if ( l1[1]=l2[1]) ( l1[2]=l2[2]) … ( l1[k-1]=l2[k-1]) ( l1[k]<l2[k]) then
(6) begin
(7) c= l1 l2;
(8) if Is_include_infrenquent_subset(c,Lk) then
(9) delete c;
(10) else add c to Ck+1;
(11) end
(12) end
(13) return Ck+1 ;
(14) end
Procedure Is_include_infrenquent_subset(c,Lk)
(1)begin
(2) for each k-subset s of c
(3) if s Lk then
(4) reture TURE;
(5) return FALSE;
(6)end
在主程序中,第一步首先掃描整個交易數據庫D,統計每個項目(item)的支持數,計算其支持度,將支持度大於等於最小支持度minsup的項目構成的集合放入到L1 中;從第2步到第11步,用k-1頻繁項目集構成的Lk-1生成候選集的集合Ck,以便從中生成Lk,其中apriori_gen函數(第4步)用來從Lk-1中生成Ck,然後對數據庫進行掃描(第5步),對於數據庫中的每一個交易,subset函數用來發現此交易包含的所有候選集(第7步),併爲這些候選集的計數器加1(第8-9步)。最後滿足minsup的候選集被放入到Lk中。
apriori_gen過程完成兩種操作:並(join)和剪枝(prune)。在並運算步驟中,Lk-1 與Lk-1 進行並運算生成潛在的候選集(2-7步),條件l1[k-1]<l2[k-1]保證不會有重複的候選集生成(第5步)。在剪枝步驟中(8-10步),利用性質2.1,刪除那些存在子集不是頻繁項目集的候選集,測試子集是否爲頻繁項目集由過程Is_include_infrenquent_subset完成。
爲了清楚的闡述Apriori算法的挖掘過程,現舉例如下:
【例1】設事務數據庫D如表2.1所示,D中包含4個事務,即|D|=4,最小支持數mincount=2,即最小支持度minsup=2/4=50%。挖掘頻繁項目集的具體過程如下所述:C1={{A},{B},{C},{D},{F}},第一次循環產生L1={{A},{B},{C},{F}},由Apriori_gen(L1)生成C2,掃描數據庫,計算C2中每個候選集得到L2。依此循環,得到L3。整個挖掘過程如圖2.1所示。
表1 事務數據庫D
Tid |
事務 |
100 200 300 400 |
B,C,F A,C,D B,F A,B,C,F |
圖1 Apriori算法的執行過程
在找到了事務數據庫中的所有頻繁項集後,利用這些頻繁項集可以產生關聯規則,產生關聯規則的步驟如下:
(1) 對於每個頻繁項目集l,產生l的所有非空子集。
(2) 對於l的每個非空子集m,如果support(l)/support(m)≥minconf,則輸出規則“m (l-m)”。
例如,在上例中產生的頻繁項目集l={B,C,F},l的非空子集有{B,C}、{B,F}、{C,F}、{B}、{C}和{F},則運用上述產生關聯規則的方法可以得到以下關聯規則:
B C F confidence=(2/4)/(4/4)=1
B F C confidence=(2/4)/(3/4)=0.667
C F B confidence=(2/4)/(2/4)=1
F B C confidence=(2/4)/(3/4)= 0.667
C B F confidence=(2/4)/(3/4)= 0.667
B C F confidence=(2/4)/(3/4)= 0.667
源代碼 apriori.c
//////////////////////////////////////////////////////////////////////////
/*
*
*
*
* 文件名稱:apriori.c
*
* 摘 要:apriori的最簡單實現
*
* 當前版本:1.0
* 完成日期:2006.05
*
*/////////////////////////////////////////////////////////////////////////
#include<stdio.h>
typedef struct
{
int item[100]; //數據項
} D_Node; //數據庫D
typedef struct
{
int item[100]; //數據項,用item[0]保存支持度
} C_Node; //候選集
typedef struct
{
int item[100]; //數據項,用item[0]保存支持度
} L_Node;//頻繁集
C_Node C[100][100];
L_Node L[100][100];
D_Node D[100];
int min_supp; //最小支持度
void InPut()
{
int i,j,n,n1;
printf("請輸入最小支持度:");
scanf("%d",&min_supp);
printf("請輸入交易集的大小");
scanf("%d",&D[0].item[0]);
n=D[0].item[0];
for(i=1;i<=n;i++) //for1
{
printf("請輸入交易[%d]中記錄的個數(n)",i);
scanf("%d",&n1);
D[i].item[0]=n1;
for(j=1;j<=n1;j++) //for2
{
printf("請輸入交易[%d]中記錄項,直接輸入數字:",i);
scanf("%d",&D[i].item[j]);
}//for2
} //for1
}//end of InPut
void C1()
{
//功能:掃描數據集D生成1項候選集C1
//輸入:數據集D
//輸出1項候選集C1
//初始條件 數據集D 非空
int i,j,k;
int no=1,temp=0;
C[1][0].item[0]=0; //1 項集的個數,在本算法中,用C[n][k].item[0]來保存候選集Cn的第k項的支持度
if(D[0].item[0]!=0)
{
C[1][1].item[1]=D[1].item[1];
}
for(i=1;i<=D[0].item[0];i++) //for1
{
for(j=1;j<=D[i].item[0];j++) //for2
{
temp=1;
for(k=1;k<=no;k++) //for3
{
if(C[1][k].item[1]==D[i].item[j])
{
C[1][k].item[0]++; //支持度加1
temp=0; //
} //if
}//end for3
if(temp)//生成新的項集
{
C[1][++no].item[1]=D[i].item[j];
C[1][no].item[0]=1;
}
}//end for2
} // end for1
C[1][0].item[0]=no;//數據項的個數
} //end of C1()
void Cn( int n)
{
//用頻繁集Ln-1爲基礎,通過連接得到n項候選集Cn
int i,j,k,p,q,s,t,num;
int no=0,temp=0,count;
C[n][0].item[0]=0; //初始化
//printf("in Cn(%d) n=%d/n",n,n);
//printf("in Cn(%d) C[%d][0].item[0]=%d/n",n,n,C[n][0].item[0]);
num=L[n-1][0].item[0]; //num是Ln-1項集的數據個數
for(i=1;i<=num;i++)
for(j=i+1;j<=num;j++) //for2
{
temp=1; //測試是否滿足聯結條件
if(n>2)//if 1
{
for(k=1;k<n-1;k++) //for3
{
if(L[n-1][i].item[k]!=L[n-1][j].item[k])
{ temp=0;
break; }//if 1
}//end for3
}//end if1
if(temp==1)//滿足聯結條件
{
// printf("in if 2 no=%d/n",no);
no++;
for(p=1;p<=n-1;p++)
C[n][no].item[p]=L[n-1][i].item[p];
C[n][no].item[p]=L[n-1][j].item[p-1];
C[n][no].item[0]=0;
for(q=1;q<=D[0].item[0];q++) //for5 測試其支持度
{
count=0; //count用來記數,當所測試的項存在時,count加1,當count=n時,則子集存在
for(s=1;C[n][no].item[s]!=0;s++) //for6
{
for(t=1;t<=D[q].item[0];t++) //for7
{
if(C[n][no].item[s]==D[q].item[t])
{ count+=1;
break;
}
}//end for7
}//end for 6
if(count==n) C[n][no].item[0]+=1;//子集存在,第no項的支持度加1
}//end for5
C[n][0].item[0]+=1;
}//end if2
}//end for2
/* num=C[n][0].item[0];
printf("in Cn(%d) num=%d/n",n,num);
for(i=1;i<=num;i++)
for(j=0;j<=n;j++)
{
printf("in Cn(%d) C[%d][%d].item[%d]=%d/n",n,n,i,j,C[n][i].item[j]);
}
printf("in Cn(%d) C[%d][0].item[0]=%d/n",n,n,C[n][0].item[0]); */
}//end of Cn()
void L1()
{
int i,j,k;
j=0;
L[1][0].item[0]=0;
//printf("C[1][0].item[0]=%d/n",C[1][0].item[0]);
for(i=1;i<=C[1][0].item[0];i++)
{
if(C[1][i].item[0]>=min_supp)
{
j+=1;
for(k=1;k<=1;k++)
L[1][j].item[k]=C[1][i].item[k];
L[1][j].item[0]=C[1][i].item[0];
// printf("L[1][%d].item[1]=%d ",j,L[1][j].item[1]); 測試功能時加的
// printf(" -------------%d/n",L[1][j].item[0]);
}
}//end for1
L[1][0].item[0]=j;
}//end of L1()
void Ln(int n)
{
int i,j,k;
Cn(n);
j=0;
L[n][0].item[0]=0;
// printf("in Ln(%d) C[%d][0].item[0]=%d/n",n,n,C[n][0].item[0]);
for(i=1;i<=C[n][0].item[0];i++) //for 1
{
if(C[n][i].item[0]>=min_supp)
{
j+=1;
for(k=1;k<=n;k++)
L[n][j].item[k]=C[n][i].item[k];
L[n][j].item[0]=C[n][i].item[0];
} //end if
}//end for1
/* for(i=1;i<=j;i++)
for(k=0;k<=n;k++)
{printf("L[%d][%d].item[%d]=%d /n",n,i,k,L[n][i].item[k]);
} */
L[n][0].item[0]=j; //保存數據的個數
}//end of Ln(int n)
void OutPut(int n)
{
int i,j,k;
printf("頻繁項目集L%d如下:/n",n);
k=L[n][0].item[0];
if(k!=0)
{
for(i=1;i<=k;i++)
{
printf("{");
for(j=1;j<=n;j++)
printf(" I%d ",L[n][i].item[j]);
printf("} 支持度:%d/n",L[n][i].item[0]);
}//for
}
else printf("項目集爲空/n");
}
void main()
{
int i;
int n=1;
InPut();
C1();//初始化,生成1項候選集C1
L1();//得到1項頻繁集L1
while(L[n][0].item[0]!=0)
{
n+=1;
Ln(n);
}
for(i=1;i<=n;i++)
OutPut(i);
char ch;
scanf("%d",&i);
}
--------------------------------------------------------------------------------------
FAST apriori.cpp
//////////////////////////////////////////////////////////////////////////
/*
*
*
*
* 文件名稱:FAST apriori.cpp
*
* 摘 要:採用位運算提高算法的效率
*
* 當前版本:1.0
* 完成日期:2006.06.20
*
*/////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <string.h>
typedef struct
{
char item[10]; //數據項
int min_supp_count;//最小支持度數
} C_Node; //候選集
typedef struct
{
char item[10]; //數據項
int min_supp_count;//最小支持度數
} L_Node; //頻繁集
char D[10][10];
L_Node L[100];
C_Node C[100];
int min_supp_count=2;
int num=100;
void InPut()
{
strcpy(D[1],"abe");
strcpy(D[2],"bd");
strcpy(D[3],"bc");
strcpy(D[4],"abd");
strcpy(D[5],"ac");
strcpy(D[6],"bc");
strcpy(D[7],"ac");
strcpy(D[8],"abce");
strcpy(D[9],"abc");
}//end of InPut
int * DB=new int[num];
void suppDB()
{
int m='e';
int n;
int k;
for (int i=1;i<=9;i++)
{
n=strlen(D[i]);
DB [i]=0;
for (int j=0;j<n;j++)
{
k=1;
DB [i]+=k<<(int)(m-D[i][j]);
}
}
}
void check_supp(int num,int no)
{
int i,j,k,m;
int check;
m='e';
for(i=1;i<=num;i++)
{ check=0;
C[i].min_supp_count=0;
for (j=0;j<no;j++)
{
k=1;
check+=(int)(k<<(m-C[i].item[j]));
}
for (j=1;j<=9;j++)
{
if (check==(check&DB[j]))
{
C[i].min_supp_count+=1;//子集存在,支持度數加1
}
}
}
}
void C1()
{
//功能:掃描數據集D生成1項候選集C1
//輸入:數據集D
//輸出1項候選集C1
//初始條件 數據集D 非空
strcpy(C[1].item,"a");
strcpy(C[2].item,"b");
strcpy(C[3].item,"c");
strcpy(C[4].item,"d");
strcpy(C[5].item,"e");
C[0].min_supp_count=5; //1 項候選集的個數,在本算法中,用C[0].min_supp_count來保存候選集Cn的個數
check_supp(5,1);
} //end of C1()
void Cn( int n)
{
//用頻繁集Ln-1爲基礎,通過連接得到n項候選集Cn
int i,j,k,p,num;
int no=0,temp=0;
C[0].min_supp_count=0; //初始化
num=L[0].min_supp_count; //num是Ln-1項集的數據個數
for(i=1;i<=num;i++)
for(j=i+1;j<=num;j++) //for2
{
temp=1; //測試是否滿足聯結條件
if(n>2)//if 1
{
for(k=0;k<n-2;k++) //for3
{
if(L[i].item[k]!=L[j].item[k])
{ temp=0;
break; }//if 1
}//end for3
}//end if1
if(temp==1)//滿足聯結條件
{
// printf("in if 2 no=%d/n",no);
no++;
for(p=0;p<=n-2;p++)
C[no].item[p]=L[i].item[p];
C[no].item[p]=L[j].item[p-1];
C[no].min_supp_count=0;
C[0].min_supp_count+=1;
}//end if2
}//end for2
num=C[0].min_supp_count;
check_supp(num,n);//測試支持度
}//end of Cn()
void L1()
{ int n=1;
int i,j,k;
j=0;
L[0].min_supp_count=0;//頻繁集的個數,初始爲0
for(i=1;i<=C[0].min_supp_count;i++)
{
if(C[i].min_supp_count>=min_supp_count)
{
j+=1;
strcpy(L[j].item,C[i].item);
L[j].min_supp_count=C[i].min_supp_count;
}
}//end for1
L[0].min_supp_count=j;///頻繁集的個數,最後爲j個
printf("頻繁項目集L%d如下:/n",n);
k=L[0].min_supp_count;
if(k!=0)
{
for(i=1;i<=k;i++)
{
printf("{");
for(j=0;j<n;j++)
printf(" %c ",L[i].item[j]);
printf("} 支持度:%d/n",L[i].min_supp_count);
}//for
}
else printf("項目集爲空/n");
}//end of L1()
void Ln(int n)
{
int i,j,k;
Cn(n);
j=0;
L[0].min_supp_count=0;
for(i=1;i<=C[0].min_supp_count;i++) //for 1
{
if(C[i].min_supp_count >=min_supp_count)
{
j+=1;
strcpy(L[j].item,C[i].item);
L[j].min_supp_count=C[i].min_supp_count;
} //end if
}//end for1
L[0].min_supp_count=j; //保存數據的個數
printf("頻繁項目集L%d如下:/n",n);
k=L[0].min_supp_count;
if(k!=0)
{
for(i=1;i<=k;i++)
{
printf("{");
for(j=0;j<n;j++)
printf(" %c ",L[i].item[j]);
printf("} 支持度:%d/n",L[i].min_supp_count);
}//for
}
else printf("項目集爲空/n");
}//end of Ln(int n)
void main()
{
int n=1;
InPut();
suppDB();
C1();//初始化,生成1項候選集C1
L1();//得到1項頻繁集L1
while(L[0].min_supp_count!=0)
{
n+=1;
Ln(n);
}
char ch;
printf("press any key to eixe/n");
scanf("%c",&ch);
}