機器學習實戰 決策樹的構造

決策樹的構造

我的微信公衆號: s406205391; 歡迎大家一起學習,一起進步!!!

​        有一個二十個問題的小遊戲,遊戲規則很簡單:參與遊戲的一方在腦海了想某個事物,其他參與者向他提出問題,只允許提問20個問題,問題的答案也只能用對和錯來回答。問問題的人通過推斷分解,逐步縮小猜測事物的範圍。決策樹的工作原理與20個問題類似,用戶輸入一系列數據,然後給出遊戲的答案。

        我們經常使用決策樹處理分類問題,近來的調查表明**決策樹也是最經常使用的數據挖掘算法。**決策樹流行的很重要的原因就是他的工作很簡單,不需要了解機器學習的知識。下圖所示的流程圖就是一個決策樹。圖中構造了一個假象的郵件分類系統,它首先檢測發送郵件的域名地址。如果地址爲:myEmployer.com,則將其放在“無聊時需要閱讀的郵件”中,如果郵件不是來自這個域名,則檢查郵件內容裏是否包含單詞曲棍球,如果包含則將郵件歸類到“需要及時處理的朋友郵件”,如果不包含,則將郵件歸類到“無需閱讀的垃圾郵件”。
在這裏插入圖片描述

        決策樹的主要優勢再與數據形式非常容易理解。決策樹的一個重要任務是爲了理解數據中所蘊含的知識信息,因此決策樹可以使用不熟悉的數據集合,並從中提取出一系列規則,這些機器根據數據集創建規則的過程,就是機器學習的過程。專家系統中經常使用決策樹,而且決策樹給出的結果往往可以匹敵在當前領域具有幾十年工作經驗的人類專家。

決策樹的一般流程

​        決策樹的優點是計算複雜度不高,輸出結果易於理解,對中間值的缺失不敏感,可以處理不相干特徵數據。缺點是可能會產生過度匹配問題。適用數據爲數值型和標稱型。

        在構造決策樹時,首先需要解決的問題是,當前數據集上,哪個特徵在劃分數據集中起決定性作用。爲了找到決定性特徵,劃分出最好的結果,我們必須評估每個特徵。完成測試之後,原始數據集就被劃分爲幾個數據子集。這些數據子集會分佈在第一個決策點的所有分支上。

決策樹構建的僞代碼如下:

檢測數據集中的每個子項是否屬於同一分類:

        if so return 類標籤
        else
                尋找劃分數據集的最好特徵
                劃分數據集
                創建分支節點
                for 每個劃分的子集
                遞歸調用本函數,並增加返回結果到分支節點中
        return 分支節點

爲了解決尋找劃分數據集的最好特徵問題,就需要引入信息增益的概念。

信息增益

        劃分數據的最大原則是:將無序的數據變得更加有序。在劃分數據集之前之後信息發生的變化成爲信息增益,知道如何計算信息增益,我們就可以計算每個特徵值劃分數據集獲得的信息增益,獲得信息增益最高的特徵就是最好的選擇。這就得引入熵的概念。

​        集合信息的度量方式成爲香農熵或者簡稱爲熵。熵定義爲信息的期望值。如果待分類的事物可能劃分在多個分類之中,則符號xi的信息定義爲:

l(xi)=log2p(xi)l(x_i) = -log_2p(x_i)

其中p(xi)是選擇該分類的概率。那麼其所有類別所有可能值包含的信息期望值的計算公式就爲:

i=1np(xi)log2p(xi)-\sum_{i=1}^{n}p(x_i)log_2p(x_i)

其中,n是分類數目。

我們首先定義一個函數,來計算數據集的香農熵

from math import log

def calc_shannon(data_set):
    """calculation shannon entropy"""

    # 默認數據集的最後一列是標籤列
    feat_vec = [f[-1] for f in data_set]
    label_counts = Counter(feat_vec)

    # 使用信息期望值的計算公式,計算香農熵
    prob_list = [value / len(data_set) for key, value in label_counts.items()]
    shannon_ent = sum([-(prob * log(prob, 2)) for prob in prob_list])
    return shannon_ent

        熵越高,則混合的數據也越多。在得到熵之後,我們就可以按照獲取最大信息增益的方法劃分數據集。

        另一個度量集合無需程度的方法是基尼不純度(Gini impurity),簡單地說就是衝一個數據集中隨機選取子項,度量其被錯誤風雷刀其他分組的概率。

