1. 程式人生 > >決策樹(基於增益率)之python實現

決策樹(基於增益率)之python實現

如圖,為使用到的公式,資訊熵表明樣本的混亂程度,增益表示熵減少了,即樣本開始分類,增益率是為了平衡增益準則對可取值較多的屬性的偏好,同時增益率帶來了對可取值偏小的屬性的偏好,實際中,先用增益進行篩選,選取大於增益平均值的,然後再選取其中增益率最高的。

以下程式碼純粹手寫,未參考其他人程式碼,如果問題,請不吝賜教。

1,計算資訊熵的函式

import numpy as np
# 計算資訊熵 # data:like np.array # data.shape=(num_data,data_features+1) 即屬性與label放一起了 def entropy(data,num_class): class_set=list(set(data[:,-1])) result=0 length=len(data) # 這裡修改一下,不使用num_class for i in range(len(class_set)): l=len(data[data[:,-1]==class_set[i]]) p=l/length result-=p*np.log2(p) return result

2,計算增益及屬性a的固有值(IV)

# 計算不同屬性的資訊增益
# detail_features:特徵構成的list,每個特徵的可取值構成list元素,即也是list
def calculate_gain(data,detail_features,num_class):
  '''返回各屬性對應的資訊增益及平均值''' result=[] ent_data=entropy(data,num_class) for i in range(len(detail_features)): res=ent_data for j in range(len(detail_features[i])): part_data=data[data[:,i]==detail_features[i][j]] length=len(part_data) res-=length*entropy(part_data,num_class)/len(data) result.append(res) return result,np.array(result).mean() # 計算某個屬性的固有值 def IVa(data,attr_index): attr_values=list(set(data[:,attr_index])) v=len(attr_values) res=0 for i in range(v): part_data=data[data[:,attr_index]==attr_values[i]] p=len(part_data)/len(data) res-=p*np.log2(p) return res

3,構建節點類,以便構建樹

class Node:
    def __init__(self,key,childs):
        self.childs=[]
        self.key=key
    def add_node(self,node):
        self.childs.append(node)

4,構建樹

# 判斷資料是否在所有屬性的取值都一樣,以致無法劃分
def same_data(data,attrs):
    for i in range(len(attrs)):
        if len(set(data[:,i]))>1:
            return False
    return True

# attrs:屬性的具體形式
def create_tree(data,attrs,num_class,root):
#     注意這裡3個退出條件
    #     1,如果資料為空,不能劃分,此時這個葉節點不知標記為哪個分類了
    if len(data)==0:
        return
#   2,如果屬性集為空,或所有樣本在所有屬性的取值相同,無法劃分,返回樣本最多的類別
    if len(attrs)==0 or same_data(data,attrs):
        class_set=list(set(data[:,-1]))
        max_len=0
        index=0
        for i in range(len(class_set)):
            if len(data[data[:,-1]==class_set[i]])>max_len:
                max_len=len(data[data[:,-1]==class_set[i]])
                index=i
        root.key=root.key+class_set[index]
        return 
#     3,如果當前節點包含同一類的樣本,無需劃分
    if len(set(data[:,-1]))==1:
        root.key=root.key+data[0,-1]
        return
    ent=entropy(data,num_class)
    gain_result,mean=calculate_gain(data,attrs,num_class)
    max=0
    max_index=-1
#     求增益率最大
    for i in range(len(gain_result)):
        if gain_result[i]>=mean:
            iva=IVa(data,i)
            if gain_result[i]/iva>max:
                max=gain_result[i]/iva
                max_index=i
    for j in range(len(attrs[max_index])):
        part_data=data[data[:,max_index]==attrs[max_index][j]]
#         刪除該列特徵
        part_data=np.delete(part_data,max_index,axis=1)
#         新增節點
        root.add_node(Node(key=attrs[max_index][j],childs=[]))
