熱線電話:13121318867

登錄
首頁精彩閱讀決策分類樹算法之ID3,C4.5算法系列
決策分類樹算法之ID3,C4.5算法系列
2015-12-03
收藏

決策分類樹算法之ID3,C4.5算法系列


一、引言

在最開始的時候,我本來準備學習的是C4.5算法,后來發現C4.5算法的核心還是ID3算法,所以又輾轉回到學習ID3算法了,因為C4.5是他的一個改進。至于是什么改進,在后面的描述中我會提到。

二、ID3算法

ID3算法是一種分類決策樹算法。他通過一系列的規則,將數據最后分類成決策樹的形式。分類的根據是用到了熵這個概念。熵在物理這門學科中就已經出現過,表示是一個物質的穩定度,在這里就是分類的純度的一個概念。公式為:

在ID3算法中,是采用Gain信息增益來作為一個分類的判定標準的。他的定義為:

每次選擇屬性中信息增益最大作為劃分屬性,在這里本人實現了一個java版本的ID3算法,為了模擬數據的可操作性,就把數據寫到一個input.txt文件中,作為數據源,格式如下:

[java] view plaincopyprint?
  1. Day OutLook Temperature Humidity Wind PlayTennis  
  2. 1 Sunny Hot High Weak No  
  3. 2 Sunny Hot High Strong No  
  4. 3 Overcast Hot High Weak Yes  
  5. 4 Rainy Mild High Weak Yes  
  6. 5 Rainy Cool Normal Weak Yes  
  7. 6 Rainy Cool Normal Strong No  
  8. 7 Overcast Cool Normal Strong Yes  
  9. 8 Sunny Mild High Weak No  
  10. 9 Sunny Cool Normal Weak Yes  
  11. 10 Rainy Mild Normal Weak Yes  
  12. 11 Sunny Mild Normal Strong Yes  
  13. 12 Overcast Mild High Strong Yes  
  14. 13 Overcast Hot Normal Weak Yes  
  15. 14 Rainy Mild High Strong No  

PalyTennis屬性為結構屬性,是作為類標識用的,中間的OutLool,Temperature,Humidity,Wind才是劃分屬性,通過將源數據與執行程序分類,這樣可以模擬巨大的數據量了。下面是ID3的主程序類,本人將ID3的算法進行了包裝,對外只開放了一個構建決策樹的方法,在構造函數時候,只需傳入一個數據路徑文件即可:

