機器學習實戰---決策樹CART簡介及分類樹實現

https://blog.csdn.net/weixin_43383558/article/details/84303339?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecasehtml

http://www.javashuo.com/article/p-faoywzla-et.html算法

一:CART算法簡介

(一)CART、ID三、C4.5比較

CART算法的全稱是Classification And Regression Tree,採用的是Gini指數(選Gini指數最小的特徵s)做爲分裂標準,同時它也是包含後剪枝操做。ide

ID3算法和C4.5算法雖然在對訓練樣本集的學習中能夠儘量多地挖掘信息,但其生成的決策樹分支較大,規模較大。函數

爲了簡化決策樹的規模,提升生成決策樹的效率,就出現了根據GINI係數來選擇測試屬性的決策樹算法CART。學習

(二)CART分類與迴歸

CART分類與迴歸樹本質上是同樣的,構建過程都是逐步分割特徵空間,預測過程都是從根節點開始一層一層的判斷直到葉節點給出預測結果。測試

只不過度類樹給出離散值,而回歸樹給出連續值(一般是葉節點包含樣本的均值),atom

另外分類樹基於Gini指數選取分割點,而回歸樹基於平方偏差選取分割點。spa

(三)基尼指數

1.ID三、C4.5

在ID3算法中咱們使用了信息增益來選擇特徵,信息增益大的優先選擇。在C4.5算法中,採用了信息增益比來選擇特徵,以減小信息增益容易選擇特徵值多的特徵的問題。可是不管是ID3仍是C4.5,都是基於信息論的熵模型的,這裏面會涉及大量的對數運算。.net

2.能不能簡化模型同時也不至於徹底丟失熵模型的優勢呢?

CART分類樹算法使用基尼係數來代替信息增益比,基尼係數表明了模型的不純度,基尼係數越小,則不純度越低,特徵越好。這和信息增益(比)是相反的。3d

從上圖能夠看出,基尼係數和熵之半的曲線很是接近,僅僅在45度角附近偏差稍大。所以,基尼係數能夠作爲熵模型的一個近似替代。而CART分類樹算法就是使用的基尼係數來選擇決策樹的特徵。
同時,爲了進一步簡化,CART分類樹算法每次僅僅對某個特徵的值進行二分,而不是多分,這樣CART分類樹算法創建起來的是二叉樹,而不是多叉樹。這樣一能夠進一步簡化基尼係數的計算,二能夠創建一個更加優雅的二叉樹模型。

3.基尼指數定義

假設有數據集 [公式] ,且 [公式] 有 [公式] 個分類,那麼可定義基尼指數爲:

 

從公式能夠看到,基尼指數的意義是:從數據集D中隨機抽取兩個樣本,其類別不一樣的機率。直覺地,基尼指數越小,則數據集D的純度越高。

若是訓練數據集D根據特徵A是否取某一可能值a被分割爲D1D2兩部分,則在特徵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))
View Code

除了計算基尼指數,其餘大多同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
View Code
相關文章
相關標籤/搜索