劃分數據集

        分類算法除了需要測量信息熵,還需要劃分數據集,度量劃分數據集的熵,以便判斷當前是否正確地劃分了數據集。下面的函數將對每個特徵劃分數據集的結果計算一次信息熵, 然後判斷按照那個特徵劃分數據集是最好的方式。

def split_data_set(data_set, axis, value):
    """按照給定特徵劃分數據集
    
    從待劃分的數據集中,返回特徵值列爲value的數據集
    
    :param data_set: 待劃分的數據集
    :param axis: 劃分數據集的特徵所在列的索引
    :param value: 需要返回的特徵值
    
    """

    ret_data_set = [feat_vec[: axis] + feat_vec[axis+1:] for feat_vec in data_set if feat_vec[axis] == value]
    return ret_data_set

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

​        在瞭解瞭如何劃分數據集合求信息熵之後,就可以通過信息增益來選擇最好的數據集劃分方式了。首先,我們會求整個數據集的原始信息熵,然後遍歷沒一個特徵值,對特徵值下的所有特徵求一遍信息熵,其和就是該特徵值的信息熵。原始信息熵與該特徵值的信息熵的差就是該特徵值的信息增益。信息增益越大,則該特徵值越好劃分。

def choose_best_feature_to_split(data_set):
    """選擇最好的數據劃分方式
    
    函數調用需要滿足要求:
    1. 數據必須是由一種列表元素組成的列表,且所有的列表元素都具有相同的數據長度
    2. 數據的最後一列或者每個實例的最後一個元素時當前實例的類別標籤 
    
    """

    num_feature = len(data_set[0]) - 1  # 確定特徵的數量
    base_ent = calc_shannon(data_set)  # 計算原始的信息熵

    # 遍歷所有的特徵以及特徵值,確定最好的劃分數據集的特徵
    best_info_gain = 0
    best_feature = -1
    for i in range(num_feature):
        new_ent = 0
        # 計算該特徵的信息熵
        for value in set([ex[i] for ex in data_set]):
            sub_data_set = split_data_set(data_set, i, value)
            prob = len(sub_data_set) / len(data_set)
            new_ent += prob * calc_shannon(sub_data_set)
        # 計算信息增益,確定最好的特徵
        info_gain = base_ent - new_ent
        if info_gain > best_info_gain:
            best_info_gain = info_gain
            best_feature = i
    return best_feature

遞歸構建決策樹

        現在,我們可以通過前面的函數,來構建決策樹了。其工作原理如下:得到原始數據集,然後基於最好的屬性值劃分數據集,由於特徵值可能多於兩個,因此可能存在大於兩個分支的數據集劃分。第一次劃分後,數據將被向下傳遞到樹分支的下一個節點,在這個節點上,我們可以再次劃分數據。**因此我們可以採用遞歸的原則處理數據集。**遞歸結束的條件是:程序遍歷完所有劃分數據集的屬性,或者每個分支下的所有實例都具有相同的分類。如果所有實例具有相同的分類,則得到一個葉子節點或者終止塊。如果遍歷完所有屬性,但是類標籤仍然不是唯一的,那麼就採用多數表決的方法,決定該葉子節點的分類。

def create_tree(data_set, labels):
    """構建決策樹
    
    遞歸函數終止條件:
    1. 遍歷完所有的特徵,返回數量最多的標籤作爲葉子節點
    2. 所有的類標籤完全相同,直接返回包含唯一類別的分組
    
    """
	
    # 遞歸函數終止條件
    class_list = [ex[-1] for ex in data_set]
    if len(set(class_list)) == 1:
        return class_list[0]
    if len(data_set[0]) == 1:
        return Counter(class_list).most_common(1)[0][0]

    # 尋找最好的特徵屬性
    best_feat = choose_best_feature_to_split(data_set)
    best_feat_label = labels[best_feat]
    my_tree = {best_feat_label: {}}
    del(labels[best_feat])
    # 遞歸構建決策樹
    for value in set([ex[best_feat] for ex in data_set]):
        sub_labels = labels[:]
        my_tree[best_feat_label][value] = create_tree(split_data_set(data_set, best_feat, value), sub_labels)
    return my_tree

使用決策樹執行分類

