寫在前面的話
可憐了我這個系列的博客,寫的這么好,花了很多心思去寫的,卻沒有人知道欣賞。就像我這么好也沒有人懂得欣賞,哈哈哈,我好不要臉。。。
如果您有任何地方看不懂的,那一定是我寫的不好,請您告訴我,我會爭取寫的更加簡單易懂!
如果您有任何地方看着不爽,請您盡情的噴,使勁的噴,不要命的噴,您的槽點就是幫助我要進步的地方!
1.划分數據集
1.1 基本概念
在度量數據集的無序程度的時候,分類算法除了需要測量信息熵,還需要划分數據集,度量花費數據集的熵,以便判斷當前是否正確的划分了數據集。
我們將對每個特征數據集划分的結果計算一次信息熵,然后判斷按照那個特征划分數據集是最好的划分方式。
也就是說,我們依次選取我們數據集當中的所有特征作為我們划定的特征,然后計算選取該特征時的信息增益,當信息增益最大時我們就選取對應信息增益最大的特征作為我們分類的最佳特征。
下面是我們的數據集:

我們用python語言表示出這個數據集
dataSet= [[1, 1, ‘yes’], [1, 1, ‘yes’], [1, 0, ‘no’], [0, 1, ‘no’], [0, 1, ‘no’]]
在這個數據集當中有兩個特征,就是每個樣本的第一列和第二列,最后一列是它們所屬的分類。
我們划分數據集是為了計算根據那個特征我們可以得到最大的信息增益,那么根據這個特征來划分數據就是最好的分類方法。
因此我們需要遍歷每一個特征,然后計算按照這種划分方式得出的信息增益。信息增益是指數據集在划分數據前后信息的變化量。
1.2 具體操作
划分數據集的方式我們首先選取第一個特征的第一個可能取值來篩選信息。然后再選取第一個特征的第二個可能的取值來划分我們的信息。之后我們再選取第二個特征的第一個可能的取值來划分數據集,以此類推。
e.g:
[[1, 1, ‘yes’], [1, 1, ‘yes’], [1, 0, ‘no’], [0, 1, ‘no’], [0, 1, ‘no’]]
這個是我們的數據集。
如果我們選取第一個特征值也就是需不需要浮到水面上才能生存來划分我們的數據,這里生物有兩種可能,1就是需要,0就是不需要。那么第一個特征的取值就是兩種。
如果我們按照第一個特征的第一個可能的取值來划分數據也就是當所有的樣本的第一列取1的時候滿足的樣本,那就是如下三個:
[1, 1, ‘yes’], [1, 1, ‘yes’], [1, 0, ‘no’]
可以理解為這個特征為一條分界線,我們選取完這個特征之后這個特征就要從我們數據集中剔除,因為要把他理解為分界線。那么划分好的數據就是:
[[1, ‘yes’], [1, ‘yes’], [0, ‘no’]]
如果我們以第一個特征的第二個取值來划分數據集,也就是當所有樣本的第二列取1的時候滿足的樣本,那么就是
[[1, 1, ‘yes’], [1, 1, ‘yes’], [0, 1, ‘no’], [0, 1, ‘no’]]
那么得到的數據子集就是下面這個樣子:
[[1,’yes’],[1,’yes’],[1, ‘no’], [1, ‘no’]]
因此我們可以很容易的來構建出我們的代碼:

下面我們來分析一下這段代碼,
# 代碼功能:划分數據集 def splitDataSet(dataSet,axis,value): #傳入三個參數第一個參數是我們的數據集,是一個鏈表形式的數據集;第二個參數是我們的要依據某個特征來划分數據集 retDataSet = [] #由於參數的鏈表dataSet我們拿到的是它的地址,也就是引用,直接在鏈表上操作會改變它的數值,所以我們新建一格鏈表來做操作 for featVec in dataSet: if featVec[axis] == value: #如果某個特征和我們指定的特征值相等 #除去這個特征然后創建一個子特征 reduceFeatVec = featVec[:axis] reduceFeatVec.extend(featVec[axis+1:]) #將滿足條件的樣本並且經過切割后的樣本都加入到我們新建立的樣本中 retDataSet.append(reduceFeatVec) return retDataSe
總的來說,這段代碼的功能就是按照某個特征的取值來划分數據集。
為方便您測試實驗我們在貼出這段代碼:
def splitDataSet(dataSet,axis,value): retDataSet = [] for featVec in dataSet: if featVec[axis] == value: reduceFeatVec = featVec[:axis] reduceFeatVec.extend(featVec[axis+1:]) retDataSet.append(reduceFeatVec) return retDataSet
在這里我們可以注意到一個關於鏈表的操作:
那就是extend 和append
它們的用法和區別如下所示:


