ID3和C4.5的理論和應用


 

本文是汽車評估系統的核心算法,利用決策樹進行分類,本文對決策樹進行了介紹,同時比較C4.5和ID3算法的不同,對C4.5提出隨機深林的想法提高分類預測的准確性。

關鍵詞:汽車評估,決策樹,C4.5

決策樹(Decision tree

它從一組無次序、無規則的元組中推理出決策樹表示形式的分類規則。它采用自頂向下的遞歸方式,在決策樹的內部結點進行屬性值的比較,並根據不同的屬性值從該結點向下分支,葉結點是要學習划分的類。從根到葉結點的一條路徑就對應着一條合取規則,整個決策樹就對應着一組析取表達式規則。1986年 Quinlan提出了著名的ID3算法。在ID3算法的基礎上,1993年Quinlan又提出了C4.5算法。為了適應處理大規模數據集的需要,后來又提出了若干改進的算法,其中SLIQ(super-vised learning in quest)和SPRINT(scalable parallelizableinduction of decision trees)是比較有代表性的兩個算法,本文主要用C4.5算法對汽車評估系統建立分類決策樹,同時比較C4.5和ID3算法的不同,對C4.5提出隨機深林的想法提高分類預測的准確性。

信息熵的含義及分類

信息熵是信息論中的一個重要的指標,是由香農在1948年提出的。香農借用了熱力學中熵的概念來描述信息的不確定性。因此信息學中的熵和熱力學的熵是有聯系的。根據Charles H. Bennett對Maxwell’s Demon的重新解釋,對信息的銷毀是一個不可逆過程,所以銷毀信息是符合熱力學第二定律的。而產生信息,則是為系統引入負(熱力學)熵的過程。所以信息熵的符號與熱力學熵應該是相反的。

簡單的說信息熵是衡量信息的指標,更確切的說是衡量信息的不確定性或混亂程度的指標。信息的不確定性越大,熵越大。決定信息的不確定性或者說復雜程度主要因素是概率。決策樹中使用的與熵有關的概念有三個:信息熵,條件熵和互信息。下面分別來介紹這三個概念的含義和計算方法。

1信息熵

信息熵是用來衡量一元模型中信息不確定性的指標。信息的不確定性越大,熵的值也就越大。而影響熵值的主要因素是概率。這里所說的一元模型就是指單一事件,而不確定性是一個事件出現不同結果的可能性。例如拋硬幣,可能出現的結果有兩個,分別是正面和反面。而每次拋硬幣的結果是一個非常不確定的信息。因為根據我們的經驗或者歷史數據來看,一個均勻的硬幣出現正面和反面的概率相等,都是50%。因此很難判斷下一次出現的是正面還是反面。這時拋硬幣這個事件的熵值也很高。而如果歷史數據告訴我們這枚硬幣在過去的100次試驗中99次都是正面,也就是說這枚硬幣的質量不均勻,出現正面結果的概率很高。那么我們就很容易判斷下一次的結果了。這時的熵值很低,只有0.08。

 

我們把拋硬幣這個事件看做一個隨機變量S,它可能的取值有2種,分別是正面x1和反面x2。每一種取值的概率分別為P1和P2。我們要獲得隨機變量S的取值結果至少要進行1次試驗,試驗次數與隨機變量S可能的取值數量(2種)的對數函數Log有聯系。Log2=1(以2為底),其計算公式是:

Pi為子集合中不同性(而二元分類即正樣例和負樣例)的樣例的比例。

在拋硬幣的例子中,我們借助一元模型自身的概率,也就是前100次的歷史數據來消除了判斷結果的不確定性。而對於很多現實生活中的問題,則無法僅僅通過自身概率來判斷。例如:對於天氣情況,我們無法像拋硬幣一樣通過晴天,雨天和霧霾在歷史數據中出現的概率來判斷明天的天氣,因為天氣的種類很多,並且影響天氣的因素也有很多。同理,對於網站的用戶我們也無法通過他們的歷史購買頻率來判斷這個用戶在下一次訪問時是否會完成購買。因為用戶是的購買行為存在着不確定性,要消除這些不確定性需要更多的信息。例如用戶歷史行為中的廣告創意,促銷活動,商品價格,配送時間等信息。因此這里我們不能只借助一元模型來進行判斷和預測了,需要獲得更多的信息並通過二元模型或更高階的模型了解用戶的購買行為與其他因素間的關系來消除不確定性。衡量這種關系的指標叫做條件熵。

2、條件熵

條件熵是通過獲得更多的信息來消除一元模型中的不確定性。也就是通過二元或多元模型來降低一元模型的熵。我們知道的信息越多,信息的不確定性越小。例如,只使用一元模型時我們無法根據用戶歷史數據中的購買頻率來判斷這個用戶本次是否也會購買。因為不確定性太大。在加入了促銷活動,商品價格等信息后,在二元模型中我們可以發現用戶購買與促銷活動,或者商品價格變化之間的聯系。並通過購買與促銷活動一起出現的概率,和不同促銷活動時購買出現的概率來降低不確定性。

 

計算條件熵時使用到了兩種概率,分別是購買與促銷活動的聯合概率P(c),和不同促銷活動出現時購買也出現的條件概率E(c)。以下是條件熵E(T,X)的計算公式。條件熵的值越低說明二元模型的不確定性越小。

3互信息(信息增益)

互信息是用來衡量信息之間相關性的指標。當兩個信息完全相關時,互信息為1,不相關時為0。在前面的例子中用戶購買與促銷活動這兩個信息間的相關性究竟有多高,我們可以通過互信息這個指標來度量。具體的計算方法就熵與條件熵之間的差。用戶購買的熵E(T)減去促銷活動出現時用戶購買的熵E(T,X)。以下為計算公式:

熵,條件熵和互信息是構建決策樹的三個關鍵的指標。下面我們將通過信息增益划分決策樹,即ID3算法。

ID3算法

在決策樹各級節點選擇屬性時,以信息熵增益(Information gain)作為屬性的選擇標准,在檢測所有屬性值時,選擇信息熵增益最大的屬性產生決策樹的節點,由該屬性的不同取值作為分支,然后遞歸建立分支,最后等到一個完整的決策樹,可以用來對新的樣本進行分類。

信息熵增益定義為樣本按照某屬性划分時造成熵減少的期望,可以區分訓練樣本中正負樣本的能力,其公式是:

 

ID3算法存在的缺點

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

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

C4.5算法做出的改進

(1)用信息增益率來選擇屬性,選擇信息增益率大的產生決策樹的節點,克服了用信息增益來選擇屬性時偏向選擇值多的屬性的不足。信息增益率定義為:

 

分子表示信息增益,和ID3算法一樣,分母表示分裂因子,代表按照屬性A分裂樣本集S的廣度和均勻性。分裂因子公式如下:

 

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

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

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

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

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

 

(3)采用悲觀剪枝(Pessimistic Error Pruning (PEP)),避免樹的高度無節制的增長,避免過度擬合數據。

  PEP后剪枝技術是由大師Quinlan提出的。它不需要像REP(錯誤率降低修剪)樣,需要用部分樣本作為測試數據,而是完全使用訓練數據來生成決策樹,又用這些訓練數據來完成剪枝。決策樹生成和剪枝都使用訓練集, 所以會產生錯分。現在我們先來介紹幾個定義:

符號

含義

T1

決策樹T的所有內部節點(非葉子節點)

T2

決策樹T的所有葉子節點

T3

決策樹T的所有節點,T3=T1∪T2

n(t)

節點t的所有樣本數

ni(t)

節點t中類別i的所有樣本數

e(t)

t中不屬於節點t所標識類別的樣本數

 

  在剪枝時,我們使用r(t)=e(t)/n(t),就是當節點被剪枝后在訓練集上的錯誤率,而下面公式表示具體的計算公式,其中s為t節點的葉子節點。

  在此,我們把錯誤分布看成是二項式分布,由上面“二項分布的正態逼近”相關介紹知道,上面的式子是有偏差的,因此需要連續性修正因子來矯正數據, r‘(t)=[e(t) + 1/2]/n(t)

其中s為t節點的葉子節點,你不認識的那個符號為 t的所有葉子節點的數目。

  為了簡單,我們就只使用錯誤數目而不是錯誤率了,如下e'(t) = [e(t) + 1/2]:

  接着求e'(Tt)的標准差,由於誤差近似看成是二項式分布,根據u = np, σ2=npq可以得到

  

  當節點t滿足下面公式是,Tt子樹就會被剪掉:

(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就是使用這種方法處理缺少的屬性值。

C4.5算法的優缺點

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

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

汽車評估系統決策樹的建立

本文所用數據來自某汽車評估系統的一部分,下載地址:http://archive.ics.uci.edu/ml/machine-learning-databases/car/

訓練集的數據表示法

變量

變量取值域

變量含義

buying

vhigh, high, med, low

購買價格

maint

vhigh, high, med, low.

維修價格

doors

2, 3, 4, 5more

門有多少

persons

2, 4, more

載人數

lug_boot

small, med, big

載行李能力

safety

low, med, high

安全性

將數據可以分為四類:unacc, acc, good, vgood 

部分訓練數據集

buying

maint

doors

persons

lug_boot

safety

class

med

med

5more

more

small

low

unacc

med

med

5more

more

small

med

acc

med

med

5more

more

small

high

acc

med

med

5more

more

med

low

unacc

med

med

5more

more

med

med

acc

med

med

5more

more

med

high

vgood

med

med

5more

more

big

low

unacc

med

med

5more

more

big

med

acc

med

med

5more

more

big

high

vgood

med

low

2

2

med

low

unacc

med

low

2

2

med

med

unacc

med

low

2

2

med

high

unacc

med

low

2

2

big

high

unacc

med

low

2

4

small

low

unacc

med

low

2

4

small

med

acc

med

low

2

4

small

high

good

med

low

2

4

med

low

unacc

med

low

2

4

med

med

acc

med

low

2

4

med

high

good

med

low

2

4

big

low

unacc

med

low

2

4

big

med

good

med

low

2

4

big

high

vgood

核心代碼:

 

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 struct Node
  4 {
  5     string attribute;
  6     string attribute_value;
  7     vector<Node*> child;
  8     Node()
  9     {
 10         attribute = "";
 11         attribute_value="";
 12     }
 13 };
 14 Node * root = new Node();
 15 vector<string> item;
 16 map<string,vector<string> >item_range;
 17 vector<vector<string> > states;
 18 int item_num = 0;
 19 void Input()
 20 {
 21 
 22     ifstream myfile("C:/Users/Administrator/Desktop/data4.csv");
 23     if(!myfile)
 24     {
 25         cout<<"unalbe to open myfile";
 26         exit(1);
 27     }
 28     char buff[1000];
 29     myfile.getline(buff,1000);
 30     string temp = "";
 31     int bufflen = strlen(buff);
 32     for(int i = 0; i <= bufflen; i++)
 33     {
 34         if(buff[i] == ',' || i == bufflen)
 35         {
 36             item.push_back(temp);
 37             item_num ++;
 38             temp ="";
 39         }
 40         else
 41             temp +=buff[i];
 42     }
 43     while(!myfile.eof())
 44     {
 45         vector<string> row;
 46         myfile.getline(buff,1000);
 47         int bufflen = strlen(buff);
 48         for(int i = 0; i <= bufflen; i++)
 49         {
 50             if(buff[i] == ',' || i == bufflen)
 51             {
 52                 row.push_back(temp);
 53                 temp ="";
 54             }
 55             else
 56                 temp +=buff[i];
 57         }
 58         states.push_back(row);
 59     }
 60 }
 61 void computeAttributeRange()
 62 {
 63     int states_num = states.size();
 64     for(int i = 1; i < item_num; i++)
 65     {
 66         vector<string> valuetemp;
 67         vector<string>::iterator it;
 68         for(int j = 0; j < states_num; j++)
 69         {
 70             it = find(valuetemp.begin(),valuetemp.end(),states[j][i]);
 71             if(it == valuetemp.end())
 72                 valuetemp.push_back(states[j][i]);
 73         }
 74         item_range[item[i]] = valuetemp;
 75     }
 76 }
 77 
 78 double computeEntropy(vector<vector<string> > remain_states,string attribute,string attribute_value)
 79 {
 80     vector<string> lastItem = item_range[item[item_num -1]];
 81     int Size = remain_states.size();
 82     vector<string>::iterator it;
 83     int P[10],cnt,cntnum = 0;
 84     memset(P,0,sizeof(P));
 85     it = find(item.begin(),item.end(),attribute);
 86     if(it != item.end())
 87         cnt = it - item.begin();
 88     for(int i = 0; i < Size; i++)
 89     {
 90         if(remain_states[i][cnt] == attribute_value)
 91         {
 92             cntnum ++;
 93             it = find(lastItem.begin(),lastItem.end(),remain_states[i][item_num - 1]);
 94             if(it != lastItem.end())
 95                 P[it - lastItem.begin()] ++;
 96         }
 97     }
 98     double ans = 0.0;
 99     int lastItem_size = lastItem.size();
100     for(int i = 0; i < lastItem_size ; i++)
101     {
102         double temp = cntnum == 0 ? 0.0 : double(P[i])/cntnum;
103         ans -=temp != 0.0 ? temp * log(temp)/log(2.0) : 0.0;
104     }
105     return ans*cntnum;
106 }
107 double computeEntropy(vector<vector<string> > remain_states)
108 {
109     vector<string> lastItem = item_range[item[item_num -1]];
110     int Size = remain_states.size();
111     vector<string>::iterator it;
112     int P[10];
113     memset(P,0,sizeof(P));
114     for(int i = 0; i < Size; i++)
115     {
116         it = find(lastItem.begin(),lastItem.end(),remain_states[i][item_num - 1]);
117         if(it != lastItem.end())
118             P[it - lastItem.begin()] ++;
119     }
120     double ans = 0.0;
121     int lastItem_size = lastItem.size();
122     for(int i = 0; i < lastItem_size ; i++)
123     {
124 
125         double temp = Size == 0 ? 0.0 :  double(P[i])/Size;
126         ans -=temp == 0 ? 0.0 : temp * log(temp)/log(2.0);
127     }
128     return ans;
129 }
130 
131 double computeGain(vector<vector<string> > remain_states,string attribute)
132 {
133     int Size = remain_states.size();
134     vector<string> cntItem = item_range[attribute];
135     double ans = computeEntropy(remain_states);
136     int cntItem_num = cntItem.size();
137     for(int i = 0; i < cntItem_num; i++)
138     {
139         ans -= Size== 0 ? 0.0 :computeEntropy(remain_states,attribute,cntItem[i])/Size;
140     }
141     return ans;
142 }
143 
144 string allSameOfLastItem(vector<vector<string> > remain_states,bool& ok)
145 {
146     ok = true;
147     string lastItem = remain_states[0][item_num - 1];
148     for(int i = 1; i < remain_states.size(); i++)
149         if(remain_states[i][item_num-1] != lastItem)
150         {
151             ok = false;
152             break;
153         }
154     return lastItem;
155 }
156 
157 string mostCommonValue(vector<vector<string> > remain_states)
158 {
159     int p[10];
160     memset(p,0,sizeof(p));
161     vector<string> lastItems = item_range[item[item_num - 1]];
162     for(int i = 0; i < remain_states.size(); i++)
163         p[ find(lastItems.begin(),lastItems.end(),remain_states[i][item_num - 1]) - lastItems.begin()] ++;
164     int Max = 0,maxIndex = 0,lastItems_num = lastItems.size();
165     for(int i = 0; i < lastItems_num; i++)
166     {
167         if(Max < p[i])
168             p[i] = Max,maxIndex = i;
169     }
170     return lastItems[maxIndex];
171 }
172 
173 Node* BuildDecisionTree(Node * p,vector<vector<string> > remain_states,vector<string> remain_item)
174 {
175     if(p == NULL)
176     {
177         p = new Node();
178     }
179     bool Ok = true;
180     string lastItem = allSameOfLastItem(remain_states,Ok);
181     if(Ok == true)
182     {
183         p->attribute = lastItem;
184         return p;
185     }
186     if(remain_item.size() == 2)
187     {
188         p->attribute = mostCommonValue(remain_states);
189         return p;
190     }
191     double Max = computeGain(remain_states,remain_item[1]);
192     string maxAttribute = remain_item[1];
193     for(int i = 1; i < remain_item.size()- 1; i++)
194     {
195         double temp = computeGain(remain_states,remain_item[i]);
196         if(temp > Max)
197         {
198             Max = temp;
199             maxAttribute = remain_item[i];
200         }
201     }
202     p->attribute = maxAttribute;
203     vector<string> maxAttribute_range = item_range[maxAttribute];
204     int maxAttribute_rangeNum = maxAttribute_range.size();
205     int cnt = find(item.begin(),item.end(),maxAttribute) - item.begin();
206     for(int i = 0; i < maxAttribute_rangeNum; i++)
207     {
208         vector<vector<string> > newRemain_states;
209         Node* childNode = new Node();
210         childNode->attribute_value = maxAttribute_range[i];
211         for(int j = 0; j < remain_states.size(); j++)
212         {
213             if(remain_states[j][cnt] == childNode->attribute_value)
214                 newRemain_states.push_back(remain_states[j]);
215         }
216         if(newRemain_states.size() == 0)
217         {
218             childNode->attribute = mostCommonValue(remain_states);
219         }
220         else
221         {
222 
223             vector<string>::iterator it = find(remain_item.begin(),remain_item.end(),maxAttribute);
224             if(it != remain_item.end())
225                 remain_item.erase(it);
226             BuildDecisionTree(childNode,newRemain_states,remain_item);
227         }
228 
229         p->child.push_back(childNode);
230     }
231     return p;
232 }
233 void printTree(Node* p, int dep)
234 {
235     for(int i = 0; i < dep; i++)
236         printf("\t");
237     if(p->attribute_value != "")
238     {
239         cout<<p->attribute_value<<endl;
240         for(int i = 0; i <dep+1; i++)
241             printf("\t");
242     }
243         cout<<p->attribute<<endl;
244     for(vector<Node*>::iterator it = p->child.begin(); it != p->child.end(); it ++)
245         printTree(*it,dep+1);
246 
247 }
248 bool traceTree(vector<string> state,Node *p)
249 {
250     //cout<<p->attribute<<" ";
251     if(p->child.size() <= 0 )
252         {
253             //cout<<p->attribute<< " " << state[item_num-1]<<endl;
254             //cout<<(p->attribute == state[item_num - 1])<<endl;
255             //cout<<"return"<<endl;
256             return p->attribute == state[item_num - 1];
257         }
258     vector<string>::iterator it = find(item.begin(),item.end(),p->attribute);
259     if(it != item.end())
260     {
261         for(int i = 0; i < p->child.size(); i++)
262          if(p->child[i]->attribute_value == state[it-item.begin()] )
263                 return traceTree(state,p->child[i]);
264     }
265     //cout<<"erro"<<endl;
266     return 0;
267 }
268 void test()
269 {
270     int fm = 0,fz = 0;
271     ifstream testfile("C:/Users/Administrator/Desktop/data5.csv");
272     char buff[1000];
273     testfile.getline(buff,1000);
274     string temp = "";
275     while(!testfile.eof())
276     {
277         vector<string> row;
278         testfile.getline(buff,1000);
279         int bufflen = strlen(buff);
280         for(int i = 0; i <= bufflen; i++)
281         {
282             if(buff[i] == ',' || i == bufflen)
283             {
284                 row.push_back(temp);
285                 temp ="";
286             }
287             else
288                 temp +=buff[i];
289         }
290             if(traceTree(row,root))
291                 fz++;
292         fm++;
293 
294     }
295     cout<<fz<<" "<<fm<<endl;
296     cout<<double(fz)/double(fm)<<endl;
297 }
298 
299 int main()
300 {
301     freopen("C:/Users/Administrator/Desktop/res1.txt", "w", stdout);
302     Input();
303     computeAttributeRange();
304     BuildDecisionTree(root,states,item);
305     printTree(root,0);
306     test();
307     return 0;
308 }
View Code

 

通過隨機森林提高准確率

決策樹是建立在已知的歷史數據及概率上的,一課決策樹的預測可能會不太准確,提高准確率最好的方法是構建隨機森林(Random Forest)。所謂隨機森林就是通過隨機抽樣的方式從歷史數據表中生成多張抽樣的歷史表,對每個抽樣的歷史表生成一棵決策樹。由於每次生成抽樣表后數據都會放回到總表中,因此每一棵決策樹之間都是獨立的沒有關聯。將多顆決策樹組成一個隨機森林。當有一條新的數據產生時,讓森林里的每一顆決策樹分別進行判斷,以投票最多的結果作為最終的判斷結果。以此來提高正確的概率。

 


免責聲明!

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



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