決策樹學習筆記
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算法,後面繼續學習。
決策樹做分類似乎只能對簡單特徵分類,如果出現長度、寬度、距離等浮點數據時候無法分類,暫時不知道怎麼處理。
參考文獻:《機器學習實戰》