決策樹----熵、資訊增益(ID3)、C4.、多方式原始碼實現
談決策樹之前先做一些預備性知識:
1.什麼是資訊?如何衡量資訊的多少?怎麼衡量?
資訊:從廣義上講,是事物運動時發出的訊號所帶來的訊息,是事物存在方式和運動規律的一種表現形式。不同的事物具有不同的存在方式和運動規律,從而構成了各種事物的不同特徵。資訊普遍存在於自然界、社會界以及人的思維之中,是客觀事物本質特徵千差萬別的反應。資訊分為兩大類:自然資訊與社會資訊。
訊息:訊息是資訊的具體反映形式,資訊是訊息的實質內容,是能給人帶來新認識的訊息。不同的訊息中所包含的資訊量是不同的,有的訊息中包含的資訊量大一些,有的小一些,有的對某些人來說甚至不包含資訊。只有哪些為接受者瞭解、認識而且事先不知道的訊息中才蘊含著資訊。
好,上面提到了資訊量大一些和資訊量小一些,那麼什麼才是資訊量大和資訊小呢?在現實生活中如果可以預料到事情會發生,例如明天太陽會從東邊升起、明天有24小時,晚上會睡覺,,,等,從常理來理解這些大家都知道會發生,是不是意味著這些事發生的概率很大,因為發生的概率很大(常識,大家都知道的事情),給我們帶來的新的資訊並沒有那麼多,所以資訊量少,如果你購買彩票並中獎、太陽從西邊出來了,晚上不睡覺了,,,等等,這些是不是發生的概率很低,和我們經驗不符合,並且帶給我們額外的巨大資訊,如這個號中獎的概率很大,太陽從西邊出來的原因,晚上不睡覺的原因等,因此這樣的事件發生概率很低,但是帶給我們的資訊量卻是很大的,知道了資訊量了,如何衡量他呢?如何定性的分析他呢?這個問題香濃很好的解決了
資訊量的定義:
一個事件,發生的概率為,那麼資訊量定義為:
註釋:為什麼要這樣定義呢?上面說了,發生的概率越大,資訊量越小,從定義可以看出可以寫為,那麼概率越大,他的倒數就越小,取對數後也會很小,因此和上面說的相符合,如明天太陽會從東邊升起,發生的概率很大,給我們新的資訊很小,因此資訊量很小,通過資訊量公式可以發現很小,因此從數學角度給出了資訊量的定義。
在資料處理中,通常的解釋為待分類的事物可能劃分為多個分類中,就是待分類的資料,即為分到某一類的概率。
香濃熵:香濃熵的定義是從資訊需要多少二進位制資料表示定義的,這裡只從資料的意義進行解釋。
從定義試可以看出,H是概率論方面的求期望的公式,所以資訊熵也是計算所有類別的所有可能值包含的資訊期望
因此熵是衡量事物的無序程度的度量方式,大家可以這樣理解熵,在現在世界中有序的狀態,例如太陽從東方升起,晚上睡覺等,因為都是有次序的發生,因此資訊量少,期望值小,無序狀態是指不確定性很高,每種狀態都可能發生,例如一滴墨水滴入清水中,剛開始熵值很大,過段時間後墨水均勻的在水中,此時熵值趨於穩定並達到最小,也就是說,熵總是向著有序的方向進行,所以熵是衡量事物無序程度的衡量指標,有序和無序在分類中可以表示為已分類和未分類資料,未分類時,雜亂無章,熵最大,分類後熵減小,因為未分類可以看做資料無序化,分類後開始有序化,因此資料處理也可以引入熵的概念即:
未處理的資料是無序化的,經過分類後資料變得有序化,所有熵會變低。
資訊增益(ID3演算法):
資訊增益理解起來其實很簡單,未經分類的資料是無序化、不確定的也就是熵最大的時候,此時的熵使用表示,如果根據某一個特徵A進行分類後資料的熵為,此時的熵比要小,因為分為某一類後資料開始有序化了或者確定了,那麼定義資訊增益為:
由上面的解釋可知資訊增益其實就是通過資料分類後,資料的熵變低了(因為分類代表有序),而根據某個特徵使熵降低了多少稱為資訊增益,強調一下增益的概念,其實是指資料通過這個特徵有序化的程度即熵的變化量。
下面給出韓家偉的資料探勘概念與技術中的例子結合理解:
介紹一下資料,表8.1共有14個數據樣本,共有4個特徵分別為age、income、student、credit_rating。類別為是否購買電腦即buys_computer
解釋一下就是資訊熵,並計算了初始無序化的熵,無序化的熵也是根據類計算,有兩個類分別為yes和no,其中
yes出現9個數據樣本,no出現5個數據樣本,所以概率為yes:9/14,no:5/14,按照公式計算熵即可,計算結果為0.940,此時再根據特徵計算熵,文中選取的是age特徵進行計算,即分別根據youth、midde_age、senior按照分類yes和no計算熵在求和即可,例如youth中有5個數據樣本,在這五個樣本中兩個yes三個no,一次類推計算的到
和原始資料的熵對比發現減小了,下面看看減小了多少即資訊增益:
同理可得:
由上面可知,使資訊增益最大的是特徵age的劃分,其次是student、、、那麼決策樹是不是可以根據這種資訊增益的方法進行分類呢,每一次都選擇資訊增益最大的的特徵進行分支,這樣不停的迭代下去,直到分類結束,以此使資料達到最佳有序化即熵最小,這就是ID3演算法了。
資訊增益總結:
對於待劃分的資料集D,其 entroy(前)是一定的,但是劃分之後的熵 entroy(後)是不定的,entroy(後)越小說明使用此特徵劃分得到的子集的不確定性越小(也就是純度越高(就是類別很少。當只是一類時,純度達到做大),也就是資料有序化更好,即確定性的越多),因此 entroy(前) - entroy(後)差異越大,說明使用當前特徵劃分資料集D的話,其純度上升的更快。而我們在構建最優的決策樹的時候總希望能更快速到達純度更高的集合,這一點可以參考優化演算法中的梯度下降演算法,每一步沿著負梯度方法最小化損失函式的原因就是負梯度方向是函式值減小最快的方向。同理:在決策樹構建的過程中我們總是希望集合往最快到達純度更高的子集合方向發展,因此我們總是選擇使得資訊增益最大的特徵來劃分當前資料集D。
但是ID3演算法的缺點是:資訊增益偏向取值較多的特徵
原因:當特徵的取值較多時,根據此特徵劃分更容易得到純度更高的子集,因此劃分之後的熵更低,由於劃分前的熵是一定的,因此資訊增益更大,因此資訊增益比較 偏向取值較多的特徵。
解決辦法是引入增益率(演算法):
資訊增益率 = 懲罰引數 * 資訊增益
具體參考韓家偉的書《資料探勘:概念與技術》
還有另外的分類指標是基於基尼指數的,在CART中使用,這個演算法很簡單,和基於熵的來說,該演算法具有的優勢是計算量少,效果和資訊增益差不多。下面對比一下:
ID3(Iterative Dichotomiser 3)由 Ross Quinlan 在1986年提出。該演算法建立一個多路樹,找到每個節點(即以貪心的方式)分類特徵,這將產生分類目標的最大資訊增益。決策樹發展到其最大尺寸,然後通常利用剪枝來提高樹對未知資料的泛華能力。
C4.5 是 ID3 的後繼者,並且通過動態定義將連續屬性值分割成一組離散間隔的離散屬性(基於數字變數),消除了特徵必須被明確分類的限制。C4.5 將訓練的樹(即,ID3演算法的輸出)轉換成 if-then 規則的集合。然後評估每個規則的這些準確性,以確定應用它們的順序。如果規則的準確性沒有改變,則需要決策樹的樹枝來解決。
C5.0 是 Quinlan 根據專有許可證釋出的最新版本。它使用更少的記憶體,並建立比 C4.5 更小的規則集,同時更準確。
CART(Classification and Regression Trees (分類和迴歸樹))與 C4.5 非常相似,但它不同之處在於它支援數值目標變數(迴歸),並且不計算規則集。CART 使用在每個節點產生最大資訊增益的特徵和閾值來構造二叉樹。
下面開始迎來主角決策樹:
決策樹的定義很簡單,在這裡先舉一個簡單的例子,後面再舉一個複雜的例子,並給出實現程式碼。
簡單的例子來源機器學習實戰郵件分類問題:
我們經常使用決策樹處理分類問題,它的過程類似二十個問題的遊戲:參與遊戲的一方在腦海裡想某個事物,其他參與者向他提出問題,只允許提20個問 題,問題的答案也只能用對或錯回答。問問題的人通過推斷分解,逐步縮小帶猜測事物的範圍。
如圖1所示的流程圖就是一個決策樹,長方形代表判斷模組(decision block),橢圓形代表終止模組(terminating block),表示已經得出結論,可以終止執行。從判斷模組引出的左右箭頭稱作分支(branch),它可以到達另一個判斷模組或終止模組。
圖 1構造了一個假象的郵件分類系統,它首先檢測傳送郵件域名地址。如果地址為myEmployer.com,則將其放在分類"無聊時需要閱讀的郵件"中。如 果郵件不是來自這個域名,則檢查內容是否包括單詞曲棍球,如果包含則將郵件歸類到"需要及時處理的朋友郵件",否則將郵件歸類到"無須閱讀的垃圾郵件"。
這個決策樹的目的是分類郵件,可以把郵件分為3類,分別為:一類是無聊時閱讀的郵件、一類是需要及時處理的郵件、一類是無需閱讀的垃圾郵件。既然是分類郵件,就需要有一個依據進行分類,該決策樹首先通過郵件地址進行分類,即先判斷地址是否為myEmployer.com的地址(當然這只是例子,現實中可能有很多這樣的郵件地址),如果是則分為無聊時閱讀,到此分類結束了。如果不是這個郵件地址,則需要繼續判斷根據依據分為哪一類,這個例子給的依據是包含單詞“曲棍球”的郵件為依據進行分類,如果包含則屬於及時處理的郵件,反之都屬於垃圾郵件。到此分類結束了,從這個例子中,我們可以看到決策樹的重點是通過某個特徵進行分類判斷的,現在難點是如何在適當的節點找到適合的特徵進行分類,為了找到適合的特徵,我們引入上面提到的資訊增益(ID3演算法)進行分類,首先計算各個特徵的資訊增益,把當前資訊增益最大的特徵作為判斷依據即節點,每一次節點都需要重新計算剩餘的特徵資訊增益,以此下去進行分類,直到分類結束。當然選擇依據(特徵)也可以根據其他演算法如基尼不存度,原理是類似的。
下面貼出機器學習實戰的手寫程式碼,並給出了詳細的註釋,且執行條件為Python3.6.5,課本上執行環境是Python2.7,中間出錯的均已修改完畢:
#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 趙守風
# File name: trees.py
# Time:2018/9/10
# 本程式碼是在Python3.6.5版本執行
from math import log
import operator
def createdataset():
dataset = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing', 'flipper']
print(dataset, labels)
return dataset, labels
# 計算資料中的香濃熵: H = -∑p(x)log2 p(x)
def calcshannongent(dataset):
numentries = len(dataset)
labelcounts = {} # 建立標籤字典
for featvec in dataset:
currentlabel = featvec[-1] # 資料的最後一列是標籤作為字典的鍵值
if currentlabel not in labelcounts.keys(): # 記錄屬於該標籤資料的次數,後面計算熵使用,
labelcounts[currentlabel] = 0 # 如果字典裡沒有這個標籤即鍵值,則建立並賦值為0,如果存在該標籤則加1
labelcounts[currentlabel] += 1
shannonent = 0.0 # 計算熵值
for key in labelcounts:
prob = float(labelcounts[key]) / numentries # 以每個標籤出現的頻次為概率計算熵值
shannonent -= prob * log(prob, 2)
return shannonent
# 按照給定特徵劃分資料集
def splitdataset(dataset, axis, value): # dataset為原始輸入資料,axis是原始資料的特徵位置,value是資料對應的特徵值
retdataset = []
for featvec in dataset: # 迭代dataset中的資料
if featvec[axis] == value: # 通過迭代判斷每個資料特徵位置的資料和給定的特徵值是否一致
reducedfeatvec = featvec[:axis] # 如果條件成立,那麼就把該位置前面的資料儲存
reducedfeatvec.extend(featvec[axis+1:]) # 把該位置後面的資料儲存,通過這兩句就可以把該特徵位置刪除
retdataset.append(reducedfeatvec) # 把保留下來的資料,儲存在新的列表裡作為一個元素
'''下面有例子解釋一下上面的三句程式碼
trees.splitdataset(mydata, 0, 1),輸入的資料為mydata, 特徵位置為0即axis, 資料特徵值為1
執行後的資料
原始資料: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
劃分好的資料: [[1, 'yes'], [1, 'yes'], [0, 'no']]
原始資料是一個列表,這個列表有5個元素,而每個元素又是一個列表,每個元素列表中又有三個元素
featvec[axis] == value這句程式碼的意思是featvec從dataset中獲得元素後,例如為第一個元素[1, 1, 'yes'],
此時featvec[axis]是指該元素的第零個位置資料即為1,value為給定的特徵劃分值為1,此時if成立,執行下面的程式
reducedfeatvec = featvec[:axis],列表的切片,不會的請檢視列表的切片,featvec[:axis]是指把axis位置前面的資料傳給reducedfeatvec
,而featvec[axis+1:]是把特徵位置後面的資料都儲存下來如[1, 'yes'],把儲存下來,簡單說 這兩句意思就是把axis位置刪除
把重新組建的資料傳給retdata
'''
return retdataset
# 選擇資料集劃分方式,主要通過資訊增益進行劃分
def choose_bestfeature_to_split(dataset):
num_features = len(dataset[0]) - 1 # 計算每個元素有多少特徵
print(num_features) # 只計算前兩個數值特徵
base_entropy = calcshannongent(dataset) # 計算基礎香濃熵
best_info_gain = 0.0
best_feature = -1
# 下面為計算資訊增益做準備工作
for i in range(num_features):
feat_list = [example[i] for example in dataset] # 利用列表推倒,把每個元素資料的特徵提取出來,並建立新列表
print('feat_list', feat_list) # 打印出來發現是feat_list [1, 1, 1, 0, 0],即表明把dataset的第0號位置的特徵提取出來
unique_vals = set(feat_list) # 使用集合元素資料中重複的特徵合併
print('unique_vals', unique_vals) # 把feat_list [1, 1, 1, 0, 0]重合的合併為unique_vals {0, 1}
new_entropy = 0.0
for value in unique_vals:
subdataset = splitdataset(dataset, i, value) # 根據特徵把同類的資料歸類,並計算香濃熵
prob = len(subdataset)/float(len(dataset)) # 計算概率
new_entropy += prob * calcshannongent(subdataset) # 計算 新熵
info_gain = base_entropy - new_entropy # 計算資訊增益
if (info_gain > best_info_gain): # 找出資訊增益最大的特徵
best_info_gain = info_gain
best_feature = i
return best_feature
# 多數表決,和K-近鄰類似的作用
def majority_cnt(classlist):
classcount = {}
for vote in classlist:
if vote not in classcount.keys():
classcount[vote] = 0
classcount[vote] += 1
sortedclasscount = sorted(classcount.items(), key=operator.itemgetter(1), reverse=True)
# 返回classlist列表中資料出現次數最多的元素
return sortedclasscount[0][0]
# 建立決策樹
def createtree(dataset, labels):
''' 本次結合輸入資料進行講解,詳細探討該程式碼是如何實現決策樹的
dataset = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing', 'flipper']
其中輸入的資料集是dataset有5個元素資料,每個元素有兩個特徵和一個類別,前兩列的特徵分別為'no surfacing', 'flipper'
類別為是否是魚類,是的話為‘yes’,反之為‘no'
'''
classlist = [example[-1] for example in dataset]
# 該句程式碼的意思是,把元素資料的分類資訊提取出來,此時classlist = ['yes', 'yes', 'no', 'no', 'no']
if classlist.count(classlist[0]) == len(classlist):
return classlist[0]
'''
# 第一個遞迴停止條件,如果classlist中的資訊都為同一類,則說明分類已經是純淨的了,無須再劃分,返回類別就好
# 如classlist = ['no', 'no', 'no'],此時classlist[0]出現三次,而列表長度也為3,等式成立執行if語句,即返回類別’no‘
'''
if len(dataset[0]) == 1: # 使用完了所有的特徵,但還是無法將資料劃分為唯一類別的分組,因此就挑選出現次數最多的類別進行分類
return majority_cnt(classlist)
'''
# 第二個遞迴停止條件是根據特徵判斷,因為每次根據特徵劃分時,後面就會刪除該特徵,如果遍歷完所有特徵以後發現classlist還是不純淨,
# 那麼就通過出現的次數劃分。如剛開始dataset[0] = [1, 1, 'yes'],有兩個特徵,一個分類標籤,當每根據一個特徵進行分類結束時就會
# 刪除該特徵,最後只剩下dataset[0] = ['yes'],無法再根據特徵進行分類,此時,根據分類列表中把該元素劃分到出現類別最多的次數的
# 分類中,如classlist = ['no', 'yes', 'yes','yes'],此時‘no’出現一次,‘yes’出現三次,因此把該類分為‘yes’,其中通過函式
# majority_cnt()進行統計。
'''
bestfeat = choose_bestfeature_to_split(dataset) # 使用資訊增益返回最好的劃分特徵的列表位置資訊
# 分類的標準是通過ID3演算法即資訊增益進行劃分,返回的是使資訊增益增加最大的特徵
bestfeatlabel = labels[bestfeat]
# 提出該最優特徵 第一次特徵為'no surfacing'
mytree = {bestfeatlabel: {}}
# 以當前使資訊增益最大的特徵建立以字典形式迭代的迭代器,此時為mytree = {no surfacing: {}}
del (labels[bestfeat])
# 刪除該使用的特徵
featvalues = [example[bestfeat] for example in dataset]
# 遍歷所有元素的最優特徵位置對應的特徵值featvalues = [1, 1, 1, 0, 0]
unique_vals = set(featvalues)
# 轉換成集合形式,即去除重複的特徵值,集合的互異性可以使相同特徵值合併,此時unique_vals = [1,0]
for value in unique_vals:
sublabels = labels[:]
# 複製一份 標籤列表
mytree[bestfeatlabel][value] = createtree(splitdataset(dataset, bestfeat, value), sublabels)
'''
# 該部分是根據特徵進行分類,並進行迭代繼續分類,如剛開始是mytree = {no surfacing: {}},此時
# mytree[bestfeatlabel][value] = createtree(splitdataset(dataset, bestfeat, value), sublabels) 輸入的是當前特徵值
# 根據當前特徵我們可以知道,該特徵值有兩個值,['0','1'],通過類別劃分後,把基於該特徵的都返回,同時,刪除該特徵資訊
# 當再次進入createtree()時,先對‘0’值進行判斷,發現[0, 1, 'no'],[0, 1, 'no'],他們的標籤為都為‘no’是純的,滿足
# 第一個停止條件,即當分類為純的分類後,停止迭代,返回‘no'分類,返回後,繼續進行for迴圈,此時該特徵值為’1‘,
# [1, 1, 'yes'],[1, 1, 'yes'],[1, 0, 'no'],發現不滿足停止條件即既不是純的,特徵也沒有使用完,此時就會再次進入迭代,根據資訊
# 增益進行分類,然後進入for迴圈,,,,直到滿足條件迭代返回,返回到最初第一層後結束迭代。
# 做好輸出結果為{'no surfacing': {0: 'no', 1: {'flipper': {0: 'no', 1: 'yes'}}}}
'''
return mytree
# 使用決策樹的分類函式
def classify(inputtree, featlabels, testvec):
firststr = list(inputtree.keys())[0]
second_dict = inputtree[firststr]
feat_index = featlabels.index(firststr)
for key in second_dict.keys():
if testvec[feat_index] == key:
if type(second_dict[key]) == dict:
classlabel = classify(second_dict[key], featlabels, testvec)
else:
classlabel = second_dict[key]
return classlabel
# 決策樹的儲存
def storetree(inputtree, filename):
import pickle
fw = open(filename, 'wb') # 需要注意的是執行Python2的程式碼時,出現錯誤,錯誤的原因是需要加上寫位元組即‘wb’
pickle.dump(inputtree, fw)
fw.close()
def grabtree(filename):
import pickle
fr = open(filename, 'rb') # 同理需要加上讀位元組’rb’
return pickle.load(fr)
畫圖的就不貼了,下面開始給出示例,該示例來源機器學習實戰的示例即使用決策樹預測隱形眼鏡型別,只是使用了兩種方法進行預測。
先給出基於上面的程式碼的預測,然後給出基於sklearn的機器學習庫進行分類的程式碼
一共有24組資料,資料的Labels依次是age
、prescript
、astigmatic
、tearRate
、class
,也就是第一列是年齡,第二列是症狀,第三列是是否散光,第四列是眼淚數量,第五列是最終的分類標籤。資料如下圖所示:
資料清楚後,下面給出程式碼,註釋在程式碼中很詳細:
#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 趙守風
# File name: ex_Contact lens type.py
# Time:2018/9/26
# 機器學習實戰的隱形眼鏡分類和樹視覺化
import trees_ex
import plot_tree
import trees
fr = open('lenses.txt')
# lenses = [inst.strip().split('\t') for inst in fr.readline()]
# 該語句在Python3無法執行,因為執行的結果並不能得到構建決策樹所需的資料集,需要使用下面的語句, 把所有資料取出來
lenses = []
for line in fr:
line = fr.readline()
inst = line.strip().split('\t')
lenses.append(inst)
print(lenses)
lenseslabels = ['age', 'prescript', 'astigmatic', 'tearRate']
lensestree = trees_ex.createTree(lenses, lenseslabels)
# lensestree = trees.createtree(lenses, lenseslabels)
print(lensestree)
plot_tree.createPlot(lensestree)
'''
本程式碼呼叫的是前面貼的手寫程式碼,畫圖的程式碼沒給出
'''
先分析一下決策樹,首先決策樹通過計算各個特徵的資訊增益,對比以後astigmatic(是否散光) 特徵的資訊增益最大,根據此特徵分為閃光和不散光,在不散光(no)的條件下,從新計算資訊增益,發現age的增益最大,因此根據此特徵繼續劃分,,,直到劃分結束為止。
下面貼出sklearn的程式碼,首先需要大家安裝pydotplus和Grphviz,如果使用conda可以通過 conda install pydotplus/grphviz直接安裝即可
貼程式碼之前先給出幾個連結:
先貼出幾個重要的API介面函式:
class sklearn.tree.
DecisionTreeClassifier
(criterion ='gini',splitter ='best',max_depth = None,min_samples_split = 2,min_samples_leaf = 1,min_weight_fraction_leaf = 0.0,max_features = None,random_state = None,max_leaf_nodes = None,min_impurity_decrease = 0.0,min_impurity_split = None,class_weight = None,presort = False )¶
引數: |
criterion:string,optional(default =“gini”)
splitter:string,optional(default =“best”)
max_depth:int或None,可選(預設=無)
min_samples_split:int,float,optional(default = 2)
min_samples_leaf:int,float,optional(default = 1)
min_weight_fraction_leaf:float,optional(預設= 0。)
max_features:int,float,string或None,可選(預設=無)
random_state:int,RandomState例項或None,可選(預設=無)
max_leaf_nodes:int或None,可選(預設=無)
min_impurity_decrease:float,optional(預設= 0。)
min_impurity_split:float,
class_weight:dict,dicts列表,“balanced”或None,預設= None
presort:bool,optional(預設值= False)
|
---|
屬性: |
classes_:shape = [n_classes]的陣列或此類陣列的列表
feature_importances_:shape陣列= [n_features]
max_features_:int,
n_classes_:int或list
n_features_:int
n_outputs_:int
tree_:樹物件
|
---|
下面貼出使用sklearn演算法的決策樹預測隱形眼鏡型別
#!/usr/bin/env/python
# -*- coding: utf-8 -*-
# Author: 趙守風
# File name: ex_Contact lens type.py
# Time:2018/9/26
from sklearn import preprocessing
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.externals.six import StringIO
from sklearn import tree
import pandas as pd
import numpy as np
import pydotplus
# import graphviz
fr = open('lenses.txt')
# lenses = [inst.strip().split('\t') for inst in fr.readline()]
# 該語句執行的結果並不能得到構建決策樹所需的資料集,需要使用下面的語句, 把所有資料取出來
lenses = []
for line in fr:
lines = fr.readline() # 讀取整行
inst = lines.strip().split('\t') # 刪除空格,同時以製表符為分割符,最後返回的是一行資料字串列表
lenses.append(inst) # 把一行字串列表新增到lenses列表中,如下
# [['young', 'myope', 'no', 'normal', 'soft'], ['young', 'myope', 'yes', 'normal', 'hard'], ['y....
print(lenses)
'''fit()函式不能接收string型別的資料,通過列印的資訊可以看到,資料都是string型別的。在使用fit()函式之前,
我們需要對資料集進行編碼,這裡可以使用兩種方法:
LabelEncoder :將字串轉換為增量值
OneHotEncoder:使用One-of-K演算法將字串轉換為整數
為了對string型別的資料序列化,需要先生成pandas資料,這樣方便我們的序列化工作。這裡我使用的方法是,
原始資料->字典->pandas資料
'''
# 在使用sklearn前需要把字串型別的資料轉換成數值型資料,此時需要pandas進行轉換
lenses_target = [] # 提取每組資料的類別,儲存在列表裡
for each in lenses:
lenses_target.append(each[-1]) # 因為lenses列表中的每個元素都是原始資料的每一行資料,而每一行資料的最後有個數為分類資料
lenseslabels = ['age', 'prescript', 'astigmatic', 'tearRate'] # 特徵資料
lenses_list = [] # 儲存lenses資料的臨時列表
lenses_dict = {}
for each_label in lenseslabels: # 提取資訊,生成字典
for each in lenses: # 根據特徵資料進行提取,例如把所有樣本為age這個特徵的資料都要提取出來歸為一個列表
lenses_list.append(each[lenseslabels.index(each_label)])
lenses_dict[each_label] = lenses_list # 把提取樣本特徵為age的資料生成字典
lenses_list = [] # 清空列表,為下一個提取特徵資料做準備
print(lenses_dict) # 列印字典資訊
lenses_pd = pd.DataFrame(lenses_dict) # 生成pandas.DataFrame
print(lenses_pd)
# 資料序列化
le = preprocessing.LabelEncoder() #建立LabelEncoder()物件,用於序列化
for col in lenses_pd.columns: #為每一列序列化
lenses_pd[col] = le.fit_transform(lenses_pd[col])
print(lenses_pd)
clf = tree.DecisionTreeClassifier(max_depth=4, criterion='entropy') # 建立DecisionTreeClassifier()類,預設為gini
clf = clf.fit(lenses_pd.values.tolist(), lenses_target) # 使用資料,構建決策樹
dot_data = StringIO()
tree.export_graphviz(clf, out_file= dot_data, # 繪製決策樹,sklearn的示例給出。直接複製過來使用即可
feature_names= lenses_pd.keys(),
class_names= clf.classes_,
filled=True, rounded=True,
special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_pdf("tree_entropy.pdf")
貼出資料的序列化和決策樹圖
基於熵的決策樹和基於gini 的決策樹對比
對比發現兩者相差不大,但是sklearn預設是gini,也許經過多方面權衡以後做的決定
到目前為止,已經完整的完成決策樹的學習,後面會陸續通過手寫各個機器學習演算法,然後再通過呼叫sklearn的方式處理資料,相結合學習,以此達到知其然,更知其所以然的目的。有錯誤歡迎指正