機器學習實踐之決策樹演算法學習

来源:http://www.cnblogs.com/georgeli/archive/2017/12/22/8087660.html
-Advertisement-
Play Games

關於本文說明,本人原博客地址位於http://blog.csdn.net/qq_37608890,本文來自筆者於2017年12月06日 18:06:30所撰寫內容(http://blog.csdn.net/qq_37608890/article/details/78731169)。 本文根據最近學習 ...


關於本文說明,本人原博客地址位於http://blog.csdn.net/qq_37608890,本文來自筆者於2017年12月06日 18:06:30所撰寫內容(http://blog.csdn.net/qq_37608890/article/details/78731169)。

 

  本文根據最近學習機器學習書籍 網路文章的情況,特將一些學習思路做了歸納整理,詳情如下.如有不當之處,請各位大拿多多指點,在此謝過.

一、決策樹(decision tree)概述

1、決策樹概念

       決策樹(decision tree)是一個樹結構(可以是二叉樹或非二叉樹)。其每個非葉節點表示一個特征屬性上的測試,每個分支代表這個特征屬性在某個值域上的輸出,而每個葉節點存放一個類別。使用決策樹進行決策的過程就是從根節點開始,測試待分類項中相應的特征屬性,並按照其值選擇輸出分支,直到到達葉子節點,將葉子節點存放的類別作為決策結果。

2 工作原理

      在構造決策樹時,需要解決的第一個問題就是,當前數據集上哪個特征在劃分數據分類時起到來決定性的作用。為了找到決定性的特征,我們需要對每個特征都要進行評估.完成測試後,原始數據就被劃分為幾個數據子集.這些數據子集會分佈在第一個決策點的所有分支上.若某一分支下的數據屬於同一類型,則當前無需閱讀的垃圾郵件已經被正確地劃分數據分類,沒必要再對數據集進行分類.否則,則需要重覆劃分數據子集的過程.這裡劃分子集的演算法和劃分原始數據集的方法相同,直至所有具有相同類型的數據都進入一個數據子集內.構造決策樹偽代碼函數createBranch()如下:

 

    檢測數據集中的每個子項是否屬於同一分類:  
          IF so return 類標簽  
      
          Else  
                  尋找劃分數據集的最好特征  
                  劃分數據集  
                   創建分支節點  
                             for 每個劃分的子集  
                                     調用函數createBranch()並增加返回結果到分支節點中  
                    return 分支節點  
                    

        一旦我們構造了一個決策樹模型,以它為基礎來進行分類將是非常容易的。具體做法是,從根節點開始,對實例的某一特征進行測試,根據測試結構將實例分配到其子節點(也就是選擇適當的分支);沿著該分支可能達到葉子節點或者到達另一個內部節點時,那麼就使用新的測試條件遞歸執行下去,直到抵達一個葉子節點。當到達葉子節點時,我們便得到了最終的分類結果。下麵介紹一個小例子。

    通俗來說,決策樹分類的思想類似於找對象。現想象一個女孩的母親要給這個女孩介紹男朋友,於是有了下麵的對話:

女兒:多大年紀了?
母親:26。
女兒:長的帥不帥?
母親:挺帥的。
女兒:收入高不?
母親:不算很高,中等情況。
女兒:是公務員不?
母親:是,在稅務局上班呢。
女兒:那好,我去見見。


      這個女孩的決策過程就是典型的分類樹決策。相當於通過年齡、長相、收入和是否公務員對將男人分為兩個類別:見和不見。假設這個女孩對男人的要求是:30歲以下、長相中等以上並且是高收入者或中等以上收入的公務員,那麼這個可以用下圖表示女孩的決策邏輯:

 

        上圖完整表達了這個女孩決定是否見一個約會對象的策略,其中綠色節點表示判斷條件,橙色節點表示決策結果,箭頭表示在一個判斷條件在不同情況下的決策路徑,圖中紅色箭頭表示了上面例子中女孩的決策過程。
這幅圖基本可以算是一顆決策樹,說它“基本可以算”是因為圖中的判定條件沒有量化,如收入高中低等等,還不能算是嚴格意義上的決策樹,如果將所有條件量化,則就變成真正的決策樹了。

