自己動手寫決策樹(一)——初步搭建決策樹框架

數挖實驗課的時候,老師讓我們自己動手寫決策樹,還不能調用 scikit-learn 包,頓時感覺有點難(可能是我比較菜),想到網上找一找看能不能學一下,可是許多大佬都是調包做的,剩下的大佬們寫的代碼也無法短時間去理解,於是我只好照着書本以及參考網上大佬們的思想來自己動手寫一寫。

實驗題目是這樣的,要求使用 ID3 算法在鳶尾花卉 Iris 數據集上訓練出一個基本的決策樹模型,拿到題目時我是一點思路都沒有的,以前從來沒有自己動手實現過機器學習的那些經典算法(倒是拿着西瓜書匆匆地翻過一遍,現在真的是後悔了),所以只能一步一步來了,接下來我講一講我的思路,或許可以給朋友們提供一些參照。

1 數據集的下載與處理

首先,在網上找到鳶尾花卉數據集,一般像這樣經典的數據集網上都是公開的可以直接下載,這裏給出數據集網站地址:http://archive.ics.uci.edu/ml/datasets/Iris,根據以下標示下載數據集。
在這裏插入圖片描述
在這裏插入圖片描述
下載好數據後,我們發現它是一個文本格式的文件,我們來觀察一下這個數據集,這個文件裏存儲了鳶尾花卉的四個屬性——花萼長度(sepal length)、花萼寬度(sepal width)、花瓣長度(petal length)和花瓣寬度(petal width),以及它們的三個類別(class)——山鳶尾(Iris Setosa),變色鳶尾(Iris Versicolor)和維吉尼亞鳶尾(Iris Virginica)。以我們看到的第一個樣本爲例,“5.1,3.5,1.4,0.2”分別代表樣本的四個屬性的取值,“Iris-setosa”代表當前樣本的類別,該數據集共有 150 個這樣的樣本,且每類樣本分別有 50 個數據:
在這裏插入圖片描述
一般文本格式的不太好處理,所以最好轉成 CSV 格式的文件,然後用 pandas 庫處理,較爲直接的辦法就是重命名爲 CSV 格式的文件,然後使用 Excel 打開文件,給每一列添加一個列名,也可以通過代碼折騰一下來實現(對了,Windows 系統上面跑的話路徑需要注意改一下):

import numpy as np
import pandas as pd


def dataProcess(source_fpath, target_fpath):
    with open(source_fpath) as source_f:
        sample_list = []
        for line in source_f:
            content = line.strip().split(",")
            sample_list.append(np.array(content))
        csvdf = pd.DataFrame(sample_list)
        csvdf.columns = ["SepalLength", "SepalWidth", "PetalLength", "PetalWidth", "Class"]
        csvdf.to_csv(target_fpath, index=0) # 保存爲 CSV 文件時不保留行索引


if __name__ == "__main__":
    source_fpath = "iris.data"
    target_fpath = source_fpath.replace("data", "csv")
    dataProcess(source_fpath, target_fpath)

使用 pandas 讀取剛剛生成的 CSV 文件,以 DataFrame 形式打印出來:

dataset = pd.read_csv("iris.csv")
print(datset)

我這裏是把五個列名分別命名爲 SepalLength、SepalWidth、PetalLength、PetalWidth 以及 Class 的,大家可以根據自己的命名喜好修改一下:
在這裏插入圖片描述

2 隨機劃分數據集

現在,數據集準備好了,我們要開始劃分數據集來爲模型訓練、預測以及評估做準備,一般是以 8:2 的比例隨機劃分出訓練集和測試集(其實也可以使用交叉驗證,不過我覺得交叉驗證框架主要是用來選取超參數的,單純地被用來評估模型我個人感覺有點浪費了,而且加上我也有點懶,所以就沒有整上去了,感興趣的朋友們可以試一下)。

一開始,我是像下面這樣直接劃分數據集的:

