決策樹(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)對噪聲數據有很好的健壯性。於是是目前應用最爲普遍的概括推理算法之一,在數據挖掘中受到研究者的普遍關注。學習
(2)ID3算法只能對描述屬性爲離散型屬性的數據集構造決策樹。測試
克服了用信息增益來選擇屬性時偏向選擇值多的屬性的不足。信息增益率定義爲: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)
避免樹的高度無節制的增加,避免過分擬合數據,
該方法使用訓練樣本集自己來估計剪枝先後的偏差,從而決定是否真正剪枝。方法中使用的公式以下:
其中N是實例的數量,f=E/N爲觀察到的偏差率(其中E爲N個實例中分類錯誤的個數),q爲真實的偏差率,c爲置信度(C4.5算法的一個輸入參數,默認值爲0.25),z爲對應於置信度c的標準差,其值可根據c的設定值經過查正態分佈表獲得。經過該公式便可計算出真實偏差率q的一個置信度上限,用此上限爲該節點偏差率e作一個悲觀的估計:
經過判斷剪枝先後e的大小,從而決定是否須要剪枝。
在某些狀況下,可供使用的數據可能缺乏某些屬性的值。假如〈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就是使用這種方法處理缺乏的屬性值。
優勢:產生的分類規則易於理解,準確率較高。
缺點:在構造樹的過程當中,須要對數據集進行屢次的順序掃描和排序,於是致使算法的低效。此外,C4.5只適合於可以駐留於內存的數據集,當訓練集大得沒法在內存容納時程序沒法運行。
// 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++; } }