機器學習實戰筆記(四):決策樹(Python3 實現)

1 決策樹的構造

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

    在構造決策樹時,我們需要解決的第一個問題就是,當前數據集上哪個特徵在劃分數據分類時起決定性作用。爲了找到決定性的特徵,劃分出最好的結果,我們必須評估每個特徵。完成測試之後,原始數據集就被劃分爲幾個數據子集。這些數據子集會分佈在第一個決策點的所有分支上。如果某個分支下的數據屬於同一類型,則當前無需閱讀的垃圾郵件已經正確地劃分數據分類,無需進一步對數據集進行分割。如果數據子集內的數據不屬於同一類型,則需要重複劃分數據子集的過程。如何劃分數據子集的算法和劃分原始數據集的方法相同,直到所有具有相同類型的數據均在一個數據子集內。創建分支的僞代碼函數createBranch()如下所示:
檢測數據集中的每個子項是否屬於同一分類:
If so return 類標籤;
Else
尋找劃分數據集的最好特徵
劃分數據集
創建分支節點
for 每個劃分的子集
調用函數createBranch並增加返回結果到分支節點中
return 分支節點
1.2 決策樹的一般流程
(1) 收集數據:可以使用任何方法。
(2) 準備數據:樹構造算法只適用於標稱型數據,因此數值型數據必須離散化。
(3) 分析數據:可以使用任何方法,構造樹完成之後,我們應該檢查圖形是否符合預期。
(4) 訓練算法:構造樹的數據結構。
(5) 測試算法:使用經驗樹計算錯誤率。
(6) 使用算法:此步驟可以適用於任何監督學習算法,而使用決策樹可以更好地理解數據的內在含義。

1.3 信息增益
# 計算給定數據集的香農熵
def cal_shannon_ent(data_set):
    # 獲取數據集的行數
    num_entries = len(data_set)
    # 設置字典
    label_counts = {}
    # 提取數據集的每一行的特徵向量
    for feat_vec in data_set:
        # 獲取特徵向量的最後一列的標籤
        current_label = feat_vec[-1]
        # 檢測字典的關鍵字key中是否存在該標籤
        # 如果不存在keys()關鍵字
        if current_label not in label_counts.keys():
            # 將當前標籤/0鍵值對存入字典中
            label_counts[current_label] = 0
        # 否則將當前標籤對應的鍵值加1
        label_counts[current_label] += 1
    # 初始化熵爲0
    shannon_ent = 0.0
    # 對於數據集中所有的分類類別
    for key in label_counts:
        # 計算各個類別出現的頻率
        prob = float(label_counts[key]) / num_entries
        # 計算各個類別信息期望值
        shannon_ent -= prob * log(prob, 2)
    # 返回熵
    return shannon_ent
# 創建一個簡單的數據集
def creat_data_set():
    data_set = [[1, 1, 'yes'],
                [1, 1, 'yes'],
                [1, 0, 'no'],
                [0, 1, 'no'],
                [0, 1, 'no']]
    labels = ['no surfacing', 'flippers']
    # 返回數據集和類標籤
    return data_set, labels
1.4  劃分數據集 
# 按照給定特徵劃分數據集
def split_data_set(data_set, axis, value):
    # 不修改原始數據集,創建一個新的列表對象進行操作
    ret_data_set = []
    # 提取數據集的每一行的特徵向量
    for feat_vec in data_set:
        # 針對axis特徵不同的取值,將數據集劃分爲不同的分支
        if feat_vec[axis] == value:
            # 將特徵向量的0~axis-1列存入列表reduced_feat_vec
            reduced_feat_vec = feat_vec[:axis]
            # 將特徵向量的axis+1~最後一列存入列表reduced_feat_vec
            reduced_feat_vec.extend(feat_vec[axis + 1:])
            ret_data_set.append(reduced_feat_vec)
    return ret_data_set


# 選擇最好的數據集劃分方式
def choose_best_feature_to_split(data_set):
    # 獲取數據集特徵的數目(不包含最後一列的類標籤)
    num_features = len(data_set[0]) - 1
    # 計算未進行劃分的信息熵
    base_entropy = cal_shannon_ent(data_set)
    best_info_gain = 0.0
    best_feature = -1
    # 利用每一個特徵分別對數據集進行劃分,計算信息增益
    for i in range(num_features):
        # 得到特徵i的特徵值列表
        feat_list = [example[i] for example in data_set]
        # 利用set集合的性質--元素的唯一性,得到特徵i的取值
        unique_vals = set(feat_list)
        new_entropy = 0.0
        # 對特徵的每一個取值,分別構建相應的分支
        for value in unique_vals:
            sub_data_set = split_data_set(data_set, i, value)
            prob = len(sub_data_set) / float(len(data_set))
            new_entropy += prob * cal_shannon_ent(sub_data_set)
        info_gain = base_entropy - new_entropy
        # 比較此增益與當前保存的最大的信息增益
        if info_gain > best_info_gain:
            # 保存信息增益的最大值
            best_info_gain = info_gain
            # 相應地保存得到此最大增益的特徵i
            best_feature = i
    return best_feature
1.5 遞歸構建決策樹
def majority_cnt(class_list):
    # 創建一個類標籤的字典
    class_count = {}
    for vote in class_list:
        if vote not in class_count.keys():
            class_count[vote] = 0
        class_count[vote] += 1
    sorted_class_count = sorted(class_count.items, key=operator.itemgetter(1), reverse=True)
    # 返回出現次數最多的類標籤
    return sorted_class_count[0][0]


def create_tree(data_set, labels):
    # 獲取數據集中的最後一列的類標籤,存入class_list列表
    class_list = [example[-1] for example in data_set]
    if class_list.count(class_list[0]) == len(class_list):
        return class_list[0]
    # 遍歷完所有的特徵屬性,此時數據集的列爲1,即只有類標籤列
    if len(data_set[0]) == 1:
        # 多數表決原則,確定類標籤
        return majority_cnt(class_list)
    # 確定出當前最優的分類特徵
    best_feat = choose_best_feature_to_split(data_set)
    # 在特徵標籤列表中獲取該特徵對應的值
    best_feat_label = labels[best_feat]
    # 採用字典嵌套字典的方式,存儲分類樹信息
    my_tree = {best_feat_label: {}}
    del (labels[best_feat])
    # 獲取數據集中最優特徵所在列
    feat_values = [example[best_feat] for example in data_set]
    unique_vals = set(feat_values)
    for value in unique_vals:
        # 複製類標籤,並將其存儲在新列表變量sub_labels中
        sub_labels = labels[:]
        my_tree[best_feat_label][value] = create_tree(split_data_set(data_set,               best_feat, value), sub_labels)
    return my_tree
1.6 測試算法:使用決策樹執行分類

def classify(input_tree, feat_labels, test_vec):
    first_str = list(input_tree.keys())[0]
    # 從樹中得到該分類特徵的分支,有0和1
    second_dict = input_tree[first_str]
    # 根據分類特徵的索引找到對應的標稱型數據值
    feat_index = feat_labels.index(first_str)
    for key in second_dict.keys():
        # 測試實例的第0個特徵取值等於第key個子節點
        if test_vec[feat_index] == key:
            # type()函數判斷該子節點是否爲字典類型
            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

2 總結

詳細的代碼解釋可以看下這篇博文《機器學習實戰之決策樹》。

3 參考及閱讀資料

[1] 機器學習實戰
發佈了48 篇原創文章 · 獲贊 17 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章