數據挖掘分類算法之決策樹(zz)


策樹(Decision tree)

   決策樹是以實例為基礎的歸納學習算法

數據挖掘分類算法之決策樹
    它從一組無次序、無規則的元組中推理出決策樹表示形式的分類規則。它采用自頂向下的遞歸方式,在決策樹的內部結點進行屬性值的比較,並根據不同的屬性值從 該結點向下分支,葉結點是要學習划分的類。從根到葉結點的一條路徑就對應着一條合取規則,整個決策樹就對應着一組析取表達式規則。1986年 Quinlan提出了著名的ID3算法。在ID3算法的基礎上,1993年Quinlan又提出了C4.5算法。為了適應處理大規模數據集的需要,后來又 提出了若干改進的算法,其中SLIQ(super-vised learning in quest)和SPRINT (scalable parallelizableinduction of decision trees)是比較有代表性的兩個算法。
    (1) ID3算法
    ID3算法的核心是:在決策樹各級結點上選擇屬性時,用信息增益(information gain)作為屬性的選擇標准,以使得在每一個非葉結點進行測試時,能獲得關於被測試記錄最大的類別信息。其具體方法是:檢測所有的屬性,選擇信息增益最大的屬性產生決策樹結點,由該屬性的不同取值建立分支,再對各分支的子集遞歸調用該方法建立決策樹結點的分支,直到所有子集僅包含同一類別的數據為止。最后得到一棵決策樹,它可以用來對新的樣本進行分類。
    某屬性的信息增益按下列方法計算。通過計算每個屬性的信息增益,並比較它們的大小,就不難獲得具有最大信息增益的屬性。
    設S是s個數據樣本的集合。假定類標號屬性具有m個不同值,定義m個不同類Ci(i=1,…,m)。設si是類Ci中的樣本數。對一個給定的樣本分類所需的期望信息由下式給出:
    其中pi=si/s是任意樣本屬於Ci的概率。注意,對數函數以2為底,其原因是信息用二進制編碼。
    設屬性A具有v個不同值{a1,a2,……,av}。可以用屬性A將S划分為v個子集{S1,S2,……,Sv},其中Sj中的樣本在屬性A上具有相同的值aj(j=1,2,……,v)。設sij是子集Sj中類Ci的樣本數。由A划分成子集的熵或信息期望由下式給出:
    熵值越小,子集划分的純度越高。對於給定的子集Sj,其信息期望為
    其中pij=sij/sj 是Sj中樣本屬於Ci的概率。在屬性A上分枝將獲得的信息增益是
    Gain(A)= I(s1, s2, …,sm)-E(A)
    ID3算法的優點是:算法的理論清晰,方法簡單,學習能力較強。其缺點是:只對比較小的數據集有效,且對噪聲比較敏感,當訓練數據集加大時,決策樹可能會隨之改變。
    (2) C4.5算法
    C4.5算法繼承了ID3算法的優點,並在以下幾方面對ID3算法進行了改進:
    1) 用信息增益率來選擇屬性,克服了用信息增益選擇屬性時偏向選擇取值多的屬性的不足;
    2) 在樹構造過程中進行剪枝;
    3) 能夠完成對連續屬性的離散化處理;
    4) 能夠對不完整數據進行處理。
    C4.5算法與其它分類算法如統計方法、神經網絡等比較起來有如下優點:產生的分類規則易於理解,准確率較高。其缺點是:在構造樹的過程中,需要對數據集 進行多次的順序掃描和排序,因而導致算法的低效。此外,C4.5只適合於能夠駐留於內存的數據集,當訓練集大得無法在內存容納時程序無法運行。
    (3) SLIQ算法
    SLIQ算法對C4.5決策樹分類算法的實現方法進行了改進,在決策樹的構造過程中采用了“預排序”和“廣度優先策略”兩種技術。
    1)預排序。對於連續屬性在每個內部結點尋找其最優分裂標准時,都需要對訓練集按照該屬性的取值進行排序,而排序是很浪費時間的操作。為此,SLIQ算法 采用了預排序技術。所謂預排序,就是針對每個屬性的取值,把所有的記錄按照從小到大的順序進行排序,以消除在決策樹的每個結點對數據集進行的排序。具體實 現時,需要為訓練數據集的每個屬性創建一個屬性列表,為類別屬性創建一個類別列表。
    2)廣度優先策略。在C4.5算法中,樹的構造是按照深度優先策略完成的,需要對每個屬性列表在每個結點處都進行一遍掃描,費時很多,為此,SLIQ采用 廣度優先策略構造決策樹,即在決策樹的每一層只需對每個屬性列表掃描一次,就可以為當前決策樹中每個葉子結點找到最優分裂標准。
    SLIQ算法由於采用了上述兩種技術,使得該算法能夠處理比C4.5大得多的訓練集,在一定范圍內具有良好的隨記錄個數和屬性個數增長的可伸縮性。
    然而它仍然存在如下缺點:
    1)由於需要將類別列表存放於內存,而類別列表的元組數與訓練集的元組數是相同的,這就一定程度上限制了可以處理的數據集的大小。
    2)由於采用了預排序技術,而排序算法的復雜度本身並不是與記錄個數成線性關系,因此,使得SLIQ算法不可能達到隨記錄數目增長的線性可伸縮性。

    (4)SPRINT算法
    為了減少駐留於內存的數據量,SPRINT算法進一步改進了決策樹算法的數據結構,去掉了在SLIQ中需要駐留於內存的類別列表,將它的類別列合並到每個 屬性列表中。這樣,在遍歷每個屬性列表尋找當前結點的最優分裂標准時,不必參照其他信息,將對結點的分裂表現在對屬性列表的分裂,即將每個屬性列表分成兩 個,分別存放屬於各個結點的記錄。
    SPRINT算法的優點是在尋找每個結點的最優分裂標准時變得更簡單。其缺點是對非分裂屬性的屬性列表進行分裂變得很困難。解決的辦法是對分裂屬性進行分 裂時用哈希表記錄下每個記錄屬於哪個孩子結點,若內存能夠容納下整個哈希表,其他屬性列表的分裂只需參照該哈希表即可。由於哈希表的大小與訓練集的大小成 正比,當訓練集很大時,哈希表可能無法在內存容納,此時分裂只能分批執行,這使得SPRINT算法的可伸縮性仍然不是很好。

 

