https://www.cnblogs.com/pinard/p/6053344.html
一:CART算法簡介
(一)CART、ID3、C4.5比較
CART算法的全稱是Classification And Regression Tree,采用的是Gini指數(選Gini指數最小的特征s)作為分裂標准,同時它也是包含后剪枝操作。
ID3算法和C4.5算法雖然在對訓練樣本集的學習中可以盡可能多地挖掘信息,但其生成的決策樹分支較大,規模較大。
為了簡化決策樹的規模,提高生成決策樹的效率,就出現了根據GINI系數來選擇測試屬性的決策樹算法CART。
(二)CART分類與回歸
CART分類與回歸樹本質上是一樣的,構建過程都是逐步分割特征空間,預測過程都是從根節點開始一層一層的判斷直到葉節點給出預測結果。
只不過分類樹給出離散值,而回歸樹給出連續值(通常是葉節點包含樣本的均值),
另外分類樹基於Gini指數選取分割點,而回歸樹基於平方誤差選取分割點。
(三)基尼指數
1.ID3、C4.5
在ID3算法中我們使用了信息增益來選擇特征,信息增益大的優先選擇。在C4.5算法中,采用了信息增益比來選擇特征,以減少信息增益容易選擇特征值多的特征的問題。但是無論是ID3還是C4.5,都是基於信息論的熵模型的,這里面會涉及大量的對數運算。
2.能不能簡化模型同時也不至於完全丟失熵模型的優點呢?
CART分類樹算法使用基尼系數來代替信息增益比,基尼系數代表了模型的不純度,基尼系數越小,則不純度越低,特征越好。這和信息增益(比)是相反的。
3.基尼指數定義
假設有數據集 ,且
有
個分類,那么可定義基尼指數為:
從公式可以看到,基尼指數的意義是:從數據集D中隨機抽取兩個樣本,其類別不同的概率。直覺地,基尼指數越小,則數據集D的純度越高。
如果訓練數據集D根據特征A是否取某一可能值a被分割為D1和D2兩部分,則在特征A的條件下,集合D的基尼指數定義為:
基尼指數Gini(D)表示集合D的不確定性,基尼指數Gini(D,A)表示經過A=a分割后集合D的不確定性。基尼指數越大,樣本的不確定性也就越大。
相對於用信息增益/信息增益率來作為決策指標(含log操作),基尼指數的運算量比較小,也很易於理解,這是cart算法使用基尼指數的主要目的。
二:CART分類樹算法對於連續特征和離散特征處理的改進
(一)連續特征
對於CART分類樹連續值的處理問題,其思想和C4.5是相同的,都是將連續的特征離散化。
唯一的區別在於在選擇划分點時的度量方式不同,C4.5使用的是信息增益比,則CART分類樹使用的是基尼系數。
具體的思路如下,比如m個樣本的連續特征A有m個,從小到大排列為a1,a2,...,am,則CART算法取相鄰兩樣本值的平均數,一共取得m-1個划分點。
其中第i個划分點Ti表示為:Ti=(ai+ai+1)/2。
對於這m-1個點,分別計算以該點作為二元分類點時的基尼系數。
選擇基尼系數最小的點作為該連續特征的二元離散分類點。
比如取到的基尼系數最小的點為at,則小於at的值為類別1,大於at的值為類別2,這樣我們就做到了連續特征的離散化。
要注意的是,與ID3或者C4.5處理離散屬性不同的是,如果當前節點為連續屬性,則該屬性后面還可以參與子節點的產生選擇過程。
(二)離散值
對於CART分類樹離散值的處理問題,采用的思路是不停的二分離散特征。
回憶下ID3或者C4.5,如果某個特征A被選取建立決策樹節點,如果它有A1,A2,A3三種類別,我們會在決策樹上一下建立一個三叉的節點。這樣導致決策樹是多叉樹。但是CART分類樹使用的方法不同,他采用的是不停的二分。
還是這個例子,CART分類樹會考慮把A分成{A1}和{A2,A3}, {A2}和{A1,A3}, {A3}和{A1,A2}三種情況,找到基尼系數最小的組合,比如{A2}和{A1,A3},然后建立二叉樹節點,一個節點是A2對應的樣本,另一個節點是{A1,A3}對應的節點。
同時,由於這次沒有把特征A的取值完全分開,后面我們還有機會在子節點繼續選擇到特征A來划分A1和A3。這和ID3或者C4.5不同,在ID3或者C4.5的一棵子樹中,離散特征只會參與一次節點的建立。
三:分類算法邏輯
四:決策樹分類代碼實現
https://www.cnblogs.com/ssyfj/p/13229743.html
(一)實現求解基尼指數
import numpy as np def calcGini(data_y): #根據基尼指數的定義,根據當前數據集中不同標簽類出現次數,獲取當前數據集D的基尼指數 m = data_y.size #獲取全部數據數量 labels = np.unique(data_y) #獲取所有標簽值類別(去重后) gini = 1.0 #初始基尼系數 for i in labels: #遍歷每一個標簽值種類 y_cnt = data_y[np.where(data_y==i)].size / m #出現概率 gini -= y_cnt**2 #基尼指數 return gini
測試:
print(calcGini(np.array([1,1,2,3,2,2,1,1,3])))
(二)實現數據集切分
def splitDataSet(data_X,data_Y,fea_axis,fea_val): #根據特征、和該特征下的特征值種類,實現切分數據集和標簽 #根據偽算法可以知道,我們要將數據集划分為2部分:特征值=a和特征值不等於a eqIdx = np.where(data_X[:,fea_axis]==fea_val) neqIdx = np.where(data_X[:,fea_axis]!=fea_val) return data_X[eqIdx],data_Y[eqIdx],data_X[neqIdx],data_Y[neqIdx]
(三)實現選取最優特征和特征值划分
def chooseBestFeature(data_X,data_Y): #遍歷所有特征和特征值,選取最優划分 m,n = data_X.shape bestFeature = -1 bestFeaVal = -1 minFeaGini = np.inf for i in range(n): #遍歷所有特征 fea_cls = np.unique(data_X[:,i]) #獲取該特征下的所有特征值 # print("{}---".format(fea_cls)) for j in fea_cls: #遍歷所有特征值 newEqDataX,newEqDataY,newNeqDataX,newNeqDataY=splitDataSet(data_X,data_Y,i,j) #進行數據集切分 feaGini = 0 #計算基尼指數 feaGini += newEqDataY.size/m*calcGini(newEqDataY) + newNeqDataY.size/m*calcGini(newNeqDataY) if feaGini < minFeaGini: bestFeature = i bestFeaVal = j minFeaGini = feaGini return bestFeature,bestFeaVal #返回最優划分方式
(四)創建CART決策樹
def createTree(data_X,data_Y,fea_idx): #創建決策樹 y_labels = np.unique(data_Y) #1.如果數據集中,所有實例都屬於同一類,則返回 if y_labels.size == 1: return data_Y[0] #2.如果特征集為空,表示遍歷了所有特征,使用多數投票進行決定 if data_X.shape[1] == 0: bestFea,bestCnt = 0,0 for i in y_labels: cnt = data_Y[np.where(data_Y==i)].size if cnt > bestCnt: bestFea = i bestCnt = cnt return bestFea #按照基尼指數,選擇特征,進行繼續遞歸創建樹 bestFeature, bestFeaVal = chooseBestFeature(data_X,data_Y) # print(bestFeature,bestFeaVal) feaBestIdx = fea_idx[bestFeature] my_tree = {feaBestIdx:{}} #獲取划分結果 newEqDataX,newEqDataY,newNeqDataX,newNeqDataY = splitDataSet(data_X,data_Y,bestFeature,bestFeaVal) #刪除我們選擇的最優特征 newEqDataX = np.delete(newEqDataX,bestFeature,1) newNeqDataX = np.delete(newNeqDataX,bestFeature,1) fea_idx = np.delete(fea_idx,bestFeature,0) my_tree[feaBestIdx]["{}_{}".format(1,bestFeaVal)] = createTree(newEqDataX,newEqDataY,fea_idx) my_tree[feaBestIdx]["{}_{}".format(0,bestFeaVal)] = createTree(newNeqDataX,newNeqDataY,fea_idx) return my_tree
(五)測試函數
def preDealData(filename): df = pd.read_table(filename,'\t',header = None) columns = ["age","prescript","astigmatic","tearRate"] # df.columns = ["age","prescript","astigmatic","tearRate","Result"] #https://zhuanlan.zhihu.com/p/60248460 #數據預處理,變為可以處理的數據 #https://blog.csdn.net/liuweiyuxiang/article/details/78222818 new_df = pd.DataFrame() for i in range(len(columns)): new_df[i] = pd.factorize(df[i])[0] ##factorize函數可以將Series中的標稱型數據映射稱為一組數字,相同的標稱型映射為相同的數字。 data_X = new_df.values data_Y = pd.factorize(df[df.shape[1]-1])[0] #factorize返回的是ndarray類型 data_Y = np.array([data_Y]).T return data_X,data_Y,columns data_X,data_Y,fea_names = preDealData("lenses.txt")
fea_Idx = np.arange(len(fea_names)) print(createTree(data_X,data_Y,fea_Idx))
step 1:特征0-特征3
划分后:可以看到選取特征3中特征值為0或者1時,基尼指數值最小,當特征3中特征值為0時,全部是同一類別,所以不再進行下一步處理。后面只處理特征值為1的數據集
step 2:處理了特征3,還有特征0-特征2
划分后:可以看到選取特征2中的特征值為0或者1時,出現基尼指數值最小。以此為划分依據,分兩種情況討論
step 3_1:處理特征2中值為0的數據集
可以看到選取特征0中特征值為2作為最優划分依據,因為基尼指數最小。
可以看到選取特征值為2時,需要進一步划分。當特征值為非2時(0_2),結果為1,不需要進行下一步處理
step 3_1_1:處理特征0中值為2的數據集
可以看到,對於特征1中的特征值0或者1都是純凈數據,不需要進一步划分。
以上推導滿足下面決策:
注意:其中x_y表示是否選取特征值y。比如圖中1_2,表示選取特征0下,特征值選取2的數據集划分。1_y表示選取這個特征值,0_y表示選取非該特征值下的其他數據集
step 3_2:處理特征2中值為1的數據集
計算基尼指數:
根據最小基尼指數,我們選取特征1中,特征值為0進行划分數據集,由於特征1中特征值為0時數據為純凈數據,不需要進一步划分。對於特征值為1,我們需要進行下一步划分
step 3_2_1:划分特征1中特征值為1 的數據集
根據最小基尼指數值,我們選取特征0中,特征值為0進行數據集划分。此時特征值為0或者非0情況下,都是純凈數據,不需要下一步划分。於是達到下面情況:
(六)全部代碼

