參考:機器學習&深度學習算法及代碼實現
傳統機器學習算法
決策樹、K鄰近算法、支持向量機、朴素貝葉斯、神經網絡、Logistic回歸算法,聚類等。
決策樹學習筆記(Decision Tree)
引自:Python3《機器學習實戰》學習筆記(二):決策樹基礎篇之讓我們從相親說起
github:https://github.com/Jack-Cherish/Machine-Learning/tree/master/Decision%20Tree
決策樹(decision tree)是一種基本的分類與回歸方法。
決策樹算法的核心在於決策樹的構建,每次選擇讓整體數據香農熵(描述數據的混亂程度)減小最多的特征,使用其特征值對數據進行划分,每次消耗一個特征,不斷迭代分類,直到所有特征消耗完(選擇剩下數據中出現次數最多的類別作為這堆數據的類別),或剩下的數據全為同一類別,不必繼續划分,至此決策樹構建完成,之后我們依照這顆決策樹對新進數據進行分類。
一個相親的例子:

結點和模塊的概念:
一個決策樹,長方形代表判斷模塊(decision block),橢圓形成代表終止模塊(terminating block),表示已經得出結論,可以終止運行。從判斷模塊引出的左右箭頭稱作為分支(branch),它可以達到另一個判斷模塊或者終止模塊。我們還可以這樣理解,分類決策樹模型是一種描述對實例進行分類的樹形結構。決策樹由結點(node)和有向邊(directed edge)組成。結點有兩種類型:內部結點(internal node)和葉結點(leaf node)。內部結點表示一個特征或屬性,葉結點表示一個類。如圖所示的決策樹,長方形和橢圓形都是結點。長方形的結點屬於內部結點,橢圓形的結點屬於葉結點,從結點引出的左右箭頭就是有向邊。而最上面的結點就是決策樹的根結點(root node)。
使用決策樹做預測需要以下過程:
- 收集數據:可以使用任何方法。比如想構建一個相親系統,我們可以從媒婆那里,或者通過參訪相親對象獲取數據。根據他們考慮的因素和最終的選擇結果,就可以得到一些供我們利用的數據了。
- 准備數據:收集完的數據,我們要進行整理,將這些所有收集的信息按照一定規則整理出來,並排版,方便我們進行后續處理。
- 分析數據:可以使用任何方法,決策樹構造完成之后,我們可以檢查決策樹圖形是否符合預期。
- 訓練算法:這個過程也就是構造決策樹,同樣也可以說是決策樹學習,就是構造一個決策樹的數據結構。
- 測試算法:使用經驗樹計算錯誤率。當錯誤率達到了可接收范圍,這個決策樹就可以投放使用了。
- 使用算法:此步驟可以使用適用於任何監督學習算法,而使用決策樹可以更好地理解數據的內在含義。
決策樹構建的准備工作
3個步驟:特征選擇、決策樹的生成和決策樹的修剪。
1 特征選擇
特征選擇在於選取對訓練數據具有分類能力的特征。這樣可以提高決策樹學習的效率,如果利用一個特征進行分類的結果與隨機分類的結果沒有很大差別,則稱這個特征是沒有分類能力的。經驗上扔掉這樣的特征對決策樹學習的精度影響不大。通常特征選擇的標准是信息增益(information gain)或信息增益比,為了簡單,本文章使用信息增益作為選擇特征的標准。那么,什么是信息增益?在講解信息增益之前,讓我們看一組實例,貸款申請樣本數據表。
| ID | 年齡 | 有工作 | 有自己的房子 | 信貸情況 | 類別(是否個給貸款) |
|---|---|---|---|---|---|
| 1 | 青年 | 否 | 否 | 一般 | 否 |
| 2 | 青年 | 否 | 否 | 好 | 否 |
| 3 | 青年 | 是 | 否 | 好 | 是 |
| 4 | 青年 | 是 | 是 | 一般 | 是 |
| 5 | 青年 | 否 | 否 | 一般 | 否 |
| 6 | 中年 | 否 | 否 | 一般 | 否 |
| 7 | 中年 | 否 | 否 | 好 | 否 |
| 8 | 中年 | 是 | 是 | 好 | 是 |
| 9 | 中年 | 否 | 是 | 非常好 | 是 |
| 10 | 中年 | 否 | 是 | 非常好 | 是 |
| 11 | 老年 | 否 | 是 | 非常好 | 是 |
| 12 | 老年 | 否 | 是 | 好 | 是 |
| 13 | 老年 | 是 | 否 | 好 | 是 |
| 14 | 老年 | 是 | 否 | 非常好 | 是 |
| 15 | 老年 | 否 | 否 | 一般 | 否 |
希望通過所給的訓練數據學習一個貸款申請的決策樹,用以對未來的貸款申請進行分類,即當新的客戶提出貸款申請時,根據申請人的特征利用決策樹決定是否批准貸款申請。
特征選擇就是決定用哪個特征來划分特征空間。比如,我們通過上述數據表得到兩個可能的決策樹,分別由兩個不同特征的根結點構成。