3、決策樹的相關特性

  •    優點:計算複雜度不高,輸出結果易於理解,對中間值的缺失不敏感,可以處理不相關特征數據。

  •    缺點:可能會產生過度匹配問題。

  •    使用數據類型: 數值型和標稱型。

4、 一般流程

    (1) 收集數據: 可以使用任何方法.

    (2) 準備數據: 構造演算法只適用於標稱型數據,因此數值型數據必須離散化.

    (3) 分析數據: 可以使用任何方法,構造樹完成之後,應該檢查圖形是否符合預期.

    (4) 訓練演算法: 構造樹的數據結構.

    (5) 測試演算法: 使用經驗樹計算錯誤率.

    (6) 使用演算法: 此步驟可以適用於任何監督學習演算法,而使用決策樹可以更好地理解數據的內在含義.

 

二 決策樹場景

       假設,現在有一個叫做 "十五個問題" 的游戲,游戲的規則很簡單:參與游戲的一方在腦海中想某個事物,其他參與者向他提問,只允許提 15個問題,問題的答案也只能用對或錯回答。問問題的人通過推斷分解,逐步縮小待猜測事物的範圍,最後得到游戲的答案。決策樹的工作原理與15個問題類似,用戶輸入一系列數據後給出游戲答案。

      下圖給出了一個假想的郵件分類系統,它首先檢測發送郵件功能變數名稱.如果地址為myEmployer.com,則將其放在"無聊時需要閱讀的郵件"中。否則,則需要檢查郵件內容中是否包含單詞 曲棍球 ,若包含則將郵件歸入"需要及時處理的朋友郵件",否則則歸類到"無需閱讀的垃圾郵件"。

 

   

 

   決策樹一個很重要的任務就是為了理解數據中所蘊含的知識信息(這與K-近鄰演算法無法給出數據的內在含義有著顯著不同),因此決策樹可以使用不熟悉的數據集合,並從中提取出一系列規則,這些機器根據數據集創建規則的過程,就是機器學習的過程。

三 決策樹項目案例一  對海洋生物進行魚和非魚判斷

1 項目情況

      下表中的數據包含5個海洋生物,特征: 不浮出水面是否可以生存和是否有腳蹼.現將動物劃分為兩類: 魚和非魚.如果想依據給出的特征選出一個來劃分數據,就涉及到要將劃分數據的依據進行量化後才可以判斷出來.

    

  我們先構造進行數據輸入的createDataSet()函數和計算給定數據集的香農熵函數calcShannonEnt()

   

    def createDataSet():  
        dataSet = [[1,1,'yes'],  
                  [1,1,'yes'],  
                  [1,0,'no'],  
                  [0,1,'no'],  
                  [0,1,'no']]  
        labels=['no surfacing','flippers']  
        # change to discrete values  
        return dataSet,labels  
    #信息增益  
    #計算給定數據的香農熵  
      
    def calcShannonEnt(dataSet): #the the number of unique elements and their occurance  
        numEntries = len(dataSet)  
        labelCounts = {}  
        for featVec in dataSet:  
            currentLabel=featVec[-1]  
            if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0  
            labelCounts[currentLabel] +=1  
        shannonEnt = 0.00000  
        for key in labelCounts:  
            prob = float(labelCounts[key]) /numEntries  
            shannonEnt -= prob * log(prob,2)   #log base 2  
              
        return shannonEnt  

 

 執行

    myDat,labels=createDataSet()  
    myDat  

 得到 

[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]

 執行

calcShannonEnt(myDat)  

得到

0.9709505944546686

熵越高,則混合的數據越多,我們可用在數據集中添加更多的分類,觀察熵是如何變化的.