​        依靠訓練數據構造了決策樹之後,就可以將它用於實際數據的分類了。下面的函數,可以根據輸入的測試數據集構建決策樹,並預測測試向量的所屬分類

def classify(input_tree, feat_labels, test_vec):
    """使用決策樹進行分類"""

    first_str = list(input_tree.keys())[0]
    second_dict = input_tree[first_str]
    feat_index = feat_labels.index(first_str)
    class_label = "unknown"
    for key in second_dict.keys():
        if test_vec[feat_index] == key:
            if type(second_dict[key]).__name__ == 'dict':
                class_label = classify(second_dict[key], feat_labels, test_vec)
            else:
                class_label = second_dict[key]
    return class_label

示例: 使用決策樹預測隱形眼鏡類型

        接下來,我們通過一個例子講解如何預測患者需要佩戴的隱形眼鏡類型。數據集見lenses.txt(提取碼爲:uhww)。數據一共分爲五列,前四列爲特徵列,最後一列是分類標籤。

#!/usr/bin/env python3
# coding: utf-8
# Author:Shen Yi
# Date :2020/2/15 2:35

"""機器學習實戰 決策樹的python實現"""

from collections import Counter
from math import log


def calc_shannon(data_set):
    """計算香農熵"""

    feat_vec = [f[-1] for f in data_set]
    label_counts = Counter(feat_vec)

    prob_list = [value / len(data_set) for key, value in label_counts.items()]
    shannon_ent = sum([-(prob * log(prob, 2)) for prob in prob_list])
    return shannon_ent


def split_data_set(data_set, axis, value):
    """按照給定特徵劃分數據集"""

    ret_data_set = [feat_vec[: axis] + feat_vec[axis+1:] for feat_vec in data_set if feat_vec[axis] == value]
    return ret_data_set


def choose_best_feature_to_split(data_set):
    """選擇最好的數據劃分方式"""

    num_feature = len(data_set[0]) - 1
    base_ent = calc_shannon(data_set)

    best_info_gain = 0
    best_feature = -1
    for i in range(num_feature):
        new_ent = 0
        for value in set([ex[i] for ex in data_set]):
            sub_data_set = split_data_set(data_set, i, value)
            prob = len(sub_data_set) / len(data_set)
            new_ent += prob * calc_shannon(sub_data_set)
        info_gain = base_ent - new_ent
        if info_gain > best_info_gain:
            best_info_gain = info_gain
            best_feature = i
    return best_feature


def create_tree(data_set, labels):
    """構建決策樹"""

    class_list = [ex[-1] for ex in data_set]
    if len(set(class_list)) == 1:
        return class_list[0]
    if len(data_set[0]) == 1:
        return Counter(class_list).most_common(1)[0][0]

    best_feat = choose_best_feature_to_split(data_set)
    best_feat_label = labels[best_feat]
    my_tree = {best_feat_label: {}}
    del(labels[best_feat])
    for value in set([ex[best_feat] for ex in data_set]):
        sub_labels = labels[:]
        my_tree[best_feat_label][value] = create_tree(split_data_set(data_set, best_feat, value), sub_labels)
    return my_tree


def classify(input_tree, feat_labels, test_vec):
    """使用決策樹進行分類"""

    first_str = list(input_tree.keys())[0]
    second_dict = input_tree[first_str]
    feat_index = feat_labels.index(first_str)
    class_label = "unknown"
    for key in second_dict.keys():
        if test_vec[feat_index] == key:
            if type(second_dict[key]).__name__ == 'dict':
                class_label = classify(second_dict[key], feat_labels, test_vec)
            else:
                class_label = second_dict[key]
    return class_label


def demo_predict_glass():
    fr = open('data\\Ch03\\lenses.txt')
    lenses = list(map(lambda line: line.strip().split('\t'), fr))
    lenses_label = ['age', 'prescript', 'astigmatic', 'tearRate']
    lenses_tree = create_tree(lenses, lenses_label)
    test_age = input("age: ")
    test_prescript = input("prescript: ")
    test_astigmatic = input("astigmatic: ")
    test_tear_rate = input("tearRate: ")
    lenses_label = ['age', 'prescript', 'astigmatic', 'tearRate']
    class_label = classify(lenses_tree, lenses_label, [test_age, test_prescript, test_astigmatic, test_tear_rate])
    print(class_label)


if __name__ == '__main__':
    demo_predict_glass()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章