下面我們再來測試一下我們的數據:
先給出實驗的完整代碼
#!/usr/bin/env python # coding=utf-8 # author: chicho # running: python trees.py # filename : trees.py from math import log def createDataSet(): dataSet = [[1,1,'yes'], [1,1,'yes'], [1,0,'no'], [0,1,'no'], [0,1,'no']] labels = ['no surfacing','flippers'] return dataSet, labels def calcShannonEnt(dataSet): countDataSet = len(dataSet) labelCounts={} for featVec in dataSet: currentLabel=featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 print labelCounts shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key])/countDataSet shannonEnt -= prob * log(prob,2) return shannonEnt def splitDataSet(dataSet,axis,value): retDataSet = [] for featVec in dataSet: if featVec[axis] == value: reduceFeatVec = featVec[:axis] reduceFeatVec.extend(featVec[axis+1:]) retDataSet.append(reduceFeatVec) return retDataSet
我們輸入下面代碼測試一下,你可以在linux系統的終端中輸入python或者是ipython 來進行測試:

In [1]: import trees In [2]: reload(trees) Out[2]: <module 'trees' from 'trees.pyc'> In [3]: myDat,labels=trees.createDataSet() In [4]: myDat Out[4]: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']] In [5]: trees.splitDataSet(myDat,0,1) Out[5]: [[1, 'yes'], [1, 'yes'], [0, 'no']] In [6]: trees.splitDataSet(myDat,0,0) Out[6]: [[1, 'no'], [1, 'no']]
知道怎么划分數據集之后,我們接下來的工作就是遍歷整個樣本集合的特征值,然后循環計算香農熵,找到最好的特征划分方式。
2.計算信息增益
我們主要是要找到划分數據前后的最大信息增益,然后找到根據那個特征來做划分,分類出來的效果更好。
下面給出代碼是怎么實現的

我們來分析一下代碼:
注意: 在使用這段代碼的時候我們對處理的數據是有要求的。
1.數據集必須是鏈表的表示出來的
2.數據集的每一個樣本也是鏈表形式
3.數據集的每一個樣本都必須是前面的所有列都是樣本的特征值的取值范圍,所有樣本的最后一列是樣本的類別。
4.每個樣本的列數必須相同
首先我們的樣本集合是:

dataSet = [[1,1,’yes’],
[1,1,’yes’],
[1,0,’no’],
[0,1,’no’],
[0,1,’no’]] # 我們定義了一個list來表示我們的數據集,這里的數據對應的是上表中的數據
我們的目的已經很明確了,就是依次遍歷每一個特征,在這里我們的特征只有兩個,就是需不需要浮出水面,有沒有腳蹼。然后計算出根據每一個特征划分產生的數據集的熵,和初始的數據集的熵比較,我們找出和初始數據集差距最大的。那么這個特征就是我們划分時最合適的分類特征。
def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0])-1 # 獲取我們樣本集中的某一個樣本的特征數(因為每一個樣本的特征數是相同的,相當於這個代碼就是我們可以作為分類依據的所有特征個數)我們的樣本最后一列是樣本所屬的類別,所以要減去類別信息,在我們的例子中特征數就是2 baseEntropy = calcShannonEnt(dataSet) #計算樣本的初始香農熵 bestInfoGain =0.0 #初始化最大信息增益 bestFeature = -1 #和最佳划分特征 for i in range(numFeatures): # range(2)那么i的取值就是0,1。 在這里i表示的我們的第幾個特征 featList = [sample[i] for sample in dataSet] # 我們首先遍歷整個數據集,首先得到第一個特征值可能的取值,然后把它賦值給一個鏈表,我們第一個特征值取值是[1,1,1,0,0],其實只有【1,0】兩個取值 uniqueVals = set(featList)#我們使用集合這個數據類型刪除多余重復的原始使得其中只有唯一的值。 #執行的結果如下所示: ``` In [8]: featList=[1,1,1,0,0] In [9]: uniqueVals=set(featList) In [10]: uniqueVals Out[10]: {0, 1} ``` newEntropy = 0.0 for value in uniqueVals: #uniqueVals中保存的是我們某個樣本的特征值的所有的取值的可能性 subDataSet = splitDataSet(dataSet,i,value) # 在這里划分數據集,比如說第一個特征的第一個取值得到一個子集,第一個特征的特征又會得到另一個特征。當然這是第二次循環 prob = len(subDataSet)/float(len(dataSet))#我們以第一個特征來說明,根據第一個特征可能的取值划分出來的子集的概率 newEntropy += prob * calcShannonEnt(subDataSet)# 這里比較難理解我們下面在詳細說明 infoGain = baseEntropy - newEntropy # 計算出信息增益 #找出最佳信息增益,是個學計算機的小朋友都懂吧,哨兵法 if(infoGain > bestInfoGain): bestInfoGain = infoGain bestFeature = i return bestFeature
被我標注的有點惡心,我在貼一遍代碼,方便大家測試:
def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0])-1 baseEntropy = calcShannonEnt(dataSet) bestInfoGain =0.0 bestFeature = -1 for i in range(numFeatures): featList = [sample[i] for sample in dataSet] uniqueVals = set(featList) newEntropy = 0.0 for value in uniqueVals: subDataSet = splitDataSet(dataSet,i,value) prob = len(subDataSet)/float(len(dataSet)) newEntropy += prob * calcShannonEnt(subDataSet) infoGain = baseEntropy - newEntropy if(infoGain > bestInfoGain): bestInfoGain = infoGain bestFeature = i return bestFeature
難點
下面我們來重點解釋一下剛才我們沒有說的那句代碼:
newEntropy += prob * calcShannonEnt(subDataSet)
為什么根據這個代碼就可以計算出划分子集的熵了呢?
首先我們還是要回顧一下計算數學期望的方法,我們必須明確熵是信息的數學期望。
對於隨機變量要計算它的數學期望我們是這樣來計算的:
| X | X1 | X2 |
|---|---|---|
| p | p1 | p2 |
E(x)=P1*X1 + P2 *X2
我么都以第一特征值來說明問題
同理,我們可以先計算出按照第一個特征值的第一個可能取值划分滿足的子集的概率,和第一個特征值的第二個可能取值划分滿足子集的概率。之后分別乘以它們子集計算出的香農熵。
| 香農熵 | H1 | H2 |
|---|---|---|
| 特征值划分出子集概率 | p1 | p2 |
E(H)=H1×P1+H2×P2
e.g. 第一個特征為例
這是我們的數據集