按照給定特征劃分數據集,將指定特征的特征值等於 value 的行剩下列作為子數據集。

 

    def splitDataSet(dataSet, index, value):  
        """splitDataSet(通過遍歷dataSet數據集,求出index對應的colnum列的值為value的行) 
            就是依據index列進行分類,如果index列的數據等於 value的時候,就要將 index 劃分到我們創建的新的數據集中 
        Args: 
            dataSet 數據集                 待劃分的數據集 
            index 表示每一行的index列        劃分數據集的特征 
            value 表示index列對應的value值   需要返回的特征的值。 
        Returns: 
            index列為value的數據集【該數據集需要排除index列】 
        """  
        retDataSet = []  
        for featVec in dataSet:   
            # index列為value的數據集【該數據集需要排除index列】  
            # 判斷index列的值是否為value  
            if featVec[index] == value:  
                # chop out index used for splitting  
                # [:index]表示前index行,即若 index 為2,就是取 featVec 的前 index 行  
                reducedFeatVec = featVec[:index]  
                ''''' 
                請百度查詢一下: extend和append的區別 
                list.append(object) 向列表中添加一個對象object 
                list.extend(sequence) 把一個序列seq的內容添加到列表中 
                1、使用append的時候,是將new_media看作一個對象,整體打包添加到music_media對象中。 
                2、使用extend的時候,是將new_media看作一個序列,將這個序列和music_media序列合併,並放在其後面。 
                result = [] 
                result.extend([1,2,3]) 
                print result 
                result.append([4,5,6]) 
                print result 
                result.extend([7,8,9]) 
                print result 
                結果: 
                [1, 2, 3] 
                [1, 2, 3, [4, 5, 6]] 
                [1, 2, 3, [4, 5, 6], 7, 8, 9] 
                '''  
                reducedFeatVec.extend(featVec[index+1:])  
                # [index+1:]表示從跳過 index 的 index+1行,取接下來的數據  
                # 收集結果值 index列為value的行【該行需要排除index列】  
                retDataSet.append(reducedFeatVec)  
        return retDataSet  

 選擇最好的數據集劃分方式:

 

    def chooseBestFeatureToSplit(dataSet):  
        """chooseBestFeatureToSplit(選擇最好的特征) 
     
        Args: 
            dataSet 數據集 
        Returns: 
            bestFeature 最優的特征列 
        """  
        # 求第一行有多少列的 Feature, 最後一列是label列嘛  
        numFeatures = len(dataSet[0]) - 1  
        # 數據集的原始信息熵  
        baseEntropy = calcShannonEnt(dataSet)  
        # 最優的信息增益值, 和最優的Featurn編號  
        bestInfoGain, bestFeature = 0.0, -1  
        # iterate over all the features  
        for i in range(numFeatures):  
            # create a list of all the examples of this feature  
            # 獲取對應的feature下的所有數據  
            featList = [example[i] for example in dataSet]  
            # get a set of unique values  
            # 獲取剔重後的集合,使用set對list數據進行去重  
            uniqueVals = set(featList)  
            # 創建一個臨時的信息熵  
            newEntropy = 0.0  
            # 遍歷某一列的value集合,計算該列的信息熵   
            # 遍歷當前特征中的所有唯一屬性值,對每個唯一屬性值劃分一次數據集,計算數據集的新熵值,並對所有唯一特征值得到的熵求和。  
            for value in uniqueVals:  
                subDataSet = splitDataSet(dataSet, i, value)  
                # 計算概率  
                prob = len(subDataSet)/float(len(dataSet))  
                # 計算信息熵  
                newEntropy += prob * calcShannonEnt(subDataSet)  
            # gain[信息增益]: 劃分數據集前後的信息變化, 獲取信息熵最大的值  
            # 信息增益是熵的減少或者是數據無序度的減少。最後,比較所有特征中的信息增益,返回最好特征劃分的索引值。  
            infoGain = baseEntropy - newEntropy  
            print 'infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy  
            if (infoGain > bestInfoGain):  
                bestInfoGain = infoGain  
                bestFeature = i  
        return bestFeature  

 

