1. 程式人生 > >資料探勘十大演算法(一):決策樹演算法 python和sklearn實現

資料探勘十大演算法(一):決策樹演算法 python和sklearn實現

學完到第三章——決策樹,python程式碼實現的僅是ID3演算法,sklearn為優化過的C4.5,這裡做一個詳細的總結包括(原理、程式碼、視覺化、scikit-learn實現),皆為親自實踐後的感悟。以下進入正文。

早前簡單瞭解了決策樹的原理,然後為了儘快使用便沒有深究直接使用sklearn實現,雖然sklearn使用起來極其極其的方便,但是我還是想理解到其中的程式碼實現機制以及一些數學知識,所以在《機器學習實戰》的第三章我結合它的思路用自己的程式碼實現了(夏農熵、資訊增益、建立決策樹字典、視覺化決策樹)。思路和程式碼都不是很難,較容易理解。這樣實踐後最大的收穫不僅是程式碼的編寫能力,還有什麼樣的資料以及如何調整資料集才能更好的適用於決策樹。(個人程式碼主要資料格式為DataFrame)

這裡補充幾個知識點

    1:資料集種類(目標變數)越多越複雜熵越大,所以原始資料的熵最大

    2:熵公式:   n代表X的n種不同的離散取值,pi代表X取值為i的概率,log以2或e為底的對數

    3:資訊增益(簡單處理):原始資料熵-目前特徵的熵

決策樹原理:(這裡只是重點描述,決策樹還是涉及很多知識的,詳細請參考其他博文)

    1:求得每個特徵的熵,與目前原始資料熵比較從而得到該特徵的資訊增益。

    2:從中選出資訊增益最大的那個最優特徵,將它取出來當作當前節點。

    3:排除當前節點,遞迴繼續重複1、2步驟。

    4:兩個條件結束3的遞迴。1:當前節點下目標變數唯一。 2:所有特徵都迴圈完了。

上面的4點是建立決策樹中最關鍵的點,也是其基本原理,圍繞這四個點我們可以創建出簡單的決策樹,下面為程式碼段。

程式碼實現:

下面是建立的測試資料集,當然複雜的資料集都能實現,只要滿足該DataFrame格式

from math import log
import pandas as pd
import numpy as np
#資料集種類越多越複雜熵越大

#建立資料集 (返回DataFrame)
def createdata():
    data = pd.DataFrame({'water':[1,1,1,0,0],'feet':[1,1,0,1,1],'survive':['yes','yes','no','no','no']})
    return data

#計算夏農熵  (DataFrame)
def calculateshang(data):
    names = data[data.columns[-1]]      #依據公式求某列特徵的熵 目標變數作為概率依據
    n = len(names)
    labels = {}
    for i,j in names.value_counts().items():
        labels[i] = j
    shang = 0
    for i in labels:            #利用迴圈求熵
        pi = labels[i]/n
        shang -= pi * log(pi,2)
    return shang

#劃分資料集  (DataFrame,特徵列名,該列某個特徵值)
def splitdataSet(data,feature,feature_value):
    recvdata = []
    n = len(data)
    for i in range(n):          #如果該行的這個特徵值==迴圈到的這個特徵值,去掉該特徵加入返回列表
        if(data.iloc[[i],:][feature].values[0] == feature_value):
            temp = data.iloc[[i],:]  #問題一:DataFrame如何取一行為Series,就可以直接drop了
            k = temp.index.values[0]
            temp_t = temp.ix[k]         #問題二:DataFrame為什麼這裡使用iloc或loc不行,明明我是按照位置取值的而非行號
            tem = temp_t.drop(feature)
            recvdata.append(tem)
    recvDF = pd.DataFrame(recvdata)     #將滿足條件的所有行定義為DataFrame
    return recvDF

#得出最好的特徵名,用來劃分資料集 (DataFrame)
def choosebestfeaturetosplit(data):
    nameFeatures = data.columns
    baseEntropy = calculateshang(data)  #基礎最大原始夏農熵
    bestinfoGain = 0.0                  #初始化最好資訊增益
    bestFeature = -1                    #初始化最好的特徵名
    for Feature in nameFeatures[:-1]:   #迴圈所有特徵
        uniquevalue = set(data[Feature])#該特徵的所有唯一值
        newEntropy = 0.0            #中間熵
        for value in uniquevalue:
            subdata = splitdataSet(data,Feature,value)
            pi = len(subdata) / len(data)
            newEntropy += pi * calculateshang(subdata)
        infoGain = baseEntropy - newEntropy     #中間資訊增益
        if(infoGain > bestinfoGain):
            bestinfoGain = infoGain
            bestFeature = Feature   #迴圈比較所有特徵,返回資訊增益最大的特徵列名
    return bestFeature

#若建立數結束後目標變數仍不唯一,則以最多的一類為準  (Series)
def major_k(classlist):
    classcount = classlist.value_counts()
    result = classcount.sort_values(ascending=False).index[0]
    return result

#建立決策樹  (DataFrame)(返回dict): {'water': {0: 'no', 1: {'food': {0: 'no', 1: 'yes'}}}}
def createtree(data):
    labels = data.columns
    classlist = data[labels[-1]]
    if(len(classlist.values) == classlist.value_counts()[0]):   #結束條件1:該分支目標變數唯一
        return classlist.values[0]
    if(len(labels) == 1):                          #結束條件2:所有特徵名都迴圈完了
        return major_k(classlist)   #這裡並不能直接返回目標變數名,可能不唯一,所以呼叫major_k
    bestFeature = choosebestfeaturetosplit(data)
    myTree = {bestFeature:{}}   #這裡很巧妙,以此來建立巢狀字典
    unique = set(data[bestFeature])
    for value in unique:
        myTree[bestFeature][value] = createtree(splitdataSet(data,bestFeature,value))   #遞迴建立樹
    return myTree