def datasetPartitioning(dataset):

    # 打亂索引
    index = [i for i in range(len(dataset))]
    np.random.seed(1) # 設置隨機種子爲 1
    np.random.shuffle(index)

    # 以 8:2 劃分訓練集和測試集
    traindatasetlen = int(len(dataset) * 0.8)
    traindataset = dataset.loc[index[:traindatasetlen]]
    testdataset = dataset.loc[index[traindatasetlen:]]
    traindataset = traindataset.reset_index() # 重置索引
    testdataset = testdataset.reset_index() # 重置索引

    return traindataset, testdataset

但是,在後面進行模型評估的時候發現模型精確度不是太高,比如說設置隨機數種子爲 1 時,精確度都不到 0.9,不過我把隨機數種子修改爲其他值時,比如說,隨機數種子設置爲 2 時,精確度會在 0.95 以上,我就在想爲什麼設置不同隨機數種子的模型之間的差別會像這樣子比較大,講道理,差別肯定是有的,但一般不會相差太大的,出現我這樣的情況肯定有一些原因,後來在調試當中我發現,有的隨機數種子會導致數據集劃分的不均勻:
在這裏插入圖片描述
我們可以看到隨機數種子爲 1 時,訓練數據中不同類別的數量不如隨機數種子爲 2 時均勻,這也導致模型精確度的較大差別:
在這裏插入圖片描述
因此,不能單純地直接劃分數據集,需要做一些改變,在這裏,我選擇了類似分層抽樣的方法,因爲我是按 8:2 劃分數據集,因此每個類別隨機劃分 40 個樣本給訓練集,10 個樣本給測試集,這樣子既達到了隨機劃分數據集的目的,又避免了數據集劃分的不均勻:

# 將數據集劃分爲訓練集和測試集
def subdatasetPartitioning(dataset):

    # 打亂索引
    index = [i for i in range(len(dataset))]
    np.random.seed(1)
    np.random.shuffle(index)

    # 以 8:2 劃分訓練集和測試集
    traindatasetlen = int(len(dataset) * 0.8)
    traindataset = dataset.loc[index[:traindatasetlen]]
    testdataset = dataset.loc[index[traindatasetlen:]]

    return traindataset, testdataset


# 類似於分層抽樣,每個類別隨機劃分同樣個數的樣本給訓練集
def datasetPartitioning(dataset):

    traindataset_list = []
    testdataset_list = []
    for i in range(3):
        subdataset = dataset.loc[i * 50 : (i + 1) * 50 - 1]
        subdataset = subdataset.reset_index() # 重置索引
        subtraindataset, subtestdataset = subdatasetPartitioning(subdataset)
        traindataset_list.append(subtraindataset)
        testdataset_list.append(subtestdataset)

    traindataset = pd.concat(traindataset_list, ignore_index=True) # 合併 DataFrame,ignore_index=True 表示忽略原索引,類似重置索引的效果
    testdataset = pd.concat(testdataset_list, ignore_index=True)

    return traindataset, testdataset

給大家看一看這樣更改後的模型精確度,可以看到效果非常好,我這個展示的是隨機數種子設爲 1 的情況,其他的大家也可以試一下,我覺得結果不會像剛纔那樣差別巨大了:
在這裏插入圖片描述

3 計算信息熵

之前都是關於數據集處理的,到這裏就要正式接觸 ID3 算法了,關於 ID3 算法以及信息熵和信息增益的概念,我相信看到這篇博文的同學都基本學過,沒學過網上也都有大量的教程可以去看看(這句話寫的我自己都噁心了,相信朋友們看到也噁心,什麼叫基本都學過,什麼叫過網上都有大量的教程可以去看看,所以如果時間充裕的話,我之後會寫一個關於決策樹的博客,然後把鏈接放在這裏,讓大家看的舒心一點,不過有一點是真的,網上寫的都是別人的,不管看書還是看教程只有自己學到的纔是自己的)。

關於信息熵,大家主要記得一點就差不多可以了,就是如果一個數據集的信息熵越小,說明這個數據集的純度越高,數據集中樣本越有可能屬於同一類別,在這裏,先給出信息熵的公式:

Ent(D)=k=1Ypklog2pkD pk  D  k k=1,2,,YY  Ent(D) = - \sum_{k=1}^{|Y|} p_k log_2 p_k \\ 其中,D ~ 表示當前數據集,p_k ~ 表示數據集 ~ D ~ 中第 ~ k ~ 類樣本所佔的比例(k = 1, 2, \ldots , |Y|),|Y| ~ 是類別數

然後,我們根據上面的公式寫出計算信息熵的代碼,給定數據集,返回其信息熵:

# 給定數據集,計算並返回其信息熵
def informationEntropy(dataset):
    entropysum = 0
    category_list = list(dataset["Class"])
    for category in set(dataset["Class"]):
        pk = category_list.count(category) / len(dataset)
        entropysum += pk * math.log(pk, 2)
    return (-1) * entropysum

4 計算劃分屬性的信息增益

我們已經知道,數據集的純度和信息熵息息相關,而 ID3 算法就是基於信息熵,該算法想要達到一個目的,就是將數據集劃分的越來越純,讓同一類別的樣本儘可能在一起,以達到分類的目的,在決策樹當中就是讓子結點的信息熵加起來要小於父節點的信息熵,信息熵的減少量就是信息增益,ID3 算法就是要讓信息增益越大越好,這裏給出屬性離散情況下的信息增益公式:

Gain(D,a)=Ent(D)v=1VDvDEnt(Dv)D a  V  {a1,a2,,aV} Dv  D  a  av  V  Gain(D , a) = Ent(D) - \sum_{v=1}^{|V|} \frac {|D^v|} {|D|} Ent(D^v) \\ 其中,D~爲當前數據集,a ~爲離散屬性,有~V~個可能的取值~\{a^1,a^2,\ldots,a^V\},\\ 而~D^v ~ 即是數據集~D~中在屬性~a~上取值爲~a^v~的樣本,對應劃分出的第~V~個分支節點

同一個數據集 D,劃分屬性 a 不同,對應的信息增益也不一樣,我們要做的就是找出信息增益最大的劃分屬性,然後根據它劃分數據集,生成分支節點,這將在下一步進行分析,現在根據上面的公式編寫代碼,給定數據集和離散類型屬性,返回根據該屬性劃分數據集得到的信息增益:

# 給定數據集和離散類型屬性,計算並返回根據該屬性劃分數據集得到的信息增益
def informationDiscreteGain(dataset, attribute):
    entropy = informationEntropy(dataset)
    entropysum = 0
    attribute_value_list = list(dataset[attribute])
    for attribute_value in set(dataset[attribute]):
        weight = attribute_value_list.count(attribute_value) / len(dataset)
        entropysum += weight * informationEntropy(dataset[dataset[attribute] == attribute_value])
    return entropy - entropysum

如果數據集中的屬性是離散的,那麼這一步基本就做完了,但是我們現在的數據集是鳶尾花卉數據集,它的數據的四個屬性均是連續屬性,所以我們要做不同的處理。

由於連續屬性的可取值數目不再有限,因此,不能直接根據連續屬性的可取值來對結點進行劃分,此時,我們需要使用連續屬性離散化技術,最簡單的策略是採用二分法(bi-partition)對連續屬性進行處理。

那麼二分法是如何操作的呢?二分法採用的是基於劃分點的方法,通過選取劃分點 t 將數據集 D 分爲子集 Dt 和 Dt+,其中 Dt 包含那些在屬性 a 上取值不大於 t 的樣本,而 Dt+ 則包含那些在屬性 a 上取值大於 t 的樣本。

那麼如何選取劃分點 t 呢?給點數據集 D 和連續屬性 a,假定 a 在 D 上出現了 n 個不同的取值,將這些值從小到大進行排序,記爲 { a1, a2, …, an },我們準備一個包含 n - 1 個元素的候選劃分點集合:

Ta={ai+ai+121in1} T_a = \{ \frac {a^i + a^{i+1}} 2 | 1 \leqslant i \leqslant n - 1 \}

也就是說,把每對相鄰屬性取值 ai 與 ai+1 的均值即區間 [ai, ai+1) 的中位點作爲候選劃分點,然後,我們就可以像考察離散值一樣來考察這些劃分點,選取最優的劃分點來進行數據集的劃分,接着,我們需要對信息增益公式改造一下,讓其能處理連續屬性:

Gain(D,a)=maxtTaGain(D,a,t)=maxtTa{Ent(D)λ{,+}DtλDEnt(Dtλ)}Gain(D,a,t) D  t  Gain(D, a) = \max_{t \in T_a} Gain(D, a, t) = \max_{t \in T_a} \{ Ent(D) - \sum_{\lambda \in \{ -, + \} } \frac {| D_t^{\lambda} |} {| D |} Ent(D_t^{\lambda}) \} \\ 其中,Gain(D, a, t) 是數據集 ~D~ 基於劃分點 ~t~ 二分後的信息增益

我們認爲使 Gain(D, a, t) 達到最大的劃分點即爲最優劃分點,好了,既然思路比較清晰了,那就繼續開始編寫代碼,給定數據集和連續類型屬性,返回根據該屬性劃分數據集得到的信息增益以及該屬性上的最優劃分點:

# 給定數據集和連續類型屬性,計算根據該屬性劃分數據集得到的信息增益,並返回在該屬性上的劃分點以及信息增益
def informationContinuousGain(dataset, attribute):
    entropy = informationEntropy(dataset)
    attribute_value_list = sorted(set(dataset[attribute]))
    if len(attribute_value_list) == 1:
        thresholds = [attribute_value_list[0]]
    else:
        thresholds = [(attribute_value_list[i] + attribute_value_list[i + 1]) / 2 for i in range(len(attribute_value_list) - 1)] # 候選劃分點集合
    
    threshold_entropysum_dict = {}
    for threshold in thresholds:
        lessthreshold = dataset[dataset[attribute] <= threshold]
        morethreshold = dataset[dataset[attribute] > threshold]
        lessweight = len(lessthreshold) / len(dataset)
        moreweight = len(morethreshold) / len(dataset)
        entropysum = lessweight * informationEntropy(lessthreshold) + moreweight * informationEntropy(morethreshold)
        threshold_entropysum_dict[threshold] = entropysum
        
    threshold_entropysum_sorted = sorted(threshold_entropysum_dict.items(), key=lambda item: item[1])
    minentropysum_threshold = threshold_entropysum_sorted[0][0]
    minentropysum = threshold_entropysum_sorted[0][1]
    return minentropysum_threshold, entropy - minentropysum

5 遞歸生成決策樹

信息熵和信息增益模塊都已經分別處理好了,現在到了最重要的一環了,我們要開始根據訓練集學習並構造決策樹,決策樹學習的目的是爲了產生一棵泛化能力強,即處理未見示例能力強的決策樹,其基本流程遵循簡單且直觀的分而治之(divide-and-conquer)策略,在這裏給出西瓜書上的一張圖:
在這裏插入圖片描述
這張圖我覺得脈絡非常清晰,我當時學習的時候還根據這張圖做了一張流程圖,這裏也給大家展示一下(我的這張圖曾經被吐槽畫得太 low 了,還是用 WPS 畫得,我的同學們都覺得太撈了這張圖,大家看到不要笑蛤):
在這裏插入圖片描述
現在,決策樹構造流程有了,那應該構造成什麼樣呢,如果真的用數據結構弄出樹形結構,我覺得有點麻煩,所以我採取了一個大佬的博客上的方法,構建一個決策樹字典,這樣即方便又直觀,這裏給出大佬博客地址:https://www.cnblogs.com/further-further-further/p/9429257.html,大家可以看一下,我覺得寫得非常好。

準備工作已經做好了,現在開始編寫代碼,根據數據集和屬性列表遞歸生成決策樹結點,返回決策樹模型字典:

# 計算數據集中類數量最多的類
def maxNumOutcome(dataset):
    category_list = list(dataset["Class"])
    category_dict = {}
    for category in set(dataset["Class"]):
        category_dict[category] = category_list.count(category)
    category_sorted = sorted(category_dict.items(), key=lambda item: item[1], reverse=True)
    return category_sorted[0][0]


# 遞歸生成決策樹結點,返回決策樹模型字典
def treeNodeGenerate(dataset, attribute_list):
    if len(set(dataset["Class"])) == 1:
        node = list(set(dataset["Class"]))[0]
    elif len(attribute_list) == 0 or sum([len(set(dataset[attribute])) - 1 for attribute in attribute_list]) == 0:
        node = maxNumOutcome(dataset)
    else:
        attribute_gain_dict = {}
        for attribute in attribute_list:
            threshold, attribute_gain = informationContinuousGain(dataset, attribute)
            attribute_gain_dict[attribute] = threshold, attribute_gain
        attribute_gain_sorted = sorted(attribute_gain_dict.items(), key=lambda item: item[1][1], reverse=True)
        maxgain_attribute = attribute_gain_sorted[0][0]
        maxgain_threshold = attribute_gain_sorted[0][1][0]

        son_node_attribute_list = attribute_list.copy()
        son_node_attribute_list.remove(maxgain_attribute)

        left_node_dataset = dataset[dataset[maxgain_attribute] <= maxgain_threshold]
        if len(left_node_dataset) == 0:
            leftnode = maxNumOutcome(dataset)
        else:
            leftnode = treeNodeGenerate(left_node_dataset, son_node_attribute_list)
        
        right_node_dataset = dataset[dataset[maxgain_attribute] > maxgain_threshold]
        if len(right_node_dataset) == 0:
            rightnode = maxNumOutcome(dataset)
        else:
            rightnode = treeNodeGenerate(right_node_dataset, son_node_attribute_list)
        
        if leftnode == rightnode:
            node = leftnode
        else:
            node = {}
            node[(maxgain_attribute, maxgain_threshold)] = {"<=":leftnode, ">":rightnode}

    return node

這是我隨機數種子設爲 1 時在訓練集上訓練出來的決策樹模型,大家要注意,不同隨機數種子劃分出的訓練集上構造的決策樹會有一定程度不同:
在這裏插入圖片描述

6 模型的預測與評估

決策樹模型訓練好了,到了最後一個版塊了,我們要使用訓練好的決策樹模型去預測測試集上的樣本,並對預測結果進行評估,這兩個模塊代碼都比較好寫,前者是通過遞歸得出樣本預測結果,後者我只寫了一個簡單的精確度評估指標:

# 預測一條數據的結果
def predictOne(tree_train_model, testdata):
    if type(tree_train_model) == str:
        predict_value = tree_train_model
    elif type(tree_train_model) == dict:
        key = list(tree_train_model)[0]
        if testdata[key[0]] <= key[1]:
            son_tree_train_model = tree_train_model[key]["<="]
        else:
            son_tree_train_model = tree_train_model[key][">"]
        predict_value = predictOne(son_tree_train_model, testdata)
    return predict_value


# 進行模型預測,返回預測結果列表
def predict(tree_train_model, testdataset):
    predict_list = []
    for i in range(len(testdataset)):
        predict_value = predictOne(tree_train_model, testdataset.loc[i])
        predict_list.append((testdataset.loc[i]["Class"], predict_value))
    return predict_list

這是我的預測結果列表,大家參照着看一下就好:
在這裏插入圖片描述
然後對預測模型進行精確度評估:

# 對預測模型進行精確度評估
def predictAccuracy(predict_list):
    predict_true_num = 0
    for bigram in predict_list:
        if bigram[0] == bigram[1]:
            predict_true_num += 1
    accuracy = predict_true_num / len(predict_list)
    return accuracy

得出模型評估結果,這個大家剛纔也已經看到了,不可思議的 1.0:
在這裏插入圖片描述

7 組合模塊形成完整代碼

最後,將前面的所有模塊進行組合,並添加 main 函數,得到完整代碼:

import pandas as pd
import math, sys, os
import numpy as np


# 給定數據集,計算並返回其信息熵
def informationEntropy(dataset):
    entropysum = 0
    category_list = list(dataset["Class"])
    for category in set(dataset["Class"]):
        pk = category_list.count(category) / len(dataset)
        entropysum += pk * math.log(pk, 2)
    return (-1) * entropysum


# 給定數據集和離散類型屬性,計算並返回根據該屬性劃分數據集得到的信息增益
def informationDiscreteGain(dataset, attribute):
    entropy = informationEntropy(dataset)
    entropysum = 0
    attribute_value_list = list(dataset[attribute])
    for attribute_value in set(dataset[attribute]):
        weight = attribute_value_list.count(attribute_value) / len(dataset)
        entropysum += weight * informationEntropy(dataset[dataset[attribute] == attribute_value])
    return entropy - entropysum


# 給定數據集和連續類型屬性,計算根據該屬性劃分數據集得到的信息增益,並返回在該屬性上的劃分點以及信息增益
def informationContinuousGain(dataset, attribute):
    entropy = informationEntropy(dataset)
    attribute_value_list = sorted(set(dataset[attribute]))
    if len(attribute_value_list) == 1:
        thresholds = [attribute_value_list[0]]
    else:
        thresholds = [(attribute_value_list[i] + attribute_value_list[i + 1]) / 2 for i in range(len(attribute_value_list) - 1)] # 候選劃分點集合
    
    threshold_entropysum_dict = {}
    for threshold in thresholds:
        lessthreshold = dataset[dataset[attribute] <= threshold]
        morethreshold = dataset[dataset[attribute] > threshold]
        lessweight = len(lessthreshold) / len(dataset)
        moreweight = len(morethreshold) / len(dataset)
        entropysum = lessweight * informationEntropy(lessthreshold) + moreweight * informationEntropy(morethreshold)
        threshold_entropysum_dict[threshold] = entropysum
        
    threshold_entropysum_sorted = sorted(threshold_entropysum_dict.items(), key=lambda item: item[1])
    minentropysum_threshold = threshold_entropysum_sorted[0][0]
    minentropysum = threshold_entropysum_sorted[0][1]
    return minentropysum_threshold, entropy - minentropysum


# 計算數據集中類數量最多的類
def maxNumOutcome(dataset):
    category_list = list(dataset["Class"])
    category_dict = {}
    for category in set(dataset["Class"]):
        category_dict[category] = category_list.count(category)
    category_sorted = sorted(category_dict.items(), key=lambda item: item[1], reverse=True)
    return category_sorted[0][0]


# 遞歸生成決策樹結點,返回決策樹模型字典
def treeNodeGenerate(dataset, attribute_list):
    if len(set(dataset["Class"])) == 1:
        node = list(set(dataset["Class"]))[0]
    elif len(attribute_list) == 0 or sum([len(set(dataset[attribute])) - 1 for attribute in attribute_list]) == 0:
        node = maxNumOutcome(dataset)
    else:
        attribute_gain_dict = {}
        for attribute in attribute_list:
            threshold, attribute_gain = informationContinuousGain(dataset, attribute)
            attribute_gain_dict[attribute] = threshold, attribute_gain
        attribute_gain_sorted = sorted(attribute_gain_dict.items(), key=lambda item: item[1][1], reverse=True)
        maxgain_attribute = attribute_gain_sorted[0][0]
        maxgain_threshold = attribute_gain_sorted[0][1][0]

        son_node_attribute_list = attribute_list.copy()
        son_node_attribute_list.remove(maxgain_attribute)

        left_node_dataset = dataset[dataset[maxgain_attribute] <= maxgain_threshold]
        if len(left_node_dataset) == 0:
            leftnode = maxNumOutcome(dataset)
        else:
            leftnode = treeNodeGenerate(left_node_dataset, son_node_attribute_list)
        
        right_node_dataset = dataset[dataset[maxgain_attribute] > maxgain_threshold]
        if len(right_node_dataset) == 0:
            rightnode = maxNumOutcome(dataset)
        else:
            rightnode = treeNodeGenerate(right_node_dataset, son_node_attribute_list)
        
        if leftnode == rightnode:
            node = leftnode
        else:
            node = {}
            node[(maxgain_attribute, maxgain_threshold)] = {"<=":leftnode, ">":rightnode}

    return node


# 預測一條數據的結果
def predictOne(tree_train_model, testdata):
    if type(tree_train_model) == str:
        predict_value = tree_train_model
    elif type(tree_train_model) == dict:
        key = list(tree_train_model)[0]
        if testdata[key[0]] <= key[1]:
            son_tree_train_model = tree_train_model[key]["<="]
        else:
            son_tree_train_model = tree_train_model[key][">"]
        predict_value = predictOne(son_tree_train_model, testdata)
    return predict_value


# 進行模型預測,返回預測結果列表
def predict(tree_train_model, testdataset):
    predict_list = []
    for i in range(len(testdataset)):
        predict_value = predictOne(tree_train_model, testdataset.loc[i])
        predict_list.append((testdataset.loc[i]["Class"], predict_value))
    return predict_list


# 對預測模型進行精確度評估
def predictAccuracy(predict_list):
    predict_true_num = 0
    for bigram in predict_list:
        if bigram[0] == bigram[1]:
            predict_true_num += 1
    accuracy = predict_true_num / len(predict_list)
    return accuracy


# 將數據集劃分爲訓練集和測試集
def subdatasetPartitioning(dataset):

    # 打亂索引
    index = [i for i in range(len(dataset))]
    np.random.seed(1)
    np.random.shuffle(index)

    # 以 8:2 劃分訓練集和測試集
    traindatasetlen = int(len(dataset) * 0.8)
    traindataset = dataset.loc[index[:traindatasetlen]]
    testdataset = dataset.loc[index[traindatasetlen:]]

    return traindataset, testdataset


# 類似於分層抽樣,每個類別劃分同樣個數的樣本給訓練集
def datasetPartitioning(dataset):

    traindataset_list = []
    testdataset_list = []
    for i in range(3):
        subdataset = dataset.loc[i * 50 : (i + 1) * 50 - 1]
        subdataset = subdataset.reset_index() # 重置索引
        subtraindataset, subtestdataset = subdatasetPartitioning(subdataset)
        traindataset_list.append(subtraindataset)
        testdataset_list.append(subtestdataset)

    traindataset = pd.concat(traindataset_list, ignore_index=True) # ignore_index=True 表示忽略原索引,類似重置索引的效果
    testdataset = pd.concat(testdataset_list, ignore_index=True)

    return traindataset, testdataset


if __name__ == "__main__":
    
    # 讀取數據
    if os.path.exists(r"E:\jupyter-notebook\datasets\iris.csv"):
        dataset = pd.read_csv(r"E:\jupyter-notebook\datasets\iris.csv")
    else:
        path = sys.argv[0]
        dataset = pd.read_csv(path.replace("DecisionTree.py", "iris.csv"))

    # 將數據集以 8:2 劃分爲訓練集和測試集(每一類別抽 40 個作訓練集)
    traindataset, testdataset =  datasetPartitioning(dataset)

    # 使用訓練集進行模型訓練
    attribute_list = ["SepalLength", "SepalWidth", "PetalLength", "PetalWidth"]
    tree_train_model = treeNodeGenerate(traindataset, attribute_list)
    print("The Dict of Trained Model:")
    print(tree_train_model, "\n")

    # 使用訓練好的模型在測試集上進行測試得到預測結果
    predict_list = predict(tree_train_model, testdataset)
    print("The List of Predicting Outcomes (Actual Label, Predicted Value) :")
    print(predict_list, "\n")

    # 對預測結果進行評估
    print("The Accuracy of Model Prediction: ", predictAccuracy(predict_list))

編寫完成後,運行得到最終結果:
在這裏插入圖片描述
好了本文到此就快結束了,以上就是我自己動手寫決策樹的想法和思路,大家參照一下即可,重要的還是經過自己的思考來編寫代碼,文章中還有很多不足和不正確的地方,歡迎大家指正(也請大家體諒,寫一篇博客真的挺累的,花的時間比我寫出代碼的時間還要長),我會盡快修改,之後我也會盡量完善本文,儘量寫得通俗易懂,畢竟如果我寫得大家看不懂那我還不如不寫,回家種田算了。

博文創作不易,轉載請註明本文地址:https://blog.csdn.net/qq_44009891/article/details/105787894

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