1. 程式人生 > 其它 >三種決策樹演算法(ID3, CART, C4.5)及Python實現

三種決策樹演算法(ID3, CART, C4.5)及Python實現

1. 決策樹(Decision Tree)簡介

1.1. 決策樹的原理

決策樹是屬於機器學習監督學習分類演算法中比較簡單的一種,決策樹是一個預測模型;他代表的是物件屬性與物件值之間的一種對映關係。樹中每個節點表示某個物件,而每個分叉路徑則代表的某個可能的屬性值,而每個葉結點則對應從根節點到該葉節點所經歷的路徑所表示的物件的值。決策樹僅有單一輸出,若欲有複數輸出,可以建立獨立的決策樹以處理不同輸出。 是通過一系列規則對資料進行分類的過程。

示例: 一個週末是否出去玩的例子

決策樹即是將資料集轉換成樹形的結構,如下:

1.2. 決策樹的構造過程

一般包含三個部分 ​ 1、特徵選擇:特徵選擇是指從訓練資料中眾多的特徵中選擇一個特徵作為當前節點的分裂標準,如何選擇特徵有著很多不同量化評估標準標準,從而衍生出不同的決策樹演算法,如CART, ID3, C4.5等。 ​ 2、決策樹生成: 根據選擇的特徵評估標準,從上至下遞迴地生成子節點,直到資料集不可分則停止決策樹停止生長。 樹結構來說,遞迴結構是最容易理解的方式。 ​ 3、剪枝:決策樹容易過擬合,一般來需要剪枝,縮小樹結構規模、緩解過擬合。剪枝技術有預剪枝後剪枝兩種。

虛擬碼

if 遇到終止條件:
    return 類標籤
else:
    尋找一個最優特徵對資料集進行分類
    建立分支點
    對每個分支節點進行劃分,將分支點返回到主分支
    return 分支節點

1.3. 決策樹的優缺點

決策樹適用於數值型和標稱型(離散型資料,變數的結果只在有限目標集中取值),能夠讀取資料集合,提取一些列資料中蘊含的規則。在分類問題中使用決策樹模型有很多的優點,決策樹計算複雜度不高、便於使用、而且高效,決策樹可處理具有不相關特徵的資料、可很容易地構造出易於理解的規則,而規則通常易於解釋和理解。決策樹模型也有一些缺點,比如處理缺失資料時的困難、過擬合以及忽略資料集中屬性之間的相關性等。

1.4. 三種決策樹演算法簡介

決策樹演算法中的核心部分即是:如何選擇劃分屬性?

最早的決策樹演算法起源於CLS(Concept Learning System)系統,即概念學習系統。

這就必須採用量化的方法來進行判斷,量化的方法有很多,其中一項就是通過”資訊理論度量資訊分類“。基於資訊理論的決策樹演算法有:ID3, CART, C4.5等演算法。

ID3 演算法是由Ross Quinlan發明的,建立在“奧卡姆剃刀”的基礎上,越簡單的決策樹越優於越大的決策樹(Be Simple),ID3演算法中,根據資訊理論的資訊增益來進行評估和特徵的選擇,每次選擇資訊增益最大的特徵作為判斷模組。ID3演算法可以用於劃分標稱型資料集,沒有剪枝的過程,為了去除過度資料匹配的問題,可通過裁剪合併相鄰的無法產生大量資訊增益的葉子節點(例如設定資訊增益閥值)。使用資訊增益的話其實是有一個缺點,那就是它偏向於具有大量值的屬性–就是說在訓練集中,某個屬性所取的不同值的個數越多,那麼越有可能拿它來作為分裂屬性,而這樣做有時候是沒有意義的,另外ID3不能處理連續分佈的資料特徵,於是就有了C4.5演算法(目前也出現了C5.0演算法 商業版)。CART演算法也支援連續分佈的資料特徵。

C4.5是ID3的一個改進演算法,繼承了ID3演算法的優點。C4.5演算法用資訊增益率來選擇劃分屬性,克服了用資訊增益選擇屬性時偏向選擇取值多的屬性的不足在樹構造過程中進行剪枝;能夠完成對連續屬性的離散化處理;能夠對不完整資料進行處理。C4.5演算法產生的分類規則易於理解、準確率較高;但效率低,因樹構造過程中,需要對資料集進行多次的順序掃描和排序。也是因為必須多次資料集掃描,C4.5只適合於能夠駐留於記憶體的資料集。

CART演算法的全稱是Classification And Regression Tree,採用的是Gini指數(選Gini指數最小的特徵s)作為分裂標準,同時它也是包含後剪枝操作。ID3演算法和C4.5演算法雖然在對訓練樣本集的學習中可以儘可能多地挖掘資訊,但其生成的決策樹分支較大,規模較大。為了簡化決策樹的規模,提高生成決策樹的效率,就出現了根據GINI係數來選擇測試屬性的決策樹演算法CART。

熵:度量隨機變數的不確定性。(純度)

(1)資訊熵