import numpy as np import pandas as pd # 創建數據集 def createDataSet(): dataSet = [[1, 1], [1, 1], [1, 0], [0, 1], [0, 1]] labels = [1, 1, 0, 0, 0] features_names = ['水下', '腳蹼'] # 特征名稱 return dataSet, labels, features_names def calcGini(data_y): #根據基尼指數的定義,根據當前數據集中不同標簽類出現次數,獲取當前數據集D的基尼指數 m = data_y.size #獲取全部數據數量 labels = np.unique(data_y) #獲取所有標簽值類別(去重后) gini = 1.0 #初始基尼系數 for i in labels: #遍歷每一個標簽值種類 y_cnt = data_y[np.where(data_y==i)].size / m #出現概率 gini -= y_cnt**2 #基尼指數 return gini def splitDataSet(data_X,data_Y,fea_axis,fea_val): #根據特征、和該特征下的特征值種類,實現切分數據集和標簽 #根據偽算法可以知道,我們要將數據集划分為2部分:特征值=a和特征值不等於a eqIdx = np.where(data_X[:,fea_axis]==fea_val) neqIdx = np.where(data_X[:,fea_axis]!=fea_val) return data_X[eqIdx],data_Y[eqIdx],data_X[neqIdx],data_Y[neqIdx] def chooseBestFeature(data_X,data_Y): #遍歷所有特征和特征值,選取最優划分 m,n = data_X.shape bestFeature = -1 bestFeaVal = -1 minFeaGini = np.inf for i in range(n): #遍歷所有特征 fea_cls = np.unique(data_X[:,i]) #獲取該特征下的所有特征值 # print("{}---".format(fea_cls)) for j in fea_cls: #遍歷所有特征值 newEqDataX,newEqDataY,newNeqDataX,newNeqDataY=splitDataSet(data_X,data_Y,i,j) #進行數據集切分 feaGini = 0 #計算基尼指數 feaGini += newEqDataY.size/m*calcGini(newEqDataY) + newNeqDataY.size/m*calcGini(newNeqDataY) if feaGini < minFeaGini: bestFeature = i bestFeaVal = j minFeaGini = feaGini return bestFeature,bestFeaVal #返回最優划分方式 def createTree(data_X,data_Y,fea_idx): #創建決策樹 y_labels = np.unique(data_Y) #1.如果數據集中,所有實例都屬於同一類,則返回 if y_labels.size == 1: return data_Y[0] #2.如果特征集為空,表示遍歷了所有特征,使用多數投票進行決定 if data_X.shape[1] == 0: bestFea,bestCnt = 0,0 for i in y_labels: cnt = data_Y[np.where(data_Y==i)].size if cnt > bestCnt: bestFea = i bestCnt = cnt return bestFea #按照基尼指數,選擇特征,進行繼續遞歸創建樹 bestFeature, bestFeaVal = chooseBestFeature(data_X,data_Y) # print(bestFeature,bestFeaVal) feaBestIdx = fea_idx[bestFeature] my_tree = {feaBestIdx:{}} #獲取划分結果 newEqDataX,newEqDataY,newNeqDataX,newNeqDataY = splitDataSet(data_X,data_Y,bestFeature,bestFeaVal) #刪除我們選擇的最優特征 newEqDataX = np.delete(newEqDataX,bestFeature,1) newNeqDataX = np.delete(newNeqDataX,bestFeature,1) fea_idx = np.delete(fea_idx,bestFeature,0) my_tree[feaBestIdx]["{}_{}".format(1,bestFeaVal)] = createTree(newEqDataX,newEqDataY,fea_idx) my_tree[feaBestIdx]["{}_{}".format(0,bestFeaVal)] = createTree(newNeqDataX,newNeqDataY,fea_idx) return my_tree def preDealData(filename): df = pd.read_table(filename,'\t',header = None) columns = ["age","prescript","astigmatic","tearRate"] # df.columns = ["age","prescript","astigmatic","tearRate","Result"] #https://zhuanlan.zhihu.com/p/60248460 #數據預處理,變為可以處理的數據 #https://blog.csdn.net/liuweiyuxiang/article/details/78222818 new_df = pd.DataFrame() for i in range(len(columns)): new_df[i] = pd.factorize(df[i])[0] ##factorize函數可以將Series中的標稱型數據映射稱為一組數字,相同的標稱型映射為相同的數字。 data_X = new_df.values data_Y = pd.factorize(df[df.shape[1]-1])[0] #factorize返回的是ndarray類型 data_Y = np.array([data_Y]).T return data_X,data_Y,columns data_X,data_Y,fea_names = preDealData("lenses.txt") print(data_X) print(data_Y) # data_x,data_y,fea_names = createDataSet() fea_Idx = np.arange(len(fea_names)) # data_X,data_Y,fea_names = createDataSet() # data_X = np.array(data_X) # data_Y = np.array(data_Y) # # fea_Idx = np.arange(len(fea_names)) print(createTree(data_X,data_Y,fea_Idx))
除了計算基尼指數,其他大多同ID3算法一致。
(七)測試數據集

young myope no reduced no lenses
young myope no normal soft
young myope yes reduced no lenses
young myope yes normal hard
young hyper no reduced no lenses
young hyper no normal soft
young hyper yes reduced no lenses
young hyper yes normal hard
pre myope no reduced no lenses
pre myope no normal soft
pre myope yes reduced no lenses
pre myope yes normal hard
pre hyper no reduced no lenses
pre hyper no normal soft
pre hyper yes reduced no lenses
pre hyper yes normal no lenses
presbyopic myope no reduced no lenses
presbyopic myope no normal no lenses
presbyopic myope yes reduced no lenses
presbyopic myope yes normal hard
presbyopic hyper no reduced no lenses
presbyopic hyper no normal soft
presbyopic hyper yes reduced no lenses
presbyopic hyper yes normal no lenses