[java] view plaincopyprint?
  1. package DataMing_ID3;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileReader;  
  6. import java.io.IOException;  
  7. import java.util.ArrayList;  
  8. import java.util.HashMap;  
  9. import java.util.Iterator;  
  10. import java.util.Map;  
  11. import java.util.Map.Entry;  
  12. import java.util.Set;  
  13.   
  14. /** 
  15.  * ID3算法實現類 
  16.  *  
  17.  * @author lyq 
  18.  *  
  19.  */  
  20. public class ID3Tool {  
  21.     // 類標號的值類型  
  22.     private final String YES = "Yes";  
  23.     private final String NO = "No";  
  24.   
  25.     // 所有屬性的類型總數,在這里就是data源數據的列數  
  26.     private int attrNum;  
  27.     private String filePath;  
  28.     // 初始源數據,用一個二維字符數組存放模仿表格數據  
  29.     private String[][] data;  
  30.     // 數據的屬性行的名字  
  31.     private String[] attrNames;  
  32.     // 每個屬性的值所有類型  
  33.     private HashMap<String, ArrayList<String>> attrValue;  
  34.   
  35.     public ID3Tool(String filePath) {  
  36.         this.filePath = filePath;  
  37.         attrValue = new HashMap<>();  
  38.     }  
  39.   
  40.     /** 
  41.      * 從文件中讀取數據 
  42.      */  
  43.     private void readDataFile() {  
  44.         File file = new File(filePath);  
  45.         ArrayList<String[]> dataArray = new ArrayList<String[]>();  
  46.   
  47.         try {  
  48.             BufferedReader in = new BufferedReader(new FileReader(file));  
  49.             String str;  
  50.             String[] tempArray;  
  51.             while ((str = in.readLine()) != null) {  
  52.                 tempArray = str.split(" ");  
  53.                 dataArray.add(tempArray);  
  54.             }  
  55.             in.close();  
  56.         } catch (IOException e) {  
  57.             e.getStackTrace();  
  58.         }  
  59.   
  60.         data = new String[dataArray.size()][];  
  61.         dataArray.toArray(data);  
  62.         attrNum = data[0].length;  
  63.         attrNames = data[0];  
  64.   
  65.         /* 
  66.          * for(int i=0; i<data.length;i++){ for(int j=0; j<data[0].length; j++){ 
  67.          * System.out.print(" " + data[i][j]); } 
  68.          *  
  69.          * System.out.print("\n"); } 
  70.          */  
  71.     }  
  72.   
  73.     /** 
  74.      * 首先初始化每種屬性的值的所有類型,用于后面的子類熵的計算時用 
  75.      */  
  76.     private void initAttrValue() {  
  77.         ArrayList<String> tempValues;  
  78.   
  79.         // 按照列的方式,從左往右找  
  80.         for (int j = 1; j < attrNum; j++) {  
  81.             // 從一列中的上往下開始尋找值  
  82.             tempValues = new ArrayList<>();  
  83.             for (int i = 1; i < data.length; i++) {  
  84.                 if (!tempValues.contains(data[i][j])) {  
  85.                     // 如果這個屬性的值沒有添加過,則添加  
  86.                     tempValues.add(data[i][j]);  
  87.                 }  
  88.             }  
  89.   
  90.             // 一列屬性的值已經遍歷完畢,復制到map屬性表中  
  91.             attrValue.put(data[0][j], tempValues);  
  92.         }  
  93.   
  94.         /* 
  95.          * for(Map.Entry entry : attrValue.entrySet()){ 
  96.          * System.out.println("key:value " + entry.getKey() + ":" + 
  97.          * entry.getValue()); } 
  98.          */  
  99.     }  
  100.   
  101.     /** 
  102.      * 計算數據按照不同方式劃分的熵 
  103.      *  
  104.      * @param remainData 
  105.      *            剩余的數據 
  106.      * @param attrName 
  107.      *            待劃分的屬性,在算信息增益的時候會使用到 
  108.      * @param attrValue 
  109.      *            劃分的子屬性值 
  110.      * @param isParent 
  111.      *            是否分子屬性劃分還是原來不變的劃分 
  112.      */  
  113.     private double computeEntropy(String[][] remainData, String attrName,  
  114.             String value, boolean isParent) {  
  115.         // 實例總數  
  116.         int total = 0;  
  117.         // 正實例數  
  118.         int posNum = 0;  
  119.         // 負實例數  
  120.         int negNum = 0;  
  121.   
  122.         // 還是按列從左往右遍歷屬性  
  123.         for (int j = 1; j < attrNames.length; j++) {  
  124.             // 找到了指定的屬性  
  125.             if (attrName.equals(attrNames[j])) {  
  126.                 for (int i = 1; i < remainData.length; i++) {  
  127.                     // 如果是父結點直接計算熵或者是通過子屬性劃分計算熵,這時要進行屬性值的過濾  
  128.                     if (isParent  
  129.                             || (!isParent && remainData[i][j].equals(value))) {  
  130.                         if (remainData[i][attrNames.length - 1].equals(YES)) {  
  131.                             // 判斷此行數據是否為正實例  
  132.                             posNum++;  
  133.                         } else {  
  134.                             negNum++;  
  135.                         }  
  136.                     }  
  137.                 }  
  138.             }  
  139.         }  
  140.   
  141.         total = posNum + negNum;  
  142.         double posProbobly = (double) posNum / total;  
  143.         double negProbobly = (double) negNum / total;  
  144.   
  145.         if (posProbobly == 1 || posProbobly == 0) {  
  146.             // 如果數據全為同種類型,則熵為0,否則帶入下面的公式會報錯  
  147.             return 0;  
  148.         }  
  149.   
  150.         double entropyValue = -posProbobly * Math.log(posProbobly)  
  151.                 / Math.log(2.0) - negProbobly * Math.log(negProbobly)  
  152.                 / Math.log(2.0);  
  153.   
  154.         // 返回計算所得熵  
  155.         return entropyValue;  
  156.     }  
  157.   
  158.     /** 
  159.      * 為某個屬性計算信息增益 
  160.      *  
  161.      * @param remainData 
  162.      *            剩余的數據 
  163.      * @param value 
  164.      *            待劃分的屬性名稱 
  165.      * @return 
  166.      */  
  167.     private double computeGain(String[][] remainData, String value) {  
  168.         double gainValue = 0;  
  169.         // 源熵的大小將會與屬性劃分后進行比較  
  170.         double entropyOri = 0;  
  171.         // 子劃分熵和  
  172.         double childEntropySum = 0;  
  173.         // 屬性子類型的個數  
  174.         int childValueNum = 0;  
  175.         // 屬性值的種數  
  176.         ArrayList<String> attrTypes = attrValue.get(value);  
  177.         // 子屬性對應的權重比  
  178.         HashMap<String, Integer> ratioValues = new HashMap<>();  
  179.   
  180.         for (int i = 0; i < attrTypes.size(); i++) {  
  181.             // 首先都統一計數為0  
  182.             ratioValues.put(attrTypes.get(i), 0);  
  183.         }  
  184.   
  185.         // 還是按照一列,從左往右遍歷  
  186.         for (int j = 1; j < attrNames.length; j++) {  
  187.             // 判斷是否到了劃分的屬性列  
  188.             if (value.equals(attrNames[j])) {  
  189.                 for (int i = 1; i <= remainData.length - 1; i++) {  
  190.                     childValueNum = ratioValues.get(remainData[i][j]);  
  191.                     // 增加個數并且重新存入  
  192.                     childValueNum++;  
  193.                     ratioValues.put(remainData[i][j], childValueNum);  
  194.                 }  
  195.             }  
  196.         }  
  197.   
  198.         // 計算原熵的大小  
  199.         entropyOri = computeEntropy(remainData, value, null, true);  
  200.         for (int i = 0; i < attrTypes.size(); i++) {  
  201.             double ratio = (double) ratioValues.get(attrTypes.get(i))  
  202.                     / (remainData.length - 1);  
  203.             childEntropySum += ratio  
  204.                     * computeEntropy(remainData, value, attrTypes.get(i), false);  
  205.   
  206.             // System.out.println("ratio:value: " + ratio + " " +  
  207.             // computeEntropy(remainData, value,  
  208.             // attrTypes.get(i), false));  
  209.         }  
  210.   
  211.         // 二者熵相減就是信息增益  
  212.         gainValue = entropyOri - childEntropySum;  
  213.         return gainValue;  
  214.     }  
  215.   
  216.     /** 
  217.      * 計算信息增益比 
  218.      *  
  219.      * @param remainData 
  220.      *            剩余數據 
  221.      * @param value 
  222.      *            待劃分屬性 
  223.      * @return 
  224.      */  
  225.     private double computeGainRatio(String[][] remainData, String value) {  
  226.         double gain = 0;  
  227.         double spiltInfo = 0;  
  228.         int childValueNum = 0;  
  229.         // 屬性值的種數  
  230.         ArrayList<String> attrTypes = attrValue.get(value);  
  231.         // 子屬性對應的權重比  
  232.         HashMap<String, Integer> ratioValues = new HashMap<>();  
  233.   
  234.         for (int i = 0; i < attrTypes.size(); i++) {  
  235.             // 首先都統一計數為0  
  236.             ratioValues.put(attrTypes.get(i), 0);  
  237.         }  
  238.   
  239.         // 還是按照一列,從左往右遍歷  
  240.         for (int j = 1; j < attrNames.length; j++) {  
  241.             // 判斷是否到了劃分的屬性列  
  242.             if (value.equals(attrNames[j])) {  
  243.                 for (int i = 1; i <= remainData.length - 1; i++) {  
  244.                     childValueNum = ratioValues.get(remainData[i][j]);  
  245.                     // 增加個數并且重新存入  
  246.                     childValueNum++;  
  247.                     ratioValues.put(remainData[i][j], childValueNum);  
  248.                 }  
  249.             }  
  250.         }  
  251.   
  252.         // 計算信息增益  
  253.         gain = computeGain(remainData, value);  
  254.         // 計算分裂信息,分裂信息度量被定義為(分裂信息用來衡量屬性分裂數據的廣度和均勻):  
  255.         for (int i = 0; i < attrTypes.size(); i++) {  
  256.             double ratio = (double) ratioValues.get(attrTypes.get(i))  
  257.                     / (remainData.length - 1);  
  258.             spiltInfo += -ratio * Math.log(ratio) / Math.log(2.0);  
  259.         }  
  260.   
  261.         // 計算機信息增益率  
  262.         return gain / spiltInfo;  
  263.     }  
  264.   
  265.     /** 
  266.      * 利用源數據構造決策樹 
  267.      */  
  268.     private void buildDecisionTree(AttrNode node, String parentAttrValue,  
  269.             String[][] remainData, ArrayList<String> remainAttr, boolean isID3) {  
  270.         node.setParentAttrValue(parentAttrValue);  
  271.   
  272.         String attrName = "";  
  273.         double gainValue = 0;  
  274.         double tempValue = 0;  
  275.   
  276.         // 如果只有1個屬性則直接返回  
  277.         if (remainAttr.size() == 1) {  
  278.             System.out.println("attr null");  
  279.             return;  
  280.         }  
  281.   
  282.         // 選擇剩余屬性中信息增益最大的作為下一個分類的屬性  
  283.         for (int i = 0; i < remainAttr.size(); i++) {  
  284.             // 判斷是否用ID3算法還是C4.5算法  
  285.             if (isID3) {  
  286.                 // ID3算法采用的是按照信息增益的值來比  
  287.                 tempValue = computeGain(remainData, remainAttr.get(i));  
  288.             } else {  
  289.                 // C4.5算法進行了改進,用的是信息增益率來比,克服了用信息增益選擇屬性時偏向選擇取值多的屬性的不足  
  290.                 tempValue = computeGainRatio(remainData, remainAttr.get(i));  
  291.             }  
  292.   
  293.             if (tempValue > gainValue) {  
  294.                 gainValue = tempValue;  
  295.                 attrName = remainAttr.get(i);  
  296.             }  
  297.         }  
  298.   
  299.         node.setAttrName(attrName);  
  300.         ArrayList<String> valueTypes = attrValue.get(attrName);  
  301.         remainAttr.remove(attrName);  
  302.   
  303.         AttrNode[] childNode = new AttrNode[valueTypes.size()];  
  304.         String[][] rData;  
  305.         for (int i = 0; i < valueTypes.size(); i++) {  
  306.             // 移除非此值類型的數據  
  307.             rData = removeData(remainData, attrName, valueTypes.get(i));  
  308.   
  309.             childNode[i] = new AttrNode();  
  310.             boolean sameClass = true;  
  311.             ArrayList<String> indexArray = new ArrayList<>();  
  312.             for (int k = 1; k < rData.length; k++) {  
  313.                 indexArray.add(rData[k][0]);  
  314.                 // 判斷是否為同一類的  
  315.                 if (!rData[k][attrNames.length - 1]  
  316.                         .equals(rData[1][attrNames.length - 1])) {  
  317.                     // 只要有1個不相等,就不是同類型的  
  318.                     sameClass = false;  
  319.                     break;  
  320.                 }  
  321.             }  
  322.   
  323.             if (!sameClass) {  
  324.                 // 創建新的對象屬性,對象的同個引用會出錯  
  325.                 ArrayList<String> rAttr = new ArrayList<>();  
  326.                 for (String str : remainAttr) {  
  327.                     rAttr.add(str);  
  328.                 }  
  329.   
  330.                 buildDecisionTree(childNode[i], valueTypes.get(i), rData,  
  331.                         rAttr, isID3);  
  332.             } else {  
  333.                 // 如果是同種類型,則直接為數據節點  
  334.                 childNode[i].setParentAttrValue(valueTypes.get(i));  
  335.                 childNode[i].setChildDataIndex(indexArray);  
  336.             }  
  337.   
  338.         }  
  339.         node.setChildAttrNode(childNode);  
  340.     }  
  341.   
  342.     /** 
  343.      * 屬性劃分完畢,進行數據的移除 
  344.      *  
  345.      * @param srcData 
  346.      *            源數據 
  347.      * @param attrName 
  348.      *            劃分的屬性名稱 
  349.      * @param valueType 
  350.      *            屬性的值類型 
  351.      */  
  352.     private String[][] removeData(String[][] srcData, String attrName,  
  353.             String valueType) {  
  354.         String[][] desDataArray;  
  355.         ArrayList<String[]> desData = new ArrayList<>();  
  356.         // 待刪除數據  
  357.         ArrayList<String[]> selectData = new ArrayList<>();  
  358.         selectData.add(attrNames);  
  359.   
  360.         // 數組數據轉化到列表中,方便移除  
  361.         for (int i = 0; i < srcData.length; i++) {  
  362.             desData.add(srcData[i]);  
  363.         }  
  364.   
  365.         // 還是從左往右一列列的查找  
  366.         for (int j = 1; j < attrNames.length; j++) {  
  367.             if (attrNames[j].equals(attrName)) {  
  368.                 for (int i = 1; i < desData.size(); i++) {  
  369.                     if (desData.get(i)[j].equals(valueType)) {  
  370.                         // 如果匹配這個數據,則移除其他的數據  
  371.                         selectData.add(desData.get(i));  
  372.                     }  
  373.                 }  
  374.             }  
  375.         }  
  376.   

數據分析咨詢請掃描二維碼

若不方便掃碼,搜微信號:CDAshujufenxi

數據分析師資訊
更多

OK
客服在線
立即咨詢
日韩人妻系列无码专区视频,先锋高清无码,无码免费视欧非,国精产品一区一区三区无码
客服在線
立即咨詢