在概率論中,資訊熵給了我們一種度量不確定性的方式,是用來衡量隨機變數不確定性的,熵就是資訊的期望值。若待分類的事物可能劃分在N類中,分別是x_1,x_2,cdots,x_n,每一種取到的概率分別是p_1,p_2,cdots,p_n,那麼資料集D的熵就定義為:

H(D)=-sum^{|n|}_{i=1}p_ilogp_i

從定義中可知:0≤H(D)≤log(n)

當隨機變數只取兩個值時,即D的分佈為P(D=1)=p, P(D=0)=1-p, 0≤p≤1則熵為:H(D)=−plog_2p−(1−p)log_2(1−p)

熵值越高,則資料混合的種類越高,其蘊含的含義是一個變數可能的變化越多(反而跟變數具體的取值沒有任何關係,只和值的種類多少以及發生概率有關),它攜帶的資訊量就越大。

(2)條件熵

假設有隨機變數(X,Y),其聯合概分佈為:

P(X=x_i,Y=y_i)=p_{ij}, i=1,2,⋯,n;j=1,2,⋯,m則條件熵H(Y∣X)表示在已知隨機變數X的條件下隨機變數Y的不確定性,其定義為X在給定條件下Y的條件概率分佈的熵對X的數學期望:

H(Y|X)=sum^{n}_{i=1}p_iH(Y|X=x_i)

(3)資訊增益

資訊增益(information gain)表示得知特徵X的資訊後,而使得Y的不確定性減少的程度。定義為:

Gain(D,A)=H(D)-H(D|A)

(4)Gini 指數

基尼指數(基尼不純度):表示在樣本集合中一個隨機選中的樣本被分錯的概率。

注意: Gini指數越小表示集合中被選中的樣本被分錯的概率越小,也就是說集合的純度越高,反之,集合越不純。

基尼指數(基尼不純度)= 樣本被選中的概率 * 樣本被分錯的概率

Gini(p)=sum^{|y|}{k=1}sum{k’}p_kp^{‘}k = 1- sum^{|y|}{k=1}p^2_k

2. ID3的Python實現

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np
import pandas as pd
import operator

def loadDataSet():
    """
    匯入資料
    @ return dataSet: 讀取的資料集
    """
    # 對資料進行處理
    dataSet = pd.read_csv('isFish.csv', delimiter=',')
    # dataSet = dataSet.replace('yes', 1).replace('no', 0)
    labelSet = list(dataSet.columns.values)
    dataSet = dataSet.values
    return dataSet, labelSet

def calcShannonEnt(dataSet):
    """
    計算給定資料集的資訊熵(夏農熵)
    @ param dataSet: 資料集
    @ return shannonEnt: 夏農熵
    """
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet:
        # 當前樣本型別
        currentLabel = featVec[-1]
        # 如果當前類別不在labelCounts裡面,則建立
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries
        shannonEnt -= prob*np.log2(prob)
    return shannonEnt

def splitDataSet(dataSet, axis, value):
    """
    劃分資料集, 提取所有滿足一個特徵的值
    @ param dataSet: 資料集
    @ param axis: 劃分資料集的特徵
    @ param value: 提取出來滿足某特徵的list
    """
    retDataSet = []
    for featVec in dataSet:
        # 將相同資料特徵的提取出來
        if featVec[axis] == value:
            reducedFeatVec = list(featVec[:axis])
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def chooseBestFeature(dataSet):
    """
    選擇最優的劃分屬性
    @ param dataSet: 資料集
    @ return bestFeature: 最佳劃分屬性
    """
    # 屬性的個數
    numFeature = len(dataSet[0])-1
    baseEntroy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeature):
        # 獲取第i個特徵所有可能的取值
        featureList = [example[i] for example in dataSet]
        # 去除重複值
        uniqueVals = set(featureList)
        newEntropy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            # 特徵為i的資料集佔總數的比例
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * np.log2(prob)
        inforGain = baseEntroy - newEntropy

        if inforGain > bestInfoGain:
            bestInfoGain = inforGain
            bestFeature = i
    return bestFeature

def majorityCnt(classList):
    """
    遞迴構建決策樹
    @ param classList: 類別列表
    @ return sortedClassCount[0][0]: 出現次數最多的類別
    """
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
        classCount += 1
    # 排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 返回出現次數最多的
    return sortedClassCount[0][0]

def createTree(dataSet, labels):
    """
    構造決策樹
    @ param dataSet: 資料集
    @ param labels: 標籤集
    @ return myTree: 決策樹
    """
    classList = [example[-1] for example in dataSet]
    # 當類別與屬性完全相同時停止
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 遍歷完所有特徵值時,返回數量最多的
    if (len(dataSet[0]) == 1):
        return majorityCnt(classList)

    # 獲取最佳劃分屬性
    bestFeat = chooseBestFeature(dataSet)
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel:{}}
    # 清空labels[bestFeat]
    del(labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:]
        # 遞迴呼叫建立決策樹
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
    return myTree


if __name__ == '__main__':
    dataSet, labelSet = loadDataSet()
    shannonEnt = calcShannonEnt(dataSet)
    tree= createTree(dataSet, labelSet)
    print (tree)

其餘演算法待補充…