C4.5算法

一.背景

最早的決策時算法是由Hunt等人於1966年提出的CLS。當前最有影響的決策樹算法是Quinlan於1986年提出的ID3和1993年提出的C4.5。ID3只能處理離散型描述屬性,它選擇信息增益最大的屬性划分訓練樣本,其目的是進行分枝時系統的熵最小,從而提高算法的運算速度和精確度。ID3算法的主要缺陷是,用信息增益作為選擇分枝屬性的標准時,偏向於取值較多的屬性,而在某些情況下,這類屬性可能不會提供太多有價值的信息。C4.5是ID3算法的改進算法,不僅可以處理離散型描述屬性,還能處理連續性描述屬性。C4.5采用了信息增益比作為選擇分枝屬性的標准,彌補了ID3算法的不足。

決策樹算法的優點如下:(1)分類精度高;(2)成的模式簡單;(3)對噪聲數據有很好的健壯性。因而是目前應用最為廣泛的歸納推理算法之一,在數據挖掘中受到研究者的廣泛關注。

二.C4.5改進的具體方面

1.ID3算法存在的缺點

(1)ID3算法在選擇根節點和各內部節點中的分支屬性時,采用信息增益作為評價標准。信息增益的缺點是傾向於選擇取值較多的屬性,在有些情況下這類屬性可能不會提供太多有價值的信息。

(2)ID3算法只能對描述屬性為離散型屬性的數據集構造決策樹。

2. C4.5算法做出的改進

(1)用信息增益率來選擇屬性

克服了用信息增益來選擇屬性時偏向選擇值多的屬性的不足。信息增益率定義為:

 

其中Gain(S,A)與ID3算法中的信息增益相同,而分裂信息SplitInfo(S,A)代表了按照屬性A分裂樣本集S的廣度和均勻性。

 

其中,S1到Sc是c個不同值的屬性A分割S而形成的c個樣本子集。

如按照屬性A把S集(含30個用例)分成了10個用例和20個用例兩個集合

則SplitInfo(S,A)=-1/3*log(1/3)-2/3*log(2/3)

(2)可以處理連續數值型屬性

