決策分類樹(shù)算法之ID3,C4.5算法系列
一、引言
在最開(kāi)始的時(shí)候,我本來(lái)準(zhǔn)備學(xué)習(xí)的是C4.5算法,后來(lái)發(fā)現(xiàn)C4.5算法的核心還是ID3算法,所以又輾轉(zhuǎn)回到學(xué)習(xí)ID3算法了,因?yàn)镃4.5是他的一個(gè)改進(jìn)。至于是什么改進(jìn),在后面的描述中我會(huì)提到。
二、ID3算法
ID3算法是一種分類決策樹(shù)算法。他通過(guò)一系列的規(guī)則,將數(shù)據(jù)最后分類成決策樹(shù)的形式。分類的根據(jù)是用到了熵這個(gè)概念。熵在物理這門學(xué)科中就已經(jīng)出現(xiàn)過(guò),表示是一個(gè)物質(zhì)的穩(wěn)定度,在這里就是分類的純度的一個(gè)概念。公式為:
在ID3算法中,是采用Gain信息增益來(lái)作為一個(gè)分類的判定標(biāo)準(zhǔn)的。他的定義為:
每次選擇屬性中信息增益最大作為劃分屬性,在這里本人實(shí)現(xiàn)了一個(gè)java版本的ID3算法,為了模擬數(shù)據(jù)的可操作性,就把數(shù)據(jù)寫到一個(gè)input.txt文件中,作為數(shù)據(jù)源,格式如下:
[java] view plaincopyprint?
-
Day OutLook Temperature Humidity Wind PlayTennis
-
1 Sunny Hot High Weak No
-
2 Sunny Hot High Strong No
-
3 Overcast Hot High Weak Yes
-
4 Rainy Mild High Weak Yes
-
5 Rainy Cool Normal Weak Yes
-
6 Rainy Cool Normal Strong No
-
7 Overcast Cool Normal Strong Yes
-
8 Sunny Mild High Weak No
-
9 Sunny Cool Normal Weak Yes
-
10 Rainy Mild Normal Weak Yes
-
11 Sunny Mild Normal Strong Yes
-
12 Overcast Mild High Strong Yes
-
13 Overcast Hot Normal Weak Yes
-
14 Rainy Mild High Strong No
PalyTennis屬性為結(jié)構(gòu)屬性,是作為類標(biāo)識(shí)用的,中間的OutLool,Temperature,Humidity,Wind才是劃分屬性,通過(guò)將源數(shù)據(jù)與執(zhí)行程序分類,這樣可以模擬巨大的數(shù)據(jù)量了。下面是ID3的主程序類,本人將ID3的算法進(jìn)行了包裝,對(duì)外只開(kāi)放了一個(gè)構(gòu)建決策樹(shù)的方法,在構(gòu)造函數(shù)時(shí)候,只需傳入一個(gè)數(shù)據(jù)路徑文件即可:
[java] view plaincopyprint?
-
package DataMing_ID3;
-
-
import java.io.BufferedReader;
-
import java.io.File;
-
import java.io.FileReader;
-
import java.io.IOException;
-
import java.util.ArrayList;
-
import java.util.HashMap;
-
import java.util.Iterator;
-
import java.util.Map;
-
import java.util.Map.Entry;
-
import java.util.Set;
-
-
/**
-
* ID3算法實(shí)現(xiàn)類
-
*
-
* @author lyq
-
*
-
*/
-
public class ID3Tool {
-
// 類標(biāo)號(hào)的值類型
-
private final String YES = "Yes";
-
private final String NO = "No";
-
-
// 所有屬性的類型總數(shù),在這里就是data源數(shù)據(jù)的列數(shù)
-
private int attrNum;
-
private String filePath;
-
// 初始源數(shù)據(jù),用一個(gè)二維字符數(shù)組存放模仿表格數(shù)據(jù)
-
private String[][] data;
-
// 數(shù)據(jù)的屬性行的名字
-
private String[] attrNames;
-
// 每個(gè)屬性的值所有類型
-
private HashMap<String, ArrayList<String>> attrValue;
-
-
public ID3Tool(String filePath) {
-
this.filePath = filePath;
-
attrValue = new HashMap<>();
-
}
-
-
/**
-
* 從文件中讀取數(shù)據(jù)
-
*/
-
private void readDataFile() {
-
File file = new File(filePath);
-
ArrayList<String[]> dataArray = new ArrayList<String[]>();
-
-
try {
-
BufferedReader in = new BufferedReader(new FileReader(file));
-
String str;
-
String[] tempArray;
-
while ((str = in.readLine()) != null) {
-
tempArray = str.split(" ");
-
dataArray.add(tempArray);
-
}
-
in.close();
-
} catch (IOException e) {
-
e.getStackTrace();
-
}
-
-
data = new String[dataArray.size()][];
-
dataArray.toArray(data);
-
attrNum = data[0].length;
-
attrNames = data[0];
-
-
/*
-
* for(int i=0; i<data.length;i++){ for(int j=0; j<data[0].length; j++){
-
* System.out.print(" " + data[i][j]); }
-
*
-
* System.out.print("\n"); }
-
*/
-
}
-
-
/**
-
* 首先初始化每種屬性的值的所有類型,用于后面的子類熵的計(jì)算時(shí)用
-
*/
-
private void initAttrValue() {
-
ArrayList<String> tempValues;
-
-
// 按照列的方式,從左往右找
-
for (int j = 1; j < attrNum; j++) {
-
// 從一列中的上往下開(kāi)始尋找值
-
tempValues = new ArrayList<>();
-
for (int i = 1; i < data.length; i++) {
-
if (!tempValues.contains(data[i][j])) {
-
// 如果這個(gè)屬性的值沒(méi)有添加過(guò),則添加
-
tempValues.add(data[i][j]);
-
}
-
}
-
-
// 一列屬性的值已經(jīng)遍歷完畢,復(fù)制到map屬性表中
-
attrValue.put(data[0][j], tempValues);
-
}
-
-
/*
-
* for(Map.Entry entry : attrValue.entrySet()){
-
* System.out.println("key:value " + entry.getKey() + ":" +
-
* entry.getValue()); }
-
*/
-
}
-
-
/**
-
* 計(jì)算數(shù)據(jù)按照不同方式劃分的熵
-
*
-
* @param remainData
-
* 剩余的數(shù)據(jù)
-
* @param attrName
-
* 待劃分的屬性,在算信息增益的時(shí)候會(huì)使用到
-
* @param attrValue
-
* 劃分的子屬性值
-
* @param isParent
-
* 是否分子屬性劃分還是原來(lái)不變的劃分
-
*/
-
private double computeEntropy(String[][] remainData, String attrName,
-
String value, boolean isParent) {
-
// 實(shí)例總數(shù)
-
int total = 0;
-
// 正實(shí)例數(shù)
-
int posNum = 0;
-
// 負(fù)實(shí)例數(shù)
-
int negNum = 0;
-
-
// 還是按列從左往右遍歷屬性
-
for (int j = 1; j < attrNames.length; j++) {
-
// 找到了指定的屬性
-
if (attrName.equals(attrNames[j])) {
-
for (int i = 1; i < remainData.length; i++) {
-
// 如果是父結(jié)點(diǎn)直接計(jì)算熵或者是通過(guò)子屬性劃分計(jì)算熵,這時(shí)要進(jìn)行屬性值的過(guò)濾
-
if (isParent
-
|| (!isParent && remainData[i][j].equals(value))) {
-
if (remainData[i][attrNames.length - 1].equals(YES)) {
-
// 判斷此行數(shù)據(jù)是否為正實(shí)例
-
posNum++;
-
} else {
-
negNum++;
-
}
-
}
-
}
-
}
-
}
-
-
total = posNum + negNum;
-
double posProbobly = (double) posNum / total;
-
double negProbobly = (double) negNum / total;
-
-
if (posProbobly == 1 || posProbobly == 0) {
-
// 如果數(shù)據(jù)全為同種類型,則熵為0,否則帶入下面的公式會(huì)報(bào)錯(cuò)
-
return 0;
-
}
-
-
double entropyValue = -posProbobly * Math.log(posProbobly)
-
/ Math.log(2.0) - negProbobly * Math.log(negProbobly)
-
/ Math.log(2.0);
-
-
// 返回計(jì)算所得熵
-
return entropyValue;
-
}
-
-
/**
-
* 為某個(gè)屬性計(jì)算信息增益
-
*
-
* @param remainData
-
* 剩余的數(shù)據(jù)
-
* @param value
-
* 待劃分的屬性名稱
-
* @return
-
*/
-
private double computeGain(String[][] remainData, String value) {
-
double gainValue = 0;
-
// 源熵的大小將會(huì)與屬性劃分后進(jìn)行比較
-
double entropyOri = 0;
-
// 子劃分熵和
-
double childEntropySum = 0;
-
// 屬性子類型的個(gè)數(shù)
-
int childValueNum = 0;
-
// 屬性值的種數(shù)
-
ArrayList<String> attrTypes = attrValue.get(value);
-
// 子屬性對(duì)應(yīng)的權(quán)重比
-
HashMap<String, Integer> ratioValues = new HashMap<>();
-
-
for (int i = 0; i < attrTypes.size(); i++) {
-
// 首先都統(tǒng)一計(jì)數(shù)為0
-
ratioValues.put(attrTypes.get(i), 0);
-
}
-
-
// 還是按照一列,從左往右遍歷
-
for (int j = 1; j < attrNames.length; j++) {
-
// 判斷是否到了劃分的屬性列
-
if (value.equals(attrNames[j])) {
-
for (int i = 1; i <= remainData.length - 1; i++) {
-
childValueNum = ratioValues.get(remainData[i][j]);
-
// 增加個(gè)數(shù)并且重新存入
-
childValueNum++;
-
ratioValues.put(remainData[i][j], childValueNum);
-
}
-
}
-
}
-
-
// 計(jì)算原熵的大小
-
entropyOri = computeEntropy(remainData, value, null, true);
-
for (int i = 0; i < attrTypes.size(); i++) {
-
double ratio = (double) ratioValues.get(attrTypes.get(i))
-
/ (remainData.length - 1);
-
childEntropySum += ratio
-
* computeEntropy(remainData, value, attrTypes.get(i), false);
-
-
// System.out.println("ratio:value: " + ratio + " " +
-
// computeEntropy(remainData, value,
-
// attrTypes.get(i), false));
-
}
-
-
// 二者熵相減就是信息增益
-
gainValue = entropyOri - childEntropySum;
-
return gainValue;
-
}
-
-
/**
-
* 計(jì)算信息增益比
-
*
-
* @param remainData
-
* 剩余數(shù)據(jù)
-
* @param value
-
* 待劃分屬性
-
* @return
-
*/
-
private double computeGainRatio(String[][] remainData, String value) {
-
double gain = 0;
-
double spiltInfo = 0;
-
int childValueNum = 0;
-
// 屬性值的種數(shù)
-
ArrayList<String> attrTypes = attrValue.get(value);
-
// 子屬性對(duì)應(yīng)的權(quán)重比
-
HashMap<String, Integer> ratioValues = new HashMap<>();
-
-
for (int i = 0; i < attrTypes.size(); i++) {
-
// 首先都統(tǒng)一計(jì)數(shù)為0
-
ratioValues.put(attrTypes.get(i), 0);
-
}
-
-
// 還是按照一列,從左往右遍歷
-
for (int j = 1; j < attrNames.length; j++) {
-
// 判斷是否到了劃分的屬性列
-
if (value.equals(attrNames[j])) {
-
for (int i = 1; i <= remainData.length - 1; i++) {
-
childValueNum = ratioValues.get(remainData[i][j]);
-
// 增加個(gè)數(shù)并且重新存入
-
childValueNum++;
-
ratioValues.put(remainData[i][j], childValueNum);
-
}
-
}
-
}
-
-
// 計(jì)算信息增益
-
gain = computeGain(remainData, value);
-
// 計(jì)算分裂信息,分裂信息度量被定義為(分裂信息用來(lái)衡量屬性分裂數(shù)據(jù)的廣度和均勻):
-
for (int i = 0; i < attrTypes.size(); i++) {
-
double ratio = (double) ratioValues.get(attrTypes.get(i))
-
/ (remainData.length - 1);
-
spiltInfo += -ratio * Math.log(ratio) / Math.log(2.0);
-
}
-
-
// 計(jì)算機(jī)信息增益率
-
return gain / spiltInfo;
-
}
-
-
/**
-
* 利用源數(shù)據(jù)構(gòu)造決策樹(shù)
-
*/
-
private void buildDecisionTree(AttrNode node, String parentAttrValue,
-
String[][] remainData, ArrayList<String> remainAttr, boolean isID3) {
-
node.setParentAttrValue(parentAttrValue);
-
-
String attrName = "";
-
double gainValue = 0;
-
double tempValue = 0;
-
-
// 如果只有1個(gè)屬性則直接返回
-
if (remainAttr.size() == 1) {
-
System.out.println("attr null");
-
return;
-
}
-
-
// 選擇剩余屬性中信息增益最大的作為下一個(gè)分類的屬性
-
for (int i = 0; i < remainAttr.size(); i++) {
-
// 判斷是否用ID3算法還是C4.5算法
-
if (isID3) {
-
// ID3算法采用的是按照信息增益的值來(lái)比
-
tempValue = computeGain(remainData, remainAttr.get(i));
-
} else {
-
// C4.5算法進(jìn)行了改進(jìn),用的是信息增益率來(lái)比,克服了用信息增益選擇屬性時(shí)偏向選擇取值多的屬性的不足
-
tempValue = computeGainRatio(remainData, remainAttr.get(i));
-
}
-
-
if (tempValue > gainValue) {
-
gainValue = tempValue;
-
attrName = remainAttr.get(i);
-
}
-
}
-
-
node.setAttrName(attrName);
-
ArrayList<String> valueTypes = attrValue.get(attrName);
-
remainAttr.remove(attrName);
-
-
AttrNode[] childNode = new AttrNode[valueTypes.size()];
-
String[][] rData;
-
for (int i = 0; i < valueTypes.size(); i++) {
-
// 移除非此值類型的數(shù)據(jù)
-
rData = removeData(remainData, attrName, valueTypes.get(i));
-
-
childNode[i] = new AttrNode();
-
boolean sameClass = true;
-
ArrayList<String> indexArray = new ArrayList<>();
-
for (int k = 1; k < rData.length; k++) {
-
indexArray.add(rData[k][0]);
-
// 判斷是否為同一類的
-
if (!rData[k][attrNames.length - 1]
-
.equals(rData[1][attrNames.length - 1])) {
-
// 只要有1個(gè)不相等,就不是同類型的
-
sameClass = false;
-
break;
-
}
-
}
-
-
if (!sameClass) {
-
// 創(chuàng)建新的對(duì)象屬性,對(duì)象的同個(gè)引用會(huì)出錯(cuò)
-
ArrayList<String> rAttr = new ArrayList<>();
-
for (String str : remainAttr) {
-
rAttr.add(str);
-
}
-
-
buildDecisionTree(childNode[i], valueTypes.get(i), rData,
-
rAttr, isID3);
-
} else {
-
// 如果是同種類型,則直接為數(shù)據(jù)節(jié)點(diǎn)
-
childNode[i].setParentAttrValue(valueTypes.get(i));
-
childNode[i].setChildDataIndex(indexArray);
-
}
-
-
}
-
node.setChildAttrNode(childNode);
-
}
-
-
/**
-
* 屬性劃分完畢,進(jìn)行數(shù)據(jù)的移除
-
*
-
* @param srcData
-
* 源數(shù)據(jù)
-
* @param attrName
-
* 劃分的屬性名稱
-
* @param valueType
-
* 屬性的值類型
-
*/
-
private String[][] removeData(String[][] srcData, String attrName,
-
String valueType) {
-
String[][] desDataArray;
-
ArrayList<String[]> desData = new ArrayList<>();
-
// 待刪除數(shù)據(jù)
-
ArrayList<String[]> selectData = new ArrayList<>();
-
selectData.add(attrNames);
-
-
// 數(shù)組數(shù)據(jù)轉(zhuǎn)化到列表中,方便移除
-
for (int i = 0; i < srcData.length; i++) {
-
desData.add(srcData[i]);
-
}
-
-
// 還是從左往右一列列的查找
-
for (int j = 1; j < attrNames.length; j++) {
-
if (attrNames[j].equals(attrName)) {
-
for (int i = 1; i < desData.size(); i++) {
-
if (desData.get(i)[j].equals(valueType)) {
-
// 如果匹配這個(gè)數(shù)據(jù),則移除其他的數(shù)據(jù)
-
selectData.add(desData.get(i));
-
}
-
}
-
}
-
}
-
CDA數(shù)據(jù)分析師考試相關(guān)入口一覽(建議收藏):
? 想報(bào)名CDA認(rèn)證考試,點(diǎn)擊>>>
“CDA報(bào)名”
了解CDA考試詳情;
? 想學(xué)習(xí)CDA考試教材,點(diǎn)擊>>> “CDA教材” 了解CDA考試詳情;
? 想加入CDA考試題庫(kù),點(diǎn)擊>>> “CDA題庫(kù)” 了解CDA考試詳情;
? 想了解CDA考試含金量,點(diǎn)擊>>> “CDA含金量” 了解CDA考試詳情;