決策樹python實現(ID3 和 C4.5)

最近在看機器學習實戰,記錄一些不寫代碼,真的很難發現的問題。
ID3代碼見github

ID3的問題:

1、從信息增益的計算方法來看,信息增益無法直接處理連續取值的的屬性數據,只能處理離散型的數據
2、信息增益的計算方法需要對某列屬性進行分類,如果屬性是ID,按照ID分類後,所有的分類都只包含一個元素,即ID就是信息增益最大的屬性,導致ID3算法失效。
3、**如果預測數據中出現了訓練樣本中沒有出現過的情況,則無法預測分類。**例如

  • 某個屬性A在訓練集中只有3個取值(1,2,3),但是測試集中屬性A的值爲4,則ID3也是沒有辦法處理的。程序會報錯。
  • 某個屬性A在訓練集中未出現,但是在測試集中某條數據含有屬性A,則ID3在以屬性A進行分裂時無法處理此條數據。

決策樹概論

決策樹是根據訓練數據集,按屬性跟類型,構建一棵樹形結構。一棵決策樹包含一個根節點、若干內部節點和若干葉節點。葉節點對應於決策結果,其他每個節點則對應於一個屬性測試;每個節點包含的樣本集合根據屬性測試的結果被劃分到子節點中;根節點包含樣本全集。從根節點到每個葉節點的路徑對應了一個判定測試序列。決策樹學習的目的是爲了產生一棵泛化能力強,即處理未見示例能力強的決策樹,遵循‘分而治之’的策略。可以按照樹的結構,對測試數據進行分類。同時決策樹也可以用來處理預測問題(迴歸)

決策樹ID3的原理

首先按照“信息增益”找出最有判別力的屬性,把這個屬性作爲根節點,屬性的所有取值作爲該根節點的分支,把樣例分成多個子集,每個子集又是一個子樹。以此遞歸,一直進行到所有子集僅包含同一類型的數據爲止。最後得到一棵決策樹。ID3主要是按照按照每個屬性的信息增益值最大的屬性作爲根節點進行劃分。
ID3的算法思路
1、對當前訓練集,計算各屬性的信息增益(假設有屬性A1,A2,…An);
2、選擇信息增益最大的屬性Ak(1<=k<=n),作爲根節點;
3、把在Ak處取值相同的例子歸於同一子集,作爲該節點的一個樹枝,Ak取幾個值就得幾個子集;
4、若在某個子集中的所有樣本都是屬於同一個類型(本位只討論正(Y)、反(N)兩種類型的情況),則給該分支標上類型號作爲葉子節點;
5、對於同時含有多種(兩種)類型的子集,遞歸調用該算法思路來完成樹的構造。

C4.5主要是在ID3的基礎上改進

C4.5引入了新概念“信息增益率”, C4.5是選擇信息增益率最大的屬性作爲樹節點。

決策樹的構造過程

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

僞代碼

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

ID3_tree代碼實現

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

__author__ = 'WF'

from math import log
import numpy as np
import operator

# step1 計算給定數據的熵
def calShannonEnt(dataset):
    # dataset 爲list  並且裏面每一個list的最後一個元素爲label
    # 如[[1,1,'yes'],
    #    [1,1,'yes'],
    #    [1,0,'no'],
    #    [0,0,'no'],
    #    [0,1,'no']]

    numClass = len(dataset)  # 獲得list的長度 即實例總數 注(a)若爲矩陣,則 len(dataset.shape[0])
    e_class = {}  # 創建一個字典,來存儲數據集合中不同label的數量 如 dataset包含3 個‘yes’  2個‘no’ (用鍵-值對來存儲)
    #遍歷樣本統計每個類中樣本數量
    for example in dataset:
        if example[-1] not in e_class.keys(): # 如果當前標籤在字典鍵值中不存在
            e_class[example[-1]] = 0 # 初值
        e_class[example[-1]] += 1 # 若已經存在 該鍵所對應的值加1
    shannonEnt = 0.0
    for k in e_class.keys():
        prob = e_class[k]/numClass   # 計算單個類的熵值
        shannonEnt -= prob * log(prob, 2)  # 累加每個類的熵值
    return shannonEnt


# step2 計算信息增益 (判斷哪個屬性的分類效果好)

