如圖,為使用到的公式,信息熵表明樣本的混亂程度,增益表示熵減少了,即樣本開始分類,增益率是為了平衡增益准則對可取值較多的屬性的偏好,同時增益率帶來了對可取值偏小的屬性的偏好,實際中,先用增益進行篩選,選取大於增益平均值的,然后再選取其中增益率最高的。
以下代碼純粹手寫,未參考其他人代碼,如果問題,請不吝賜教。
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函數。其次,這是個簡陋版的實現,有很多待優化的地方,如連續值處理、缺失值處理、剪枝防止過擬合,樹的創建使用的是遞歸(樣本大導致棧溢出,改成隊列實現較好),也有基於基尼指數的實現,還有多變量決策樹(可實現復雜的分類邊界)。