#分類器預測  (巢狀字典 列表特徵名 列表測試資料)
def classfiy(myTree,labels,test):
    firstStr = list(myTree.keys())[0]       #需要獲取首個特徵的列號,以便從測試資料中取值比較
    secondDict = myTree[firstStr]           #獲得第二個字典
    featIndex = labels.index(firstStr)      #獲取測試集對應特徵數值
    for key in secondDict.keys():
        if(test[featIndex] == key):
            if(type(secondDict[key]).__name__ == 'dict'):       #判斷該值是否還是字典,如果是,則繼續遞迴
                classlabel = classfiy(secondDict[key],labels,test)
            else:
                classlabel = secondDict[key]
    return classlabel

#畫決策樹pdf圖   (DataFrame)
def showtree_pdf(data):
    from sklearn import tree    #匯入sklearn的決策樹模型(包括分類和迴歸兩種)
    import pydotplus    #畫句子的依存結構樹

    a = data.iloc[:,:-1]    #特徵矩陣
    b = data.iloc[:,-1]     #目標變數
    clf = tree.DecisionTreeClassifier() #分類決策樹
    clf.fit(a,b)
    dot_data = tree.export_graphviz(clf, out_file=None) #利用export_graphviz將樹匯出為Graphviz格式
    graph = pydotplus.graph_from_dot_data(dot_data)
    graph.write_pdf("iris1.pdf")  #儲存樹圖iris.pdf到本地

if __name__=="__main__":
    data = createdata() #建立資料集

    myTree = createtree(data)   #建立巢狀字典樹
    print(myTree)

    result = classfiy(myTree,list(data.columns),[1,0])  #預測測試資料
    print(result)

    showtree_pdf(data)    #使用sklearn和pydotplus來畫決策樹,windows需要提前安裝graphviz工具

下面結果第一行是巢狀字典格式的決策樹,第二行為預測值(沒問題)

下面是我使用sklearn構建的模型再用pydotplus庫和graphviz繪製的pdf圖,graphviz需要到這裡下載msi來安裝,然後新增系統路徑。

講講這個決策樹圖的含義(很多人的部落格抄襲別人就是畫個圖,我感覺他都不知道這個圖什麼意思。。。由於沒找到參考,以下個人反覆理解總結):

    1:X[0]表示第一個節點特徵(這裡為water)的值,<=0.5(特徵值)可以分為兩類,目標變數不唯一的3個數據再判斷X[1]第二個節點特徵feet

    2:gini係數越小,不純度越低,特徵越好(葉子節點很純gini=0)

    3:samples表示當前節點的樣本數

    4:目標變數有多少個離散變數種類,value列表長度就多長(例如:no、yes為兩個:[3個no,2個yes])

--------------------------------------------------------------------------------------------------------------------------------

好了以上便是有關決策樹簡單實現的所有內容,從頭編寫一次程式碼,理解底層原理,接觸原始資料後基本算是決策樹入門了,那麼為了提高編碼和工作效率我們可以使用sklearn來極為方便的實現。

sklearn決策樹演算法類庫內部實現是使用了調優過的CART樹演算法,既可以做分類(DecisionTreeClassifier),又可以做迴歸(DecisionTreeRegressor),兩者引數幾乎一致,部分意義有差別。

C4.5為優化過的ID3演算法,改進:

    1:用資訊增益率來選擇屬性,克服了用資訊增益選擇屬性時偏向選擇取值多的屬性不足;

    2:在樹構造過程中進行剪枝;

    3:能夠完成對連續屬性的離散化處理;

    4:能夠對不完整資料進行處理。

這裡我還會提到一個很方便、很爽的方法Categorical

    如果使用我上面編寫的程式碼那麼則可以處理資料特徵為非數值型的特徵值,但是如果要在sklearn中使用決策樹模型那麼則需要將沒有大小的離散型值轉換為數值如:[red,blue,yellow]->[0,1,2],這裡我們使用Categorical可以輕鬆實現。下面是轉換、畫圖完整程式碼。

原始資料:

程式碼:

import pandas as pd
import numpy as np
#資料集種類越多越複雜熵越大

#畫決策樹pdf圖   (DataFrame)
def showtree_pdf(data):
    from sklearn import tree    #匯入sklearn的決策樹模型(包括分類和迴歸兩種)
    import pydotplus    #畫句子的依存結構樹

    a = data.iloc[:,:-1]    #特徵矩陣
    b = data.iloc[:,-1]     #目標變數
    clf = tree.DecisionTreeClassifier() #分類決策樹
    clf.fit(a,b)    #訓練
    dot_data = tree.export_graphviz(clf, out_file=None) #利用export_graphviz將樹匯出為Graphviz格式
    graph = pydotplus.graph_from_dot_data(dot_data)
    graph.write_pdf("iris4.pdf")  #儲存樹圖iris.pdf到本地

def change(data):
    names = data.columns[:-1]
    for i in names:
        col = pd.Categorical(data[i])
        data[i] = col.codes
    print(data)

if __name__=="__main__":
    data = pd.read_table('lenses.txt',header=None,sep='\t') #讀取檔案
    change(data)        #轉換非大小離散型為數值型
    showtree_pdf(data)  #畫pdf圖

看看轉換後的資料:

再看看決策圖pdf:

相當的簡單方便,如果需要預測直接使用predict方法就行,當然模型裡面有很多的引數可能需要調整,這也就是為什麼我們需要深入瞭解底層的原理,這樣能幫助我們理解如何去調參。引數部落格參考

以上為全部內容,有問題還請還請指教。