# # 以屬性i,value劃分數據集
def split_dataset(dataset, i, value):
    ret_dataset = []
    for example in dataset:
        if example[i] == value:  # 將符合特徵的數據抽取出來 比如 屬性wind={weak,strong} 分別去抽取: weak多少樣本,strong多少樣本
            ret_feature = example[:i]  # 0-(attribute-1)位置的元素
            ret_feature.extend(example[i+1:])  # 去除了 attribute屬性
            ret_dataset.append(ret_feature)
    return ret_dataset   # 返回 attribbute-{A}

def choseBestFeature(dataset):    # 選擇最優的分類特徵
    feature_count = len(dataset[0]) - 1
    baseEnt = calShannonEnt(dataset)  # 原始的熵
    best_gain = 0.0
    best_feature = -1
    for i in range(feature_count):
        #  python中的集合(set)數據類型,與列表類型相似,唯一不同的是set類型中元素不可重複
        unique_feature = set([example[i] for example in dataset])
        new_entropy = 0.0
        for value in unique_feature:
            sub_dataset = split_dataset(dataset, i, value)  # 調用函數返回屬性i下值爲value的子集
            prob = len(sub_dataset)/len(dataset)
            new_entropy += prob * calShannonEnt(sub_dataset)  # 計算每個類別的熵
        info_gain = baseEnt - new_entropy  # 求信息增益
        if best_gain < info_gain:
            best_gain = info_gain
            best_feature = i
    return best_feature  # 返回分類能力最好的屬性索引值



def createTree(dataset, attribute):
    class_lable = [example[-1] for example in dataset]  # 類別:男或女
    if class_lable.count(class_lable[0]) == len(class_lable):
        return class_lable[0]
    if len(dataset[0]) == 1:
        return majority_count(class_lable)
    best_feature_index = choseBestFeature(dataset)  # 選擇最優特徵
    best_feature = attribute[best_feature_index]
    my_tree = {best_feature: {}}  # 分類結果以字典形式保存
    del(attribute[best_feature_index])
    feature_value = [example[best_feature_index] for example in dataset]
    unique_f_value = set(feature_value)
    for value in unique_f_value:
        sublabel = attribute[:]
        my_tree[best_feature][value] = createTree(split_dataset(dataset, best_feature_index, value), sublabel)
    return my_tree



def majority_count(classlist):
    class_count = {}
    for vote in classlist:
        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)
    # print(sorted_class_count) # [('yes', 3), ('no', 2)]
    return sorted_class_count[0][0]


def createDataSet1():  # 創造示例數據
    dataSet = [['長', '粗', '男'],
               ['短', '粗', '男'],
               ['短', '粗', '男'],
               ['長', '細', '女'],
               ['短', '細', '女'],
               ['短', '粗', '女'],
               ['長', '粗', '女'],
               ['長', '粗', '女']]
    labels = ['頭髮', '聲音']  # 兩個特徵
    return dataSet, labels


if __name__ == "__main__":
    dataset = [[1,1,'yes'],
        [1,1,'yes'],
        [1,0,'no'],
        [0,0,'no'],
        [0,1,'no']]
    print(len(dataset))
    print(calShannonEnt(dataset))

    # dataset = np.array(dataset)
    # print(dataset.shape[0])
    # print(calShannonEnt(dataset))

    # classlist = ['yes', 'yes', 'no', 'no', 'yes']
    # print(majority_count(classlist))
    dataSet, labels = createDataSet1()  # 創造示列數據
    print(createTree(dataSet, labels))  # 輸出決策樹模型結果

result:

5
0.9709505944546686
{'聲音': {'粗': {'頭髮': {'長': '女', '短': '男'}}, '細': '女'}}

C4.5_tree代碼實現

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

__author__ = 'WF'

from math import log
import numpy as np
import operator

def createDataSet1():  # 創造示例數據
    dataSet = [['長', '粗', '高', '男'],
               ['短', '粗', '高', '男'],
               ['短', '粗', '中', '男'],
               ['長', '細', '低', '女'],
               ['短', '細', '高', '女'],
               ['短', '粗', '低', '女'],
               ['長', '粗', '低', '女'],
               ['長', '粗', '中', '女']]
    labels = ['頭髮', '聲音', '身高']  # 兩個特徵
    return dataSet, labels