我們按照第一個特征的第一個取值划分得到的數據集是:

得到了兩個數據集,那么占總的數據集就是2/5
我們按照第一個特征的第二個取值划分得到的數據集是:
得到了三個數據集,那么占總的數據集就是3/5
我們分別來計算一下它們的香農熵

××××××
我們觀察一下數據,也充分的驗證了分類越多,香農熵會越大,當我們的分類只有一類是香農熵是0
××××××
我們采用列表法來計算一下熵:
| 某個特征的不同取值對應的香農熵 | 0.0 | 0.9182958340544896 |
|---|---|---|
| 特征值划分出子集概率 | 0.4 | 0.6 |
根據期望的定義,我們在第一章講過的公式,那么就可以計算出這個特征的信息熵
我們來測試一下結果:

In [1]: import trees In [2]: reload(trees) Out[2]: <module 'trees' from 'trees.pyc'> In [3]: myDat,labels=trees.createDataSet() In [4]: myDat Out[4]: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']] In [5]: trees.chooseBestFeatureToSplit(myDat) #總的數據分類 {'yes': 2, 'no': 3} #按照第一個特征的第一個取值來划分 {'no': 2} #按照第一個特征的第二個取值來划分 {'yes': 2, 'no': 1} ##按照第二個特征的第一個取值來划分 {'no': 1} #按照第二個特征的第二個取值來划分 {'yes': 2, 'no': 2} Out[5]: 0
我們可以看出計算的結果是根據第一個特征划分比較好。
這個其實也很明顯了,我們可以觀察一下我們的數據按照第一個特征來划分,當特征為1時生物分組有兩個屬於魚類,一個屬於非魚類,另一個分組全部屬於非魚類。
如果按照第二個特征分類,一組中兩個屬於魚類,兩個屬於非魚來,另一組中只有一個是非魚類。也就是按照第二特征來划分,錯誤率比較大。
完整的代碼:
#!/usr/bin/env python # coding=utf-8 # author: chicho # running: python trees.py # filename : trees.py from math import log def createDataSet(): dataSet = [[1,1,'yes'], [1,1,'yes'], [1,0,'no'], [0,1,'no'], [0,1,'no']] labels = ['no surfacing','flippers'] return dataSet, labels def calcShannonEnt(dataSet): countDataSet = len(dataSet) labelCounts={} for featVec in dataSet: currentLabel=featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 print labelCounts shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key])/countDataSet shannonEnt -= prob * log(prob,2) return shannonEnt def splitDataSet(dataSet,axis,value): retDataSet = [] for featVec in dataSet: if featVec[axis] == value: reduceFeatVec = featVec[:axis] reduceFeatVec.extend(featVec[axis+1:]) retDataSet.append(reduceFeatVec) return retDataSet def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0])-1 baseEntropy = calcShannonEnt(dataSet) bestInfoGain =0.0 bestFeature = -1 for i in range(numFeatures): featList = [sample[i] for sample in dataSet] uniqueVals = set(featList) newEntropy = 0.0 for value in uniqueVals: subDataSet = splitDataSet(dataSet,i,value) prob = len(subDataSet)/float(len(dataSet)) newEntropy += prob * calcShannonEnt(subDataSet) infoGain = baseEntropy - newEntropy if(infoGain > bestInfoGain): bestInfoGain = infoGain bestFeature = i return bestFeature