圖(a)所示的根結點的特征是年齡,有3個取值,對應於不同的取值有不同的子結點。圖(b)所示的根節點的特征是工作,有2個取值,對應於不同的取值有不同的子結點。兩個決策樹都可以從此延續下去。
問題是:究竟選擇哪個特征更好些?這就要求確定選擇特征的准則。直觀上,如果一個特征具有更好的分類能力,或者說,按照這一特征將訓練數據集分割成子集,使得各個子集在當前條件下有最好的分類,那么就更應該選擇這個特征。信息增益就能夠很好地表示這一直觀的准則。
什么是信息增益呢?在划分數據集之前之后信息發生的變化成為信息增益,知道如何計算信息增益,我們就可以計算每個特征值划分數據集獲得的信息增益,獲得信息增益最高的特征就是最好的選擇。
香農熵
如何計算信息增益。集合信息的度量方式成為香農熵或者簡稱為熵(entropy),
熵定義為信息的期望值。在信息論與概率統計中,熵是表示隨機變量不確定性的度量。如果待分類的事務可能划分在多個分類之中,則符號xi的信息定義為

其中p(xi)是選擇該分類的概率。
通過上式,我們可以得到所有類別的信息。為了計算熵,我們需要計算所有類別所有可能值包含的信息期望值(數學期望),通過下面的公式得到:

期中n是分類的數目。熵越大,隨機變量的不確定性就越大。
當熵中的概率由數據估計(特別是最大似然估計)得到時,所對應的熵稱為經驗熵(empirical entropy)。什么叫由數據估計?比如有10個數據,一共有兩個類別,A類和B類。其中有7個數據屬於A類,則該A類的概率即為十分之七。其中有3個數據屬於B類,則該B類的概率即為十分之三。淺顯的解釋就是,這概率是我們根據數據數出來的。我們定義貸款申請樣本數據表中的數據為訓練數據集D,則訓練數據集D的經驗熵為H(D),|D|表示其樣本容量,及樣本個數。設有K個類Ck,k = 1,2,3,···,K,|Ck|為屬於類Ck的樣本個數,這經驗熵公式可以寫為

根據此公式計算經驗熵H(D),分析貸款申請樣本數據表中的數據。最終分類結果只有兩類,即放貸和不放貸。根據表中的數據統計可知,在15個數據中,9個數據的結果為放貸,6個數據的結果為不放貸。所以數據集D的經驗熵H(D)為:

經過計算可知,數據集D的經驗熵H(D)的值為0.971。
3.1.2 編寫代碼計算經驗熵
在編寫代碼之前,我們先對數據集進行屬性標注。
- 年齡:0代表青年,1代表中年,2代表老年;
- 有工作:0代表否,1代表是;
- 有自己的房子:0代表否,1代表是;
- 信貸情況:0代表一般,1代表好,2代表非常好;
- 類別(是否給貸款):no代表否,yes代表是。
確定這些之后,我們就可以創建數據集,並計算經驗熵了,代碼編寫如下:
# -*- coding: UTF-8 -*- from math import log """ 函數說明:創建測試數據集 Parameters: 無 Returns: dataSet - 數據集 labels - 分類屬性 Author: Jack Cui Modify: 2017-07-20 """ def createDataSet(): dataSet = [[0, 0, 0, 0, 'no'], #數據集 [0, 0, 0, 1, 'no'], [0, 1, 0, 1, 'yes'], [0, 1, 1, 0, 'yes'], [0, 0, 0, 0, 'no'], [1, 0, 0, 0, 'no'], [1, 0, 0, 1, 'no'], [1, 1, 1, 1, 'yes'], [1, 0, 1, 2, 'yes'], [1, 0, 1, 2, 'yes'], [2, 0, 1, 2, 'yes'], [2, 0, 1, 1, 'yes'], [2, 1, 0, 1, 'yes'], [2, 1, 0, 2, 'yes'], [2, 0, 0, 0, 'no']] labels = ['年齡', '有工作', '有自己的房子', '信貸情況'] #分類屬性 return dataSet, labels #返回數據集和分類屬性 """ 函數說明:計算給定數據集的經驗熵(香農熵) Parameters: dataSet - 數據集 Returns: shannonEnt - 經驗熵(香農熵) """ def calcShannonEnt(dataSet): numEntires = len(dataSet) #返回數據集的行數 labelCounts = {} #保存每個標簽(Label)出現次數的字典 for featVec in dataSet: #對每組特征向量進行統計 currentLabel = featVec[-1] #提取標簽(Label)信息 if currentLabel not in labelCounts.keys(): #如果標簽(Label)沒有放入統計次數的字典,添加進去 labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 #Label計數 shannonEnt = 0.0 #經驗熵(香農熵) for key in labelCounts: #計算香農熵 prob = float(labelCounts[key]) / numEntires #選擇該標簽(Label)的概率 shannonEnt -= prob * log(prob, 2) #利用公式計算 return shannonEnt #返回經驗熵(香農熵) if __name__ == '__main__': dataSet, features = createDataSet() print(dataSet) print(calcShannonEnt(dataSet))
信息增益
在上面,我們已經說過,如何選擇特征,需要看信息增益。也就是說,信息增益是相對於特征而言的,信息增益越大,特征對最終的分類結果影響也就越大,我們就應該選擇對最終分類結果影響最大的那個特征作為我們的分類特征。
,信息增益是相對於特征而言的。所以,特征A對訓練數據集D的信息增益g(D,A),定義為集合D的經驗熵H(D)與特征A給定條件下D的經驗條件熵H(D|A)之差,即

