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

策樹(Decision tree)html

   決策樹是以實例爲基礎的概括學習算法算法

數據挖掘分類算法之決策樹
    它從一組無次序、無規則的元組中推理出決策樹表示形式的分類規則。它採用自頂向下的遞歸方式,在決策樹的內部結點進行屬性值的比較,並根據不一樣的屬性值從 該結點向下分支,葉結點是要學習劃分的類。從根到葉結點的一條路徑就對應着一條合取規則,整個決策樹就對應着一組析取表達式規則。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算法app

一.背景

最先的決策時算法是由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)用信息增益率來選擇屬性

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

 

其中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++;
 }
}
相關文章
相關標籤/搜索