# step1 計算給定數據的熵
def calShannonEnt(dataset):
    # dataset 爲list  並且裏面每一個list的最後一個元素爲label
    # 如[[1,1,'yes'],
    #    [1,1,'yes'],
    #    [1,0,'no'],
    #    [0,0,'no'],
    #    [0,1,'no']]

    numClass = len(dataset)  # 獲得list的長度 即實例總數 注(a)若爲矩陣,則 len(dataset.shape[0])
    e_class = {}  # 創建一個字典,來存儲數據集合中不同label的數量 如 dataset包含3 個‘yes’  2個‘no’ (用鍵-值對來存儲)
    #遍歷樣本統計每個類中樣本數量
    for example in dataset:
        if example[-1] not in e_class.keys(): # 如果當前標籤在字典鍵值中不存在
            e_class[example[-1]] = 0 # 初值
        e_class[example[-1]] += 1 # 若已經存在 該鍵所對應的值加1
    shannonEnt = 0.0
    for k in e_class.keys():
        prob = e_class[k]/numClass   # 計算單個類的熵值
        shannonEnt -= prob * log(prob, 2)  # 累加每個類的熵值
    return shannonEnt


# step2 計算信息增益率 (判斷哪個屬性的分類效果好)
# 選擇最好的數據集劃分方式

# # 以屬性i,value劃分數據集
def split_dataset(dataset, i, value):
    ret_dataset = []
    for example in dataset:
        if example[i] == value:  # 將符合特徵的數據抽取出來 比如 屬性wind={weak,strong} 分別去抽取: weak多少樣本,strong多少樣本
            ret_feature = example[:i]  # 0-(attribute-1)位置的元素
            ret_feature.extend(example[i+1:])  # 去除了 attribute屬性
            ret_dataset.append(ret_feature)
    return ret_dataset   # 返回 attribbute-{A}

# # 只使用增益率
# def choseBestFeature(dataset):    # 選擇最優的分類特徵
#     feature_count = len(dataset[0]) - 1
#     baseEnt = calShannonEnt(dataset)  # 原始的熵
#     best_gain = 0.0
#     best_feature = -1
#     for i in range(feature_count):
#         #  python中的集合(set)數據類型,與列表類型相似,唯一不同的是set類型中元素不可重複
#         unique_feature = set([example[i] for example in dataset])
#         new_entropy = 0.0
#         splitInfo = 0.0
#         for value in unique_feature:
#             sub_dataset = split_dataset(dataset, i, value)  # 調用函數返回屬性i下值爲value的子集
#             prob = len(sub_dataset)/len(dataset)
#             new_entropy += prob * calShannonEnt(sub_dataset)  # 計算每個類別的熵
#             splitInfo -= prob * log(prob, 2)
#         info_gain = baseEnt - new_entropy  # 求信息增益
#         # print(info_gain, splitInfo)
#         gain_ratio = info_gain / splitInfo  # 求出第i列屬性的信息增益率
#         # print(gain_ratio)
#         if best_gain < gain_ratio:
#             best_gain = gain_ratio
#             best_feature = i
#     return best_feature  # 返回分類能力最好的屬性索引值

# 先選出信息增益高於平均水平的屬性,再從中選擇增益率最高的
def choseBestFeature(dataset):    # 選擇最優的分類特徵
    feature_count = len(dataset[0]) - 1
    baseEnt = calShannonEnt(dataset)  # 原始的熵
    best_gain_ratio = 0.0
    best_feature = -1

    info_gain_ratio = []

    for i in range(feature_count):
        #  python中的集合(set)數據類型,與列表類型相似,唯一不同的是set類型中元素不可重複
        unique_feature = set([example[i] for example in dataset])
        new_entropy = 0.0
        splitInfo = 0.0
        for value in unique_feature:
            sub_dataset = split_dataset(dataset, i, value)  # 調用函數返回屬性i下值爲value的子集
            prob = len(sub_dataset)/len(dataset)
            new_entropy += prob * calShannonEnt(sub_dataset)  # 計算每個類別的熵
            splitInfo -= prob * log(prob, 2)
        info_gain = baseEnt - new_entropy  # 求信息增益
        # print(info_gain, splitInfo)
        gain_ratio = info_gain / splitInfo  # 求出第i列屬性的信息增益率
        # print(gain_ratio)
        info_gain_ratio.append([info_gain, gain_ratio])
    print('info_gain_ratio:', info_gain_ratio)
    sum = 0
    for i in range(len(info_gain_ratio)):
        sum += info_gain_ratio[i][0]
    aver_gain = sum / len(info_gain_ratio)

    for i in range(len(info_gain_ratio)):
        if info_gain_ratio[i][0] >= aver_gain:
            if info_gain_ratio[i][1] > best_gain_ratio:
                best_gain_ratio = info_gain_ratio[i][1]
                best_feature = i
    return best_feature  # 返回分類能力最好的屬性索引值