C4.5既可以處理離散型描述屬性,也可以處理連續性描述屬性。在選擇某節點上的分枝屬性時,對於離散型描述屬性,C4.5的處理方法與ID3相同,按照該屬性本身的取值個數進行計算;對於某個連續性描述屬性Ac,假設在某個結點上的數據集的樣本數量為total,C4.5將作以下處理。

l  將該結點上的所有數據樣本按照連續型描述屬性的具體數值,由小到大進行排序,得到屬性值的取值序列{A1c,A2c,……Atotalc}。

l  在取值序列中生成total-1個分割點。第i(0<i<total)個分割點的取值設置為Vi=(Aic+A(i+1)c)/2,它可以將該節點上的數據集划分為兩個子集。

l  從total-1個分割點中選擇最佳分割點。對於每一個分割點划分數據集的方式,C4.5計算它的信息增益比,並且從中選擇信息增益比最大的分割點來划分數據集。

(3)采用了一種后剪枝方法

避免樹的高度無節制的增長,避免過度擬合數據,

該方法使用訓練樣本集本身來估計剪枝前后的誤差,從而決定是否真正剪枝。方法中使用的公式如下:

 

其中N是實例的數量,f=E/N為觀察到的誤差率(其中E為N個實例中分類錯誤的個數),q為真實的誤差率,c為置信度(C4.5算法的一個輸入參數,默認值為0.25),z為對應於置信度c的標准差,其值可根據c的設定值通過查正態分布表得到。通過該公式即可計算出真實誤差率q的一個置信度上限,用此上限為該節點誤差率e做一個悲觀的估計:

 

通過判斷剪枝前后e的大小,從而決定是否需要剪枝。

(4)對於缺失值的處理

在某些情況下,可供使用的數據可能缺少某些屬性的值。假如〈x,c(x)〉是樣本集S中的一個訓練實例,但是其屬性A的值A(x)未知。處理缺少屬性值的一種策略是賦給它結點n所對應的訓練實例中該屬性的最常見值;另外一種更復雜的策略是為A的每個可能值賦予一個概率。例如,給定一個布爾屬性A,如果結點n包含6個已知A=1和4個A=0的實例,那么A(x)=1的概率是0.6,而A(x)=0的概率是0.4。於是,實例x的60%被分配到A=1的分支,40%被分配到另一個分支。這些片斷樣例(fractional examples)的目的是計算信息增益,另外,如果有第二個缺少值的屬性必須被測試,這些樣例可以在后繼的樹分支中被進一步細分。C4.5就是使用這種方法處理缺少的屬性值。

3.  C4.5算法的優缺點

優點:產生的分類規則易於理解,准確率較高。

缺點:在構造樹的過程中,需要對數據集進行多次的順序掃描和排序,因而導致算法的低效。此外,C4.5只適合於能夠駐留於內存的數據集,當訓練集大得無法在內存容納時程序無法運行。

三.C4.5算法源代碼(C++)

// C4.5_test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#include <math.h>
#include "malloc.h"
#include <stdlib.h>
const int MAX = 10;
int** iInput;
int i = 0;//列數
int j = 0;//行數
void build_tree(FILE *fp, int* iSamples, int* iAttribute,int ilevel);//輸出規則
int choose_attribute(int* iSamples, int* iAttribute);//通過計算信息增益率選出test_attribute
double info(double dTrue,double dFalse);//計算期望信息
double entropy(double dTrue, double dFalse, double dAll);//求熵
double splitinfo(int* list,double dAll);
int check_samples(int *iSamples);//檢查samples是否都在同一個類里
int check_ordinary(int *iSamples);//檢查最普通的類
int check_attribute_null(int *iAttribute);//檢查attribute是否為空
void get_attributes(int *iSamples,int *iAttributeValue,int iAttribute);
int _tmain(int argc, _TCHAR* argv[])
{
 FILE *fp;
 FILE *fp1;
 char iGet;
 int a = 0;
 int b = 0;//a,b是循環變量
 int* iSamples;
 int* iAttribute;
 fp = fopen("c:\\input.txt","r");
 if (NULL == fp)
 {
  printf("error\n");
  return 0;
 }
 iGet = getc(fp);
 
 while (('\n' != iGet)&&(EOF != iGet))
 {
  if (',' == iGet)
  {
   i++;
  }
  iGet = getc(fp);
 }
 i++;
 
 iAttribute = (int *)malloc(sizeof(int)*i);
 for (int k = 0; k<i; k++)
 {
  iAttribute[k] = (int)malloc(sizeof(int));
  iAttribute[k] = 1;
 }
 while (EOF != iGet)
 {
  if ('\n' == iGet)
  {
   j++;
  }
  iGet = getc(fp);
 }
 j++;
 
 iInput = (int **)malloc(sizeof(int*)*j);
 iSamples = (int *)malloc(sizeof(int)*j);
 for (a = 0;a < j;a++)
 {
  iInput[a] = (int *)malloc(sizeof(int)*i);
  iSamples[a] = (int)malloc(sizeof(int));
  iSamples[a] = a;
 }
 a = 0;
 fclose(fp);
 fp=fopen("c:\\input.txt","r");
 iGet = getc(fp);
 while(EOF != iGet)
 {
  if ((',' != iGet)&&('\n' != iGet))
  {
   iInput[a][b] = iGet - 48;
   b++;
  }
  if (b == i)
  {
   a++;
   b = 0;
  }
  iGet = getc(fp);
 }
 
 fp1 = fopen("d:\\output.txt","w");
 build_tree(fp1,iSamples,iAttribute,0);
 fclose(fp);
 return 0;
}