訓練演算法:構造樹的數據結構

  創建樹的函數

    def createTree(dataSet, labels):  
        classList = [example[-1] for example in dataSet]  
        # 如果數據集的最後一列的第一個值出現的次數=整個集合的數量,也就說只有一個類別,就只直接返回結果就行  
        # 第一個停止條件:所有的類標簽完全相同,則直接返回該類標簽。  
        # count() 函數是統計括弧中的值在list中出現的次數  
        if classList.count(classList[0]) == len(classList):  
            return classList[0]  
        # 如果數據集只有1列,那麼最初出現label次數最多的一類,作為結果  
        # 第二個停止條件:使用完了所有特征,仍然不能將數據集劃分成僅包含唯一類別的分組。  
        if len(dataSet[0]) == 1:  
            return majorityCnt(classList)  
      
        # 選擇最優的列,得到最優列對應的label含義  
        bestFeat = chooseBestFeatureToSplit(dataSet)  
        # 獲取label的名稱  
        bestFeatLabel = labels[bestFeat]  
        # 初始化myTree  
        myTree = {bestFeatLabel: {}}  
        # 註:labels列表是可變對象,在PYTHON函數中作為參數時傳址引用,能夠被全局修改  
        # 所以這行代碼導致函數外的同名變數被刪除了元素,造成例句無法執行,提示'no surfacing' is not in list  
        del(labels[bestFeat])  
        # 取出最優列,然後它的branch做分類  
        featValues = [example[bestFeat] for example in dataSet]  
        uniqueVals = set(featValues)  
        for value in uniqueVals:  
            # 求出剩餘的標簽label  
            subLabels = labels[:]  
            # 遍歷當前選擇特征包含的所有屬性值,在每個數據集劃分上遞歸調用函數createTree()  
            myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)  
            # print 'myTree', value, myTree  
        return myTree  

   測試演算法:使用決策樹執行分類

 

    def classify(inputTree, featLabels, testVec):  
        """classify(給輸入的節點,進行分類) 
     
        Args: 
            inputTree  決策樹模型 
            featLabels Feature標簽對應的名稱 
            testVec    測試輸入的數據 
        Returns: 
            classLabel 分類的結果值,需要映射label才能知道名稱 
        """  
        # 獲取tree的根節點對於的key值  
        firstStr = inputTree.keys()[0]  
        # 通過key得到根節點對應的value  
        secondDict = inputTree[firstStr]  
        # 判斷根節點名稱獲取根節點在label中的先後順序,這樣就知道輸入的testVec怎麼開始對照樹來做分類  
        featIndex = featLabels.index(firstStr)  
        # 測試數據,找到根節點對應的label位置,也就知道從輸入的數據的第幾位來開始分類  
        key = testVec[featIndex]  
        valueOfFeat = secondDict[key]  
        print '+++', firstStr, 'xxx', secondDict, '---', key, '>>>', valueOfFeat  
        # 判斷分枝是否結束: 判斷valueOfFeat是否是dict類型  
        if isinstance(valueOfFeat, dict):  
            classLabel = classify(valueOfFeat, featLabels, testVec)  
        else:  
            classLabel = valueOfFeat  
        return classLabel  

 

 

三  項目案例2: 使用決策樹預測隱形眼鏡類型

項目概述

隱形眼鏡類型包括硬材質、軟材質以及不適合佩戴隱形眼鏡。我們需要使用決策樹預測患者需要佩戴的隱形眼鏡類型。
開發流程

(1)收集數據: 提供的文本文件。
(2)解析數據: 解析 tab 鍵分隔的數據行
(3)分析數據: 快速檢查數據,確保正確地解析數據內容,使用 createPlot() 函數繪製最終的樹形圖。
(4)訓練演算法: 使用 createTree() 函數。
(5)測試演算法: 編寫測試函數驗證決策樹可以正確分類給定的數據實例。
(6)使用演算法: 存儲樹的數據結構,以便下次使用時無需重新構造樹。

收集數據:提供的文本文件

文本文件數據格式如下:

  

young   myope   no  reduced no lenses  
pre myope   no  reduced no lenses  
presbyopic  myope   no  reduced no lenses 

 

  解析數據:解析 tab 鍵分隔的數據行

    lecses = [inst.strip().split('\t') for inst in fr.readlines()]  
    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']  

 

分析數據:快速檢查數據,確保正確地解析數據內容,使用 createPlot() 函數繪製最終的樹形圖。

treePlotter.createPlot(lensesTree)  

 
訓練演算法:使用 createTree() 函數

 

    >>> lensesTree = trees.createTree(lenses, lensesLabels)  
    >>> lensesTree  

 

 
