CART分類回歸樹(shù)算法
CART分類回歸樹(shù)算法
與上次文章中提到的ID3算法和C4.5算法類似,CART算法也是一種決策樹(shù)分類算法。CART分類回歸樹(shù)算法的本質(zhì)也是對(duì)數(shù)據(jù)進(jìn)行分類的,最終數(shù)據(jù)的表現(xiàn)形式也是以樹(shù)形的模式展現(xiàn)的,與ID3,C4.5算法不同的是,他的分類標(biāo)準(zhǔn)所采用的算法不同了。下面列出了其中的一些不同之處:
1、CART最后形成的樹(shù)是一個(gè)二叉樹(shù),每個(gè)節(jié)點(diǎn)會(huì)分成2個(gè)節(jié)點(diǎn),左孩子節(jié)點(diǎn)和右孩子節(jié)點(diǎn),而在ID3和C4.5中是按照分類屬性的值類型進(jìn)行劃分,于是這就要求CART算法在所選定的屬性中又要?jiǎng)澐殖鲎罴训膶傩詣澐种?,?jié)點(diǎn)如果選定了劃分屬性名稱還要確定里面按照那個(gè)值做一個(gè)二元的劃分。
2、CART算法對(duì)于屬性的值采用的是基于Gini系數(shù)值的方式做比較,gini某個(gè)屬性的某次值的劃分的gini指數(shù)的值為:
,pk就是分別為正負(fù)實(shí)例的概率,gini系數(shù)越小說(shuō)明分類純度越高,可以想象成與熵的定義一樣。因此在最后計(jì)算的時(shí)候我們只取其中值最小的做出劃分。最后做比較的時(shí)候用的是gini的增益做比較,要對(duì)分類號(hào)的數(shù)據(jù)做出一個(gè)帶權(quán)重的gini指數(shù)的計(jì)算。舉一個(gè)網(wǎng)上的一個(gè)例子:
比如體溫為恒溫時(shí)包含哺乳類5個(gè)、鳥(niǎo)類2個(gè),則:
體溫為非恒溫時(shí)包含爬行類3個(gè)、魚(yú)類3個(gè)、兩棲類2個(gè),則
所以如果按照“體溫為恒溫和非恒溫”進(jìn)行劃分的話,我們得到GINI的增益(類比信息增益):
最好的劃分就是使得GINI_Gain最小的劃分。
通過(guò)比較每個(gè)屬性的最小的gini指數(shù)值,作為最后的結(jié)果。
3、CART算法在把數(shù)據(jù)進(jìn)行分類之后,會(huì)對(duì)樹(shù)進(jìn)行一個(gè)剪枝,常用的用前剪枝和后剪枝法,而常見(jiàn)的后剪枝發(fā)包括代價(jià)復(fù)雜度剪枝,悲觀誤差剪枝等等,我寫(xiě)的此次算法采用的是代價(jià)復(fù)雜度剪枝法。代價(jià)復(fù)雜度剪枝的算法公式為:
α表示的是每個(gè)非葉子節(jié)點(diǎn)的誤差增益率,可以理解為誤差代價(jià),最后選出誤差代價(jià)最小的一個(gè)節(jié)點(diǎn)進(jìn)行剪枝。
里面變量的意思為:
是子樹(shù)中包含的葉子節(jié)點(diǎn)個(gè)數(shù);
是節(jié)點(diǎn)t的誤差代價(jià),如果該節(jié)點(diǎn)被剪枝;
r(t)是節(jié)點(diǎn)t的誤差率;
p(t)是節(jié)點(diǎn)t上的數(shù)據(jù)占所有數(shù)據(jù)的比例。
是子樹(shù)Tt的誤差代價(jià),如果該節(jié)點(diǎn)不被剪枝。它等于子樹(shù)Tt上所有葉子節(jié)點(diǎn)的誤差代價(jià)之和。下面說(shuō)說(shuō)我對(duì)于這個(gè)公式的理解:其實(shí)這個(gè)公式的本質(zhì)是對(duì)于剪枝前和剪枝后的樣本偏差率做一個(gè)差值比較,一個(gè)好的分類當(dāng)然是分類后的樣本偏差率相較于沒(méi)分類(就是剪枝掉的時(shí)候)的偏差率小,所以這時(shí)的值就會(huì)大,如果分類前后基本變化不大,則意味著分類不起什么效果,α值的分子位置就小,所以誤差代價(jià)就小,可以被剪枝。但是一般分類后的偏差率會(huì)小于分類前的,因?yàn)?a href='/map/piancha/' style='color:#000;font-size:inherit;'>偏差數(shù)在高層節(jié)點(diǎn)的時(shí)候肯定比子節(jié)點(diǎn)的多,子節(jié)點(diǎn)偏差數(shù)最多與父親節(jié)點(diǎn)一樣。
CART算法實(shí)現(xiàn)
首先是程序的備用數(shù)據(jù),我是把他存在了一個(gè)文字中,通過(guò)程序進(jìn)行逐行的讀取:
[java] view plaincopyprint?
-
Rid Age Income Student CreditRating BuysComputer
-
1 Youth High No Fair No
-
2 Youth High No Excellent No
-
3 MiddleAged High No Fair Yes
-
4 Senior Medium No Fair Yes
-
5 Senior Low Yes Fair Yes
-
6 Senior Low Yes Excellent No
-
7 MiddleAged Low Yes Excellent Yes
-
8 Youth Medium No Fair No
-
9 Youth Low Yes Fair Yes
-
10 Senior Medium Yes Fair Yes
-
11 Youth Medium Yes Excellent Yes
-
12 MiddleAged Medium No Excellent Yes
-
13 MiddleAged High Yes Fair Yes
-
14 Senior Medium No Excellent No
下面是主程序,里面有具體的注釋:
[java] view plaincopyprint?
-
package DataMing_CART;
-
-
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.LinkedList;
-
import java.util.Map;
-
import java.util.Queue;
-
-
import javax.lang.model.element.NestingKind;
-
import javax.swing.text.DefaultEditorKit.CutAction;
-
import javax.swing.text.html.MinimalHTMLWriter;
-
-
/**
-
* CART分類回歸樹(shù)算法工具類
-
*
-
* @author lyq
-
*
-
*/
-
public class CARTTool {
-
// 類標(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 CARTTool(String filePath) {
-
this.filePath = filePath;
-
attrValue = new HashMap<>();
-
}
-
-
/**
-
* 從文件中讀取數(shù)據(jù)
-
*/
-
public 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í)用
-
*/
-
public 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ì)算機(jī)基尼指數(shù)
-
*
-
* @param remainData
-
* 剩余數(shù)據(jù)
-
* @param attrName
-
* 屬性名稱
-
* @param value
-
* 屬性值
-
* @param beLongValue
-
* 分類是否屬于此屬性值
-
* @return
-
*/
-
public double computeGini(String[][] remainData, String attrName,
-
String value, boolean beLongValue) {
-
// 實(shí)例總數(shù)
-
int total = 0;
-
// 正實(shí)例數(shù)
-
int posNum = 0;
-
// 負(fù)實(shí)例數(shù)
-
int negNum = 0;
-
// 基尼指數(shù)
-
double gini = 0;
-
-
// 還是按列從左往右遍歷屬性
-
for (int j = 1; j < attrNames.length; j++) {
-
// 找到了指定的屬性
-
if (attrName.equals(attrNames[j])) {
-
for (int i = 1; i < remainData.length; i++) {
-
// 統(tǒng)計(jì)正負(fù)實(shí)例按照屬于和不屬于值類型進(jìn)行劃分
-
if ((beLongValue && remainData[i][j].equals(value))
-
|| (!beLongValue && !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;
-
gini = 1 - posProbobly * posProbobly - negProbobly * negProbobly;
-
-
// 返回計(jì)算基尼指數(shù)
-
return gini;
-
}
-
-
/**
-
* 計(jì)算屬性劃分的最小基尼指數(shù),返回最小的屬性值劃分和最小的基尼指數(shù),保存在一個(gè)數(shù)組中
-
*
-
* @param remainData
-
* 剩余誰(shuí)
-
* @param attrName
-
* 屬性名稱
-
* @return
-
*/
-
public String[] computeAttrGini(String[][] remainData, String attrName) {
-
String[] str = new String[2];
-
// 最終該屬性的劃分類型值
-
String spiltValue = "";
-
// 臨時(shí)變量
-
int tempNum = 0;
-
// 保存屬性的值劃分時(shí)的最小的基尼指數(shù)
-
double minGini = Integer.MAX_VALUE;
-
ArrayList<String> valueTypes = attrValue.get(attrName);
-
// 屬于此屬性值的實(shí)例數(shù)
-
HashMap<String, Integer> belongNum = new HashMap<>();
-
-
for (String string : valueTypes) {
-
// 重新計(jì)數(shù)的時(shí)候,數(shù)字歸0
-
tempNum = 0;
-
// 按列從左往右遍歷屬性
-
for (int j = 1; j < attrNames.length; j++) {
-
// 找到了指定的屬性
-
if (attrName.equals(attrNames[j])) {
-
for (int i = 1; i < remainData.length; i++) {
-
// 統(tǒng)計(jì)正負(fù)實(shí)例按照屬于和不屬于值類型進(jìn)行劃分
-
if (remainData[i][j].equals(string)) {
-
tempNum++;
-
}
-
}
-
}
-
}
-
-
belongNum.put(string, tempNum);
-
}
-
-
double tempGini = 0;
-
double posProbably = 1.0;
-
double negProbably = 1.0;
-
for (String string : valueTypes) {
-
tempGini = 0;
-
-
posProbably = 1.0 * belongNum.get(string) / (remainData.length - 1);
-
negProbably = 1 - posProbably;
-
-
tempGini += posProbably
-
* computeGini(remainData, attrName, string, true);
-
tempGini += negProbably
-
* computeGini(remainData, attrName, string, false);
-
-
if (tempGini < minGini) {
-
minGini = tempGini;
-
spiltValue = string;
-
}
-
}
-
-
str[0] = spiltValue;
-
str[1] = minGini + "";
-
-
return str;
-
}
-
-
public void buildDecisionTree(AttrNode node, String parentAttrValue,
-
String[][] remainData, ArrayList<String> remainAttr,
-
boolean beLongParentValue) {
-
// 屬性劃分值
-
String valueType = "";
-
// 劃分屬性名稱
-
String spiltAttrName = "";
-
double minGini = Integer.MAX_VALUE;
-
double tempGini = 0;
-
// 基尼指數(shù)數(shù)組,保存了基尼指數(shù)和此基尼指數(shù)的劃分屬性值
-
String[] giniArray;
-
-
if (beLongParentValue) {
-
node.setParentAttrValue(parentAttrValue);
-
} else {
-
node.setParentAttrValue("!" + parentAttrValue);
-
}
-
-
if (remainAttr.size() == 0) {
-
if (remainData.length > 1) {
-
ArrayList<String> indexArray = new ArrayList<>();
-
for (int i = 1; i < remainData.length; i++) {
-
indexArray.add(remainData[i][0]);
-
}
-
node.setDataIndex(indexArray);
-
}
-
System.out.println("attr remain null");
-
return;
-
}
-
-
for (String str : remainAttr) {
-
giniArray = computeAttrGini(remainData, str);
-
tempGini = Double.parseDouble(giniArray[1]);
-
-
if (tempGini < minGini) {
-
spiltAttrName = str;
-
minGini = tempGini;
-
valueType = giniArray[0];
-
}
-
}
-
// 移除劃分屬性
-
remainAttr.remove(spiltAttrName);
-
node.setAttrName(spiltAttrName);
-
-
// 孩子節(jié)點(diǎn),分類回歸樹(shù)中,每次二元?jiǎng)澐郑殖?個(gè)孩子節(jié)點(diǎn)
-
AttrNode[] childNode = new AttrNode[2];
-
String[][] rData;
-
-
boolean[] bArray = new boolean[] { true, false };
-
for (int i = 0; i < bArray.length; i++) {
-
// 二元?jiǎng)澐謱儆趯傩灾档膭澐?nbsp;
-
rData = removeData(remainData, spiltAttrName, valueType, bArray[i]);
-
-
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;
-
}
-
}
-
-
childNode[i] = new AttrNode();
-
if (!sameClass) {
-
// 創(chuàng)建新的對(duì)象屬性,對(duì)象的同個(gè)引用會(huì)出錯(cuò)
-
ArrayList<String> rAttr = new ArrayList<>();
-
for (String str : remainAttr) {
-
rAttr.add(str);
-
}
-
buildDecisionTree(childNode[i], valueType, rData, rAttr,
-
bArray[i]);
-
} else {
-
String pAtr = (bArray[i] ? valueType : "!" + valueType);
-
childNode[i].setParentAttrValue(pAtr);
-
childNode[i].setDataIndex(indexArray);
-
}
-
}
-
-
node.setChildAttrNode(childNode);
-
}
-
-
/**
-
* 屬性劃分完畢,進(jìn)行數(shù)據(jù)的移除
-
*
-
* @param srcData
-
* 源數(shù)據(jù)
-
* @param attrName
-
* 劃分的屬性名稱
-
* @param valueType
-
* 屬性的值類型
-
* @parame beLongValue 分類是否屬于此值類型
-
*/
-
private String[][] removeData(String[][] srcData, String attrName,
-
String valueType, boolean beLongValue) {
-
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));
-
}
-
}
-
}
-
}
-
-
if (beLongValue) {
-
desDataArray = new String[selectData.size()][];
-
selectData.toArray(desDataArray);
-
} else {
-
// 屬性名稱行不移除
-
selectData.remove(attrNames);
-
// 如果是劃分不屬于此類型的數(shù)據(jù)時(shí),進(jìn)行移除
-
desData.removeAll(selectData);
-
desDataArray = new String[desData.size()][];
-
&
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考試詳情;