編寫代碼計算信息增益
# -*- coding: UTF-8 -*- from math import log """ 函數說明:計算給定數據集的經驗熵(香農熵) Parameters: dataSet - 數據集 Returns: shannonEnt - 經驗熵(香農熵) Author: Jack Cui Modify: 2017-03-29 """ def calcShannonEnt(dataSet): numEntires = len(dataSet) #返回數據集的行數 labelCounts = {} #保存每個標簽(Label)出現次數的字典 for featVec in dataSet: #對每組特征向量進行統計 currentLabel = featVec[-1] #提取標簽(Label)信息 if currentLabel not in labelCounts.keys(): #如果標簽(Label)沒有放入統計次數的字典,添加進去 labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 #Label計數 shannonEnt = 0.0 #經驗熵(香農熵) for key in labelCounts: #計算香農熵 prob = float(labelCounts[key]) / numEntires #選擇該標簽(Label)的概率 shannonEnt -= prob * log(prob, 2) #利用公式計算 return shannonEnt #返回經驗熵(香農熵) """ 函數說明:創建測試數據集 Parameters: 無 Returns: dataSet - 數據集 labels - 分類屬性 Author: Jack Cui Modify: 2017-07-20 """ def createDataSet(): dataSet = [[0, 0, 0, 0, 'no'], #數據集 [0, 0, 0, 1, 'no'], [0, 1, 0, 1, 'yes'], [0, 1, 1, 0, 'yes'], [0, 0, 0, 0, 'no'], [1, 0, 0, 0, 'no'], [1, 0, 0, 1, 'no'], [1, 1, 1, 1, 'yes'], [1, 0, 1, 2, 'yes'], [1, 0, 1, 2, 'yes'], [2, 0, 1, 2, 'yes'], [2, 0, 1, 1, 'yes'], [2, 1, 0, 1, 'yes'], [2, 1, 0, 2, 'yes'], [2, 0, 0, 0, 'no']] labels = ['年齡', '有工作', '有自己的房子', '信貸情況'] #分類屬性 return dataSet, labels #返回數據集和分類屬性 """ 函數說明:按照給定特征划分數據集 Parameters: dataSet - 待划分的數據集 axis - 划分數據集的特征 value - 需要返回的特征的值 Returns: 無 Author: Jack Cui Modify: 2017-03-30 """ def splitDataSet(dataSet, axis, value): retDataSet = [] #創建返回的數據集列表 for featVec in dataSet: #遍歷數據集 if featVec[axis] == value: reducedFeatVec = featVec[:axis] #去掉axis特征 reducedFeatVec.extend(featVec[axis+1:]) #將符合條件的添加到返回的數據集 retDataSet.append(reducedFeatVec) return retDataSet #返回划分后的數據集 """ 函數說明:選擇最優特征 Parameters: dataSet - 數據集 Returns: bestFeature - 信息增益最大的(最優)特征的索引值 """ def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 #特征數量 baseEntropy = calcShannonEnt(dataSet) #計算數據集的香農熵 bestInfoGain = 0.0 #信息增益 bestFeature = -1 #最優特征的索引值 for i in range(numFeatures): #遍歷所有特征 #獲取dataSet的第i個所有特征 featList = [example[i] for example in dataSet] uniqueVals = set(featList) #創建set集合{},元素不可重復 newEntropy = 0.0 #經驗條件熵 for value in uniqueVals: #計算信息增益 subDataSet = splitDataSet(dataSet, i, value) #subDataSet划分后的子集 prob = len(subDataSet) / float(len(dataSet)) #計算子集的概率 newEntropy += prob * calcShannonEnt(subDataSet) #根據公式計算經驗條件熵 infoGain = baseEntropy - newEntropy #信息增益 print("第%d個特征的增益為%.3f" % (i, infoGain)) #打印每個特征的信息增益 if (infoGain > bestInfoGain): #計算信息增益 bestInfoGain = infoGain #更新信息增益,找到最大的信息增益 bestFeature = i #記錄信息增益最大的特征的索引值 return bestFeature #返回信息增益最大的特征的索引值 if __name__ == '__main__': dataSet, features = createDataSet() print("最優特征索引值:" + str(chooseBestFeatureToSplit(dataSet)))