void build_tree(FILE * fp, int* iSamples, int* iAttribute,int level)//
{
 int iTest_Attribute = 0;
 int iAttributeValue[MAX];
 int k = 0;
 int l = 0;
 int m = 0;
 int *iSamples1;
 for (k = 0; k<MAX; k++)
 {
  iAttributeValue[k] = -1;
 }
 if (0 == check_samples(iSamples))
 {
  fprintf(fp,"result: %d\n",iInput[iSamples[0]][i-1]);
  return;
 }
 if (1 == check_attribute_null(iAttribute))
 {
  fprintf(fp,"result: %d\n",check_ordinary(iSamples));
  return;
 }
 iTest_Attribute = choose_attribute(iSamples,iAttribute);
 iAttribute[iTest_Attribute] = -1;
 get_attributes(iSamples,iAttributeValue,iTest_Attribute);
 k = 0;
 while ((-1 != iAttributeValue[k])&&(k < MAX))
 {
  l = 0;
  m = 0;
  while ((-1 != iSamples[l])&&(l < j))
  {
   if (iInput[iSamples[l]][iTest_Attribute] == iAttributeValue[k])
   {
    m++;
   }
   l++;
  }
  iSamples1 = (int *)malloc(sizeof(int)*(m+1));
  l = 0;
  m = 0;
  while ((-1 != iSamples[l])&&(l < j))
  {
   if (iInput[iSamples[l]][iTest_Attribute] == iAttributeValue[k])
   {
    iSamples1[m] = iSamples[l];
    m++;
   }
   l++;
  }
  iSamples1[m] = -1;
  if (-1 == iSamples1[0])
  {
   fprintf(fp,"result: %d\n",check_ordinary(iSamples));
   return;
  }
  fprintf(fp,"level%d: %d = %d\n",level,iTest_Attribute,iAttributeValue[k]);
  build_tree(fp,iSamples1,iAttribute,level+1);
  k++;
 }
}
int choose_attribute(int* iSamples, int* iAttribute)
{
 int iTestAttribute = -1;
 int k = 0;
 int l = 0;
 int m = 0;
 int n = 0;
 int iTrue = 0;
 int iFalse = 0;
 int iTrue1 = 0;
 int iFalse1 = 0;
 int iDepart[MAX];
 int iRecord[MAX];
 double dEntropy = 0.0;
 double dGainratio = 0.0;
 double test = 0.0;
 
 for (k = 0;k<MAX;k++)
 {
  iDepart[k] = -1;
  iRecord[k] = 0;
 }
 k = 0;
 while ((l!=2)&&(k<(i - 1)))
 {
  if (iAttribute[k] == -1)
  {
   l++;
  }
  k++;
 }
 if (l == 1)
 {
  for (k = 0;k<(k-1);k++)
  {
   if (iAttribute[k] == -1)
   {
    return iAttribute[k];
   }
  }
 }
 for (k = 0;k < (i-1);k++)
 {
  l = 0;
  iTrue = 0;
  iFalse = 0;
  if (iAttribute[k] != -1)
  {
   while ((-1 != iSamples[l])&&(l < j))
   {
    if (0 == iInput[iSamples[l]][i-1])
    {
     iFalse++;
    }
    if (1 == iInput[iSamples[l]][i-1])
    {
     iTrue++;
    }
    l++;    
   }
   for (n = 0;n<l;n++)//計算該屬性有多少不同的值並記錄
   {
    m = 0;
    while((iDepart[m]!=-1)&&(m!=MAX))
    {
     if (iInput[iSamples[n]][iAttribute[k]] == iDepart[m])
     {
      break;
     }
     m++;
    }
    if (-1 == iDepart[m])
    {
     iDepart[m] = iInput[iSamples[n]][iAttribute[k]];
    }
   }
   while ((iDepart[m] != -1)&&(m!=MAX))
   {
    for (n = 0;n<l;n++)
    {
     if (iInput[iSamples[n]][iAttribute[k]] == iDepart[m])
     {
      if (1 == iInput[iSamples[n]][i-1])
      {
       iTrue1++;
      }
      if (0 == iInput[iSamples[n]][i-1])
      {
       iFalse1++;
      }
      iRecord[m]++;
     }
    }
    
    dEntropy += entropy((double)iTrue1,(double)iFalse1,(double)l);
    iTrue1 = 0;
    iFalse1 = 0;
    m++;
   }
   double dSplitinfo = splitinfo(iRecord,(double)l);
   if (-1 == iTestAttribute)
   {
    iTestAttribute = k;
    dGainratio = (info((double)iTrue,(double)iFalse)-dEntropy)/dSplitinfo;
   }
   else
   {
    test = (info((double)iTrue,(double)iFalse)-dEntropy)/dSplitinfo;
    if (dGainratio < test)
    {
     iTestAttribute = k;
     dGainratio = test;
    }
   }
  }
 }
 return iTestAttribute;
}
double info(double dTrue,double dFalse)
{
 double dInfo = 0.0;
 dInfo = ((dTrue/(dTrue+dFalse))*(log(dTrue/(dTrue+dFalse))/log(2.0))+(dFalse/(dTrue+dFalse))*(log(dFalse/(dTrue+dFalse))/log(2.0)))*(-1);
 return dInfo;
}
double entropy(double dTrue, double dFalse, double dAll)
{
 double dEntropy = 0.0;
 dEntropy = (dTrue + dFalse)*info(dTrue,dFalse)/dAll;
 return dEntropy;
}
double splitinfo(int* list,double dAll)
{
 int k = 0;
 double dSplitinfo = 0.0;
 while (0!=list[k])
 {
  dSplitinfo -= ((double)list[k]/(double)dAll)*(log((double)list[k]/(double)dAll));
  k++;
 }
 return dSplitinfo;
}
int check_samples(int *iSamples)
{
 int k = 0;
 int b = 0;
 while ((-1 != iSamples[k])&&(k < j-1))
 {
  if (iInput[k][i-1] != iInput[k+1][i-1])
  {
   b = 1;
   break;
  }
  k++;
 }
 return b;
}
int check_ordinary(int *iSamples)
{
 int k = 0;
 int iTrue = 0;
 int iFalse = 0;
 while ((-1 != iSamples[k])&&(k < i))
 {
  if (0 == iInput[iSamples[k]][i-1])
  {
   iFalse++;
  }
  else
  {
   iTrue++;
  }
  k++;
 }
 if (iTrue >= iFalse)
 {
  return 1;
 }
 else
 {
  return 0;
 }
}
int check_attribute_null(int *iAttribute)
{
 int k = 0;
 while (k < (i-1))
 {
  if (-1 != iAttribute[k])
  {
   return 0;
  }
  k++;
 }
 return 1;
}
void get_attributes(int *iSamples,int *iAttributeValue,int iAttribute)
{
 int k = 0;
 int l = 0;
 while ((-1 != iSamples[k])&&(k < j))
 {
  l = 0;
  while (-1 != iAttributeValue[l])
  {
   if (iInput[iSamples[k]][iAttribute] == iAttributeValue[l])
   {
    break;
   }
   l++;
  }
  if (-1 == iAttributeValue[l])
  {
   iAttributeValue[l] = iInput[iSamples[k]][iAttribute];
  }
  k++;
 }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM