決策樹(ID3算法)

決策樹學習筆記

1、決策樹的概念

    顧名思義,決策樹是用來:根據已知的若干條件,來對事件作出判斷。從根節點到葉子節點,是將不同特徵不斷劃分的過程,最後將類別分出。

    在理論學習前,先了解下面一個例子:

    如下圖所示,根據“不浮出水面是否可以生存”和“是否有腳蹼”兩種特徵來判斷該海洋動物是否魚類。圖中有5個樣本,樣本中有兩種是魚類,三種不是魚類。

    可建立的特徵向量如下:

_data_set = [[1, 1, "yes"],
                 [1, 1, "yes"],
                 [1, 0, "no"],
                 [0, 1, "no"],
                 [0, 1, "no"]]
    _labels = ["no surfacing", "flippers"]
    _labels儲存的是特徵的實際含義,在建立決策樹時作爲分類節點名稱存在,no surfaciing就是不需要浮出水面是否可以生存。最後一列儲存的是對應特徵向量的標籤。後續的代碼中都遵循這一約定。


 2、用ID3算法來構造決策樹

    ID3算法將信息增益做貪心算法來劃分算法,總是挑選是的信息增益最大的特徵來劃分數據,使得數據更加有序。

    2.1信息增益

    熵:熵是信息的期望,一般表示信息的混亂、無序程度

    信息的定義如下:


    於是信息的期望,熵的定義爲:

                                        

    信息增益指的是熵減小的量,也就是,數據集變得有序了多少。

    下面給出熵的計算代碼,直接輸入上面給出的數據集可以得到信息熵

# 計算香農熵
def calc_shannon_ent(dataset):
    n = len(dataset)
    label_counts = {}
    # 統計每個類別出現的次數
    for feature in dataset:
        label = feature[-1]
        if label not in label_counts:
            label_counts[label] = 0     # 創建該元素並清零
        label_counts[label] += 1
    entropy = 0
    for key in label_counts:
        p = float(label_counts[key]) / n  # 計算類概率,或者說類在所有數據中的比例
        entropy -= p * log(p, 2)
    return entropy

    再次注意:特徵向量最後一維是類別標籤

    2.2數據集劃分

    構造樹的過程中,要將數據進行劃分,爲了方便,寫一個子函數專門做數據劃分,劃分的原則是:挑出指定維度上和指定值相等的所有特徵向量,並將該維去除後返回

# 在axis維度上對dataset進行劃分,抽取axis維度上等於value的特徵,這裏沒有用pandas,否則不需要這麼麻煩
def splite_dataset(dataset, axis, value):
    splt_dset = []
    for f in dataset:
        if f[axis] == value:
            reduce_fv = f[:axis]
            reduce_fv.extend(f[axis+1:])
            splt_dset.append(reduce_fv)
    return splt_dset
    2.3選擇最好的劃分特徵

    遍歷每一個特徵,嘗試使用每一個特徵劃分數據集,並計算對應的信息增益,選擇最大的那一個特徵來劃分數據。值得注意的是,這裏並不是使用二分類劃分,而是,對應的特徵有多少個值就將數據集劃分爲幾個子集。在海洋動物的例子中,只是恰好兩個特徵都只有兩個值。

# 通過遍歷所有的特徵,求取熵最小的劃分方式
# 返回劃分數據最好的特徵,和最大的信息增益
def min_entropy_split_feature(dataset):
    f_n = len(dataset[0]) - 1
    base_entropy = calc_shannon_ent(dataset)
    best_info_gain = 0.0
    #print("entropy calc:"+str(dataset))
    best_feature = -1
    for i in range(f_n):
        f_list = [feature[i] for feature in dataset]
        unique_values = set(f_list)
        new_entropy = 0.0
        for value in unique_values:
            sub_dataset = splite_dataset(dataset, i, value)
            pro = len(sub_dataset) / float(len(dataset))
            new_entropy += pro * calc_shannon_ent(sub_dataset)
        info_gain = base_entropy - new_entropy  # 信息增益是信息熵的減小量
        if info_gain > best_info_gain:
            best_info_gain = info_gain
            best_feature = i
    return best_feature, best_info_gain

    2.4遞歸構建決策樹

    2.3中給出了劃分數據集的方法,可以使得數據局部整體上最有序,劃分得到若干個子數據集中依然可以繼續劃分,使用同樣的方法使得子數據集最有序,依次遞歸進行,直到最底層子數據集中只有一個類別