得到

 

    {'tearRate': {'reduced': 'no lenses', 'normal': {'astigmatic':{'yes':  
    {'prescript':{'hyper':{'age':{'pre':'no lenses', 'presbyopic':  
    'no lenses', 'young':'hard'}}, 'myope':'hard'}}, 'no':{'age':{'pre':  
    'soft', 'presbyopic':{'prescript': {'hyper':'soft', 'myope':  
    'no lenses'}}, 'young':'soft'}}}}}  

 

 

五 小結

其實決策樹跟帶終止塊的流程圖類似,所以這裡的終止塊就是分類結果.當我們進行數據處理時,首先要對集合中數據的不一致性進行測量評估,也就是計算香農熵,下一步才可以尋找最有方案劃分數據,最終實現所有具有相同類型的數據都劃分到同一個數據子集裡面.在構建數據樹時,我們一般採用遞歸方把數據集轉化為決策樹.多數情況下,我們不構造新的數據結構,而是採用Python語言內嵌的數據結構字典存儲樹節點信息.每一步選擇信息增益最大的特征作為決策塊,最終來完成決策樹的生成.


Matplotlib的註解功能,可以讓將存儲的樹結構轉化為容易理解的圖形.隱形眼鏡的例子說明決策樹可能會產生過多的數據集劃分,結果導致過度匹配數據集的問題.當然可以通過裁剪決策樹,合併相鄰的不能產生信息增益的葉節點,來解決這個問題(過度匹配).


關於決策樹的構造演算法,這裡本文只是用了ID3演算法,當然還有C4.5和CART演算法.對於決策樹的完整工作過程而言,包括三部分:


1 特征選擇;


2 生成決策樹;


3 剪枝部分


而除去ID3演算法,其他兩個演算法都有剪枝部分過程.所以這也迎合來隱形眼鏡過擬合的問題.

  關於決策樹部分,筆者先整理到這裡,後續有機會會針對C4.5和CART演算法做些歸納整理.有不足之處,請各位同仁多多指導.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Spring是一個開源框架,Spring是於2003 年興起的一個輕量級的Java 開發框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中闡述的部分理念和原型衍生而來。 它是為瞭解決企業應用開發的複雜性而創建的。框架的 ...
  • 解決完畢後效果圖: 好友列表Vector添加的時候進行判斷,如果有相同的則不添加 int flag=0; for (int i = 0; i < names.size(); i++) { if (name.equals(names.get(i))) { flag=1; } } if(flag==0) ...
  • 學Python這麼久了,第一次寫一個這麼多的代碼(我承認只有300多行,重覆的代碼挺多的,我承認我確實垃圾),但是也挺不容易的 自定義函數+裝飾器,每一個模塊寫的一個函數 很多地方能用裝飾器(邏輯跟不上,有的地方沒用),包括雙層裝飾器(不會),很多地方需要優化,重覆代碼太多 我還是把我的流程圖拿出來 ...
  • Supervisor是用Python開發的一套client/server架構的進程管理程式,能做到開機啟動,以daemon進程的方式運行程式,並可以監控進程狀態等等。 linux進程管理方式有傳統的rc.d、新興的upstart、systemd等,與這些相比,Supervisor有著自己的特點。 便 ...
  • 如題,具體效果及代碼如下: 1 print("你好你叫什麼名字?") 2 print("我叫張三") 3 print("你好,張三,我叫李四") 4 5 print("你好你叫什麼名字?",end="") 6 print("我叫張三!",end="") 7 print("你好,張三,我叫李四",en ...
  • 版本:3.4.10 問題:啟動 zkServer.cmd時報錯如下, 解決辦法: bin目錄下 zkEnv.cmd配置 修改為: 把雙引號放在外面。 此時運行zkServer.cmd 用zkCli.cmd連接成功: 成功! ...
  • 使用While迴圈時經常會犯的一些小錯誤。以及猜年齡程式的2種編寫方式。 ...
  • 為什麼要用插件 主要還是自動化的考慮,如果額外使用Dockerfile進行鏡像生成,可能會需要自己手動指定jar/war位置,並且打包和生成鏡像間不同步,帶來很多瑣碎的工作。 插件選擇 使用比較多的是spotify的插件:https://github.com/spotify/docker maven ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...