#         刪除某一類已判斷屬性
        new_attrs=attrs[0:max_index]
        new_attrs.extend(attrs[max_index+1:])
        create_tree(part_data,new_attrs,num_class,root.childs[j])        

5,使用西瓜資料集2.0測試,資料這裡就手寫了,比較少

def createDataSet():
    """
    建立測試的資料集
    :return:
    """
    dataSet = [
        # 1
        ['青綠', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', '好瓜'],
        # 2
        ['烏黑', '蜷縮', '沉悶', '清晰', '凹陷', '硬滑', '好瓜'],
        # 3
        ['烏黑', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', '好瓜'],
        # 4
        ['青綠', '蜷縮', '沉悶', '清晰', '凹陷', '硬滑', '好瓜'],
        # 5
        ['淺白', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', '好瓜'],
        # 6
        ['青綠', '稍蜷', '濁響', '清晰', '稍凹', '軟粘', '好瓜'],
        # 7
        ['烏黑', '稍蜷', '濁響', '稍糊', '稍凹', '軟粘', '好瓜'],
        # 8
        ['烏黑', '稍蜷', '濁響', '清晰', '稍凹', '硬滑', '好瓜'],

        # ----------------------------------------------------
        # 9
        ['烏黑', '稍蜷', '沉悶', '稍糊', '稍凹', '硬滑', '壞瓜'],
        # 10
        ['青綠', '硬挺', '清脆', '清晰', '平坦', '軟粘', '壞瓜'],
        # 11
        ['淺白', '硬挺', '清脆', '模糊', '平坦', '硬滑', '壞瓜'],
        # 12
        ['淺白', '蜷縮', '濁響', '模糊', '平坦', '軟粘', '壞瓜'],
        # 13
        ['青綠', '稍蜷', '濁響', '稍糊', '凹陷', '硬滑', '壞瓜'],
        # 14
        ['淺白', '稍蜷', '沉悶', '稍糊', '凹陷', '硬滑', '壞瓜'],
        # 15
        ['烏黑', '稍蜷', '濁響', '清晰', '稍凹', '軟粘', '壞瓜'],
        # 16
        ['淺白', '蜷縮', '濁響', '模糊', '平坦', '硬滑', '壞瓜'],
        # 17
        ['青綠', '蜷縮', '沉悶', '稍糊', '稍凹', '硬滑', '壞瓜']
    ]

    # 特徵值列表
    labels = ['色澤', '根蒂', '敲擊', '紋理', '臍部', '觸感']

    # 特徵對應的所有可能的情況
    labels_full = []

    for i in range(len(labels)):
        items=[item[i] for item in dataSet]
        uniqueLabel = set(items)
        labels_full.append(list(uniqueLabel))
    return np.array(dataSet), labels, labels_full

6,開始構建樹

dataset,labels,labels_full=createDataSet()

root=Node('',[])
create_tree(dataset, labels_full, 2, root)

7,列印樹結構

def print_root(n,root):print(n,root.key)
    for node in root.childs:
        print_root(n+1,node)
print_root(0,root)      

列印結果為:數字表示層次

0 
1 模糊壞瓜
1 稍糊
2 硬滑壞瓜
2 軟粘好瓜
1 清晰
2 硬滑好瓜
2 軟粘
3 青綠
4 稍蜷好瓜
4 蜷縮
4 硬挺壞瓜
3 烏黑壞瓜
3 淺白

8,繪製樹形結構,這裡我就手動繪製了。圖中有2個葉節點為空白,即模型不知道該推測其為好瓜還是壞瓜。這裡我暫時沒有好的思路解決,只能隨機處理?

9,總結

首先,暫時沒有新增predict函式。其次,這是個簡陋版的實現,有很多待優化的地方,如連續值處理、缺失值處理、剪枝防止過擬合,樹的建立使用的是遞迴(樣本大導致棧溢位,改成佇列實現較好),也有基於基尼指數的實現,還有多變數決策樹(可實現複雜的分類邊界