# 遞歸構建決策樹
def create_tree(dataset, __labels):
    labels = __labels.copy()
    classlist = [f[-1] for f in dataset]
    # 只剩下一個類了,因此返回類名稱
    if classlist.count(classlist[0]) == len(classlist):
        return classlist[0]
    # 數據集中只剩下最後一列了,也就是所有的特徵都用完了,沒法再向下分類了
    # 這時候如果數據集中有多個類,那就認爲是出現最多的那個類
    # 實際中應該是,所給定的特徵無法將數據進行完全分類導致的
    if len(dataset[0]) == 1:
        # print("******")
        return majority_cnt(classlist)
    bestfeature = min_entropy_split_feature(dataset)[0]
    # if bestfeature >= len(labels):
    #     print("**************index out of range")
    #     print(dataset)
    #     print(labels)
    #     print(bestfeature)
    bestf_label = labels[bestfeature]
    newtree = {bestf_label: {}}
    del labels[bestfeature]
    f_values = [f[bestfeature] for f in dataset]  # 找出bestfeature全部取值
    unique_f_values = set(f_values)
    for v in unique_f_values:
        sublabels = labels[:]
        _splitedtree = splite_dataset(dataset, bestfeature, v)
        # print("xxxxxxxxxxxxxx seperate:")
        # print("best fv:" + str(bestfeature) + " v:" + str(v))
        # print(_splitedtree)
        # print(labels)
        newtree[bestf_label][v] = create_tree(_splitedtree.copy(), sublabels)
    return newtree

    上面還用了一個子函數,是當數據集只剩下一列(最後一列,也就是類別),但是無法完全分類,就返回剩下的數據集中出現最多的那個類別

# 計算數據集中的主要類別,這裏計算是魚的多還是不是魚的多,其實就是判斷yes多還是no多
# 其他應用中可能會出現多個類別標籤,因此這是一個通用函數
def majority_cnt(classlist):
    classcount = {}
    for label in classlist:
        if label not in classcount.keys():  # 字典的鍵中不包含label
            classcount[label] = 0
        classcount[label] += 1
    # classcount.items()是以list形式返回字典的鍵值:dict_items([('yes', 2), ('no', 4)])
    # key中指定值獲取函數,x[1]表示用鍵值對第二個參數來排序
    # reverse表示降序排序,默認是ascending
    sorted_class_count = sorted(classcount.items(), key=lambda x: x[1], reverse=True)
    return sorted_class_count[0][0]  # 返回出現次數最多的類的標籤

    最後構建得到的決策樹輸入所示:

                            

3、用決策樹來做分類

    上面構造了決策樹,通過使用create_tree方法可以得到字典儲存的決策樹。輸入一個新的特徵向量,利用決策樹,沿着樹從上到下,可以得到分類。下面給出分類代碼:

# 用構建好的決策樹來進行分類
def classify(input_tree, feature_labels, test_fv):
    classlabel = "none"

    first_label = list(input_tree.keys())[0]  # 獲取輸入字典中的第一個鍵
    new_dict = input_tree[first_label]        #
    feature_index = feature_labels.index(first_label)  # 獲取first_label是第幾個特徵
    for key in new_dict.keys():
        if test_fv[feature_index] == key:  # 注意,key是特徵的值
            if type(new_dict[key]).__name__ == "dict":
                classlabel = classify(new_dict[key], feature_labels, test_fv)
            else:
                classlabel = new_dict[key]
    return classlabel
    對於數據集來說,字典的根節點總是儲存了本次劃分數據集的特徵,因此代碼中總是先獲取樹根鍵值,然後根據該鍵獲取特徵編號index,在for循環中,遍歷該特徵的所有取值,計算得到子數據集進行遞歸,如果子數據集已經不是一個字典,則認爲遍歷已經到達最底層,最底層儲存了類別標籤,返回類別標籤,完成分類。

4總結

    構建決策樹可以用來做分類,其他用途尚不清楚。使用了貪心法,每次劃分都是選擇信息增益最大的,推測這樣不一定能得到全局最大增益。除了ID3算法構造決策樹,較爲流行的還有C4.5和CART算法,後面繼續學習。

    決策樹做分類似乎只能對簡單特徵分類,如果出現長度、寬度、距離等浮點數據時候無法分類,暫時不知道怎麼處理。

參考文獻:《機器學習實戰》



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