使用决策树实现分类
轉載請注明出處:
http://blog.csdn.net/gane_cheng/article/details/53897669
http://www.ganecheng.tech/blog/53897669.html (瀏覽效果更好)
決策樹是一種樹形結構,為人們提供決策依據,決策樹可以用來回答yes和no問題,它通過樹形結構將各種情況組合都表示出來,每個分支表示一次選擇(選擇yes還是no),直到所有選擇都進行完畢,最終給出正確答案。
本文介紹決策樹如何來實現分類,并用來預測結果。
先拋出問題。現在統計了14天的氣象數據(指標包括outlook,temperature,humidity,windy),并已知這些天氣是否打球(play)。如果給出新一天的氣象指標數據:sunny,cool,high,TRUE,判斷一下會不會去打球。
| sunny | hot | high | FALSE | no |
| sunny | hot | high | TRUE | no |
| overcast | hot | high | FALSE | yes |
| rainy | mild | high | FALSE | yes |
| rainy | cool | normal | FALSE | yes |
| rainy | cool | normal | TRUE | no |
| overcast | cool | normal | TRUE | yes |
| sunny | mild | high | FALSE | no |
| sunny | cool | normal | FALSE | yes |
| rainy | mild | normal | FALSE | yes |
| sunny | mild | normal | TRUE | yes |
| overcast | mild | high | TRUE | yes |
| overcast | hot | normal | FALSE | yes |
| rainy | mild | high | TRUE | no |
現在,我們想讓所有輸入情況可以更快的得到答案。也就是要求平均查找時間更短。當一堆數據區分度越高的話,比較的次數就會更少一些,也就可以更快的得到答案。
下面介紹一些概念來描述這個問題。
概念簡介
決策樹
決策樹是一種樹形結構,其中每個內部節點表示一個屬性上的測試,每個分支代表一個測試輸出,每個葉節點代表一種類別。
決策樹是一種十分常用的分類方法。他是一種監管學習,所謂監管學習就是給定一堆樣本,每個樣本都有一組屬性和一個類別,這些類別是事先確定的,那么通過學習得到一個分類器,這個分類器能夠對新出現的對象給出正確的分類。這樣的機器學習就被稱之為監督學習。
香農熵
香農熵(entropy),簡稱熵,由美國數學家、信息論的創始人香農提出。用來定量表示信息的聚合程度,是信息的期望值。
劃分數據集的大原則是:將無序的數據變得更加有序。自然界各種物體已經在我們基礎教育,高等教育中被學到。世界本來是充滿各種雜亂信息的,但是被人類不停地認識到,認識的過程還是循序漸進的。原本雜亂的信息卻被我們系統地組織起來了,這就要歸功于分類了。
學語文時,我們學習白話文,文言文,詩歌,唐詩,宋詞,散文,雜文,小說,等等。
學數學時,加減乘除,指數,對數,方程,幾何,微積分,概率論,圖論,線性,離散,等等。
學英語時,名詞,動詞,形容詞,副詞,口語,語法,時態,等等。
學歷史時,中國史,世界史,原始社會,奴隸社會,封建社會,現代社會,等等。
分類分的越好,我們理解,掌握起來就會更輕松。并且一個新事物出現,我們可以基于已經學習到的經驗預測到它大概是什么。
熵就是用來描述信息的這種確定與不確定狀態的,信息越混亂,熵越大,信息分類越清晰,熵越小。
我們來看一個例子,馬上要舉行世界杯賽了。大家都很關心誰會是冠軍。假如我錯過了看世界杯,賽后我問一個知道比賽結果的觀眾“哪支球隊是冠軍”? 他不愿意直接告訴我, 而要讓我猜,并且我每猜一次,他要收一元錢才肯告訴我是否猜對了,那么我需要付給他多少錢才能知道誰是冠軍呢? 我可以把球隊編上號,從 1 到 32, 然后提問: “冠軍的球隊在 1-16 號中嗎?” 假如他告訴我猜對了, 我會接著問: “冠軍在 1-8 號中嗎?” 假如他告訴我猜錯了, 我自然知道冠軍隊在 9-16 中。 這樣最多只需要五次, 我就能知道哪支球隊是冠軍。所以,誰是世界杯冠軍這條消息的信息量只值五塊錢。(球隊第一種分類方式)
我們實際上可能不需要猜五次就能猜出誰是冠軍,因為象巴西、德國、意大利這樣的球隊得冠軍的可能性比日本、美國、韓國等隊大的多。因此,我們第一次猜測時不需要把 32 個球隊等分成兩個組,而可以把少數幾個最可能的球隊分成一組,把其它隊分成另一組。然后我們猜冠軍球隊是否在那幾只熱門隊中。我們重復這樣的過程,根據奪冠概率對剩下的候選球隊分組,直到找到冠軍隊。這樣,我們也許三次或四次就猜出結果。因此,當每個球隊奪冠的可能性(概率)不等時,“誰世界杯冠軍”的信息量的信息量比五比特少。(球隊第二種分類方式)
熵定義為信息的期望值。求得熵,需要先知道信息的定義。如果待分類的事務可能劃分在多個分類之中,則信息定義為
l(xi)=?log2p(xi)(公式1)
其中,xi 表示第 i 個分類,p(xi) 表示選擇第 i 個分類的概率。
假如有變量X,其可能的分類有n種,熵,可以通過下面的公式得到:
H(X)=?∑i=1np(xi)log2p(xi)(公式2)
其中,n 表示分類的數量。
以上面球隊為例,第一種分類的話,所得球隊的熵為:
H=?∑i=1np(xi)log2p(xi)=?(132log2132)×32=?log2132=?log232?1=log232=5(次)
如果是按第二種方式分類的話,假如,每個隊得冠軍的概率是這樣的。
| 中國 | 強隊 | 18% |
| 巴西 | 強隊 | 18% |
| 德國 | 強隊 | 18% |
| 意大利炮 | 強隊 | 18% |
| 剩下的28只球隊每隊獲勝概率都為1% | 弱隊 | 1% |
現在分成了兩個隊強隊和弱隊,需要分別計算兩隊的熵,然后再計算總的熵。
強隊的熵為:
H=?∑i=1np(xi)log2p(xi)=?(14log214)×4=?log214=?log24?1=log24=2(次)
弱隊的熵為:
H=?∑i=1np(xi)log2p(xi)=?(128log2128)×28=?log2128=?log228?1=log228≈4.8(次)
所得球隊的總的熵為:
H=0.72×2+0.28×4.8≈2.784(次)
可以看到,如果我們按照強弱隊的方式來分類,然后再猜的話,平均只需要2.8次就可以猜出冠軍球隊。
信息增益
信息增益(information gain) 指的是劃分數據集前后信息發生的變化。
在信息增益中,衡量標準是看特征能夠為分類系統帶來多少信息,帶來的信息越多,該特征越重要。對一個特征而言,系統有它和沒它時信息量將發生變化,而前后信息量的差值就是這個特征給系統帶來的信息量。所謂信息量,就是熵。
特征T給聚類C或分類C帶來的信息增益可以定義為
IG(T)=H(C)?H(C|T)(公式3)
其中,IG(T) 表示特征 T 帶來的信息增益,H(C) 表示未使用特征 T 時的熵,H(C|T) 表示使用特征 T 時的熵。并且 H(C) 一定會大于等于 H(C|T) 。
例如,上面的球隊按第一種分類得到的熵為 5,第二種分類得到的熵為 2.8,則強弱隊這個特征為 32 只球隊帶來的信息增益則為:5-2.8=2.2 。
信息增益最大的問題在于它只能考察特征對整個系統的貢獻,而不能具體到某個類別上,這就使得它只適合用來做所謂“全局”的特征選擇。
一個特征帶來的信息增益越大,越適合用來做分類的特征。
構造決策樹
ID3 算法
構造樹的基本想法是隨著樹深度的增加,節點的熵迅速地降低。熵降低的速度越快越好(即信息增益越大越好),這樣我們有望得到一棵高度最矮的決策樹。
好,現在用此算法來分析天氣的例子。
在沒有使用任何特征情況下。根據歷史數據,我們只知道新的一天打球的概率是9/14,不打的概率是5/14。此時的熵為:
H=?∑i=1np(xi)log2p(xi)=?(514log2514+914log2914)≈?(0.357×(?1.486)+0.643×(?0.637))≈0.940
如果按照每個特征分類的話。屬性有4個:outlook,temperature,humidity,windy。我們首先要決定哪個屬性作樹的根節點。
對每項指標分別統計:在不同的取值下打球和不打球的次數。
| sunny | 2 | 3 |
| overcast | 4 | 0 |
| rainy | 3 | 2 |
H(sunny)=?∑i=1np(xi)log2p(xi)=?(25log225+35log235)≈0.971
H(overcast)=?∑i=1np(xi)log2p(xi)=?(log21)=0
H(rainy)=?∑i=1np(xi)log2p(xi)=?(35log235+25log225)≈0.971
因此如果用特征outlook來分類的話,總的熵為
H(outlook)=514×0.971+0+514×0.971≈0.714×0.971≈0.694
然后,求得特征outlook獲得的信息增益。
IG(outlook)=0.940?0.694=0.246(outlook的信息增益)
用同樣的方法,可以分別求出temperature,humidity,windy的信息增益。IG(temperature)=0.029,IG(humidity)=0.152,IG(windy)=0.048。
因為 IG(outlook)>IG(humidity)>IG(windy)>IG(temperature)。所以根節點應該選擇outlook特征來進行分類。
接下來要繼續判斷取temperature、humidity還是windy?在已知outlook=sunny的情況,根據歷史數據,分別計算IG(temperature)、IG(humidity)和IG(windy),選最大者為特征。
依此類推,構造決策樹。當系統的信息熵降為0時,就沒有必要再往下構造決策樹了,此時葉子節點都是純的–這是理想情況。最壞的情況下,決策樹的高度為屬性(決策變量)的個數,葉子節點不純(這意味著我們要以一定的概率來作出決策,一般采用多數表決的方式確定此葉子節點)。
構造決策樹的一般過程
Java實現
定義數據結構
根據決策樹的形狀,我將決策樹的數據結構定義如下。lastFeatureValue表示經過某個特征值的篩選到達的節點,featureName表示答案或者信息增益最大的特征。childrenNodeList表示經過這個特征的若干個值分類后得到的幾個節點。
public class Node {/*** 到達此節點的特征值*/public String lastFeatureValue;/*** 此節點的特征名稱或答案*/public String featureName;/*** 此節點的分類子節點*/public List<Node> childrenNodeList = new ArrayList<Node>(); }定義輸入數據格式
文章最開始拋出的問題中的數據的輸入格式是這樣的。
@feature outlook,temperature,humidity,windy,play@data sunny,hot,high,FALSE,no sunny,hot,high,TRUE,no overcast,hot,high,FALSE,yes rainy,mild,high,FALSE,yes rainy,cool,normal,FALSE,yes rainy,cool,normal,TRUE,no overcast,cool,normal,TRUE,yes sunny,mild,high,FALSE,no sunny,cool,normal,FALSE,yes rainy,mild,normal,FALSE,yes sunny,mild,normal,TRUE,yes overcast,mild,high,TRUE,yes overcast,hot,normal,FALSE,yes rainy,mild,high,TRUE,no存儲輸入數據
在代碼中,特征和特征值用List來存儲,數據用Map來存儲。
//特征列表public static List<String> featureList = new ArrayList<String>();// 特征值列表public static List<List<String>> featureValueTableList = new ArrayList<List<String>>();//得到全局數據public static Map<Integer, List<String>> tableMap = new HashMap<Integer, List<String>>();初始化輸入數據
對輸入數據進行初始化。
/*** 初始化數據* * @param file*/public static void readOriginalData(File file){int index = 0;try{FileReader fr = new FileReader(file);BufferedReader br = new BufferedReader(fr);String line;while ((line = br.readLine()) != null){// 得到特征名稱if (line.startsWith("@feature")){line = br.readLine();String[] row = line.split(",");for (String s : row){featureList.add(s.trim());}}else if (line.startsWith("@data")){while ((line = br.readLine()) != null){if (line.equals("")){continue;}String[] row = line.split(",");if (row.length != featureList.size()){throw new Exception("列表數據和特征數目不一致");}List<String> tempList = new ArrayList<String>();for (String s : row){if (s.trim().equals("")){throw new Exception("列表數據不能為空");}tempList.add(s.trim());}tableMap.put(index++, tempList);}// 遍歷tableMap得到屬性值列表Map<Integer, Set<String>> valueSetMap = new HashMap<Integer, Set<String>>();for (int i = 0; i < featureList.size(); i++){valueSetMap.put(i, new HashSet<String>());}for (Map.Entry<Integer, List<String>> entry : tableMap.entrySet()){List<String> dataList = entry.getValue();for (int i = 0; i < dataList.size(); i++){valueSetMap.get(i).add(dataList.get(i));}}for (Map.Entry<Integer, Set<String>> entry : valueSetMap.entrySet()){List<String> valueList = new ArrayList<String>();for (String s : entry.getValue()){valueList.add(s);}featureValueTableList.add(valueList);}}else{continue;}}br.close();}catch (IOException e1){e1.printStackTrace();}catch (Exception e){e.printStackTrace();}}計算給定數據集的香農熵
/*** 計算熵* * @param dataSetList* @return*/public static double calculateEntropy(List<Integer> dataSetList){if (dataSetList == null || dataSetList.size() <= 0){return 0;}// 得到結果int resultIndex = tableMap.get(dataSetList.get(0)).size() - 1;Map<String, Integer> valueMap = new HashMap<String, Integer>();for (Integer id : dataSetList){String value = tableMap.get(id).get(resultIndex);Integer num = valueMap.get(value);if (num == null || num == 0){num = 0;}valueMap.put(value, num + 1);}double entropy = 0;for (Map.Entry<String, Integer> entry : valueMap.entrySet()){double prob = entry.getValue() * 1.0 / dataSetList.size();entropy -= prob * Math.log10(prob) / Math.log10(2);}return entropy;}按照給定特征劃分數據集
/*** 對一個數據集進行劃分* * @param dataSetList* 待劃分的數據集* @param featureIndex* 第幾個特征(特征下標,從0開始)* @param value* 得到某個特征值的數據集* @return*/public static List<Integer> splitDataSet(List<Integer> dataSetList, int featureIndex, String value){List<Integer> resultList = new ArrayList<Integer>();for (Integer id : dataSetList){if (tableMap.get(id).get(featureIndex).equals(value)){resultList.add(id);}}return resultList;}選擇最好的數據集劃分方式
/*** 在指定的幾個特征中選擇一個最佳特征(信息增益最大)用于劃分數據集* * @param dataSetList* @return 返回最佳特征的下標*/public static int chooseBestFeatureToSplit(List<Integer> dataSetList, List<Integer> featureIndexList){double baseEntropy = calculateEntropy(dataSetList);double bestInformationGain = 0;int bestFeature = -1;// 循環遍歷所有特征for (int temp = 0; temp < featureIndexList.size() - 1; temp++){int i = featureIndexList.get(temp);// 得到特征集合List<String> featureValueList = new ArrayList<String>();for (Integer id : dataSetList){String value = tableMap.get(id).get(i);featureValueList.add(value);}Set<String> featureValueSet = new HashSet<String>();featureValueSet.addAll(featureValueList);// 得到此分類下的熵double newEntropy = 0;for (String featureValue : featureValueSet){List<Integer> subDataSetList = splitDataSet(dataSetList, i, featureValue);double probability = subDataSetList.size() * 1.0 / dataSetList.size();newEntropy += probability * calculateEntropy(subDataSetList);}// 得到信息增益double informationGain = baseEntropy - newEntropy;// 得到信息增益最大的特征下標if (informationGain > bestInformationGain){bestInformationGain = informationGain;bestFeature = temp;}}return bestFeature;}多數表決不確定結果
如果所有屬性都劃分完了,答案還沒確定,需要通過多數表決的方式得到答案。
/*** 多數表決得到出現次數最多的那個值* * @param dataSetList* @return*/public static String majorityVote(List<Integer> dataSetList){// 得到結果int resultIndex = tableMap.get(dataSetList.get(0)).size() - 1;Map<String, Integer> valueMap = new HashMap<String, Integer>();for (Integer id : dataSetList){String value = tableMap.get(id).get(resultIndex);Integer num = valueMap.get(value);if (num == null || num == 0){num = 0;}valueMap.put(value, num + 1);}int maxNum = 0;String value = "";for (Map.Entry<String, Integer> entry : valueMap.entrySet()){if (entry.getValue() > maxNum){maxNum = entry.getValue();value = entry.getKey();}}return value;}創建決策樹
/*** 創建決策樹* * @param dataSetList* 數據集* @param featureIndexList* 可用的特征列表* @param lastFeatureValue* 到達此節點的上一個特征值* @return*/public static Node createDecisionTree(List<Integer> dataSetList, List<Integer> featureIndexList, String lastFeatureValue){// 如果只有一個值的話,則直接返回葉子節點int valueIndex = featureIndexList.get(featureIndexList.size() - 1);// 選擇第一個值String firstValue = tableMap.get(dataSetList.get(0)).get(valueIndex);int firstValueNum = 0;for (Integer id : dataSetList){if (firstValue.equals(tableMap.get(id).get(valueIndex))){firstValueNum++;}}if (firstValueNum == dataSetList.size()){Node node = new Node();node.lastFeatureValue = lastFeatureValue;node.featureName = firstValue;node.childrenNodeList = null;return node;}// 遍歷完所有特征時特征值還沒有完全相同,返回多數表決的結果if (featureIndexList.size() == 1){Node node = new Node();node.lastFeatureValue = lastFeatureValue;node.featureName = majorityVote(dataSetList);node.childrenNodeList = null;return node;}// 獲得信息增益最大的特征int bestFeatureIndex = chooseBestFeatureToSplit(dataSetList, featureIndexList);// 得到此特征在全局的下標int realFeatureIndex = featureIndexList.get(bestFeatureIndex);String bestFeatureName = featureList.get(realFeatureIndex);// 構造決策樹Node node = new Node();node.lastFeatureValue = lastFeatureValue;node.featureName = bestFeatureName;// 得到所有特征值的集合List<String> featureValueList = featureValueTableList.get(realFeatureIndex);// 刪除此特征featureIndexList.remove(bestFeatureIndex);// 遍歷特征所有值,劃分數據集,然后遞歸得到子節點for (String fv : featureValueList){// 得到子數據集List<Integer> subDataSetList = splitDataSet(dataSetList, realFeatureIndex, fv);// 如果子數據集為空,則使用多數表決給一個答案。if (subDataSetList == null || subDataSetList.size() <= 0){Node childNode = new Node();childNode.lastFeatureValue = fv;childNode.featureName = majorityVote(dataSetList);childNode.childrenNodeList = null;node.childrenNodeList.add(childNode);break;}// 添加子節點Node childNode = createDecisionTree(subDataSetList, featureIndexList, fv);node.childrenNodeList.add(childNode);}return node;}使用決策樹對測試數據進行預測
/*** 輸入測試數據得到決策樹的預測結果* @param decisionTree 決策樹* @param featureList 特征列表* @param testDataList 測試數據* @return*/public static String getDTAnswer(Node decisionTree, List<String> featureList, List<String> testDataList){if (featureList.size() - 1 != testDataList.size()){System.out.println("輸入數據不完整");return "ERROR";}while (decisionTree != null){// 如果孩子節點為空,則返回此節點答案.if (decisionTree.childrenNodeList == null || decisionTree.childrenNodeList.size() <= 0){return decisionTree.featureName;}// 孩子節點不為空,則判斷特征值找到子節點for (int i = 0; i < featureList.size() - 1; i++){// 找到當前特征下標if (featureList.get(i).equals(decisionTree.featureName)){// 得到測試數據特征值String featureValue = testDataList.get(i);// 在子節點中找到含有此特征值的節點Node childNode = null;for (Node cn : decisionTree.childrenNodeList){if (cn.lastFeatureValue.equals(featureValue)){childNode = cn;break;}}// 如果沒有找到此節點,則說明訓練集中沒有到這個節點的特征值if (childNode == null){System.out.println("沒有找到此特征值的數據");return "ERROR";}decisionTree = childNode;break;}}}return "ERROR";}測試結果
構建的決策樹輸出是這樣的。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Node><featureName>outlook</featureName><childrenNodeList><lastFeatureValue>rainy</lastFeatureValue><featureName>windy</featureName><childrenNodeList><lastFeatureValue>FALSE</lastFeatureValue><featureName>yes</featureName></childrenNodeList><childrenNodeList><lastFeatureValue>TRUE</lastFeatureValue><featureName>no</featureName></childrenNodeList></childrenNodeList><childrenNodeList><lastFeatureValue>sunny</lastFeatureValue><featureName>humidity</featureName><childrenNodeList><lastFeatureValue>normal</lastFeatureValue><featureName>yes</featureName></childrenNodeList><childrenNodeList><lastFeatureValue>high</lastFeatureValue><featureName>no</featureName></childrenNodeList></childrenNodeList><childrenNodeList><lastFeatureValue>overcast</lastFeatureValue><featureName>yes</featureName></childrenNodeList> </Node>轉換成圖是這樣的。
此時輸入數據進行測試。
rainy,cool,high,TRUE得到結果為:
判斷結果:no與圖中結果一致。
源碼下載
本文實現代碼可以從這里下載。
http://download.csdn.net/detail/gane_cheng/9724922
GitHub地址在這兒。
https://github.com/ganecheng/DecisionTree
決策樹的優缺點
優點
計算復雜度不高,輸出結果易于理解,對中間值的缺失不敏感,可以處理不相關特征數據。
缺點
可能會產生過度匹配問題。
當特征和特征值過多時,這些匹配選項可能太多了,我們將這種問題稱之為過度匹配(overfitting)。為了減少過度匹配問題,我們可以裁剪決策樹,去掉一些不必要的葉子節點。如果葉子節點只能增加少許信息,則可以刪除該結點,將它并入到其他葉子節點中去。
另外,對于標稱型數據 字符串還比較好說,對于數值型數據卻無法直接處理,雖然可以將數值型數據劃分區間轉化為標稱型數據,但是如果有很多特征都是數值型數據,還是會比較麻煩。
參考文獻
《機器學習實戰》(Machine Learning in Action),Peter Harrington著,人民郵電出版社。
http://www.99cankao.com/numbers/log-antilog.php
http://www.cnblogs.com/zhangchaoyang/articles/2196631.html
http://baike.baidu.com/item/%E9%A6%99%E5%86%9C%E7%86%B5
http://www.cnblogs.com/bourneli/archive/2013/03/15/2961568.html
http://www.cnblogs.com/leoo2sk/archive/2010/09/19/decision-tree.html
總結
- 上一篇: Boot Device简介
- 下一篇: 银行面试经验