def createTree(dataset, attribute):
    class_lable = [example[-1] for example in dataset]  # 類別:男或女
    if class_lable.count(class_lable[0]) == len(class_lable):
        return class_lable[0]
    if len(dataset[0]) == 1:
        return majority_count(class_lable)
    best_feature_index = choseBestFeature(dataset)  # 選擇最優特徵
    best_feature = attribute[best_feature_index]
    print('best_feature:', best_feature_index, best_feature)
    my_tree = {best_feature: {}}  # 分類結果以字典形式保存
    print('attribute:', attribute)
    del(attribute[best_feature_index])
    feature_value = [example[best_feature_index] for example in dataset]
    unique_f_value = set(feature_value)
    print('unique_f_value:', unique_f_value)
    for value in unique_f_value:
        sublabel = attribute[:]
        my_tree[best_feature][value] = createTree(split_dataset(dataset, best_feature_index, value), sublabel)
    return my_tree

def majority_count(classlist):
    class_count = {}
    for vote in classlist:
        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)
    # print(sorted_class_count) # [('yes', 3), ('no', 2)]
    return sorted_class_count[0][0]

# 使用決策樹進行分類
def classify(input_tree, feature_label, test_vec):
    firstStr = list(input_tree.keys())[0]
    secondDict = input_tree[firstStr]
    featIndex = feature_label.index(firstStr)
    for key in secondDict.keys():
        if test_vec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], feature_label, test_vec)
            else:
                classLabel = secondDict[key]
    return classLabel

if __name__ == "__main__":
    # dataset = [[1,1,'yes'],
    #     [1,1,'yes'],
    #     [1,0,'no'],
    #     [0,0,'no'],
    #     [0,1,'no']]
    # print(len(dataset))
    # print(calShannonEnt(dataset))

    # dataset = np.array(dataset)
    # print(dataset.shape[0])
    # print(calShannonEnt(dataset))

    # classlist = ['yes', 'yes', 'no', 'no', 'yes']
    # print(majority_count(classlist))


    dataSet, labels = createDataSet1()  # 創造示列數據
    # print(createTree(dataSet, labels))  # 輸出決策樹模型結果
    # print(type(createTree(dataSet, labels)))
    my_tree = createTree(dataSet, labels)
    print(my_tree)

    attributes = ['頭髮', '聲音', '身高']
    # print(attributes[-1])
    test_list = ['長', '細', '高']
    # for data in test_list:
    dic = classify(my_tree, attributes, test_list)
    print(dic)

result:

info_gain_ratio: [[0.04879494069539847, 0.04879494069539847], [0.20443400292496494, 0.25199003493562466], [0.3600730651545314, 0.23062711218045798]]
best_feature: 1 聲音
attribute: ['頭髮', '聲音', '身高']
unique_f_value: {'細', '粗'}
info_gain_ratio: [[0.08170416594551044, 0.08170416594551044], [0.6666666666666667, 0.42061983571430506]]
best_feature: 1 身高
attribute: ['頭髮', '身高']
unique_f_value: {'高', '低', '中'}
info_gain_ratio: [[1.0, 1.0]]
best_feature: 0 頭髮
attribute: ['頭髮']
unique_f_value: {'長', '短'}
{'聲音': {'細': '女', '粗': {'身高': {'高': '男', '低': '女', '中': {'頭髮': {'長': '女', '短': '男'}}}}}}
女

ID3即信息增益準則對可取值數目較多的屬性有所偏好,爲了減少這種偏好可能帶來的不利影響。C4.5決策樹不直接使用信息增益,而使用增益率,但是增益率準則對可取值數目較少的屬性有所偏好,因此C4.5算法並不是直接選擇增益率最大的候選劃分屬性,而是使用了一個啓發式先從候選屬性中找出信息增益高於平均水平的屬性,再從中選擇增益率最高的。

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